Upgrade to Querydsl 5.0.

Move off our own Querydsl copies, as Querydsl 5.0 ships MongoDB Document API support.
Remove package-private duplicates of Querydsl code.
Introduce SpringDataMongodbQuerySupport to provide a well-formatted toString representation of the actual query.

Original Pull Request: #3674
This commit is contained in:
Mark Paluch
2021-06-17 10:50:13 +02:00
committed by Christoph Strobl
parent 5bd7ff1413
commit 986ea39f90
11 changed files with 384 additions and 924 deletions

View File

@@ -1,459 +0,0 @@
/*
* Copyright 2018-2021 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.mongodb.repository.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Pattern;
import org.bson.BsonJavaScript;
import org.bson.BsonRegularExpression;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.mongodb.DBRef;
import com.querydsl.core.types.*;
import com.querydsl.mongodb.MongodbOps;
/**
* <p>
* Serializes the given Querydsl query to a Document query for MongoDB.
* </p>
* <p>
* Original implementation source {@link com.querydsl.mongodb.MongodbSerializer} by {@literal The Querydsl Team}
* (<a href="http://www.querydsl.com/team">http://www.querydsl.com/team</a>) licensed under the Apache License, Version
* 2.0.
* </p>
* Modified to use {@link Document} instead of {@link com.mongodb.DBObject}, updated nullable types and code format. Use
* Bson specific types and add {@link QuerydslMongoOps#NO_MATCH}.
*
* @author laimw
* @author Mark Paluch
* @author Christoph Strobl
* @author Mikhail Kaduchka
* @author Enrique Leon Molina
* @since 2.1
*/
abstract class MongodbDocumentSerializer implements Visitor<Object, Void> {
@Nullable
Object handle(Expression<?> expression) {
return expression.accept(this, null);
}
/**
* Create the MongoDB specific query document.
*
* @param predicate must not be {@literal null}.
* @return empty {@link Document} by default.
*/
Document toQuery(Predicate predicate) {
Object value = handle(predicate);
if (value == null) {
return new Document();
}
Assert.isInstanceOf(Document.class, value,
() -> String.format("Invalid type. Expected Document but found %s", value.getClass()));
return (Document) value;
}
/**
* Create the MongoDB specific sort document.
*
* @param orderBys must not be {@literal null}.
* @return empty {@link Document} by default.
*/
Document toSort(List<OrderSpecifier<?>> orderBys) {
Document sort = new Document();
orderBys.forEach(orderSpecifier -> {
Object key = orderSpecifier.getTarget().accept(this, null);
Assert.notNull(key, () -> String.format("Mapped sort key for %s must not be null!", orderSpecifier));
sort.append(key.toString(), orderSpecifier.getOrder() == Order.ASC ? 1 : -1);
});
return sort;
}
/*
* (non-Javadoc)
* @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.Constant, java.lang.Void)
*/
@Override
public Object visit(Constant<?> expr, Void context) {
if (!Enum.class.isAssignableFrom(expr.getType())) {
return expr.getConstant();
}
@SuppressWarnings("unchecked") // Guarded by previous check
Constant<? extends Enum<?>> expectedExpr = (Constant<? extends Enum<?>>) expr;
return expectedExpr.getConstant().name();
}
/*
* (non-Javadoc)
* @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.TemplateExpression, java.lang.Void)
*/
@Override
public Object visit(TemplateExpression<?> expr, Void context) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.FactoryExpression, java.lang.Void)
*/
@Override
public Object visit(FactoryExpression<?> expr, Void context) {
throw new UnsupportedOperationException();
}
protected String asDBKey(Operation<?> expr, int index) {
String key = (String) asDBValue(expr, index);
Assert.hasText(key, () -> String.format("Mapped key must not be null nor empty for expression %s.", expr));
return key;
}
@Nullable
protected Object asDBValue(Operation<?> expr, int index) {
return expr.getArg(index).accept(this, null);
}
private String regexValue(Operation<?> expr, int index) {
Object value = expr.getArg(index).accept(this, null);
Assert.notNull(value, () -> String.format("Regex for %s must not be null.", expr));
return Pattern.quote(value.toString());
}
protected Document asDocument(String key, @Nullable Object value) {
return new Document(key, value);
}
@SuppressWarnings("unchecked")
@Override
public Object visit(Operation<?> expr, Void context) {
Operator op = expr.getOperator();
if (op == Ops.EQ) {
if (expr.getArg(0) instanceof Operation) {
Operation<?> lhs = (Operation<?>) expr.getArg(0);
if (lhs.getOperator() == Ops.COL_SIZE || lhs.getOperator() == Ops.ARRAY_SIZE) {
return asDocument(asDBKey(lhs, 0), asDocument("$size", asDBValue(expr, 1)));
} else {
throw new UnsupportedOperationException("Illegal operation " + expr);
}
} else if (expr.getArg(0) instanceof Path) {
Path<?> path = (Path<?>) expr.getArg(0);
Constant<?> constant = (Constant<?>) expr.getArg(1);
return asDocument(asDBKey(expr, 0), convert(path, constant));
}
} else if (op == Ops.STRING_IS_EMPTY) {
return asDocument(asDBKey(expr, 0), "");
} else if (op == Ops.AND) {
Queue<Map<Object, Object>> pendingDocuments = collectConnectorArgs("$and", expr);
List<Map<Object, Object>> unmergeableDocuments = new ArrayList<>();
List<Map<Object, Object>> generatedDocuments = new ArrayList<>();
while (!pendingDocuments.isEmpty()) {
Map<Object, Object> lhs = pendingDocuments.poll();
for (Map<Object, Object> rhs : pendingDocuments) {
Set<Object> lhs2 = new LinkedHashSet<>(lhs.keySet());
lhs2.retainAll(rhs.keySet());
if (lhs2.isEmpty()) {
lhs.putAll(rhs);
} else {
unmergeableDocuments.add(rhs);
}
}
generatedDocuments.add(lhs);
pendingDocuments = new LinkedList<>(unmergeableDocuments);
unmergeableDocuments = new LinkedList<>();
}
return generatedDocuments.size() == 1 ? generatedDocuments.get(0) : asDocument("$and", generatedDocuments);
} else if (op == Ops.NOT) {
// Handle the not's child
Operation<?> subOperation = (Operation<?>) expr.getArg(0);
Operator subOp = subOperation.getOperator();
if (subOp == Ops.IN) {
return visit(
ExpressionUtils.operation(Boolean.class, Ops.NOT_IN, subOperation.getArg(0), subOperation.getArg(1)),
context);
} else {
Document arg = (Document) handle(expr.getArg(0));
return negate(arg);
}
} else if (op == Ops.OR) {
return asDocument("$or", collectConnectorArgs("$or", expr));
} else if (op == Ops.NE) {
Path<?> path = (Path<?>) expr.getArg(0);
Constant<?> constant = (Constant<?>) expr.getArg(1);
return asDocument(asDBKey(expr, 0), asDocument("$ne", convert(path, constant)));
} else if (op == Ops.STARTS_WITH) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1)));
} else if (op == Ops.STARTS_WITH_IC) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1), "i"));
} else if (op == Ops.ENDS_WITH) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regexValue(expr, 1) + "$"));
} else if (op == Ops.ENDS_WITH_IC) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regexValue(expr, 1) + "$", "i"));
} else if (op == Ops.EQ_IGNORE_CASE) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1) + "$", "i"));
} else if (op == Ops.STRING_CONTAINS) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(".*" + regexValue(expr, 1) + ".*"));
} else if (op == Ops.STRING_CONTAINS_IC) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(".*" + regexValue(expr, 1) + ".*", "i"));
} else if (op == Ops.MATCHES) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(asDBValue(expr, 1).toString()));
} else if (op == Ops.MATCHES_IC) {
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(asDBValue(expr, 1).toString(), "i"));
} else if (op == Ops.LIKE) {
String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regex));
} else if (op == Ops.BETWEEN) {
Document value = new Document("$gte", asDBValue(expr, 1));
value.append("$lte", asDBValue(expr, 2));
return asDocument(asDBKey(expr, 0), value);
} else if (op == Ops.IN) {
int constIndex = 0;
int exprIndex = 1;
if (expr.getArg(1) instanceof Constant<?>) {
constIndex = 1;
exprIndex = 0;
}
if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
@SuppressWarnings("unchecked") // guarded by previous check
Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant();
return asDocument(asDBKey(expr, exprIndex), asDocument("$in", values));
} else {
Path<?> path = (Path<?>) expr.getArg(exprIndex);
Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
return asDocument(asDBKey(expr, exprIndex), convert(path, constant));
}
} else if (op == Ops.NOT_IN) {
int constIndex = 0;
int exprIndex = 1;
if (expr.getArg(1) instanceof Constant<?>) {
constIndex = 1;
exprIndex = 0;
}
if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
@SuppressWarnings("unchecked") // guarded by previous check
Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant();
return asDocument(asDBKey(expr, exprIndex), asDocument("$nin", values));
} else {
Path<?> path = (Path<?>) expr.getArg(exprIndex);
Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
return asDocument(asDBKey(expr, exprIndex), asDocument("$ne", convert(path, constant)));
}
} else if (op == Ops.COL_IS_EMPTY) {
List<Object> list = new ArrayList<>(2);
list.add(asDocument(asDBKey(expr, 0), new ArrayList<Object>()));
list.add(asDocument(asDBKey(expr, 0), asDocument("$exists", false)));
return asDocument("$or", list);
} else if (op == Ops.LT) {
return asDocument(asDBKey(expr, 0), asDocument("$lt", asDBValue(expr, 1)));
} else if (op == Ops.GT) {
return asDocument(asDBKey(expr, 0), asDocument("$gt", asDBValue(expr, 1)));
} else if (op == Ops.LOE) {
return asDocument(asDBKey(expr, 0), asDocument("$lte", asDBValue(expr, 1)));
} else if (op == Ops.GOE) {
return asDocument(asDBKey(expr, 0), asDocument("$gte", asDBValue(expr, 1)));
} else if (op == Ops.IS_NULL) {
return asDocument(asDBKey(expr, 0), asDocument("$exists", false));
} else if (op == Ops.IS_NOT_NULL) {
return asDocument(asDBKey(expr, 0), asDocument("$exists", true));
} else if (op == Ops.CONTAINS_KEY) {
Path<?> path = (Path<?>) expr.getArg(0);
Expression<?> key = expr.getArg(1);
return asDocument(visit(path, context) + "." + key.toString(), asDocument("$exists", true));
} else if (op == MongodbOps.NEAR) {
return asDocument(asDBKey(expr, 0), asDocument("$near", asDBValue(expr, 1)));
} else if (op == MongodbOps.NEAR_SPHERE) {
return asDocument(asDBKey(expr, 0), asDocument("$nearSphere", asDBValue(expr, 1)));
} else if (op == MongodbOps.ELEM_MATCH) {
return asDocument(asDBKey(expr, 0), asDocument("$elemMatch", asDBValue(expr, 1)));
} else if (op == QuerydslMongoOps.NO_MATCH) {
return new Document("$where", new BsonJavaScript("function() { return false }"));
}
throw new UnsupportedOperationException("Illegal operation " + expr);
}
private Object negate(Document arg) {
List<Object> list = new ArrayList<>();
for (Map.Entry<String, Object> entry : arg.entrySet()) {
if (entry.getKey().equals("$or")) {
list.add(asDocument("$nor", entry.getValue()));
} else if (entry.getKey().equals("$and")) {
List<Object> list2 = new ArrayList<>();
for (Object o : ((Collection) entry.getValue())) {
list2.add(negate((Document) o));
}
list.add(asDocument("$or", list2));
} else if (entry.getValue() instanceof Pattern || entry.getValue() instanceof BsonRegularExpression) {
list.add(asDocument(entry.getKey(), asDocument("$not", entry.getValue())));
} else if (entry.getValue() instanceof Document) {
list.add(negate(entry.getKey(), (Document) entry.getValue()));
} else {
list.add(asDocument(entry.getKey(), asDocument("$ne", entry.getValue())));
}
}
return list.size() == 1 ? list.get(0) : asDocument("$or", list);
}
private Object negate(String key, Document value) {
if (value.size() == 1) {
return asDocument(key, asDocument("$not", value));
} else {
List<Object> list2 = new ArrayList<>();
for (Map.Entry<String, Object> entry2 : value.entrySet()) {
list2.add(asDocument(key, asDocument("$not", asDocument(entry2.getKey(), entry2.getValue()))));
}
return asDocument("$or", list2);
}
}
protected Object convert(Path<?> property, Constant<?> constant) {
if (isReference(property)) {
return asReference(constant.getConstant());
} else if (isId(property)) {
if (isReference(property.getMetadata().getParent())) {
return asReferenceKey(property.getMetadata().getParent().getType(), constant.getConstant());
} else if (constant.getType().equals(String.class) && isImplicitObjectIdConversion()) {
String id = (String) constant.getConstant();
return ObjectId.isValid(id) ? new ObjectId(id) : id;
}
}
return visit(constant, null);
}
protected boolean isImplicitObjectIdConversion() {
return true;
}
protected DBRef asReferenceKey(Class<?> entity, Object id) {
// TODO override in subclass
throw new UnsupportedOperationException();
}
protected abstract DBRef asReference(Object constant);
protected abstract boolean isReference(@Nullable Path<?> arg);
protected boolean isId(Path<?> arg) {
// TODO override in subclass
return false;
}
@Override
public String visit(Path<?> expr, Void context) {
PathMetadata metadata = expr.getMetadata();
if (metadata.getParent() != null) {
Path<?> parent = metadata.getParent();
if (parent.getMetadata().getPathType() == PathType.DELEGATE) {
parent = parent.getMetadata().getParent();
}
if (metadata.getPathType() == PathType.COLLECTION_ANY) {
return visit(parent, context);
} else if (parent.getMetadata().getPathType() != PathType.VARIABLE) {
String rv = getKeyForPath(expr, metadata);
String parentStr = visit(parent, context);
return rv != null ? parentStr + "." + rv : parentStr;
}
}
return getKeyForPath(expr, metadata);
}
protected String getKeyForPath(Path<?> expr, PathMetadata metadata) {
return metadata.getElement().toString();
}
@Override
public Object visit(SubQueryExpression<?> expr, Void context) {
throw new UnsupportedOperationException();
}
@Override
public Object visit(ParamExpression<?> expr, Void context) {
throw new UnsupportedOperationException();
}
private LinkedList<Map<Object, Object>> collectConnectorArgs(String operator, Operation<?> operation) {
LinkedList<Map<Object, Object>> pendingDocuments = new LinkedList<>();
for (Expression<?> exp : operation.getArgs()) {
Map<Object, Object> document = (Map<Object, Object>) handle(exp);
if (document.keySet().size() == 1 && document.containsKey(operator)) {
pendingDocuments.addAll((Collection<Map<Object, Object>>) document.get(operator));
} else {
pendingDocuments.add(document);
}
}
return pendingDocuments;
}
}

