From be4d6829a1fe8b73e335caccffbdd84fc41dd7fb Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 17 Jun 2021 10:48:42 +0200 Subject: [PATCH 1/2] Prepare issue branch. --- pom.xml | 6 ++++-- spring-data-mongodb-benchmarks/pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index a6d5da9170..e069d9a550 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,13 @@ - + 4.0.0 org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0.GH-2673-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index 0033bd11d5..8902e38d06 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0.GH-2673-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index f62c8dc7f4..a621d32d9f 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0.GH-2673-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 1f157e75bc..8c0d4c732f 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 3.3.0-SNAPSHOT + 3.3.0.GH-2673-SNAPSHOT ../pom.xml From 95668a24a55349219e9fca39842c6407ff1347e9 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 17 Jun 2021 10:50:13 +0200 Subject: [PATCH 2/2] 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. --- .../support/MongodbDocumentSerializer.java | 459 ------------------ .../support/QuerydslAbstractMongodbQuery.java | 32 +- .../support/QuerydslAnyEmbeddedBuilder.java | 3 + .../QuerydslFetchableMongodbQuery.java | 272 ----------- .../support/QuerydslJoinBuilder.java | 67 --- .../repository/support/QuerydslMongoOps.java | 43 -- .../ReactiveSpringDataMongodbQuery.java | 79 ++- .../support/SpringDataMongodbQuery.java | 199 +++++++- .../SpringDataMongodbQuerySupport.java | 147 ++++++ .../support/SpringDataMongodbSerializer.java | 2 + .../SimpleReactiveMongoRepositoryTests.java | 3 +- 11 files changed, 383 insertions(+), 923 deletions(-) delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java deleted file mode 100644 index 3f0d281cc4..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java +++ /dev/null @@ -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; - -/** - *

- * Serializes the given Querydsl query to a Document query for MongoDB. - *

- *

- * Original implementation source {@link com.querydsl.mongodb.MongodbSerializer} by {@literal The Querydsl Team} - * (http://www.querydsl.com/team) licensed under the Apache License, Version - * 2.0. - *

- * 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 { - - @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> 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> expectedExpr = (Constant>) 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> pendingDocuments = collectConnectorArgs("$and", expr); - List> unmergeableDocuments = new ArrayList<>(); - List> generatedDocuments = new ArrayList<>(); - - while (!pendingDocuments.isEmpty()) { - - Map lhs = pendingDocuments.poll(); - - for (Map rhs : pendingDocuments) { - Set 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>) 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>) 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 list = new ArrayList<>(2); - list.add(asDocument(asDBKey(expr, 0), new ArrayList())); - 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 list = new ArrayList<>(); - for (Map.Entry entry : arg.entrySet()) { - - if (entry.getKey().equals("$or")) { - list.add(asDocument("$nor", entry.getValue())); - } else if (entry.getKey().equals("$and")) { - - List 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 list2 = new ArrayList<>(); - for (Map.Entry 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> collectConnectorArgs(String operator, Operation operation) { - - LinkedList> pendingDocuments = new LinkedList<>(); - for (Expression exp : operation.getArgs()) { - Map document = (Map) handle(exp); - if (document.keySet().size() == 1 && document.containsKey(operator)) { - pendingDocuments.addAll((Collection>) document.get(operator)); - } else { - pendingDocuments.add(document); - } - } - return pendingDocuments; - - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java index 422eea5778..b255d20273 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java @@ -34,6 +34,8 @@ 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 @@ * @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> + extends AbstractMongodbQuery implements SimpleQuery { private static final JsonWriterSettings JSON_WRITER_SETTINGS = JsonWriterSettings.builder().outputMode(JsonMode.SHELL) @@ -67,6 +73,8 @@ public abstract class QuerydslAbstractMongodbQuery((Q) this, new DefaultQueryMetadata(), false); this.serializer = serializer; } @@ -158,22 +166,6 @@ protected Document createProjection(@Nullable Expression projectionExpression 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 @@ QueryMixin getQueryMixin() { 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.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java index 3ba84a2dd3..b6935a5e8f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java @@ -37,7 +37,10 @@ * @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, K> { private final QueryMixin queryMixin; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java deleted file mode 100644 index 36057d58c9..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java +++ /dev/null @@ -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 result type - * @param concrete subtype - * @author Mark Paluch - * @author Christoph Strobl - * @since 2.1 - */ -abstract class QuerydslFetchableMongodbQuery> - extends QuerydslAbstractMongodbQuery implements Fetchable { - - private final Class entityClass; - private final String collection; - private final MongoOperations mongoOperations; - private final FindWithProjection find; - - QuerydslFetchableMongodbQuery(MongodbDocumentSerializer serializer, Class entityClass, String collection, - MongoOperations mongoOperations) { - - super(serializer); - - this.entityClass = (Class) 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 iterate() { - - org.springframework.data.util.CloseableIterator stream = mongoOperations.stream(createQuery(), - entityClass, collection); - - return new CloseableIterator() { - - @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 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 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 QuerydslJoinBuilder join(Path ref, Path target) { - return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target); - } - - /** - * Define a join. - * - * @param ref reference - * @param target join target - * @return new instance of {@link QuerydslJoinBuilder}. - */ - public QuerydslJoinBuilder join(CollectionPathBase ref, Path 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 QuerydslAnyEmbeddedBuilder anyEmbedded(Path> collection, Path 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> 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, Predicate> predicates = new LinkedMultiValueMap<>(); - List 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 extraFilters = predicates.get(target.getRoot()); - Predicate filter = ExpressionUtils.allOf(join.getCondition(), allOf(extraFilters)); - - List 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) path, ids)); - } - - Path source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0); - return allOf(predicates.get(source.getRoot())); - } - - private Predicate allOf(Collection 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 getIds(Class targetType, Predicate condition) { - - Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList()); - return mongoOperations.findDistinct(query, "_id", targetType, Object.class); - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java deleted file mode 100644 index 344ad08826..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java +++ /dev/null @@ -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. - *

- * Original implementation source {@link com.querydsl.mongodb.JoinBuilder} by {@literal The Querydsl Team} - * (http://www.querydsl.com/team) licensed under the Apache License, Version - * 2.0. - *

- * Modified for usage with {@link QuerydslAbstractMongodbQuery}. - * - * @param - * @param - * @author tiwe - * @author Mark Paluch - * @author Christoph Strobl - * @since 2.1 - */ -public class QuerydslJoinBuilder, K, T> { - - private final QueryMixin queryMixin; - private final Path ref; - private final Path target; - - QuerydslJoinBuilder(QueryMixin queryMixin, Path ref, Path 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(); - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java deleted file mode 100644 index 0c695afd0c..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java +++ /dev/null @@ -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; - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java index 4162a79482..8b30e585e6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java @@ -22,6 +22,9 @@ 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.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 result type * @author Mark Paluch * @author Christoph Strobl * @since 2.2 */ -class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery> { +class ReactiveSpringDataMongodbQuery extends SpringDataMongodbQuerySupport> { - private final Class entityClass; private final ReactiveMongoOperations mongoOperations; private final FindWithProjection find; @@ -60,15 +65,15 @@ class ReactiveSpringDataMongodbQuery extends QuerydslAbstractMongodbQuery entityClass, @Nullable String collection) { super(serializer); - this.entityClass = (Class) 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) entityClass).inCollection(collection) + : mongoOperations.query((Class) entityClass); } /** @@ -99,48 +104,11 @@ Mono fetchCount() { return createQuery().flatMap(it -> find.matching(it).count()); } - /** - * Define a join. - * - * @param ref reference - * @param target join target - * @return new instance of {@link QuerydslJoinBuilder}. - */ - QuerydslJoinBuilder, K, T> join(Path ref, Path target) { - return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target); - } - - /** - * Define a join. - * - * @param ref reference - * @param target join target - * @return new instance of {@link QuerydslJoinBuilder}. - */ - QuerydslJoinBuilder, K, T> join(CollectionPathBase ref, - Path 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}. - */ - QuerydslAnyEmbeddedBuilder, K> anyEmbedded( - Path> collection, Path target) { - - return new QuerydslAnyEmbeddedBuilder<>(getQueryMixin(), collection); - } - protected Mono 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 @@ protected Mono createQuery(Mono filter, @Nullable Expression { - 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 @@ protected Mono createQuery(Mono filter, @Nullable Expression createFilter(QueryMetadata metadata) { + protected Mono 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 @@ protected Mono createFilter(QueryMetadata metadata) { * @return */ @SuppressWarnings("unchecked") - protected Mono createJoinFilter(QueryMetadata metadata) { + protected Mono createReactiveJoinFilter(QueryMetadata metadata) { MultiValueMap, Mono> predicates = new LinkedMultiValueMap<>(); List joins = metadata.getJoins(); @@ -230,7 +199,7 @@ protected Mono createJoinFilter(QueryMetadata metadata) { 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 allOf(@Nullable Collection> predicates) { @@ -246,8 +215,8 @@ private Mono allOf(@Nullable Collection> predicates) */ protected Flux getIds(Class targetType, Mono 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 @@ protected Flux getIds(Class targetType, Mono condition) { * @param condition must not be {@literal null}. * @return empty {@link List} if none found. */ - protected Flux getIds(Class targetType, @Nullable Predicate condition) { + protected Flux 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 getIds(Class aClass, Predicate predicate) { + throw new UnsupportedOperationException( + "Use create Flux getIds(Class targetType, Mono condition)"); + } + /** * Marker exception to indicate no matches for a query using reference Id's. */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java index 8a153d0c2c..d62aa99c5e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java @@ -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 @@ * @author Mark Paluch * @author Christoph Strobl */ -public class SpringDataMongodbQuery extends QuerydslFetchableMongodbQuery> { +public class SpringDataMongodbQuery extends SpringDataMongodbQuerySupport> + implements Fetchable { + + private final Class entityClass; + private final String collection; + private final MongoOperations mongoOperations; + private final ExecutableFindOperation.FindWithProjection find; /** * Creates a new {@link SpringDataMongodbQuery}. @@ -33,7 +59,7 @@ public class SpringDataMongodbQuery extends QuerydslFetchableMongodbQuery type) { + public SpringDataMongodbQuery(MongoOperations operations, Class type) { this(operations, type, operations.getCollectionName(type)); } @@ -44,9 +70,174 @@ public SpringDataMongodbQuery(final MongoOperations operations, final Class type, + public SpringDataMongodbQuery(MongoOperations operations, Class type, String collectionName) { + this(new SpringDataMongodbSerializer(operations.getConverter()), operations, type, collectionName); + } + + private SpringDataMongodbQuery(MongodbDocumentSerializer serializer, MongoOperations operations, + Class type, String collectionName) { + + super(serializer); - super(new SpringDataMongodbSerializer(operations.getConverter()), type, collectionName, operations); + this.entityClass = (Class) 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 iterate() { + + try { + org.springframework.data.util.CloseableIterator stream = mongoOperations.stream(createQuery(), + entityClass, collection); + + return new CloseableIterator() { + + @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 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 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> 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 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 handleException(RuntimeException e, T defaultValue) { + + if (e.getClass().getName().endsWith("$NoResults")) { + return defaultValue; + } + + throw e; + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java new file mode 100644 index 0000000000..406019cf4d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java @@ -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> + extends AbstractMongodbQuery { + + private final QueryMixin 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) fieldAccessor.getPropertyValue("queryMixin"); + } + + /** + * Returns the {@literal Mongo Shell} representation of the query.
+ * The following query + * + *
+	 *
+	 * where(p.lastname.eq("Matthews")).orderBy(p.firstname.asc()).offset(1).limit(5);
+	 * 
+ * + * results in + * + *
+	 *
+	 * find({"lastname" : "Matthews"}).sort({"firstname" : 1}).skip(1).limit(5)
+	 * 
+ * + * 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> orderSpecifiers) { + return serializer.toSort(orderSpecifiers); + } + + // TODO: Remove once https://github.com/querydsl/querydsl/pull/2916 is merged + QueryMixin getQueryMixin() { + return superQueryMixin; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java index e18f30d96a..2453e1a46c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java @@ -22,6 +22,7 @@ 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.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. diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java index 5584ae6e3b..0067eb3bf1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SimpleReactiveMongoRepositoryTests.java @@ -28,8 +28,6 @@ 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.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;