2
pom.xml
2
pom.xml
@@ -24,11 +24,11 @@
|
||||
|
||||
<eclipselink>3.0.2</eclipselink>
|
||||
<hibernate>5.6.0.Final</hibernate>
|
||||
<jsqlparser>4.3</jsqlparser>
|
||||
<mysql-connector-java>8.0.23</mysql-connector-java>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<version>${jsqlparser.version}</version>
|
||||
<version>${jsqlparser}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -15,14 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* The implementation of {@link QueryEnhancer} using {@link QueryUtils}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class DefaultQueryEnhancer implements QueryEnhancer {
|
||||
|
||||
@@ -33,17 +35,7 @@ public class DefaultQueryEnhancer implements QueryEnhancer {
|
||||
}
|
||||
|
||||
@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) {
|
||||
public String applySorting(Sort sort, @Nullable String alias) {
|
||||
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
|
||||
}
|
||||
|
||||
@@ -53,7 +45,7 @@ public class DefaultQueryEnhancer implements QueryEnhancer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createCountQueryFor(String countProjection) {
|
||||
public String createCountQueryFor(@Nullable String countProjection) {
|
||||
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,19 +69,18 @@ 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
|
||||
* @param nativeQuery is a given query native or not
|
||||
* @return A query supporting SpEL expressions.
|
||||
*/
|
||||
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata, SpelExpressionParser parser,
|
||||
boolean nativeQuery) {
|
||||
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata<?> metadata,
|
||||
SpelExpressionParser parser, boolean nativeQuery) {
|
||||
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param query, the query expression potentially containing a SpEL expression. Must not be {@literal null}.}
|
||||
* @param query, the query expression potentially containing a SpEL expression. Must not be {@literal null}.
|
||||
* @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}.
|
||||
* @param parser Must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata<?> metadata,
|
||||
SpelExpressionParser parser) {
|
||||
|
||||
@@ -15,38 +15,44 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*;
|
||||
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
|
||||
|
||||
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 net.sf.jsqlparser.statement.select.OrderByElement;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.Select;
|
||||
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
|
||||
import net.sf.jsqlparser.statement.select.SelectItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
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
|
||||
* @author Greg Turnquist
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
|
||||
private static final String DEFAULT_TABLE_ALIAS = "x";
|
||||
|
||||
private final DeclaredQuery query;
|
||||
|
||||
/**
|
||||
@@ -57,40 +63,8 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
}
|
||||
|
||||
@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);
|
||||
public String applySorting(Sort sort, @Nullable String alias) {
|
||||
|
||||
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!");
|
||||
|
||||
@@ -145,6 +119,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
* @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);
|
||||
@@ -189,7 +164,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
* @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) {
|
||||
@Nullable final String alias, final Sort.Order order) {
|
||||
|
||||
final OrderByElement orderByElement = new OrderByElement();
|
||||
orderByElement.setAsc(order.getDirection().isAscending());
|
||||
@@ -233,7 +208,9 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
* @param query must not be {@literal null}.
|
||||
* @return Might return {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
private String detectAlias(String query) {
|
||||
|
||||
Select selectStatement = parseSelectStatement(query);
|
||||
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
|
||||
return detectAlias(selectBody);
|
||||
@@ -246,13 +223,15 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
* @param selectBody must not be {@literal null}.
|
||||
* @return Might return {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
private static String detectAlias(PlainSelect selectBody) {
|
||||
|
||||
Alias alias = selectBody.getFromItem().getAlias();
|
||||
return alias == null ? null : alias.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createCountQueryFor(String countProjection) {
|
||||
public String createCountQueryFor(@Nullable String countProjection) {
|
||||
|
||||
Assert.hasText(this.query.getQueryString(), "OriginalQuery must not be null or empty!");
|
||||
|
||||
@@ -298,6 +277,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
|
||||
@Override
|
||||
public String getProjection() {
|
||||
|
||||
Assert.hasText(query.getQueryString(), "Query must not be null or empty!");
|
||||
|
||||
Select selectStatement = parseSelectStatement(query.getQueryString());
|
||||
@@ -321,6 +301,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
* @return the parsed query
|
||||
*/
|
||||
private static Select parseSelectStatement(String query) {
|
||||
|
||||
try {
|
||||
return (Select) CCJSqlParserUtil.parse(query);
|
||||
} catch (JSQLParserException e) {
|
||||
@@ -329,12 +310,13 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given projection only contains a single column definition (aka without functions, etc)
|
||||
* 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;
|
||||
|
||||
@@ -15,15 +15,10 @@
|
||||
*/
|
||||
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;
|
||||
@@ -33,58 +28,12 @@ import java.util.stream.Collectors;
|
||||
* A utility class for JSqlParser.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
* @author Greg Turnquist
|
||||
* @since 2.7.0
|
||||
*/
|
||||
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;
|
||||
}
|
||||
private JSqlParserUtils() {}
|
||||
|
||||
/**
|
||||
* Generates a count function call, based on the {@code countFields}.
|
||||
@@ -94,12 +43,14 @@ public final class JSqlParserUtils {
|
||||
* @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) //
|
||||
@@ -113,11 +64,12 @@ public final class JSqlParserUtils {
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import org.springframework.util.Assert;
|
||||
* @author Reda.Housni-Alaoui
|
||||
* @author Moritz Becker
|
||||
* @author Andrey Kovalev
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extends Object>, Predicate> {
|
||||
|
||||
@@ -166,8 +167,10 @@ 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()) {
|
||||
|
||||
@@ -239,83 +242,84 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,22 +344,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");
|
||||
return (Expression<T>) builder.upper((Expression<String>) expression);
|
||||
|
||||
case WHEN_POSSIBLE:
|
||||
|
||||
if (canUpperCase(expression)) {
|
||||
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 NEVER:
|
||||
default:
|
||||
case WHEN_POSSIBLE:
|
||||
|
||||
return (Expression<T>) expression;
|
||||
if (canUpperCase(expression)) {
|
||||
return (Expression<T>) builder.upper((Expression<String>) expression);
|
||||
}
|
||||
|
||||
case NEVER:
|
||||
default:
|
||||
|
||||
return (Expression<T>) expression;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,27 +24,11 @@ import org.springframework.lang.Nullable;
|
||||
* This interface describes the API for enhancing a given Query.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
* @author Greg Turnquist
|
||||
* @since 2.7.0
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -110,7 +94,7 @@ public interface QueryEnhancer {
|
||||
/**
|
||||
* Gets the query we want to use for enhancements.
|
||||
*
|
||||
* @return non null {@link DeclaredQuery} that wraps the query
|
||||
* @return non-null {@link DeclaredQuery} that wraps the query
|
||||
*/
|
||||
DeclaredQuery getQuery();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.apache.commons.logging.LogFactory;
|
||||
* Encapsulates different strategies for the creation of a {@link QueryEnhancer} from a {@link DeclaredQuery}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
* @author Greg Turnquist
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public final class QueryEnhancerFactory {
|
||||
|
||||
@@ -29,8 +31,7 @@ public final class QueryEnhancerFactory {
|
||||
|
||||
private static final boolean JSQLPARSER_IN_CLASSPATH = isJSqlParserInClassPath();
|
||||
|
||||
private QueryEnhancerFactory() {
|
||||
}
|
||||
private QueryEnhancerFactory() {}
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryEnhancer} for the given {@link DeclaredQuery}.
|
||||
@@ -39,6 +40,7 @@ public final class QueryEnhancerFactory {
|
||||
* @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 {
|
||||
@@ -63,6 +65,7 @@ public final class QueryEnhancerFactory {
|
||||
* @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.");
|
||||
@@ -71,5 +74,4 @@ public final class QueryEnhancerFactory {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import jakarta.persistence.criteria.ParameterExpression;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -49,8 +48,7 @@ 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.
|
||||
|
||||
@@ -666,8 +666,8 @@ public abstract class QueryUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this attribute requires an outer join. This is the case eg. if it hadn't already been fetched with an
|
||||
* inner join and if it's an a optional association, and if previous paths has already required outer joins. It also
|
||||
* Checks if this attribute requires an outer join. This is the case e.g. if it hadn't already been fetched with an
|
||||
* inner join and if it's an optional association, and if previous paths has already required outer joins. It also
|
||||
* ensures outer joins are used even when Hibernate defaults to inner joins (HHH-12712 and HHH-12999).
|
||||
*
|
||||
* @param from the {@link From} to check for fetches.
|
||||
@@ -741,6 +741,7 @@ public abstract class QueryUtils {
|
||||
return hasRequiredOuterJoin || getAnnotationProperty(attribute, "optional", true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T> T getAnnotationProperty(Attribute<?, ?> attribute, String propertyName, T defaultValue) {
|
||||
|
||||
Class<? extends Annotation> associationAnnotation = ASSOCIATION_TYPES.get(attribute.getPersistentAttributeType());
|
||||
|
||||
@@ -101,10 +101,9 @@ class StringQuery implements DeclaredQuery {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable String countQueryProjection) {
|
||||
|
||||
return DeclaredQuery.of(
|
||||
return DeclaredQuery.of( //
|
||||
countQuery != null ? countQuery : this.queryEnhancer.createCountQueryFor(countQueryProjection), //
|
||||
this.isNative);
|
||||
}
|
||||
@@ -259,36 +258,37 @@ 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) {
|
||||
|
||||
@@ -57,8 +57,6 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher;
|
||||
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -103,12 +101,10 @@ 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;
|
||||
@@ -147,7 +143,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRead() throws Exception {
|
||||
void testRead() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -172,7 +168,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void savesCollectionCorrectly() throws Exception {
|
||||
void savesCollectionCorrectly() {
|
||||
|
||||
assertThat(repository.saveAll(asList(firstUser, secondUser, thirdUser))) //
|
||||
.containsExactlyInAnyOrder(firstUser, secondUser, thirdUser);
|
||||
@@ -186,7 +182,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void savingEmptyCollectionIsNoOp() throws Exception {
|
||||
void savingEmptyCollectionIsNoOp() {
|
||||
assertThat(repository.saveAll(new ArrayList<>())).isEmpty();
|
||||
}
|
||||
|
||||
@@ -207,7 +203,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void existReturnsWhetherAnEntityCanBeLoaded() throws Exception {
|
||||
void existReturnsWhetherAnEntityCanBeLoaded() {
|
||||
|
||||
flushTestUsers();
|
||||
assertThat(repository.existsById(id)).isTrue();
|
||||
@@ -237,7 +233,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsAllSortedCorrectly() throws Exception {
|
||||
void returnsAllSortedCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -246,7 +242,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-296
|
||||
void returnsAllIgnoreCaseSortedCorrectly() throws Exception {
|
||||
void returnsAllIgnoreCaseSortedCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -306,7 +302,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesManipulatingQuery() throws Exception {
|
||||
void executesManipulatingQuery() {
|
||||
|
||||
flushTestUsers();
|
||||
repository.renameAllUsersTo("newLastname");
|
||||
@@ -320,11 +316,11 @@ public class UserRepositoryTests {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
repository.findByLastname((String) null);
|
||||
repository.findByLastname(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindByLastname() throws Exception {
|
||||
void testFindByLastname() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -335,7 +331,7 @@ public class UserRepositoryTests {
|
||||
* Tests, that searching by the email address of the reference user returns exactly that instance.
|
||||
*/
|
||||
@Test
|
||||
void testFindByEmailAddress() throws Exception {
|
||||
void testFindByEmailAddress() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -358,7 +354,7 @@ public class UserRepositoryTests {
|
||||
* Tests that all users get deleted by triggering {@link UserRepository#deleteAll()}.
|
||||
*/
|
||||
@Test
|
||||
void deleteAll() throws Exception {
|
||||
void deleteAll() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -472,14 +468,14 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesSingleEntitySpecificationCorrectly() throws Exception {
|
||||
void executesSingleEntitySpecificationCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
assertThat(repository.findOne(userHasFirstname("Oliver"))).contains(firstUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsNullIfNoEntityFoundForSingleEntitySpecification() throws Exception {
|
||||
void returnsNullIfNoEntityFoundForSingleEntitySpecification() {
|
||||
|
||||
flushTestUsers();
|
||||
assertThat(repository.findOne(userHasLastname("Beauford"))).isNotPresent();
|
||||
@@ -524,7 +520,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesMethodWithAnnotatedNamedParametersCorrectly() throws Exception {
|
||||
void executesMethodWithAnnotatedNamedParametersCorrectly() {
|
||||
|
||||
firstUser = repository.save(firstUser);
|
||||
secondUser = repository.save(secondUser);
|
||||
@@ -533,7 +529,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesMethodWithNamedParametersCorrectlyOnMethodsWithQueryCreation() throws Exception {
|
||||
void executesMethodWithNamedParametersCorrectlyOnMethodsWithQueryCreation() {
|
||||
|
||||
firstUser = repository.save(firstUser);
|
||||
secondUser = repository.save(secondUser);
|
||||
@@ -542,7 +538,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesLikeAndOrderByCorrectly() throws Exception {
|
||||
void executesLikeAndOrderByCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -551,7 +547,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesNotLikeCorrectly() throws Exception {
|
||||
void executesNotLikeCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -559,7 +555,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesSimpleNotCorrectly() throws Exception {
|
||||
void executesSimpleNotCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -567,21 +563,21 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsSameListIfNoSpecGiven() throws Exception {
|
||||
void returnsSameListIfNoSpecGiven() {
|
||||
|
||||
flushTestUsers();
|
||||
assertSameElements(repository.findAll(), repository.findAll((Specification<User>) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsSameListIfNoSortIsGiven() throws Exception {
|
||||
void returnsSameListIfNoSortIsGiven() {
|
||||
|
||||
flushTestUsers();
|
||||
assertSameElements(repository.findAll(Sort.unsorted()), repository.findAll());
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsSamePageIfNoSpecGiven() throws Exception {
|
||||
void returnsSamePageIfNoSpecGiven() {
|
||||
|
||||
Pageable pageable = PageRequest.of(0, 1);
|
||||
|
||||
@@ -590,14 +586,14 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsAllAsPageIfNoPageableIsGiven() throws Exception {
|
||||
void returnsAllAsPageIfNoPageableIsGiven() {
|
||||
|
||||
flushTestUsers();
|
||||
assertThat(repository.findAll(Pageable.unpaged())).isEqualTo(new PageImpl<>(repository.findAll()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeDetachedObject() throws Exception {
|
||||
void removeDetachedObject() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -608,14 +604,14 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesPagedSpecificationsCorrectly() throws Exception {
|
||||
void executesPagedSpecificationsCorrectly() {
|
||||
|
||||
Page<User> result = executeSpecWithSort(Sort.unsorted());
|
||||
assertThat(result.getContent()).isSubsetOf(firstUser, thirdUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesPagedSpecificationsWithSortCorrectly() throws Exception {
|
||||
void executesPagedSpecificationsWithSortCorrectly() {
|
||||
|
||||
Page<User> result = executeSpecWithSort(Sort.by(Direction.ASC, "lastname"));
|
||||
|
||||
@@ -623,7 +619,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesPagedSpecificationWithSortCorrectly2() throws Exception {
|
||||
void executesPagedSpecificationWithSortCorrectly2() {
|
||||
|
||||
Page<User> result = executeSpecWithSort(Sort.by(Direction.DESC, "lastname"));
|
||||
|
||||
@@ -631,7 +627,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesQueryMethodWithDeepTraversalCorrectly() throws Exception {
|
||||
void executesQueryMethodWithDeepTraversalCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -644,7 +640,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesFindByColleaguesLastnameCorrectly() throws Exception {
|
||||
void executesFindByColleaguesLastnameCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -658,7 +654,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesFindByNotNullLastnameCorrectly() throws Exception {
|
||||
void executesFindByNotNullLastnameCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -666,7 +662,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesFindByNullLastnameCorrectly() throws Exception {
|
||||
void executesFindByNullLastnameCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
User forthUser = repository.save(new User("Foo", null, "email@address.com"));
|
||||
@@ -675,7 +671,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void findsSortedByLastname() throws Exception {
|
||||
void findsSortedByLastname() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -1676,7 +1672,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-606
|
||||
void findByEmptyCollectionOfStrings() throws Exception {
|
||||
void findByEmptyCollectionOfStrings() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -1685,7 +1681,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-606
|
||||
void findByEmptyCollectionOfIntegers() throws Exception {
|
||||
void findByEmptyCollectionOfIntegers() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -1694,7 +1690,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-606
|
||||
void findByEmptyArrayOfIntegers() throws Exception {
|
||||
void findByEmptyArrayOfIntegers() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -1736,7 +1732,7 @@ public class UserRepositoryTests {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
try (Stream<User> stream = repository.streamAllPaged(PageRequest.of(0, 2));) {
|
||||
try (Stream<User> stream = repository.streamAllPaged(PageRequest.of(0, 2))) {
|
||||
assertThat(stream).hasSize(2);
|
||||
}
|
||||
}
|
||||
@@ -2088,13 +2084,11 @@ public class UserRepositoryTests {
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() -> {
|
||||
repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.sortBy(Sort.by("firstname")).oneValue());
|
||||
});
|
||||
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() -> repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.sortBy(Sort.by("firstname")).oneValue()));
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
@@ -2552,7 +2546,7 @@ public class UserRepositoryTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1307
|
||||
void testFindByEmailAddressJdbcStyleParameter() throws Exception {
|
||||
void testFindByEmailAddressJdbcStyleParameter() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
/**
|
||||
@@ -42,8 +41,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
class ExpressionBasedStringQueryUnitTests {
|
||||
|
||||
private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
|
||||
@Mock
|
||||
JpaEntityMetadata<?> metadata;
|
||||
@Mock JpaEntityMetadata<?> metadata;
|
||||
|
||||
@Test // DATAJPA-170
|
||||
void shouldReturnQueryWithDomainTypeExpressionReplacedWithSimpleDomainTypeName() {
|
||||
@@ -93,6 +91,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
|
||||
@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"
|
||||
@@ -105,16 +104,17 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
|
||||
@Test
|
||||
void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, SPEL_PARSER, true);
|
||||
|
||||
assertThat(query.isNativeQuery()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectSimpleNativeQueriesWithoutSpelAsNonNative() {
|
||||
void shouldDetectSimpleNativeQueriesWithoutSpelAsNative() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata, SPEL_PARSER, true);
|
||||
|
||||
assertThat(query.isNativeQuery()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -74,10 +73,8 @@ 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;
|
||||
@@ -520,7 +517,7 @@ public class JpaQueryMethodUnitTests {
|
||||
|
||||
interface ValidRepository extends Repository<User, Integer> {
|
||||
|
||||
@Query(value = "Select u from User u where u.lastname = ?1", nativeQuery = true)
|
||||
@Query(value = "select u from User u where u.lastname = ?1", nativeQuery = true)
|
||||
List<User> findByLastname(String lastname);
|
||||
|
||||
@Query(name = "HateoasAwareSpringDataWebConfiguration.bar")
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QueryEnhancerFactory}.
|
||||
@@ -28,18 +28,22 @@ class QueryEnhancerFactoryUnitTests {
|
||||
|
||||
@Test
|
||||
void createsDefaultImplementationForNonNativeQuery() {
|
||||
StringQuery query = new StringQuery("Select new User(u.firstname) from User u", false);
|
||||
|
||||
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);
|
||||
|
||||
StringQuery query = new StringQuery("select * from User", true);
|
||||
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query);
|
||||
|
||||
assertThat(queryEnhancer) //
|
||||
.isInstanceOf(JSqlParserQueryEnhancer.class);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -24,16 +32,8 @@ 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}.
|
||||
* Unit tests for {@link QueryEnhancer}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
@@ -114,6 +114,7 @@ class QueryEnhancerUnitTests {
|
||||
}
|
||||
|
||||
public static Stream<Arguments> detectsAliasWithUCorrectlySource() {
|
||||
|
||||
return Stream.of( //
|
||||
Arguments.of(new StringQuery(QUERY, true), "u"), //
|
||||
Arguments.of(new StringQuery(SIMPLE_QUERY, false), "u"), //
|
||||
@@ -130,6 +131,7 @@ class QueryEnhancerUnitTests {
|
||||
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);
|
||||
}
|
||||
@@ -138,6 +140,7 @@ class QueryEnhancerUnitTests {
|
||||
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");
|
||||
@@ -147,6 +150,7 @@ class QueryEnhancerUnitTests {
|
||||
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");
|
||||
}
|
||||
@@ -157,6 +161,7 @@ class QueryEnhancerUnitTests {
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -166,11 +171,12 @@ class QueryEnhancerUnitTests {
|
||||
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() {
|
||||
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);
|
||||
@@ -185,6 +191,7 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@Test // DATAJPA-148
|
||||
void doesNotPrefixSortsIfFunction() {
|
||||
|
||||
StringQuery query = new StringQuery("select p from Person p", true);
|
||||
Sort sort = Sort.by("sum(foo)");
|
||||
|
||||
@@ -207,6 +214,7 @@ class QueryEnhancerUnitTests {
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -222,15 +230,17 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@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() {
|
||||
void detectsAliasesInPlainJoins() {
|
||||
|
||||
StringQuery query = new StringQuery("select p from Customer c join c.productOrder p where p.delaye = true", true);
|
||||
StringQuery query = new StringQuery("select p from Customer c join c.productOrder p where p.delay = true", true);
|
||||
Sort sort = Sort.by("p.lineItems");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "c"), "order by p.lineItems asc");
|
||||
@@ -238,13 +248,17 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
@@ -259,6 +273,7 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@Test // DATAJPA-815
|
||||
void doesPrefixPropertyWithNative() {
|
||||
|
||||
StringQuery query = new StringQuery("Select * from Cat c join Dog d", true);
|
||||
Sort sort = Sort.by("dPropertyStartingWithJoinAlias");
|
||||
|
||||
@@ -267,7 +282,9 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsConstructorExpressionInDistinctQuery() {
|
||||
|
||||
StringQuery query = new StringQuery("select distinct new Foo() from Bar b", false);
|
||||
|
||||
assertThat(getEnhancer(query).hasConstructorExpression()).isTrue();
|
||||
}
|
||||
|
||||
@@ -286,19 +303,25 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
@@ -308,15 +331,17 @@ class QueryEnhancerUnitTests {
|
||||
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() {
|
||||
void doesNotPrefixUnsafeJpaSortFunctionCalls() {
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -349,7 +374,7 @@ class QueryEnhancerUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainesAliasedFunctionForDifferentProperty() {
|
||||
void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainsAliasedFunctionForDifferentProperty() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT m.name, AVG(m.price) AS avgPrice FROM Magazine m", true);
|
||||
Sort sort = Sort.by("name", "avgPrice");
|
||||
@@ -464,11 +489,13 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@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");
|
||||
}
|
||||
@@ -562,12 +589,11 @@ class QueryEnhancerUnitTests {
|
||||
@MethodSource("findProjectionClauseWithDistinctSource")
|
||||
void findProjectionClauseWithDistinct(DeclaredQuery query, String expected) {
|
||||
|
||||
SoftAssertions.assertSoftly(sofly -> {
|
||||
sofly.assertThat(getEnhancer(query).getProjection()).isEqualTo(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"), //
|
||||
@@ -591,17 +617,21 @@ class QueryEnhancerUnitTests {
|
||||
// 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() {
|
||||
void countProjectionDistinctQueryIncludesNewLineAfterFromAndBeforeJoin() {
|
||||
|
||||
StringQuery originalQuery = new StringQuery(
|
||||
"SELECT DISTINCT entity1\nFROM Entity1 entity1\nLEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", true);
|
||||
|
||||
@@ -611,17 +641,21 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@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");
|
||||
}
|
||||
@@ -651,6 +685,7 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@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);
|
||||
|
||||
@@ -661,9 +696,11 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@Test // GH-2441
|
||||
void correctApplySortOnComplexNestedFunctionQuery() {
|
||||
String queryString = "SELECT dd.institutesIds FROM (\n" + " SELECT\n"
|
||||
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"
|
||||
+ " FROM\n" //
|
||||
+ " city c\n" //
|
||||
+ " ) dd";
|
||||
|
||||
StringQuery nativeQuery = new StringQuery(queryString, true);
|
||||
@@ -671,10 +708,12 @@ class QueryEnhancerUnitTests {
|
||||
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")), //
|
||||
@@ -695,6 +734,7 @@ class QueryEnhancerUnitTests {
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -703,5 +743,4 @@ class QueryEnhancerUnitTests {
|
||||
private static QueryEnhancer getEnhancer(DeclaredQuery query) {
|
||||
return QueryEnhancerFactory.forQuery(query);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.data.jpa.repository.query.StringQuery.ParameterBindin
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class QueryParameterSetterFactoryUnitTests {
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ import jakarta.persistence.spi.PersistenceProviderResolverHolder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.jpa.domain.sample.Category;
|
||||
@@ -66,13 +65,13 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
* @author Sébastien Péralta
|
||||
* @author Jens Schauder
|
||||
* @author Patrice Blanchardie
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
public class QueryUtilsIntegrationTests {
|
||||
|
||||
@PersistenceContext
|
||||
EntityManager em;
|
||||
@PersistenceContext EntityManager em;
|
||||
|
||||
@Test // DATAJPA-403
|
||||
void reusesExistingJoinForExpression() {
|
||||
@@ -183,7 +182,7 @@ public class QueryUtilsIntegrationTests {
|
||||
CriteriaQuery<InvoiceItem> query = builder.createQuery(InvoiceItem.class);
|
||||
Root<InvoiceItem> root = query.from(InvoiceItem.class);
|
||||
|
||||
// given an existing inner join an nested optional
|
||||
// given an existing inner join a nested optional
|
||||
root.join("invoice").join("order");
|
||||
|
||||
QueryUtils.toExpressionRecursively(root, PropertyPath.from("invoice.order.customer.name", InvoiceItem.class),
|
||||
@@ -237,7 +236,7 @@ public class QueryUtilsIntegrationTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-454
|
||||
void createsJoingToTraverseCollectionPath() {
|
||||
void createsJoinToTraverseCollectionPath() {
|
||||
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<User> query = builder.createQuery(User.class);
|
||||
@@ -319,7 +318,7 @@ public class QueryUtilsIntegrationTests {
|
||||
* https://github.com/javaee/jpa-spec/issues/169 Compare to: {@link EclipseLinkQueryUtilsIntegrationTests}
|
||||
*/
|
||||
@Test // DATAJPA-1238
|
||||
void demonstrateDifferentBehavorOfGetJoin() {
|
||||
void demonstrateDifferentBehaviorOfGetJoin() {
|
||||
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<User> query = builder.createQuery(User.class);
|
||||
@@ -348,40 +347,32 @@ 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;
|
||||
}
|
||||
|
||||
@@ -399,8 +390,7 @@ 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() throws Exception {
|
||||
void createsCountQueryCorrectly() {
|
||||
assertCountQuery(QUERY, COUNT_QUERY);
|
||||
}
|
||||
|
||||
@@ -64,47 +64,45 @@ class QueryUtilsUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForDistinctQueries() throws Exception {
|
||||
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() throws Exception {
|
||||
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() throws Exception {
|
||||
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() throws Exception {
|
||||
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() throws Exception {
|
||||
|
||||
void createsCountQueryForAliasesCorrectly() {
|
||||
assertCountQuery("select u from User as u", "select count(u) from User as u");
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsShortJpaSyntax() throws Exception {
|
||||
|
||||
void allowsShortJpaSyntax() {
|
||||
assertCountQuery(SIMPLE_QUERY, COUNT_QUERY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsAliasCorrectly() throws Exception {
|
||||
void detectsAliasCorrectly() {
|
||||
|
||||
assertThat(detectAlias(QUERY)).isEqualTo("u");
|
||||
assertThat(detectAlias(SIMPLE_QUERY)).isEqualTo("u");
|
||||
@@ -180,14 +178,14 @@ class QueryUtilsUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-342
|
||||
void usesReturnedVariableInCOuntProjectionIfSet() {
|
||||
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() {
|
||||
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)");
|
||||
@@ -233,7 +231,7 @@ class QueryUtilsUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-726
|
||||
void detectsAliassesInPlainJoins() {
|
||||
void detectsAliasesInPlainJoins() {
|
||||
|
||||
String query = "select p from Customer c join c.productOrder p where p.delayed = true";
|
||||
Sort sort = Sort.by("p.lineItems");
|
||||
@@ -294,7 +292,7 @@ class QueryUtilsUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixUnsageJpaSortFunctionCalls() {
|
||||
void doesNotPrefixUnsafeJpaSortFunctionCalls() {
|
||||
|
||||
JpaSort sort = JpaSort.unsafe("sum(foo)");
|
||||
assertThat(applySorting("select p from Person p", sort, "p")).endsWith("order by sum(foo) asc");
|
||||
@@ -328,7 +326,7 @@ class QueryUtilsUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainesAliasedFunctionForDifferentProperty() {
|
||||
void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainsAliasedFunctionForDifferentProperty() {
|
||||
|
||||
String query = "SELECT m.name, AVG(m.price) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("name", "avgPrice");
|
||||
@@ -400,8 +398,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");
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.util.List;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.InParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
|
||||
|
||||
Reference in New Issue
Block a user