diff --git a/driver-core/src/main/com/mongodb/internal/operation/ListCollectionsOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ListCollectionsOperation.java
index 68ba6617e0b..bb211d7b7e3 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ListCollectionsOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ListCollectionsOperation.java
@@ -23,6 +23,7 @@
import com.mongodb.ServerCursor;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerDescription;
+import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.AsyncBatchCursor;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncConnectionSource;
@@ -57,6 +58,7 @@
import static com.mongodb.ReadPreference.primary;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.connection.ServerType.SHARD_ROUTER;
+import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback;
import static com.mongodb.internal.operation.CommandOperationHelper.CommandCreator;
import static com.mongodb.internal.operation.CommandOperationHelper.createReadCommandAndExecute;
@@ -83,6 +85,8 @@
* An operation that provides a cursor allowing iteration through the metadata of all the collections in a database. This operation
* ensures that the value of the {@code name} field of each returned document is the simple name of the collection rather than the full
* namespace.
+ *
+ * See {@code listCollections}
.
*
* @param the document type
* @since 3.0
@@ -95,6 +99,7 @@ public class ListCollectionsOperation implements AsyncReadOperation nameOnly(final boolean nameOnly) {
return this;
}
+ /**
+ * Ignored unless {@link #nameOnly(boolean)} is {@code true}.
+ *
+ * @since 4.5
+ * @mongodb.server.release 4.0
+ */
+ public ListCollectionsOperation authorizedCollections(final boolean authorizedCollections) {
+ this.authorizedCollections = authorizedCollections;
+ return this;
+ }
+
+ /**
+ * This method is used by tests via the reflection API. For example, see {@code TestHelper.assertOperationIsTheSameAs}.
+ */
+ @VisibleForTesting(otherwise = PRIVATE)
+ public boolean isAuthorizedCollections() {
+ return authorizedCollections;
+ }
+
/**
* Gets the number of documents to return per batch.
*
@@ -351,6 +375,9 @@ private BsonDocument getCommand() {
}
if (nameOnly) {
command.append("nameOnly", BsonBoolean.TRUE);
+ if (authorizedCollections) {
+ command.append("authorizedCollections", BsonBoolean.TRUE);
+ }
}
if (maxTimeMS > 0) {
command.put("maxTimeMS", new BsonInt64(maxTimeMS));
diff --git a/driver-core/src/main/com/mongodb/internal/operation/Operations.java b/driver-core/src/main/com/mongodb/internal/operation/Operations.java
index b094b4fe74c..13b37f7a7f9 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/Operations.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/Operations.java
@@ -542,11 +542,13 @@ public DropIndexOperation dropIndex(final Bson keys, final DropIndexOptions drop
public ListCollectionsOperation listCollections(final String databaseName, final Class resultClass,
final Bson filter, final boolean collectionNamesOnly,
+ final boolean authorizedCollections,
final Integer batchSize, final long maxTimeMS) {
return new ListCollectionsOperation(databaseName, codecRegistry.get(resultClass))
.retryReads(retryReads)
.filter(toBsonDocumentOrNull(filter))
.nameOnly(collectionNamesOnly)
+ .authorizedCollections(authorizedCollections)
.batchSize(batchSize == null ? 0 : batchSize)
.maxTime(maxTimeMS, MILLISECONDS);
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java b/driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java
index c9a53d3a33d..f04ea3a15fd 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/SyncOperations.java
@@ -240,8 +240,10 @@ public WriteOperation dropIndex(final Bson keys, final DropIndexOptions op
public ReadOperation> listCollections(final String databaseName, final Class resultClass,
final Bson filter, final boolean collectionNamesOnly,
+ final boolean authorizedCollections,
final Integer batchSize, final long maxTimeMS) {
- return operations.listCollections(databaseName, resultClass, filter, collectionNamesOnly, batchSize, maxTimeMS);
+ return operations.listCollections(databaseName, resultClass, filter, collectionNamesOnly, authorizedCollections,
+ batchSize, maxTimeMS);
}
public ReadOperation> listDatabases(final Class resultClass, final Bson filter,
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ListCollectionsPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ListCollectionsPublisher.java
index 2efcaf65acd..9019190cc78 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ListCollectionsPublisher.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/ListCollectionsPublisher.java
@@ -27,6 +27,7 @@
*
* @param The type of the result.
* @since 1.0
+ * @mongodb.driver.manual reference/command/listCollections/ listCollections
*/
public interface ListCollectionsPublisher extends Publisher {
@@ -39,6 +40,21 @@ public interface ListCollectionsPublisher extends Publisher {
*/
ListCollectionsPublisher filter(@Nullable Bson filter);
+ /**
+ * Sets the {@code authorizedCollections} field of the {@code listCollections} command.
+ * This method is ignored if called on a {@link ListCollectionsPublisher} obtained not via any of the
+ * {@link MongoDatabase#listCollectionNames() MongoDatabase.listCollectionNames} methods.
+ *
+ * @param authorizedCollections If {@code true}, allows executing the {@code listCollections} command,
+ * which has the {@code nameOnly} field set to {@code true}, without having the
+ *
+ * {@code listCollections} privilege on the corresponding database resource.
+ * @return {@code this}.
+ * @since 4.5
+ * @mongodb.server.release 4.0
+ */
+ ListCollectionsPublisher authorizedCollections(boolean authorizedCollections);
+
/**
* Sets the maximum execution time on the server for this operation.
*
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoDatabase.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoDatabase.java
index dabdb86ff9a..5f7fc912bbc 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoDatabase.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoDatabase.java
@@ -237,18 +237,20 @@ public interface MongoDatabase {
* Gets the names of all the collections in this database.
*
* @return a publisher with all the names of all the collections in this database
+ * @mongodb.driver.manual reference/command/listCollections listCollections
*/
- Publisher listCollectionNames();
+ ListCollectionsPublisher listCollectionNames();
/**
* Gets the names of all the collections in this database.
*
* @param clientSession the client session with which to associate this operation
* @return a publisher with all the names of all the collections in this database
+ * @mongodb.driver.manual reference/command/listCollections listCollections
* @mongodb.server.release 3.6
* @since 1.7
*/
- Publisher listCollectionNames(ClientSession clientSession);
+ ListCollectionsPublisher listCollectionNames(ClientSession clientSession);
/**
* Finds all the collections in this database.
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImpl.java
index fb852f46cb9..cf8aee2230e 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImpl.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImpl.java
@@ -17,21 +17,29 @@
package com.mongodb.reactivestreams.client.internal;
import com.mongodb.ReadConcern;
+import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.AsyncBatchCursor;
import com.mongodb.internal.operation.AsyncReadOperation;
import com.mongodb.lang.Nullable;
import com.mongodb.reactivestreams.client.ClientSession;
import com.mongodb.reactivestreams.client.ListCollectionsPublisher;
import org.bson.conversions.Bson;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
import static com.mongodb.assertions.Assertions.notNull;
+import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
final class ListCollectionsPublisherImpl extends BatchCursorPublisher implements ListCollectionsPublisher {
private final boolean collectionNamesOnly;
+ private boolean authorizedCollections;
private Bson filter;
private long maxTimeMS;
@@ -59,8 +67,72 @@ public ListCollectionsPublisherImpl filter(@Nullable final Bson filter) {
return this;
}
+ @Override
+ public ListCollectionsPublisherImpl authorizedCollections(final boolean authorizedCollections) {
+ this.authorizedCollections = authorizedCollections;
+ return this;
+ }
+
AsyncReadOperation> asAsyncReadOperation(final int initialBatchSize) {
return getOperations().listCollections(getNamespace().getDatabaseName(), getDocumentClass(), filter, collectionNamesOnly,
- initialBatchSize, maxTimeMS);
+ authorizedCollections, initialBatchSize, maxTimeMS);
+ }
+
+ ListCollectionsPublisher map(final Function mapper) {
+ return new Mapping<>(this, mapper);
+ }
+
+ private static final class Mapping implements ListCollectionsPublisher {
+ private final ListCollectionsPublisher wrapped;
+ private final Publisher mappingPublisher;
+ private final Function mapper;
+
+ Mapping(final ListCollectionsPublisher publisher, final Function mapper) {
+ this.wrapped = publisher;
+ mappingPublisher = Flux.from(publisher).map(mapper);
+ this.mapper = mapper;
+ }
+
+ @Override
+ public ListCollectionsPublisher filter(@Nullable final Bson filter) {
+ wrapped.filter(filter);
+ return this;
+ }
+
+ @Override
+ public ListCollectionsPublisher authorizedCollections(final boolean authorizedCollections) {
+ wrapped.authorizedCollections(authorizedCollections);
+ return this;
+ }
+
+ @Override
+ public ListCollectionsPublisher maxTime(final long maxTime, final TimeUnit timeUnit) {
+ wrapped.maxTime(maxTime, timeUnit);
+ return this;
+ }
+
+ @Override
+ public ListCollectionsPublisher batchSize(final int batchSize) {
+ wrapped.batchSize(batchSize);
+ return this;
+ }
+
+ @Override
+ public Publisher first() {
+ return Mono.from(wrapped.first()).map(mapper);
+ }
+
+ @Override
+ public void subscribe(final Subscriber super U> s) {
+ mappingPublisher.subscribe(s);
+ }
+
+ /**
+ * This method is used in tests via the reflection API.
+ */
+ @VisibleForTesting(otherwise = PRIVATE)
+ ListCollectionsPublisher getMapped() {
+ return wrapped;
+ }
}
}
diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoDatabaseImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoDatabaseImpl.java
index ece57b2bbdd..a6d6ae98bcf 100644
--- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoDatabaseImpl.java
+++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoDatabaseImpl.java
@@ -34,7 +34,6 @@
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.reactivestreams.Publisher;
-import reactor.core.publisher.Flux;
import java.util.Collections;
import java.util.List;
@@ -168,14 +167,14 @@ public Publisher drop(final ClientSession clientSession) {
}
@Override
- public Publisher listCollectionNames() {
- return Flux.from(new ListCollectionsPublisherImpl<>(null, mongoOperationPublisher, true))
+ public ListCollectionsPublisher listCollectionNames() {
+ return new ListCollectionsPublisherImpl<>(null, mongoOperationPublisher, true)
.map(d -> d.getString("name"));
}
@Override
- public Publisher listCollectionNames(final ClientSession clientSession) {
- return Flux.from(new ListCollectionsPublisherImpl<>(notNull("clientSession", clientSession), mongoOperationPublisher, true))
+ public ListCollectionsPublisher listCollectionNames(final ClientSession clientSession) {
+ return new ListCollectionsPublisherImpl<>(notNull("clientSession", clientSession), mongoOperationPublisher, true)
.map(d -> d.getString("name"));
}
diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncListCollectionsIterable.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncListCollectionsIterable.java
index 1cba7ff5fa8..0fe0f0ce20d 100644
--- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncListCollectionsIterable.java
+++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncListCollectionsIterable.java
@@ -37,6 +37,12 @@ public ListCollectionsIterable filter(@Nullable final Bson filter) {
return this;
}
+ @Override
+ public ListCollectionsIterable authorizedCollections(final boolean authorizedCollections) {
+ wrapped.authorizedCollections(authorizedCollections);
+ return this;
+ }
+
@Override
public ListCollectionsIterable maxTime(final long maxTime, final TimeUnit timeUnit) {
wrapped.maxTime(maxTime, timeUnit);
diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoDatabase.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoDatabase.java
index 080a664fa3c..197b6a5bbbc 100644
--- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoDatabase.java
+++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoDatabase.java
@@ -25,7 +25,6 @@
import com.mongodb.client.ListCollectionsIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
-import com.mongodb.client.MongoIterable;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.CreateViewOptions;
import org.bson.Document;
@@ -157,7 +156,7 @@ public void drop(final ClientSession clientSession) {
}
@Override
- public MongoIterable listCollectionNames() {
+ public ListCollectionsIterable listCollectionNames() {
throw new UnsupportedOperationException();
}
@@ -172,7 +171,7 @@ public ListCollectionsIterable listCollections(final Class listCollectionNames(final ClientSession clientSession) {
+ public ListCollectionsIterable listCollectionNames(final ClientSession clientSession) {
throw new UnsupportedOperationException();
}
diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImplTest.java
index c875ab7973c..390c6100785 100644
--- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImplTest.java
+++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/ListCollectionsPublisherImplTest.java
@@ -40,12 +40,15 @@ public class ListCollectionsPublisherImplTest extends TestHelper {
void shouldBuildTheExpectedOperation() {
TestOperationExecutor executor = createOperationExecutor(asList(getBatchCursor(), getBatchCursor()));
ListCollectionsPublisher publisher = new ListCollectionsPublisherImpl<>(null, createMongoOperationPublisher(executor)
- .withDocumentClass(String.class), true);
+ .withDocumentClass(String.class), true)
+ .authorizedCollections(true);
ListCollectionsOperation expectedOperation = new ListCollectionsOperation<>(DATABASE_NAME,
getDefaultCodecRegistry().get(String.class))
.batchSize(Integer.MAX_VALUE)
- .nameOnly(true).retryReads(true);
+ .nameOnly(true)
+ .authorizedCollections(true)
+ .retryReads(true);
// default input should be as expected
Flux.from(publisher).blockFirst();
diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoDatabaseImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoDatabaseImplTest.java
index ced5b201b29..f5ffbc6a2a7 100644
--- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoDatabaseImplTest.java
+++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoDatabaseImplTest.java
@@ -134,6 +134,13 @@ void testListCollectionNames() {
new ListCollectionsPublisherImpl<>(null, mongoOperationPublisher, true);
assertPublisherIsTheSameAs(expected, database.listCollectionNames(), "Default");
},
+ () -> {
+ ListCollectionsPublisher expected =
+ new ListCollectionsPublisherImpl<>(null, mongoOperationPublisher, true)
+ .authorizedCollections(true);
+ assertPublisherIsTheSameAs(expected, database.listCollectionNames().authorizedCollections(true),
+ "nameOnly & authorizedCollections");
+ },
() -> {
ListCollectionsPublisher expected =
new ListCollectionsPublisherImpl<>(clientSession, mongoOperationPublisher, true);
diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/TestHelper.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/TestHelper.java
index 4e122979ce5..c1de66f81c8 100644
--- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/TestHelper.java
+++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/TestHelper.java
@@ -44,6 +44,7 @@
import reactor.core.publisher.Mono;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@@ -52,6 +53,7 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
+import java.util.stream.Stream;
import static com.mongodb.reactivestreams.client.MongoClients.getDefaultCodecRegistry;
import static java.util.stream.Collectors.toList;
@@ -190,21 +192,23 @@ private static Object checkValueTypes(final Object instance) {
}
private static Publisher> getRootSource(final Publisher> publisher) {
- Optional> sourcePublisher = Optional.of(publisher);
+ Publisher> sourcePublisher = publisher;
// Uses reflection to find the root / source publisher
if (publisher instanceof Scannable) {
Scannable scannable = (Scannable) publisher;
List extends Scannable> parents = scannable.parents().collect(toList());
if (parents.isEmpty()) {
- sourcePublisher = getSource(scannable);
+ sourcePublisher = getSource(scannable).orElse(publisher);
} else {
sourcePublisher = parents.stream().map(TestHelper::getSource)
.filter(Optional::isPresent)
.reduce((first, second) -> second)
- .orElse(Optional.empty());
+ .flatMap(Function.identity())
+ .orElse(publisher);
}
}
- return sourcePublisher.orElse(publisher);
+ sourcePublisher = getMapped(sourcePublisher).orElse(sourcePublisher);
+ return sourcePublisher;
}
private static Optional> getSource(final Scannable scannable) {
@@ -216,6 +220,19 @@ private static Optional> getSource(final Scannable scannable) {
}
}
+ private static Optional> getMapped(final Publisher> maybeMappingPublisher) {
+ return Stream.of(maybeMappingPublisher.getClass().getDeclaredMethods())
+ .filter(m -> m.getName().equals("getMapped"))
+ .findFirst()
+ .map(m -> {
+ try {
+ return (Publisher>) m.invoke(maybeMappingPublisher);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
private static Optional> getScannableSource(final Scannable scannable) {
return (Optional>) getScannableFieldValue(scannable, "source");
}
diff --git a/driver-scala/src/it/scala/org/mongodb/scala/syncadapter/SyncListCollectionsIterable.scala b/driver-scala/src/it/scala/org/mongodb/scala/syncadapter/SyncListCollectionsIterable.scala
index 033b51b9205..b1486724dc4 100644
--- a/driver-scala/src/it/scala/org/mongodb/scala/syncadapter/SyncListCollectionsIterable.scala
+++ b/driver-scala/src/it/scala/org/mongodb/scala/syncadapter/SyncListCollectionsIterable.scala
@@ -29,6 +29,11 @@ case class SyncListCollectionsIterable[T](wrapped: ListCollectionsObservable[T])
this
}
+ override def authorizedCollections(authorizedCollections: Boolean): ListCollectionsIterable[T] = {
+ wrapped.authorizedCollections(authorizedCollections)
+ this
+ }
+
override def maxTime(maxTime: Long, timeUnit: TimeUnit): ListCollectionsIterable[T] = {
wrapped.maxTime(maxTime, timeUnit)
this
diff --git a/driver-scala/src/main/scala/org/mongodb/scala/ListCollectionsObservable.scala b/driver-scala/src/main/scala/org/mongodb/scala/ListCollectionsObservable.scala
index 06df5a5a374..4f33dd78023 100644
--- a/driver-scala/src/main/scala/org/mongodb/scala/ListCollectionsObservable.scala
+++ b/driver-scala/src/main/scala/org/mongodb/scala/ListCollectionsObservable.scala
@@ -44,6 +44,23 @@ case class ListCollectionsObservable[TResult](wrapped: ListCollectionsPublisher[
this
}
+ /**
+ * Sets the `authorizedCollections` field of the `istCollections` command.
+ * This method is ignored if called on a [[ListCollectionsObservable]] obtained not via any of the
+ * `MongoDatabase.listCollectionNames` methods.
+ *
+ * @param authorizedCollections If `true`, allows executing the `listCollections` command,
+ * which has the `nameOnly` field set to `true`, without having the
+ *
+ * `listCollections` privilege on the corresponding database resource.
+ * @return `this`.
+ * @since 4.5
+ */
+ def authorizedCollections(authorizedCollections: Boolean): ListCollectionsObservable[TResult] = {
+ wrapped.authorizedCollections(authorizedCollections)
+ this
+ }
+
/**
* Sets the maximum execution time on the server for this operation.
*
diff --git a/driver-scala/src/main/scala/org/mongodb/scala/MongoDatabase.scala b/driver-scala/src/main/scala/org/mongodb/scala/MongoDatabase.scala
index cde98735769..c283ac3e956 100644
--- a/driver-scala/src/main/scala/org/mongodb/scala/MongoDatabase.scala
+++ b/driver-scala/src/main/scala/org/mongodb/scala/MongoDatabase.scala
@@ -203,7 +203,8 @@ case class MongoDatabase(private[scala] val wrapped: JMongoDatabase) {
*
* @return a Observable with all the names of all the collections in this database
*/
- def listCollectionNames(): Observable[String] = wrapped.listCollectionNames()
+ def listCollectionNames(): ListCollectionsObservable[String] =
+ ListCollectionsObservable(wrapped.listCollectionNames())
/**
* Finds all the collections in this database.
@@ -226,7 +227,8 @@ case class MongoDatabase(private[scala] val wrapped: JMongoDatabase) {
* @since 2.2
* @note Requires MongoDB 3.6 or greater
*/
- def listCollectionNames(clientSession: ClientSession): Observable[String] = wrapped.listCollectionNames(clientSession)
+ def listCollectionNames(clientSession: ClientSession): ListCollectionsObservable[String] =
+ ListCollectionsObservable(wrapped.listCollectionNames(clientSession))
/**
* Finds all the collections in this database.
diff --git a/driver-sync/src/main/com/mongodb/client/ListCollectionsIterable.java b/driver-sync/src/main/com/mongodb/client/ListCollectionsIterable.java
index 4865e4171be..82566a0eb0b 100644
--- a/driver-sync/src/main/com/mongodb/client/ListCollectionsIterable.java
+++ b/driver-sync/src/main/com/mongodb/client/ListCollectionsIterable.java
@@ -26,6 +26,7 @@
*
* @param The type of the result.
* @since 3.0
+ * @mongodb.driver.manual reference/command/listCollections/ listCollections
*/
public interface ListCollectionsIterable extends MongoIterable {
@@ -38,6 +39,21 @@ public interface ListCollectionsIterable extends MongoIterable
*/
ListCollectionsIterable filter(@Nullable Bson filter);
+ /**
+ * Sets the {@code authorizedCollections} field of the {@code listCollections} command.
+ * This method is ignored if called on a {@link ListCollectionsIterable} obtained not via any of the
+ * {@link MongoDatabase#listCollectionNames() MongoDatabase.listCollectionNames} methods.
+ *
+ * @param authorizedCollections If {@code true}, allows executing the {@code listCollections} command,
+ * which has the {@code nameOnly} field set to {@code true}, without having the
+ *
+ * {@code listCollections} privilege on the corresponding database resource.
+ * @return {@code this}.
+ * @since 4.5
+ * @mongodb.server.release 4.0
+ */
+ ListCollectionsIterable authorizedCollections(boolean authorizedCollections);
+
/**
* Sets the maximum execution time on the server for this operation.
*
diff --git a/driver-sync/src/main/com/mongodb/client/MongoDatabase.java b/driver-sync/src/main/com/mongodb/client/MongoDatabase.java
index e1812bc6d3d..1aae0d39437 100644
--- a/driver-sync/src/main/com/mongodb/client/MongoDatabase.java
+++ b/driver-sync/src/main/com/mongodb/client/MongoDatabase.java
@@ -240,8 +240,9 @@ public interface MongoDatabase {
* Gets the names of all the collections in this database.
*
* @return an iterable containing all the names of all the collections in this database
+ * @mongodb.driver.manual reference/command/listCollections listCollections
*/
- MongoIterable listCollectionNames();
+ ListCollectionsIterable listCollectionNames();
/**
* Finds all the collections in this database.
@@ -268,8 +269,9 @@ public interface MongoDatabase {
* @return an iterable containing all the names of all the collections in this database
* @since 3.6
* @mongodb.server.release 3.6
+ * @mongodb.driver.manual reference/command/listCollections listCollections
*/
- MongoIterable listCollectionNames(ClientSession clientSession);
+ ListCollectionsIterable listCollectionNames(ClientSession clientSession);
/**
* Finds all the collections in this database.
diff --git a/driver-sync/src/main/com/mongodb/client/internal/ListCollectionsIterableImpl.java b/driver-sync/src/main/com/mongodb/client/internal/ListCollectionsIterableImpl.java
index 38c2afe1216..ec89d176340 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/ListCollectionsIterableImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/ListCollectionsIterableImpl.java
@@ -16,10 +16,13 @@
package com.mongodb.client.internal;
+import com.mongodb.Function;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
import com.mongodb.client.ClientSession;
import com.mongodb.client.ListCollectionsIterable;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.operation.BatchCursor;
import com.mongodb.internal.operation.ReadOperation;
import com.mongodb.internal.operation.SyncOperations;
@@ -28,9 +31,11 @@
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
+import java.util.Collection;
import java.util.concurrent.TimeUnit;
import static com.mongodb.assertions.Assertions.notNull;
+import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
class ListCollectionsIterableImpl extends MongoIterableImpl implements ListCollectionsIterable {
@@ -40,14 +45,9 @@ class ListCollectionsIterableImpl extends MongoIterableImpl im
private Bson filter;
private final boolean collectionNamesOnly;
+ private boolean authorizedCollections;
private long maxTimeMS;
- ListCollectionsIterableImpl(@Nullable final ClientSession clientSession, final String databaseName, final boolean collectionNamesOnly,
- final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference,
- final OperationExecutor executor) {
- this(clientSession, databaseName, collectionNamesOnly, resultClass, codecRegistry, readPreference, executor, true);
- }
-
ListCollectionsIterableImpl(@Nullable final ClientSession clientSession, final String databaseName, final boolean collectionNamesOnly,
final Class resultClass, final CodecRegistry codecRegistry, final ReadPreference readPreference,
final OperationExecutor executor, final boolean retryReads) {
@@ -64,6 +64,12 @@ public ListCollectionsIterable filter(@Nullable final Bson filter) {
return this;
}
+ @Override
+ public ListCollectionsIterableImpl authorizedCollections(final boolean authorizedCollections) {
+ this.authorizedCollections = authorizedCollections;
+ return this;
+ }
+
@Override
public ListCollectionsIterable maxTime(final long maxTime, final TimeUnit timeUnit) {
notNull("timeUnit", timeUnit);
@@ -79,6 +85,82 @@ public ListCollectionsIterable batchSize(final int batchSize) {
@Override
public ReadOperation> asReadOperation() {
- return operations.listCollections(databaseName, resultClass, filter, collectionNamesOnly, getBatchSize(), maxTimeMS);
+ return operations.listCollections(databaseName, resultClass, filter, collectionNamesOnly, authorizedCollections,
+ getBatchSize(), maxTimeMS);
+ }
+
+ @Override
+ public ListCollectionsIterable map(final Function mapper) {
+ return new Mapping<>(this, mapper);
+ }
+
+ private static final class Mapping implements ListCollectionsIterable {
+ private final ListCollectionsIterable wrapped;
+ private final Function mapper;
+
+ Mapping(final ListCollectionsIterable iterable, final Function mapper) {
+ this.wrapped = iterable;
+ this.mapper = mapper;
+ }
+
+ @Override
+ public Mapping filter(@Nullable final Bson filter) {
+ wrapped.filter(filter);
+ return this;
+ }
+
+ @Override
+ public ListCollectionsIterable authorizedCollections(final boolean authorizedCollections) {
+ wrapped.authorizedCollections(authorizedCollections);
+ return this;
+ }
+
+ @Override
+ public ListCollectionsIterable maxTime(final long maxTime, final TimeUnit timeUnit) {
+ wrapped.maxTime(maxTime, timeUnit);
+ return this;
+ }
+
+ @Override
+ public ListCollectionsIterable batchSize(final int batchSize) {
+ wrapped.batchSize(batchSize);
+ return this;
+ }
+
+ @Override
+ public MongoCursor iterator() {
+ return new MongoMappingCursor<>(wrapped.iterator(), mapper);
+ }
+
+ @Override
+ public MongoCursor cursor() {
+ return iterator();
+ }
+
+ @Nullable
+ @Override
+ public U first() {
+ T first = wrapped.first();
+ return first == null ? null : mapper.apply(first);
+ }
+
+ @Override
+ public Mapping map(final Function mapper) {
+ return new Mapping<>(this, mapper);
+ }
+
+ @Override
+ public > A into(final A target) {
+ forEach(target::add);
+ return target;
+ }
+
+ /**
+ * This method is used in tests written in Groovy.
+ */
+ @VisibleForTesting(otherwise = PRIVATE)
+ ListCollectionsIterable getMapped() {
+ return wrapped;
+ }
}
}
diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoDatabaseImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoDatabaseImpl.java
index bae604935a3..63f5807ab26 100644
--- a/driver-sync/src/main/com/mongodb/client/internal/MongoDatabaseImpl.java
+++ b/driver-sync/src/main/com/mongodb/client/internal/MongoDatabaseImpl.java
@@ -28,7 +28,6 @@
import com.mongodb.client.ListCollectionsIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
-import com.mongodb.client.MongoIterable;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.CreateViewOptions;
import com.mongodb.client.model.IndexOptionDefaults;
@@ -212,17 +211,17 @@ private void executeDrop(@Nullable final ClientSession clientSession) {
}
@Override
- public MongoIterable listCollectionNames() {
+ public ListCollectionsIterable listCollectionNames() {
return createListCollectionNamesIterable(null);
}
@Override
- public MongoIterable listCollectionNames(final ClientSession clientSession) {
+ public ListCollectionsIterable listCollectionNames(final ClientSession clientSession) {
notNull("clientSession", clientSession);
return createListCollectionNamesIterable(clientSession);
}
- private MongoIterable createListCollectionNamesIterable(@Nullable final ClientSession clientSession) {
+ private ListCollectionsIterable createListCollectionNamesIterable(@Nullable final ClientSession clientSession) {
return createListCollectionsIterable(clientSession, BsonDocument.class, true)
.map(new Function() {
@Override
@@ -253,7 +252,7 @@ public ListCollectionsIterable listCollections(final ClientSe
return createListCollectionsIterable(clientSession, resultClass, false);
}
- private ListCollectionsIterable createListCollectionsIterable(@Nullable final ClientSession clientSession,
+ private ListCollectionsIterableImpl createListCollectionsIterable(@Nullable final ClientSession clientSession,
final Class resultClass,
final boolean collectionNamesOnly) {
return new ListCollectionsIterableImpl<>(clientSession, name, collectionNamesOnly, resultClass, codecRegistry,
diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/ListCollectionsIterableSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/ListCollectionsIterableSpecification.groovy
index 3bc72838b10..5cd4182afe6 100644
--- a/driver-sync/src/test/unit/com/mongodb/client/internal/ListCollectionsIterableSpecification.groovy
+++ b/driver-sync/src/test/unit/com/mongodb/client/internal/ListCollectionsIterableSpecification.groovy
@@ -46,14 +46,17 @@ class ListCollectionsIterableSpecification extends Specification {
def 'should build the expected listCollectionOperation'() {
given:
- def executor = new TestOperationExecutor([null, null, null]);
+ def executor = new TestOperationExecutor([null, null, null, null]);
def listCollectionIterable = new ListCollectionsIterableImpl(null, 'db', false, Document, codecRegistry,
- readPreference, executor)
+ readPreference, executor, true)
.filter(new Document('filter', 1))
.batchSize(100)
.maxTime(1000, MILLISECONDS)
def listCollectionNamesIterable = new ListCollectionsIterableImpl(null, 'db', true, Document, codecRegistry,
- readPreference, executor)
+ readPreference, executor, true)
+ def listAuthorizedCollectionNamesIterable = new ListCollectionsIterableImpl(null, 'db', true, Document,
+ codecRegistry, readPreference, executor, true)
+ .authorizedCollections(true)
when: 'default input should be as expected'
listCollectionIterable.iterator()
@@ -85,6 +88,16 @@ class ListCollectionsIterableSpecification extends Specification {
then: 'should create operation with nameOnly'
expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec()).nameOnly(true)
.retryReads(true))
+
+ when: 'requesting authorized collection names only'
+ listAuthorizedCollectionNamesIterable.iterator()
+ operation = executor.getReadOperation() as ListCollectionsOperation
+
+ then: 'should create operation with `nameOnly` and `authorizedCollections`'
+ expect operation, isTheSameAs(new ListCollectionsOperation('db', new DocumentCodec())
+ .nameOnly(true)
+ .authorizedCollections(true)
+ .retryReads(true))
}
def 'should use ClientSession'() {
@@ -94,7 +107,7 @@ class ListCollectionsIterableSpecification extends Specification {
}
def executor = new TestOperationExecutor([batchCursor, batchCursor]);
def listCollectionIterable = new ListCollectionsIterableImpl(clientSession, 'db', false, Document, codecRegistry,
- readPreference, executor)
+ readPreference, executor, true)
when:
listCollectionIterable.first()
@@ -134,7 +147,7 @@ class ListCollectionsIterableSpecification extends Specification {
}
def executor = new TestOperationExecutor([cursor(), cursor(), cursor(), cursor()]);
def mongoIterable = new ListCollectionsIterableImpl(null, 'db', false, Document, codecRegistry, readPreference,
- executor)
+ executor, true)
when:
def results = mongoIterable.first()
@@ -178,7 +191,7 @@ class ListCollectionsIterableSpecification extends Specification {
when:
def batchSize = 5
def mongoIterable = new ListCollectionsIterableImpl(null, 'db', false, Document, codecRegistry, readPreference,
- Stub(OperationExecutor))
+ Stub(OperationExecutor), true)
then:
mongoIterable.getBatchSize() == null