View File

@@ -34,6 +34,8 @@ import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.ParamExpression;
import com.querydsl.core.types.Predicate;
import com.querydsl.mongodb.document.AbstractMongodbQuery;
import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* {@code QuerydslAbstractMongodbQuery} provides a base class for general Querydsl query implementation.
@@ -49,8 +51,12 @@ import com.querydsl.core.types.Predicate;
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
* @deprecated since 3.3, use Querydsl's {@link AbstractMongodbQuery} directly. This class is deprecated for removal
* with the next major release.
*/
@Deprecated
public abstract class QuerydslAbstractMongodbQuery<K, Q extends QuerydslAbstractMongodbQuery<K, Q>>
extends AbstractMongodbQuery<Q>
implements SimpleQuery<Q> {
private static final JsonWriterSettings JSON_WRITER_SETTINGS = JsonWriterSettings.builder().outputMode(JsonMode.SHELL)
@@ -67,6 +73,8 @@ public abstract class QuerydslAbstractMongodbQuery<K, Q extends QuerydslAbstract
@SuppressWarnings("unchecked")
QuerydslAbstractMongodbQuery(MongodbDocumentSerializer serializer) {
super(serializer);
this.queryMixin = new QueryMixin<>((Q) this, new DefaultQueryMetadata(), false);
this.serializer = serializer;
}
@@ -158,22 +166,6 @@ public abstract class QuerydslAbstractMongodbQuery<K, Q extends QuerydslAbstract
return projection;
}
/**
* Compute the filer {@link Document} from the given {@link Predicate}.
*
* @param predicate can be {@literal null}.
* @return an empty {@link Document} if predicate is {@literal null}.
* @see MongodbDocumentSerializer#toQuery(Predicate)
*/
protected Document createQuery(@Nullable Predicate predicate) {
if (predicate == null) {
return new Document();
}
return serializer.toQuery(predicate);
}
/**
* Compute the sort {@link Document} from the given list of {@link OrderSpecifier order specifiers}.
*
@@ -194,14 +186,6 @@ public abstract class QuerydslAbstractMongodbQuery<K, Q extends QuerydslAbstract
return queryMixin;
}
/**
* Get the where definition as a Document instance
*
* @return
*/
Document asDocument() {
return createQuery(queryMixin.getMetadata().getWhere());
}
/**
* Returns the {@literal Mongo Shell} representation of the query. <br />

View File

@@ -37,7 +37,10 @@ import com.querydsl.mongodb.MongodbOps;
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
* @deprecated since 3.3, use Querydsl's {@link com.querydsl.mongodb.document.AnyEmbeddedBuilder} directly. This class
* is deprecated for removal with the next major release.
*/
@Deprecated
public class QuerydslAnyEmbeddedBuilder<Q extends QuerydslAbstractMongodbQuery<K, Q>, K> {
private final QueryMixin<Q> queryMixin;

View File

@@ -1,272 +0,0 @@
/*
* Copyright 2018-2021 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.mongodb.repository.support;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithProjection;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import com.mysema.commons.lang.CloseableIterator;
import com.querydsl.core.Fetchable;
import com.querydsl.core.JoinExpression;
import com.querydsl.core.QueryMetadata;
import com.querydsl.core.QueryModifiers;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Operation;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.CollectionPathBase;
/**
* {@link Fetchable} MongoDB query with utilizing {@link MongoOperations} for command execution.
*
* @param <K> result type
* @param <Q> concrete subtype
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
*/
abstract class QuerydslFetchableMongodbQuery<K, Q extends QuerydslFetchableMongodbQuery<K, Q>>
extends QuerydslAbstractMongodbQuery<K, Q> implements Fetchable<K> {
private final Class<K> entityClass;
private final String collection;
private final MongoOperations mongoOperations;
private final FindWithProjection<K> find;
QuerydslFetchableMongodbQuery(MongodbDocumentSerializer serializer, Class<? extends K> entityClass, String collection,
MongoOperations mongoOperations) {
super(serializer);
this.entityClass = (Class<K>) entityClass;
this.collection = collection;
this.mongoOperations = mongoOperations;
find = mongoOperations.query(this.entityClass).inCollection(collection);
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#iterable()
*/
@Override
public CloseableIterator<K> iterate() {
org.springframework.data.util.CloseableIterator<? extends K> stream = mongoOperations.stream(createQuery(),
entityClass, collection);
return new CloseableIterator<K>() {
@Override
public boolean hasNext() {
return stream.hasNext();
}
@Override
public K next() {
return stream.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove from iterator while streaming data.");
}
@Override
public void close() {
stream.close();
}
};
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetch()
*/
@Override
public List<K> fetch() {
return find.matching(createQuery()).all();
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchFirst()
*/
@Override
public K fetchFirst() {
return find.matching(createQuery()).firstValue();
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchOne()
*/
@Override
public K fetchOne() {
return find.matching(createQuery()).oneValue();
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchResults()
*/
@Override
public QueryResults<K> fetchResults() {
long total = fetchCount();
return total > 0L ? new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total)
: QueryResults.emptyResults();
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchCount()
*/
@Override
public long fetchCount() {
return find.matching(Query.of(createQuery()).skip(-1).limit(-1)).count();
}
/**
* Define a join.
*
* @param ref reference
* @param target join target
* @return new instance of {@link QuerydslJoinBuilder}.
*/
public <T> QuerydslJoinBuilder<Q, K, T> join(Path<T> ref, Path<T> target) {
return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
}
/**
* Define a join.
*
* @param ref reference
* @param target join target
* @return new instance of {@link QuerydslJoinBuilder}.
*/
public <T> QuerydslJoinBuilder<Q, K, T> join(CollectionPathBase<?, T, ?> ref, Path<T> target) {
return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
}
/**
* Define a constraint for an embedded object.
*
* @param collection collection must not be {@literal null}.
* @param target target must not be {@literal null}.
* @return new instance of {@link QuerydslAnyEmbeddedBuilder}.
*/
public <T> QuerydslAnyEmbeddedBuilder<Q, K> anyEmbedded(Path<? extends Collection<T>> collection, Path<T> target) {
return new QuerydslAnyEmbeddedBuilder<>(getQueryMixin(), collection);
}
protected org.springframework.data.mongodb.core.query.Query createQuery() {
QueryMetadata metadata = getQueryMixin().getMetadata();
return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
metadata.getOrderBy());
}
protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullable Predicate filter,
@Nullable Expression<?> projection, QueryModifiers modifiers, List<OrderSpecifier<?>> orderBy) {
BasicQuery basicQuery = new BasicQuery(createQuery(filter), createProjection(projection));
Integer limit = modifiers.getLimitAsInteger();
Integer offset = modifiers.getOffsetAsInteger();
if (limit != null) {
basicQuery.limit(limit);
}
if (offset != null) {
basicQuery.skip(offset);
}
if (orderBy.size() > 0) {
basicQuery.setSortObject(createSort(orderBy));
}
return basicQuery;
}
@Nullable
protected Predicate createFilter(QueryMetadata metadata) {
Predicate filter;
if (!metadata.getJoins().isEmpty()) {
filter = ExpressionUtils.allOf(metadata.getWhere(), createJoinFilter(metadata));
} else {
filter = metadata.getWhere();
}
return filter;
}
@SuppressWarnings("unchecked")
@Nullable
protected Predicate createJoinFilter(QueryMetadata metadata) {
LinkedMultiValueMap<Expression<?>, Predicate> predicates = new LinkedMultiValueMap<>();
List<JoinExpression> joins = metadata.getJoins();
for (int i = joins.size() - 1; i >= 0; i--) {
JoinExpression join = joins.get(i);
Path<?> source = (Path) ((Operation<?>) join.getTarget()).getArg(0);
Path<?> target = (Path) ((Operation<?>) join.getTarget()).getArg(1);
Collection<Predicate> extraFilters = predicates.get(target.getRoot());
Predicate filter = ExpressionUtils.allOf(join.getCondition(), allOf(extraFilters));
List<? extends Object> ids = getIds(target.getType(), filter);
if (ids.isEmpty()) {
return ExpressionUtils.predicate(QuerydslMongoOps.NO_MATCH, source);
}
Path<?> path = ExpressionUtils.path(String.class, source, "$id");
predicates.add(source.getRoot(), ExpressionUtils.in((Path<Object>) path, ids));
}
Path<?> source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0);
return allOf(predicates.get(source.getRoot()));
}
private Predicate allOf(Collection<Predicate> predicates) {
return predicates != null ? ExpressionUtils.allOf(predicates) : null;
}
/**
* Fetch the list of ids matching a given condition.
*
* @param targetType must not be {@literal null}.
* @param condition must not be {@literal null}.
* @return empty {@link List} if none found.
*/
protected List<Object> getIds(Class<?> targetType, Predicate condition) {
Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList());
return mongoOperations.findDistinct(query, "_id", targetType, Object.class);
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2018-2021 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.mongodb.repository.support;
import com.querydsl.core.JoinType;
import com.querydsl.core.support.QueryMixin;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
/**
* {@code QuerydslJoinBuilder} is a builder for join constraints.
* <p>
* Original implementation source {@link com.querydsl.mongodb.JoinBuilder} by {@literal The Querydsl Team}
* (<a href="http://www.querydsl.com/team">http://www.querydsl.com/team</a>) licensed under the Apache License, Version
* 2.0.
* </p>
* Modified for usage with {@link QuerydslAbstractMongodbQuery}.
*
* @param <Q>
* @param <T>
* @author tiwe
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
*/
public class QuerydslJoinBuilder<Q extends QuerydslAbstractMongodbQuery<K, Q>, K, T> {
private final QueryMixin<Q> queryMixin;
private final Path<?> ref;
private final Path<T> target;
QuerydslJoinBuilder(QueryMixin<Q> queryMixin, Path<?> ref, Path<T> target) {
this.queryMixin = queryMixin;
this.ref = ref;
this.target = target;
}
/**
* Add the given join conditions.
*
* @param conditions must not be {@literal null}.
* @return the target {@link QueryMixin}.
* @see QueryMixin#on(Predicate)
*/
@SuppressWarnings("unchecked")
public Q on(Predicate... conditions) {
queryMixin.addJoin(JoinType.JOIN, ExpressionUtils.as((Path) ref, target));
queryMixin.on(conditions);
return queryMixin.getSelf();
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2018-2021 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.mongodb.repository.support;
import com.querydsl.core.types.Operator;
/**
* Spring Data specific {@link Operator operators} for usage with Querydsl and MongoDB.
*
* @author Christoph Strobl
* @since 2.1
*/
enum QuerydslMongoOps implements Operator {
/**
* {@link Operator} always evaluating to {@literal false}.
*/
NO_MATCH(Boolean.class);
private final Class<?> type;
QuerydslMongoOps(Class<?> type) {
this.type = type;
}
@Override
public Class<?> getType() {
return type;
}
}

View File

@@ -22,6 +22,9 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.bson.Document;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
@@ -40,19 +43,21 @@ import com.querydsl.core.types.Operation;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.CollectionPathBase;
import com.querydsl.mongodb.MongodbOps;
import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* MongoDB query with utilizing {@link ReactiveMongoOperations} for command execution.
*
* @implNote This class uses {@link MongoOperations} to directly convert documents into the target entity type. Also, we
* want entites to participate in lifecycle events and entity callbacks.
* @param <K> result type
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.2
*/
class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K, ReactiveSpringDataMongodbQuery<K>> {
class ReactiveSpringDataMongodbQuery<K> extends SpringDataMongodbQuerySupport<ReactiveSpringDataMongodbQuery<K>> {
private final Class<K> entityClass;
private final ReactiveMongoOperations mongoOperations;
private final FindWithProjection<K> find;
@@ -60,15 +65,15 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
this(new SpringDataMongodbSerializer(mongoOperations.getConverter()), mongoOperations, entityClass, null);
}
@SuppressWarnings("unchecked")
ReactiveSpringDataMongodbQuery(MongodbDocumentSerializer serializer, ReactiveMongoOperations mongoOperations,
Class<? extends K> entityClass, @Nullable String collection) {
super(serializer);
this.entityClass = (Class<K>) entityClass;
this.mongoOperations = mongoOperations;
this.find = StringUtils.hasText(collection) ? mongoOperations.query(this.entityClass).inCollection(collection)
: mongoOperations.query(this.entityClass);
this.find = StringUtils.hasText(collection) ? mongoOperations.query((Class<K>) entityClass).inCollection(collection)
: mongoOperations.query((Class<K>) entityClass);
}
/**
@@ -99,48 +104,11 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
return createQuery().flatMap(it -> find.matching(it).count());
}
/**
* Define a join.
*
* @param ref reference
* @param target join target
* @return new instance of {@link QuerydslJoinBuilder}.
*/
<T> QuerydslJoinBuilder<ReactiveSpringDataMongodbQuery<K>, K, T> join(Path<T> ref, Path<T> target) {
return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
}
/**
* Define a join.
*
* @param ref reference
* @param target join target
* @return new instance of {@link QuerydslJoinBuilder}.
*/
<T> QuerydslJoinBuilder<ReactiveSpringDataMongodbQuery<K>, K, T> join(CollectionPathBase<?, T, ?> ref,
Path<T> target) {
return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
}
/**
* Define a constraint for an embedded object.
*
* @param collection collection must not be {@literal null}.
* @param target target must not be {@literal null}.
* @return new instance of {@link QuerydslAnyEmbeddedBuilder}.
*/
<T> QuerydslAnyEmbeddedBuilder<ReactiveSpringDataMongodbQuery<K>, K> anyEmbedded(
Path<? extends Collection<T>> collection, Path<T> target) {
return new QuerydslAnyEmbeddedBuilder<>(getQueryMixin(), collection);
}
protected Mono<Query> createQuery() {
QueryMetadata metadata = getQueryMixin().getMetadata();
return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
return createQuery(createReactiveFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
metadata.getOrderBy());
}
@@ -160,7 +128,8 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
.defaultIfEmpty(createQuery(null)) //
.map(it -> {
BasicQuery basicQuery = new BasicQuery(it, createProjection(projection));
Document fields = createProjection(projection);
BasicQuery basicQuery = new BasicQuery(it, fields == null ? new Document() : fields);
Integer limit = modifiers.getLimitAsInteger();
Integer offset = modifiers.getOffsetAsInteger();
@@ -179,11 +148,11 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
});
}
protected Mono<Predicate> createFilter(QueryMetadata metadata) {
protected Mono<Predicate> createReactiveFilter(QueryMetadata metadata) {
if (!metadata.getJoins().isEmpty()) {
return createJoinFilter(metadata).map(it -> ExpressionUtils.allOf(metadata.getWhere(), it))
return createReactiveJoinFilter(metadata).map(it -> ExpressionUtils.allOf(metadata.getWhere(), it))
.switchIfEmpty(Mono.justOrEmpty(metadata.getWhere()));
}
@@ -197,7 +166,7 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
* @return
*/
@SuppressWarnings("unchecked")
protected Mono<Predicate> createJoinFilter(QueryMetadata metadata) {
protected Mono<Predicate> createReactiveJoinFilter(QueryMetadata metadata) {
MultiValueMap<Expression<?>, Mono<Predicate>> predicates = new LinkedMultiValueMap<>();
List<JoinExpression> joins = metadata.getJoins();
@@ -230,7 +199,7 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
Path<?> source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0);
return allOf(predicates.get(source.getRoot())).onErrorResume(NoMatchException.class,
e -> Mono.just(ExpressionUtils.predicate(QuerydslMongoOps.NO_MATCH, e.source)));
e -> Mono.just(ExpressionUtils.predicate(MongodbOps.NO_MATCH, e.source)));
}
private Mono<Predicate> allOf(@Nullable Collection<Mono<Predicate>> predicates) {
@@ -246,8 +215,8 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
*/
protected Flux<Object> getIds(Class<?> targetType, Mono<Predicate> condition) {
return condition.flatMapMany(it -> getIds(targetType, it))
.switchIfEmpty(Flux.defer(() -> getIds(targetType, (Predicate) null)));
return condition.flatMapMany(it -> getJoinIds(targetType, it))
.switchIfEmpty(Flux.defer(() -> getJoinIds(targetType, (Predicate) null)));
}
/**
@@ -257,12 +226,18 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
* @param condition must not be {@literal null}.
* @return empty {@link List} if none found.
*/
protected Flux<Object> getIds(Class<?> targetType, @Nullable Predicate condition) {
protected Flux<Object> getJoinIds(Class<?> targetType, @Nullable Predicate condition) {
return createQuery(Mono.justOrEmpty(condition), null, QueryModifiers.EMPTY, Collections.emptyList())
.flatMapMany(query -> mongoOperations.findDistinct(query, "_id", targetType, Object.class));
}
@Override
protected List<Object> getIds(Class<?> aClass, Predicate predicate) {
throw new UnsupportedOperationException(
"Use create Flux<Object> getIds(Class<?> targetType, Mono<Predicate> condition)");
}
/**
* Marker exception to indicate no matches for a query using reference Id's.
*/

View File

@@ -15,7 +15,27 @@
*/
package org.springframework.data.mongodb.repository.support;
import java.util.Collections;
import java.util.List;
import org.bson.Document;
import org.springframework.data.mongodb.core.ExecutableFindOperation;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.EmptyCloseableIterator;
import com.querydsl.core.Fetchable;
import com.querydsl.core.QueryMetadata;
import com.querydsl.core.QueryModifiers;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* Spring Data specific simple {@link com.querydsl.core.Fetchable} {@link com.querydsl.core.SimpleQuery Query}
@@ -25,7 +45,13 @@ import org.springframework.data.mongodb.core.MongoOperations;
* @author Mark Paluch
* @author Christoph Strobl
*/
public class SpringDataMongodbQuery<T> extends QuerydslFetchableMongodbQuery<T, SpringDataMongodbQuery<T>> {
public class SpringDataMongodbQuery<T> extends SpringDataMongodbQuerySupport<SpringDataMongodbQuery<T>>
implements Fetchable<T> {
private final Class<T> entityClass;
private final String collection;
private final MongoOperations mongoOperations;
private final ExecutableFindOperation.FindWithProjection<T> find;
/**
* Creates a new {@link SpringDataMongodbQuery}.
@@ -33,7 +59,7 @@ public class SpringDataMongodbQuery<T> extends QuerydslFetchableMongodbQuery<T,
* @param operations must not be {@literal null}.
* @param type must not be {@literal null}.
*/
public SpringDataMongodbQuery(final MongoOperations operations, final Class<? extends T> type) {
public SpringDataMongodbQuery(MongoOperations operations, Class<? extends T> type) {
this(operations, type, operations.getCollectionName(type));
}
@@ -44,9 +70,174 @@ public class SpringDataMongodbQuery<T> extends QuerydslFetchableMongodbQuery<T,
* @param type must not be {@literal null}.
* @param collectionName must not be {@literal null} or empty.
*/
public SpringDataMongodbQuery(final MongoOperations operations, final Class<? extends T> type,
public SpringDataMongodbQuery(MongoOperations operations, Class<? extends T> type,
String collectionName) {
super(new SpringDataMongodbSerializer(operations.getConverter()), type, collectionName, operations);
this(new SpringDataMongodbSerializer(operations.getConverter()), operations, type, collectionName);
}
private SpringDataMongodbQuery(MongodbDocumentSerializer serializer, MongoOperations operations,
Class<? extends T> type, String collectionName) {
super(serializer);
this.entityClass = (Class<T>) type;
this.collection = collectionName;
this.mongoOperations = operations;
this.find = mongoOperations.query(this.entityClass).inCollection(collection);
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#iterable()
*/
@Override
public CloseableIterator<T> iterate() {
try {
org.springframework.data.util.CloseableIterator<? extends T> stream = mongoOperations.stream(createQuery(),
entityClass, collection);
return new CloseableIterator<T>() {
@Override
public boolean hasNext() {
return stream.hasNext();
}
@Override
public T next() {
return stream.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove from iterator while streaming data.");
}
@Override
public void close() {
stream.close();
}
};
} catch (RuntimeException e) {
return handleException(e, new EmptyCloseableIterator<>());
}
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetch()
*/
@Override
public List<T> fetch() {
try {
return find.matching(createQuery()).all();
} catch (RuntimeException e) {
return handleException(e, Collections.emptyList());
}
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchFirst()
*/
@Override
public T fetchFirst() {
try {
return find.matching(createQuery()).firstValue();
} catch (RuntimeException e) {
return handleException(e, null);
}
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchOne()
*/
@Override
public T fetchOne() {
try {
return find.matching(createQuery()).oneValue();
} catch (RuntimeException e) {
return handleException(e, null);
}
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchResults()
*/
@Override
public QueryResults<T> fetchResults() {
long total = fetchCount();
return total > 0L ? new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total)
: QueryResults.emptyResults();
}
/*
* (non-Javadoc)
* @see com.querydsl.core.Fetchable#fetchCount()
*/
@Override
public long fetchCount() {
try {
return find.matching(Query.of(createQuery()).skip(-1).limit(-1)).count();
} catch (RuntimeException e) {
return handleException(e, 0L);
}
}
protected org.springframework.data.mongodb.core.query.Query createQuery() {
QueryMetadata metadata = getQueryMixin().getMetadata();
return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
metadata.getOrderBy());
}
protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullable Predicate filter,
@Nullable Expression<?> projection, QueryModifiers modifiers, List<OrderSpecifier<?>> orderBy) {
Document fields = createProjection(projection);
BasicQuery basicQuery = new BasicQuery(createQuery(filter), fields == null ? new Document() : fields);
Integer limit = modifiers.getLimitAsInteger();
Integer offset = modifiers.getOffsetAsInteger();
if (limit != null) {
basicQuery.limit(limit);
}
if (offset != null) {
basicQuery.skip(offset);
}
if (orderBy.size() > 0) {
basicQuery.setSortObject(createSort(orderBy));
}
return basicQuery;
}
/**
* Fetch the list of ids matching a given condition.
*
* @param targetType must not be {@literal null}.
* @param condition must not be {@literal null}.
* @return empty {@link List} if none found.
*/
protected List<Object> getIds(Class<?> targetType, Predicate condition) {
Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList());
return mongoOperations.findDistinct(query, "_id", targetType, Object.class);
}
private static <T> T handleException(RuntimeException e, T defaultValue) {
if (e.getClass().getName().endsWith("$NoResults")) {
return defaultValue;
}
throw e;
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2021 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.mongodb.repository.support;
import java.util.List;
import org.bson.Document;
import org.bson.codecs.DocumentCodec;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriterSettings;
import org.springframework.beans.DirectFieldAccessor;
import com.mongodb.MongoClientSettings;
import com.querydsl.core.support.QueryMixin;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.mongodb.document.AbstractMongodbQuery;
import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* Support query type to augment Spring Data-specific {@link #toString} representations and
* {@link org.springframework.data.domain.Sort} creation.
*
* @author Mark Paluch
* @since 3.3
*/
abstract class SpringDataMongodbQuerySupport<Q extends SpringDataMongodbQuerySupport<Q>>
extends AbstractMongodbQuery<Q> {
private final QueryMixin<Q> superQueryMixin;
private static final JsonWriterSettings JSON_WRITER_SETTINGS = JsonWriterSettings.builder().outputMode(JsonMode.SHELL)
.build();
private final MongodbDocumentSerializer serializer;
@SuppressWarnings("unchecked")
SpringDataMongodbQuerySupport(MongodbDocumentSerializer serializer) {
super(serializer);
this.serializer = serializer;
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(this);
this.superQueryMixin = (QueryMixin<Q>) fieldAccessor.getPropertyValue("queryMixin");
}
/**
* Returns the {@literal Mongo Shell} representation of the query. <br />
* The following query
*
* <pre class="code">
*
* where(p.lastname.eq("Matthews")).orderBy(p.firstname.asc()).offset(1).limit(5);
* </pre>
*
* results in
*
* <pre class="code">
*
* find({"lastname" : "Matthews"}).sort({"firstname" : 1}).skip(1).limit(5)
* </pre>
*
* Note that encoding to {@link String} may fail when using data types that cannot be encoded or DBRef's without an
* identifier.
*
* @return never {@literal null}.
*/
@Override
public String toString() {
Document projection = createProjection(getQueryMixin().getMetadata().getProjection());
Document sort = createSort(getQueryMixin().getMetadata().getOrderBy());
DocumentCodec codec = new DocumentCodec(MongoClientSettings.getDefaultCodecRegistry());
StringBuilder sb = new StringBuilder("find(" + asDocument().toJson(JSON_WRITER_SETTINGS, codec));
if (projection != null && projection.isEmpty()) {
sb.append(", ").append(projection.toJson(JSON_WRITER_SETTINGS, codec));
}
sb.append(")");
if (!sort.isEmpty()) {
sb.append(".sort(").append(sort.toJson(JSON_WRITER_SETTINGS, codec)).append(")");
}
if (getQueryMixin().getMetadata().getModifiers().getOffset() != null) {
sb.append(".skip(").append(getQueryMixin().getMetadata().getModifiers().getOffset()).append(")");
}
if (getQueryMixin().getMetadata().getModifiers().getLimit() != null) {
sb.append(".limit(").append(getQueryMixin().getMetadata().getModifiers().getLimit()).append(")");
}
return sb.toString();
}
/**
* Get the where definition as a Document instance
*
* @return
*/
public Document asDocument() {
return createQuery(getQueryMixin().getMetadata().getWhere());
}
/**
* Obtain the {@literal Mongo Shell} json query representation.
*
* @return never {@literal null}.
*/
public String toJson() {
return toJson(JSON_WRITER_SETTINGS);
}
/**
* Obtain the json query representation applying given {@link JsonWriterSettings settings}.
*
* @param settings must not be {@literal null}.
* @return never {@literal null}.
*/
public String toJson(JsonWriterSettings settings) {
return asDocument().toJson(settings);
}
/**
* Compute the sort {@link Document} from the given list of {@link OrderSpecifier order specifiers}.
*
* @param orderSpecifiers can be {@literal null}.
* @return an empty {@link Document} if predicate is {@literal null}.
* @see MongodbDocumentSerializer#toSort(List)
*/
protected Document createSort(List<OrderSpecifier<?>> orderSpecifiers) {
return serializer.toSort(orderSpecifiers);
}
// TODO: Remove once https://github.com/querydsl/querydsl/pull/2916 is merged
QueryMixin<Q> getQueryMixin() {
return superQueryMixin;
}
}

View File

@@ -22,6 +22,7 @@ import java.util.Set;
import java.util.regex.Pattern;
import org.bson.Document;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
@@ -39,6 +40,7 @@ import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.core.types.PathType;
import com.querydsl.mongodb.MongodbSerializer;
import com.querydsl.mongodb.document.MongodbDocumentSerializer;
/**
* Custom {@link MongodbSerializer} to take mapping information into account when building keys for constraints.

View File

@@ -28,8 +28,6 @@ import reactor.test.StepVerifier;
import java.util.Arrays;
import javax.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -49,6 +47,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactory;
import org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRepository;
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.util.ClassUtils;