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:
committed by
Christoph Strobl
parent
5bd7ff1413
commit
986ea39f90
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user