From e352b31e0bbd9e7a7f3af2ce31b978d7fb25ec2e Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 12 Dec 2024 17:30:16 -0800 Subject: [PATCH 01/31] Add improved Bulk Write API for Java Reactive Driver - Created and documented the new Reactive Bulk Write API - Enabled unified and prose tests for reactive Bulk Write API JAVA-5530 --- .../mongodb/internal/async/AsyncFunction.java | 2 +- .../mongodb/internal/async/AsyncRunnable.java | 82 ++++++ .../mongodb/internal/async/AsyncSupplier.java | 20 ++ .../mongodb/internal/async/MutableValue.java | 46 ++++ .../async/function/AsyncCallbackSupplier.java | 12 +- .../internal/operation/AsyncOperations.java | 9 + .../operation/ClientBulkWriteOperation.java | 254 +++++++++++++++++- .../internal/operation/CursorHelper.java | 25 ++ .../com/mongodb/ClusterFixture.java | 2 +- .../reactivestreams/client/MongoCluster.java | 115 ++++++++ .../client/internal/MongoClientImpl.java | 31 +++ .../client/internal/MongoClusterImpl.java | 37 +++ .../internal/MongoOperationPublisher.java | 20 +- .../reactivestreams/client/CrudProseTest.java | 69 ----- .../client/syncadapter/SyncMongoCluster.java | 17 +- .../unified/UnifiedReactiveStreamsTest.java | 3 +- .../client/PublisherApiTest.java | 2 +- .../com/mongodb/client/CrudProseTest.java | 50 ++-- .../mongodb/client/unified/UnifiedTest.java | 27 +- .../unified/UnifiedTestModifications.java | 3 +- 20 files changed, 688 insertions(+), 138 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/internal/async/MutableValue.java diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncFunction.java b/driver-core/src/main/com/mongodb/internal/async/AsyncFunction.java index 7203d3a4945..ad44d52f0d1 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncFunction.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncFunction.java @@ -34,7 +34,7 @@ public interface AsyncFunction { * @param value A {@code @}{@link Nullable} argument of the asynchronous function. * @param callback the callback */ - void unsafeFinish(T value, SingleResultCallback callback); + void unsafeFinish(@Nullable T value, SingleResultCallback callback); /** * Must be invoked at end of async chain or when executing a callback handler supplied by the caller. diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java index d4ead3c5b96..82db7090d6f 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java @@ -17,6 +17,8 @@ package com.mongodb.internal.async; import com.mongodb.internal.TimeoutContext; +import com.mongodb.internal.async.function.AsyncCallbackLoop; +import com.mongodb.internal.async.function.LoopState; import com.mongodb.internal.async.function.RetryState; import com.mongodb.internal.async.function.RetryingAsyncCallbackSupplier; @@ -181,6 +183,27 @@ default AsyncRunnable thenRun(final AsyncRunnable runnable) { }; } +// /** +// * @param runnable The async runnable to run after this runnable +// * @return the composition of this runnable and the runnable, a runnable +// */ +// default AsyncSupplier thenSupplyUntil( +// final AsyncSupplier supplier, +// final Predicate condition, +// final Consumer runnable) { +// return (c) -> { +// this.unsafeFinish((r, e) -> { +// if (e == null) { +// /* If 'runnable' is executed on a different thread from the one that executed the initial 'finish()', +// then invoking 'finish()' within 'runnable' will catch and propagate any exceptions to 'c' (the callback). */ +// supplier.finish(c); +// } else { +// c.completeExceptionally(e); +// } +// }); +// }; +// } + /** * The error check checks if the exception is an instance of the provided class. * @see #thenRunTryCatchAsyncBlocks(AsyncRunnable, java.util.function.Predicate, AsyncFunction) @@ -217,6 +240,18 @@ default AsyncRunnable thenRunTryCatchAsyncBlocks( }); } + default AsyncSupplier thenSupplyTryCatchAsyncBlocks( + final AsyncSupplier supplier, + final Predicate errorCheck, + final AsyncFunction errorFunction) { + return this.thenSupply(c -> { + beginAsync() + .thenSupply(supplier) + .onErrorIf(errorCheck, errorFunction) + .finish(c); + }); + } + /** * @param condition the condition to check * @param runnable The async runnable to run after this runnable, @@ -282,4 +317,51 @@ default AsyncRunnable thenRunRetryingWhile( ).get(callback); }); } + + /** + * In order to break the loop and complete the ongoing iteration, use + * {@link LoopState#breakAndCompleteIf(Supplier, SingleResultCallback)} in the loopBodyRunnable. + * + *

+ * This is equivalent to while(true) with break. + * + * @param loopBodyRunnable the loopBodyRunnable to loop + * @return the composition of this, and the looping branch + * @see AsyncCallbackLoop + */ + default AsyncRunnable thenRunWhileLoop(final AsyncRunnable loopBodyRunnable, final LoopState loopState) { + return thenRun(callback -> { + new AsyncCallbackLoop(loopState, loopBodyRunnable::finish).run(callback); + }); + } + + /** + * This method is equivalent to a do-while loop, where the loop body is executed first and + * then the condition is checked to determine whether the loop should continue. + * + * @param loopBodyRunnable the asynchronous task to be executed in each iteration of the loop + * @param whileCheck a condition to check after each iteration; the loop continues as long as this condition returns true + * @return the composition of this and the looping branch + * @see AsyncCallbackLoop + */ + + default AsyncRunnable thenRunDoWhileLoop(final AsyncRunnable loopBodyRunnable, final Supplier whileCheck) { + return thenRun(finalCallback -> { + LoopState loopState = new LoopState(); + new AsyncCallbackLoop(loopState, iterationCallback -> { + + loopBodyRunnable.finish((result, t) -> { + if (t != null) { + iterationCallback.completeExceptionally(t); + return; + } + if (loopState.breakAndCompleteIf(() -> !whileCheck.get(), iterationCallback)) { + return; + } + iterationCallback.complete(iterationCallback); + }); + + }).run(finalCallback); + }); + } } diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncSupplier.java b/driver-core/src/main/com/mongodb/internal/async/AsyncSupplier.java index 77c289c8723..f2300d3f00d 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncSupplier.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncSupplier.java @@ -81,6 +81,26 @@ default void finish(final SingleResultCallback callback) { } } + /** + * The runnable will always be executed, including on the exceptional path. + * @param runnable the runnable + * @param callback the callback + */ + default void thenAlwaysRunAndFinish(final Runnable runnable, final SingleResultCallback callback) { + this.finish((r, e) -> { + try { + runnable.run(); + } catch (Throwable t) { + if (e != null) { + t.addSuppressed(e); + } + callback.completeExceptionally(t); + return; + } + callback.onResult(r, e); + }); + } + /** * @param function The async function to run after this supplier * @return the composition of this supplier and the function, a supplier diff --git a/driver-core/src/main/com/mongodb/internal/async/MutableValue.java b/driver-core/src/main/com/mongodb/internal/async/MutableValue.java new file mode 100644 index 00000000000..68295ddc19f --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/async/MutableValue.java @@ -0,0 +1,46 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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 + * + * http://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 com.mongodb.internal.async; + +import com.mongodb.annotations.NotThreadSafe; +import com.mongodb.assertions.Assertions; +import com.mongodb.lang.Nullable; + +@NotThreadSafe +public class MutableValue { + private T value; + + public MutableValue(@Nullable final T value) { + this.value = value; + } + + public MutableValue() { + this(null); + } + + public T get() { + return Assertions.assertNotNull(value); + } + + @Nullable + public T getNullable() { + return value; + } + + public void set(@Nullable final T value) { + this.value = value; + } +} diff --git a/driver-core/src/main/com/mongodb/internal/async/function/AsyncCallbackSupplier.java b/driver-core/src/main/com/mongodb/internal/async/function/AsyncCallbackSupplier.java index 40bfd34de3d..1d98fb91a83 100644 --- a/driver-core/src/main/com/mongodb/internal/async/function/AsyncCallbackSupplier.java +++ b/driver-core/src/main/com/mongodb/internal/async/function/AsyncCallbackSupplier.java @@ -15,7 +15,7 @@ */ package com.mongodb.internal.async.function; -import com.mongodb.annotations.NotThreadSafe; +import com.mongodb.internal.async.MutableValue; import com.mongodb.internal.async.SingleResultCallback; import java.util.function.Supplier; @@ -68,16 +68,12 @@ public interface AsyncCallbackSupplier { * This is a price we have to pay to provide a guarantee similar to that of the {@code finally} block. */ default AsyncCallbackSupplier whenComplete(final Runnable after) { - @NotThreadSafe - final class MutableBoolean { - private boolean value; - } - MutableBoolean afterExecuted = new MutableBoolean(); + MutableValue afterExecuted = new MutableValue<>(false); Runnable trackableAfter = () -> { try { after.run(); } finally { - afterExecuted.value = true; + afterExecuted.set(true); } }; return callback -> { @@ -103,7 +99,7 @@ final class MutableBoolean { primaryUnexpectedException = unexpectedException; throw unexpectedException; } finally { - if (primaryUnexpectedException != null && !afterExecuted.value) { + if (primaryUnexpectedException != null && !afterExecuted.get()) { try { trackableAfter.run(); } catch (Throwable afterException) { diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java index 77434bd9781..63a3a64ff98 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncOperations.java @@ -44,6 +44,9 @@ import com.mongodb.client.model.SearchIndexModel; import com.mongodb.client.model.UpdateOptions; import com.mongodb.client.model.WriteModel; +import com.mongodb.client.model.bulk.ClientBulkWriteOptions; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.client.model.changestream.FullDocument; import com.mongodb.client.model.changestream.FullDocumentBeforeChange; import com.mongodb.internal.TimeoutSettings; @@ -293,6 +296,12 @@ public AsyncWriteOperation bulkWrite(final List clientBulkWriteOperation( + final List clientWriteModels, + @Nullable final ClientBulkWriteOptions options) { + return operations.clientBulkWriteOperation(clientWriteModels, options); + } + public AsyncReadOperation commandRead(final Bson command, final Class resultClass) { return operations.commandRead(command, resultClass); } diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index b48031c06c6..29ab99ec714 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -41,7 +41,14 @@ import com.mongodb.connection.ConnectionDescription; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.VisibleForTesting; +import com.mongodb.internal.async.AsyncBatchCursor; +import com.mongodb.internal.async.AsyncSupplier; +import com.mongodb.internal.async.MutableValue; +import com.mongodb.internal.async.SingleResultCallback; +import com.mongodb.internal.async.function.AsyncCallbackSupplier; import com.mongodb.internal.async.function.RetryState; +import com.mongodb.internal.binding.AsyncConnectionSource; +import com.mongodb.internal.binding.AsyncWriteBinding; import com.mongodb.internal.binding.ConnectionSource; import com.mongodb.internal.binding.WriteBinding; import com.mongodb.internal.client.model.bulk.AbstractClientDeleteModel; @@ -70,6 +77,7 @@ import com.mongodb.internal.client.model.bulk.ConcreteClientUpdateOptions; import com.mongodb.internal.client.model.bulk.ConcreteClientUpdateResult; import com.mongodb.internal.client.model.bulk.UnacknowledgedClientBulkWriteResult; +import com.mongodb.internal.connection.AsyncConnection; import com.mongodb.internal.connection.Connection; import com.mongodb.internal.connection.DualMessageSequences; import com.mongodb.internal.connection.IdHoldingBsonWriter; @@ -113,13 +121,18 @@ import static com.mongodb.assertions.Assertions.fail; import static com.mongodb.internal.VisibleForTesting.AccessModifier.PACKAGE; import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; +import static com.mongodb.internal.async.AsyncRunnable.beginAsync; import static com.mongodb.internal.connection.DualMessageSequences.WritersProviderAndLimitsChecker.WriteResult.FAIL_LIMIT_EXCEEDED; import static com.mongodb.internal.connection.DualMessageSequences.WritersProviderAndLimitsChecker.WriteResult.OK_LIMIT_NOT_REACHED; +import static com.mongodb.internal.operation.AsyncOperationHelper.cursorDocumentToAsyncBatchCursor; +import static com.mongodb.internal.operation.AsyncOperationHelper.decorateReadWithRetriesAsync; +import static com.mongodb.internal.operation.AsyncOperationHelper.withAsyncSourceAndConnection; import static com.mongodb.internal.operation.BulkWriteBatch.logWriteModelDoesNotSupportRetries; import static com.mongodb.internal.operation.CommandOperationHelper.commandWriteConcern; import static com.mongodb.internal.operation.CommandOperationHelper.initialRetryState; import static com.mongodb.internal.operation.CommandOperationHelper.shouldAttemptToRetryWriteAndAddRetryableLabel; import static com.mongodb.internal.operation.CommandOperationHelper.transformWriteException; +import static com.mongodb.internal.operation.CursorHelper.exhaustCursorAsync; import static com.mongodb.internal.operation.OperationHelper.isRetryableWrite; import static com.mongodb.internal.operation.SyncOperationHelper.cursorDocumentToBatchCursor; import static com.mongodb.internal.operation.SyncOperationHelper.decorateWriteWithRetries; @@ -138,12 +151,13 @@ /** * This class is not part of the public API and may be removed or changed at any time. */ -public final class ClientBulkWriteOperation implements WriteOperation { +public final class ClientBulkWriteOperation implements WriteOperation, AsyncWriteOperation { private static final ConcreteClientBulkWriteOptions EMPTY_OPTIONS = new ConcreteClientBulkWriteOptions(); private static final String BULK_WRITE_COMMAND_NAME = "bulkWrite"; private static final EncoderContext DEFAULT_ENCODER_CONTEXT = EncoderContext.builder().build(); private static final EncoderContext COLLECTIBLE_DOCUMENT_ENCODER_CONTEXT = EncoderContext.builder() .isEncodingCollectibleDocument(true).build(); + private static final int INITIAL_BATCH_MODEL_START_INDEX = 0; private final List models; private final ConcreteClientBulkWriteOptions options; @@ -180,6 +194,30 @@ public ClientBulkWriteResult execute(final WriteBinding binding) throws ClientBu return resultAccumulator.build(transformedTopLevelError, effectiveWriteConcern); } + @Override + public void executeAsync(final AsyncWriteBinding binding, + final SingleResultCallback finalCallback) { + WriteConcern effectiveWriteConcern = validateAndGetEffectiveWriteConcern(binding.getOperationContext().getSessionContext()); + ResultAccumulator resultAccumulator = new ResultAccumulator(); + + beginAsync().thenSupply(callback -> { + executeAllBatchesAsync(effectiveWriteConcern, binding, resultAccumulator, (result, t) -> { + MongoException transformedTopLevelError = null; + if (t != null) { + if (t instanceof MongoException) { + transformedTopLevelError = transformWriteException((MongoException) t); + } else { + callback.completeExceptionally(t); + return; + } + } + callback.onResult(transformedTopLevelError, null); + }); + }).thenApply((transformedTopLevelError, callback1) -> { + callback1.onResult(resultAccumulator.build(transformedTopLevelError, effectiveWriteConcern), null); + }).finish(finalCallback); + } + /** * To execute a batch means: *

    @@ -187,16 +225,48 @@ public ClientBulkWriteResult execute(final WriteBinding binding) throws ClientBu *
  • consume the cursor, which may involve executing `getMore` commands.
  • *
* - * @throws MongoException When a {@linkplain ClientBulkWriteException#getError() top-level error} happens. + * @throws MongoException When a {@linkplain ClientBulkWriteException#getCause() top-level error} happens. */ private void executeAllBatches( final WriteConcern effectiveWriteConcern, final WriteBinding binding, final ResultAccumulator resultAccumulator) throws MongoException { - Integer nextBatchStartModelIndex = 0; + Integer nextBatchStartModelIndex = INITIAL_BATCH_MODEL_START_INDEX; do { nextBatchStartModelIndex = executeBatch(nextBatchStartModelIndex, effectiveWriteConcern, binding, resultAccumulator); - } while (nextBatchStartModelIndex != null); + } while (shouldExecuteNextBatch(nextBatchStartModelIndex)); + } + + /** + * To execute a batch means: + *
    + *
  • execute a `bulkWrite` command, which creates a cursor;
  • + *
  • consume the cursor, which may involve executing `getMore` commands.
  • + *
+ * + * @throws MongoException When a {@linkplain ClientBulkWriteException#getCause() top-level error} happens. + */ + private void executeAllBatchesAsync( + final WriteConcern effectiveWriteConcern, + final AsyncWriteBinding binding, + final ResultAccumulator resultAccumulator, + final SingleResultCallback finalCallback) { + MutableValue nextBatchStartModelIndex = new MutableValue<>(INITIAL_BATCH_MODEL_START_INDEX); + + beginAsync() + .thenRunDoWhileLoop(iterationCallback -> { + beginAsync().thenSupply(c -> { + executeBatchAsync(nextBatchStartModelIndex.get(), effectiveWriteConcern, binding, resultAccumulator, c); + }).thenApply((nextBatchStartModelIdx, c) -> { + nextBatchStartModelIndex.set(nextBatchStartModelIdx); + c.complete(c); + }).finish(iterationCallback); + }, () -> shouldExecuteNextBatch(nextBatchStartModelIndex.getNullable())) + .finish(finalCallback); + } + + private static boolean shouldExecuteNextBatch(@Nullable final Integer nextBatchStartModelIndex) { + return nextBatchStartModelIndex != null; } /** @@ -238,6 +308,7 @@ private Integer executeBatch( retryState, connectionSource, connection, bulkWriteCommand, effectiveWriteConcern, operationContext); }) ); + try { ExhaustiveClientBulkWriteCommandOkResponse bulkWriteCommandOkResponse = retryingBatchExecutor.get(); return resultAccumulator.onBulkWriteCommandOkResponseOrNoResponse( @@ -258,6 +329,80 @@ private Integer executeBatch( } } + /** + * @return The start model index of the next batch, provided that the operation + * {@linkplain ExhaustiveClientBulkWriteCommandOkResponse#operationMayContinue(ConcreteClientBulkWriteOptions) may continue} + * and there are unexecuted {@linkplain ClientNamespacedWriteModel models} left. + */ + @Nullable + private void executeBatchAsync( + final int batchStartModelIndex, + final WriteConcern effectiveWriteConcern, + final AsyncWriteBinding binding, + final ResultAccumulator resultAccumulator, + final SingleResultCallback finalCallback) { + List unexecutedModels = models.subList(batchStartModelIndex, models.size()); + assertFalse(unexecutedModels.isEmpty()); + OperationContext operationContext = binding.getOperationContext(); + SessionContext sessionContext = operationContext.getSessionContext(); + TimeoutContext timeoutContext = operationContext.getTimeoutContext(); + RetryState retryState = initialRetryState(retryWritesSetting, timeoutContext); + BatchEncoder batchEncoder = new BatchEncoder(); + + AsyncCallbackSupplier retryingBatchExecutor = decorateReadWithRetriesAsync( + retryState, operationContext, + // Each batch re-selects a server and re-checks out a connection because this is simpler, + // and it is allowed by https://jira.mongodb.org/browse/DRIVERS-2502. + // If connection pinning is required, {@code binding} handles that, + // and `ClientSession`, `TransactionContext` are aware of that. + funcCallback -> withAsyncSourceAndConnection(binding::getWriteConnectionSource, true, funcCallback, + (connectionSource, connection, resultCallback) -> { + ConnectionDescription connectionDescription = connection.getDescription(); + boolean effectiveRetryWrites = isRetryableWrite( + retryWritesSetting, effectiveWriteConcern, connectionDescription, sessionContext); + retryState.breakAndThrowIfRetryAnd(() -> !effectiveRetryWrites); + resultAccumulator.onNewServerAddress(connectionDescription.getServerAddress()); + retryState.attach(AttachmentKeys.maxWireVersion(), connectionDescription.getMaxWireVersion(), + true) + .attach(AttachmentKeys.commandDescriptionSupplier(), () -> BULK_WRITE_COMMAND_NAME, + false); + ClientBulkWriteCommand bulkWriteCommand = createBulkWriteCommand( + retryState, effectiveRetryWrites, effectiveWriteConcern, sessionContext, + unexecutedModels, batchEncoder, + () -> retryState.attach(AttachmentKeys.retryableCommandFlag(), true, true)); + executeBulkWriteCommandAndExhaustOkResponseAsync( + retryState, connectionSource, connection, bulkWriteCommand, effectiveWriteConcern, + operationContext, + resultCallback); + })); + + beginAsync() + .thenSupply(callback -> { + retryingBatchExecutor.get(callback); + }) + .thenApply((bulkWriteCommandOkResponse, callback) -> { + callback.complete(resultAccumulator.onBulkWriteCommandOkResponseOrNoResponse( + batchStartModelIndex, bulkWriteCommandOkResponse, batchEncoder.intoEncodedBatchInfo())); + }).onErrorIf(throwable -> true, (t, callback) -> { + if (t instanceof MongoWriteConcernWithResponseException) { + callback.complete(resultAccumulator.onBulkWriteCommandOkResponseWithWriteConcernError( + batchStartModelIndex, (MongoWriteConcernWithResponseException) t, batchEncoder.intoEncodedBatchInfo())); + } else if (t instanceof MongoCommandException) { + resultAccumulator.onBulkWriteCommandErrorResponse((MongoCommandException) t); + callback.completeExceptionally(t); + } else if (t instanceof MongoException) { + // The server does not have a chance to add "RetryableWriteError" label to `e`, + // and if it is the last attempt failure, `RetryingSyncSupplier` also may not have a chance + // to add the label. So we do that explicitly. + shouldAttemptToRetryWriteAndAddRetryableLabel(retryState, t); + resultAccumulator.onBulkWriteCommandErrorWithoutResponse((MongoException) t); + callback.completeExceptionally(t); + } else { + callback.completeExceptionally(t); + } + }).finish(finalCallback); + } + /** * @throws MongoWriteConcernWithResponseException This internal exception must be handled to avoid it being observed by an application. * It {@linkplain MongoWriteConcernWithResponseException#getResponse() bears} the OK response to the {@code bulkWriteCommand}, @@ -287,11 +432,63 @@ private ExhaustiveClientBulkWriteCommandOkResponse executeBulkWriteCommandAndExh } List> cursorExhaustBatches = doWithRetriesDisabledForCommand(retryState, "getMore", () -> exhaustBulkWriteCommandOkResponseCursor(connectionSource, connection, bulkWriteCommandOkResponse)); - ExhaustiveClientBulkWriteCommandOkResponse exhaustiveBulkWriteCommandOkResponse = new ExhaustiveClientBulkWriteCommandOkResponse( - bulkWriteCommandOkResponse, cursorExhaustBatches); + + return getExhaustiveClientBulkWriteCommandOkResponse( + bulkWriteCommandOkResponse, + cursorExhaustBatches, + connection.getDescription()); + } + + @Nullable + private void executeBulkWriteCommandAndExhaustOkResponseAsync( + final RetryState retryState, + final AsyncConnectionSource connectionSource, + final AsyncConnection connection, + final ClientBulkWriteCommand bulkWriteCommand, + final WriteConcern effectiveWriteConcern, + final OperationContext operationContext, + final SingleResultCallback finalCallback) + throws MongoWriteConcernWithResponseException { + beginAsync().thenSupply(callback -> { + connection.commandAsync( + "admin", + bulkWriteCommand.getCommandDocument(), + NoOpFieldNameValidator.INSTANCE, + null, + CommandResultDocumentCodec.create(codecRegistry.get(BsonDocument.class), CommandBatchCursorHelper.FIRST_BATCH), + operationContext, + effectiveWriteConcern.isAcknowledged(), + bulkWriteCommand.getOpsAndNsInfo(), callback); + }).thenApply((bulkWriteCommandOkResponse, callback) -> { + if (bulkWriteCommandOkResponse == null) { + callback.complete((ExhaustiveClientBulkWriteCommandOkResponse) null); + return; + } + beginAsync().>>thenSupply(c -> { + doWithRetriesDisabledForCommandAsync(retryState, "getMore", (c1) -> { + exhaustBulkWriteCommandOkResponseCursorAsync(connectionSource, connection, bulkWriteCommandOkResponse, c1); + }, c); + }).thenApply((cursorExhaustBatches, c) -> { + ExhaustiveClientBulkWriteCommandOkResponse exhaustiveBulkWriteCommandOkResponse = + getExhaustiveClientBulkWriteCommandOkResponse( + bulkWriteCommandOkResponse, + cursorExhaustBatches, + connection.getDescription()); + c.complete(exhaustiveBulkWriteCommandOkResponse); + }).finish(callback); + }).finish(finalCallback); + } + + private static ExhaustiveClientBulkWriteCommandOkResponse getExhaustiveClientBulkWriteCommandOkResponse( + final BsonDocument bulkWriteCommandOkResponse, final List> cursorExhaustBatches, + final ConnectionDescription connection) { + ExhaustiveClientBulkWriteCommandOkResponse exhaustiveBulkWriteCommandOkResponse = + new ExhaustiveClientBulkWriteCommandOkResponse( + bulkWriteCommandOkResponse, cursorExhaustBatches); + // `Connection.command` does not throw `MongoWriteConcernException`, so we have to construct it ourselves MongoWriteConcernException writeConcernException = Exceptions.createWriteConcernException( - bulkWriteCommandOkResponse, connection.getDescription().getServerAddress()); + bulkWriteCommandOkResponse, connection.getServerAddress()); if (writeConcernException != null) { throw new MongoWriteConcernWithResponseException(writeConcernException, exhaustiveBulkWriteCommandOkResponse); } @@ -315,6 +512,25 @@ private R doWithRetriesDisabledForCommand( } } + private void doWithRetriesDisabledForCommandAsync( + final RetryState retryState, + final String commandDescription, + final AsyncSupplier actionWithCommand, + final SingleResultCallback finalCallback) { + Optional originalRetryableCommandFlag = retryState.attachment(AttachmentKeys.retryableCommandFlag()); + Supplier originalCommandDescriptionSupplier = retryState.attachment(AttachmentKeys.commandDescriptionSupplier()) + .orElseThrow(Assertions::fail); + + beginAsync().thenSupply(c -> { + retryState.attach(AttachmentKeys.retryableCommandFlag(), false, true) + .attach(AttachmentKeys.commandDescriptionSupplier(), () -> commandDescription, false); + actionWithCommand.finish(c); + }).thenAlwaysRunAndFinish(() -> { + originalRetryableCommandFlag.ifPresent(value -> retryState.attach(AttachmentKeys.retryableCommandFlag(), value, true)); + retryState.attach(AttachmentKeys.commandDescriptionSupplier(), originalCommandDescriptionSupplier, false); + }, finalCallback); + } + private List> exhaustBulkWriteCommandOkResponseCursor( final ConnectionSource connectionSource, final Connection connection, @@ -332,6 +548,28 @@ private List> exhaustBulkWriteCommandOkResponseCursor( } } + private void exhaustBulkWriteCommandOkResponseCursorAsync(final AsyncConnectionSource connectionSource, + final AsyncConnection connection, + final BsonDocument bulkWriteCommandOkResponse, + final SingleResultCallback>> finalCallback) { + AsyncBatchCursor cursor = cursorDocumentToAsyncBatchCursor( + TimeoutMode.CURSOR_LIFETIME, + bulkWriteCommandOkResponse, + 0, + codecRegistry.get(BsonDocument.class), + options.getComment().orElse(null), + connectionSource, + connection); + + beginAsync().>>thenSupply(callback -> { + exhaustCursorAsync(cursor, callback); + }).thenAlwaysRunAndFinish(() -> { + if (!cursor.isClosed()) { + cursor.close(); + }}, finalCallback); + } + + private ClientBulkWriteCommand createBulkWriteCommand( final RetryState retryState, final boolean effectiveRetryWrites, @@ -447,6 +685,8 @@ private static final class ExhaustiveClientBulkWriteCommandOkResponse { } else if (cursorExhaustBatches.size() == 1) { cursorExhaust = cursorExhaustBatches.get(0); } else { + //TODO-VALENTIN-question can we do this flatmap in cursor exasust method to simplify the flow? + // we can do either flat map above or change getCursorExhaust to return List of Lists (if any perf benefit) cursorExhaust = cursorExhaustBatches.stream().flatMap(Collection::stream).collect(toList()); } } diff --git a/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java b/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java index 26511c86885..caf0b13ad38 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java @@ -16,16 +16,41 @@ package com.mongodb.internal.operation; +import com.mongodb.internal.async.AsyncBatchCursor; +import com.mongodb.internal.async.SingleResultCallback; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.BsonInt32; +import java.util.ArrayList; +import java.util.List; + +import static com.mongodb.internal.async.AsyncRunnable.beginAsync; + final class CursorHelper { static BsonDocument getCursorDocumentFromBatchSize(@Nullable final Integer batchSize) { return batchSize == null ? new BsonDocument() : new BsonDocument("batchSize", new BsonInt32(batchSize)); } + public static void exhaustCursorAsync(final AsyncBatchCursor cursor, final SingleResultCallback>> finalCallback) { + List> results = new ArrayList<>(); + + beginAsync().thenRunDoWhileLoop(iterationCallback -> { + beginAsync(). + thenSupply(cursor::next) + .thenConsume((batch, callback) -> { + if (batch != null && !batch.isEmpty()) { + results.add(batch); + } + callback.complete(callback); + }).finish(iterationCallback); + }, () -> !cursor.isClosed()) + .>>thenSupply(callback -> { + callback.complete(results); + }).finish(finalCallback); + } + private CursorHelper() { } } diff --git a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java index a889856f394..dde9682de8d 100644 --- a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java +++ b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java @@ -125,7 +125,7 @@ public final class ClusterFixture { private static final String MONGODB_OCSP_SHOULD_SUCCEED = "org.mongodb.test.ocsp.tls.should.succeed"; private static final String DEFAULT_DATABASE_NAME = "JavaDriverTest"; private static final int COMMAND_NOT_FOUND_ERROR_CODE = 59; - public static final long TIMEOUT = 60L; + public static final long TIMEOUT = 120L; public static final Duration TIMEOUT_DURATION = Duration.ofSeconds(TIMEOUT); public static final TimeoutSettings TIMEOUT_SETTINGS = new TimeoutSettings(30_000, 10_000, 0, null, SECONDS.toMillis(5)); diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java index ef7c0ddb79d..1e2eede9aeb 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java @@ -16,7 +16,10 @@ package com.mongodb.reactivestreams.client; +import com.mongodb.ClientBulkWriteException; import com.mongodb.ClientSessionOptions; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoException; import com.mongodb.MongoNamespace; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; @@ -24,6 +27,11 @@ import com.mongodb.annotations.Alpha; import com.mongodb.annotations.Immutable; import com.mongodb.annotations.Reason; +import com.mongodb.client.model.bulk.ClientBulkWriteOptions; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedDeleteManyModel; +import com.mongodb.client.model.bulk.ClientNamespacedUpdateManyModel; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.lang.Nullable; import org.bson.Document; import org.bson.codecs.configuration.CodecRegistry; @@ -353,4 +361,111 @@ public interface MongoCluster { * @mongodb.driver.dochub core/changestreams Change Streams */ ChangeStreamPublisher watch(ClientSession clientSession, List pipeline, Class resultClass); + + /** + * Executes a client-level bulk write operation. + * This method is functionally equivalent to {@link #bulkWrite(List, ClientBulkWriteOptions)} + * with the {@linkplain ClientBulkWriteOptions#clientBulkWriteOptions() default options}. + *

+ * This operation supports {@linkplain MongoClientSettings#getRetryWrites() retryable writes}. + * Depending on the number of {@code models}, encoded size of {@code models}, and the size limits in effect, + * executing this operation may require multiple {@code bulkWrite} commands. + * The eligibility for retries is determined per each {@code bulkWrite} command: + * {@link ClientNamespacedUpdateManyModel}, {@link ClientNamespacedDeleteManyModel} in a command render it non-retryable.

+ *

+ * This operation is not supported by MongoDB Atlas Serverless instances.

+ * + * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. + * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. + * The {@link ClientBulkWriteException} If and only if the operation is unsuccessful or partially unsuccessful, + * and there is at least one of the following pieces of information to report: + * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, + * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. + * @since 5.3 + * @mongodb.server.release 8.0 + * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite + */ + Publisher bulkWrite(List models) throws ClientBulkWriteException; + + /** + * Executes a client-level bulk write operation. + *

+ * This operation supports {@linkplain MongoClientSettings#getRetryWrites() retryable writes}. + * Depending on the number of {@code models}, encoded size of {@code models}, and the size limits in effect, + * executing this operation may require multiple {@code bulkWrite} commands. + * The eligibility for retries is determined per each {@code bulkWrite} command: + * {@link ClientNamespacedUpdateManyModel}, {@link ClientNamespacedDeleteManyModel} in a command render it non-retryable.

+ *

+ * This operation is not supported by MongoDB Atlas Serverless instances.

+ * + * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. + * @param options The options. + * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. + * The {@link ClientBulkWriteException} If and only if the operation is unsuccessful or partially unsuccessful, + * and there is at least one of the following pieces of information to report: + * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, + * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. + * @since 5.3 + * @mongodb.server.release 8.0 + * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite + */ + Publisher bulkWrite( + List models, + ClientBulkWriteOptions options) throws ClientBulkWriteException; + + /** + * Executes a client-level bulk write operation. + * This method is functionally equivalent to {@link #bulkWrite(ClientSession, List, ClientBulkWriteOptions)} + * with the {@linkplain ClientBulkWriteOptions#clientBulkWriteOptions() default options}. + *

+ * This operation supports {@linkplain MongoClientSettings#getRetryWrites() retryable writes}. + * Depending on the number of {@code models}, encoded size of {@code models}, and the size limits in effect, + * executing this operation may require multiple {@code bulkWrite} commands. + * The eligibility for retries is determined per each {@code bulkWrite} command: + * {@link ClientNamespacedUpdateManyModel}, {@link ClientNamespacedDeleteManyModel} in a command render it non-retryable.

+ *

+ * This operation is not supported by MongoDB Atlas Serverless instances.

+ * + * @param clientSession The {@linkplain ClientSession client session} with which to associate this operation. + * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. + * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. + * The {@link ClientBulkWriteException} If and only if the operation is unsuccessful or partially unsuccessful, + * and there is at least one of the following pieces of information to report: + * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, + * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. + * @since 5.3 + * @mongodb.server.release 8.0 + * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite + */ + Publisher bulkWrite( + ClientSession clientSession, + List models) throws ClientBulkWriteException; + + /** + * Executes a client-level bulk write operation. + *

+ * This operation supports {@linkplain MongoClientSettings#getRetryWrites() retryable writes}. + * Depending on the number of {@code models}, encoded size of {@code models}, and the size limits in effect, + * executing this operation may require multiple {@code bulkWrite} commands. + * The eligibility for retries is determined per each {@code bulkWrite} command: + * {@link ClientNamespacedUpdateManyModel}, {@link ClientNamespacedDeleteManyModel} in a command render it non-retryable.

+ *

+ * This operation is not supported by MongoDB Atlas Serverless instances.

+ * + * @param clientSession The {@linkplain ClientSession client session} with which to associate this operation. + * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. + * @param options The options. + * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. + * The {@link ClientBulkWriteException} If and only if the operation is unsuccessful or partially unsuccessful, + * and there is at least one of the following pieces of information to report: + * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, + * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. + * @since 5.3 + * @mongodb.server.release 8.0 + * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite + */ + Publisher bulkWrite( + ClientSession clientSession, + List models, + ClientBulkWriteOptions options) throws ClientBulkWriteException; } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java index 27a0c9195c3..dd4cb79a06f 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java @@ -17,6 +17,7 @@ package com.mongodb.reactivestreams.client.internal; import com.mongodb.AutoEncryptionSettings; +import com.mongodb.ClientBulkWriteException; import com.mongodb.ClientSessionOptions; import com.mongodb.ContextProvider; import com.mongodb.MongoClientSettings; @@ -24,6 +25,9 @@ import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; +import com.mongodb.client.model.bulk.ClientBulkWriteOptions; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.connection.ClusterDescription; import com.mongodb.internal.TimeoutSettings; import com.mongodb.internal.connection.Cluster; @@ -229,6 +233,33 @@ public ChangeStreamPublisher watch( return delegate.watch(clientSession, pipeline, resultClass); } + @Override + public Publisher bulkWrite(final List models) + throws ClientBulkWriteException { + return delegate.bulkWrite(models); + } + + @Override + public Publisher bulkWrite(final List models, + final ClientBulkWriteOptions options) + throws ClientBulkWriteException { + return delegate.bulkWrite(models, options); + } + + @Override + public Publisher bulkWrite(final ClientSession clientSession, + final List models) + throws ClientBulkWriteException { + return delegate.bulkWrite(clientSession, models); + } + + @Override + public Publisher bulkWrite(final ClientSession clientSession, + final List models, + final ClientBulkWriteOptions options) throws ClientBulkWriteException { + return delegate.bulkWrite(clientSession, models, options); + } + @Override public Publisher startSession() { return delegate.startSession(); diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java index 72bcf53e303..b0e8a0b40fb 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java @@ -16,10 +16,14 @@ package com.mongodb.reactivestreams.client.internal; +import com.mongodb.ClientBulkWriteException; import com.mongodb.ClientSessionOptions; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; +import com.mongodb.client.model.bulk.ClientBulkWriteOptions; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.internal.TimeoutSettings; import com.mongodb.internal.client.model.changestream.ChangeStreamLevel; import com.mongodb.internal.connection.Cluster; @@ -42,6 +46,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import static com.mongodb.assertions.Assertions.isTrueArgument; import static com.mongodb.assertions.Assertions.notNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -237,4 +242,36 @@ public ChangeStreamPublisher watch(final ClientSession clientSession, fin resultClass, pipeline, ChangeStreamLevel.CLIENT); } + @Override + public Publisher bulkWrite(final List clientWriteModels) throws ClientBulkWriteException { + notNull("clientWriteModels", clientWriteModels); + isTrueArgument("`clientWriteModels` must not be empty", !clientWriteModels.isEmpty()); + return mongoOperationPublisher.clientBulkWrite(null, clientWriteModels, null); + } + + @Override + public Publisher bulkWrite(final List clientWriteModels, + final ClientBulkWriteOptions options) throws ClientBulkWriteException { + notNull("clientWriteModels", clientWriteModels); + isTrueArgument("`clientWriteModels` must not be empty", !clientWriteModels.isEmpty()); + notNull("options", options); + return mongoOperationPublisher.clientBulkWrite(null, clientWriteModels, options); + } + + @Override + public Publisher bulkWrite(final ClientSession clientSession, + final List clientWriteModels) throws ClientBulkWriteException { + notNull("clientSession", clientSession); + notNull("clientWriteModels", clientWriteModels); + isTrueArgument("`clientWriteModels` must not be empty", !clientWriteModels.isEmpty()); + return mongoOperationPublisher.clientBulkWrite(clientSession, clientWriteModels, null); + } + + @Override + public Publisher bulkWrite(final ClientSession clientSession, + final List models, + final ClientBulkWriteOptions options) throws ClientBulkWriteException { + return mongoOperationPublisher.clientBulkWrite(clientSession, models, options); + } + } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java index 5ccea518cb5..3da4b885bdf 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java @@ -50,6 +50,9 @@ import com.mongodb.client.model.SearchIndexModel; import com.mongodb.client.model.UpdateOptions; import com.mongodb.client.model.WriteModel; +import com.mongodb.client.model.bulk.ClientBulkWriteOptions; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.InsertManyResult; import com.mongodb.client.result.InsertOneResult; @@ -80,6 +83,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; import static java.util.Collections.singletonList; import static org.bson.codecs.configuration.CodecRegistries.withUuidRepresentation; @@ -245,7 +249,7 @@ Publisher createCollection( @Nullable final ClientSession clientSession, final String collectionName, final CreateCollectionOptions options) { return createWriteOperationMono( operations::getTimeoutSettings, - () -> operations.createCollection(collectionName, options, autoEncryptionSettings), clientSession); + operations.createCollection(collectionName, options, autoEncryptionSettings), clientSession); } Publisher createView( @@ -288,6 +292,15 @@ Publisher bulkWrite( operations::getTimeoutSettings, () -> operations.bulkWrite(notNull("requests", requests), notNull("options", options)), clientSession); } + Publisher clientBulkWrite( + @Nullable final ClientSession clientSession, + final List clientWriteModels, + @Nullable final ClientBulkWriteOptions options) { + isTrue("`autoEncryptionSettings` is null, as bulkWrite does not currently support automatic encryption", autoEncryptionSettings == null); + return createWriteOperationMono( + operations::getTimeoutSettings, + () -> operations.clientBulkWriteOperation(clientWriteModels, options), clientSession); + } Publisher insertOne(@Nullable final ClientSession clientSession, final T document, final InsertOneOptions options) { return createSingleWriteRequestMono(() -> operations.insertOne(notNull("document", document), @@ -508,6 +521,11 @@ Mono createWriteOperationMono(final Supplier timeoutSett return getExecutor(timeoutSettingsSupplier.get()) .execute(writeOperation, getReadConcern(), clientSession); } + Mono createWriteOperationMono(final Supplier timeoutSettingsSupplier, + final AsyncWriteOperation writeOperation, @Nullable final ClientSession clientSession) { + return getExecutor(timeoutSettingsSupplier.get()) + .execute(writeOperation, getReadConcern(), clientSession); + } private Mono createSingleWriteRequestMono( final Supplier> operation, diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/CrudProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/CrudProseTest.java index 22bb1a23d77..81d88e6fdb0 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/CrudProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/CrudProseTest.java @@ -18,15 +18,6 @@ import com.mongodb.MongoClientSettings; import com.mongodb.client.MongoClient; import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * See @@ -37,64 +28,4 @@ final class CrudProseTest extends com.mongodb.client.CrudProseTest { protected MongoClient createMongoClient(final MongoClientSettings.Builder mongoClientSettingsBuilder) { return new SyncMongoClient(MongoClients.create(mongoClientSettingsBuilder.build())); } - - @DisplayName("5. MongoClient.bulkWrite collects WriteConcernErrors across batches") - @Test - @Override - protected void testBulkWriteCollectsWriteConcernErrorsAcrossBatches() { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } - - @DisplayName("6. MongoClient.bulkWrite handles individual WriteErrors across batches") - @ParameterizedTest - @ValueSource(booleans = {false, true}) - @Override - protected void testBulkWriteHandlesWriteErrorsAcrossBatches(final boolean ordered) { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } - - @DisplayName("8. MongoClient.bulkWrite handles a cursor requiring getMore within a transaction") - @Test - @Override - protected void testBulkWriteHandlesCursorRequiringGetMoreWithinTransaction() { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } - - @DisplayName("11. MongoClient.bulkWrite batch splits when the addition of a new namespace exceeds the maximum message size") - @Test - @Override - protected void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo() { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } - - @DisplayName("12. MongoClient.bulkWrite returns an error if no operations can be added to ops") - @ParameterizedTest - @ValueSource(strings = {"document", "namespace"}) - @Override - protected void testBulkWriteSplitsErrorsForTooLargeOpsOrNsInfo(final String tooLarge) { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } - - @DisplayName("13. MongoClient.bulkWrite returns an error if auto-encryption is configured") - @Test - @Override - protected void testBulkWriteErrorsForAutoEncryption() { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } - - @DisplayName("15. MongoClient.bulkWrite with unacknowledged write concern uses w:0 for all batches") - @Test - protected void testWriteConcernOfAllBatchesWhenUnacknowledgedRequested() { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } - - @ParameterizedTest - @MethodSource("insertMustGenerateIdAtMostOnceArgs") - @Override - protected void insertMustGenerateIdAtMostOnce( - final Class documentClass, - final boolean expectIdGenerated, - final Supplier documentSupplier) { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoCluster.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoCluster.java index 8ded1f38865..fc3cad4b6a7 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoCluster.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoCluster.java @@ -21,7 +21,6 @@ import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; -import com.mongodb.assertions.Assertions; import com.mongodb.client.ChangeStreamIterable; import com.mongodb.client.ClientSession; import com.mongodb.client.ListDatabasesIterable; @@ -29,8 +28,8 @@ import com.mongodb.client.MongoDatabase; import com.mongodb.client.MongoIterable; import com.mongodb.client.model.bulk.ClientBulkWriteOptions; -import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import org.bson.BsonDocument; import org.bson.Document; import org.bson.codecs.configuration.CodecRegistry; @@ -286,24 +285,22 @@ public ChangeStreamIterable watch(final ClientSession clientS @Override public ClientBulkWriteResult bulkWrite( final List clientWriteModels) throws ClientBulkWriteException { - org.junit.jupiter.api.Assumptions.assumeTrue(Boolean.parseBoolean(toString()), "BULK-TODO implement"); - throw Assertions.fail("BULK-TODO implement"); + return requireNonNull(Mono.from(wrapped.bulkWrite(clientWriteModels)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); } @Override public ClientBulkWriteResult bulkWrite( final List clientWriteModels, final ClientBulkWriteOptions options) throws ClientBulkWriteException { - org.junit.jupiter.api.Assumptions.assumeTrue(Boolean.parseBoolean(toString()), "BULK-TODO implement"); - throw Assertions.fail("BULK-TODO implement"); + return requireNonNull(Mono.from(wrapped.bulkWrite(clientWriteModels, options)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); } @Override public ClientBulkWriteResult bulkWrite( final ClientSession clientSession, final List clientWriteModels) throws ClientBulkWriteException { - org.junit.jupiter.api.Assumptions.assumeTrue(Boolean.parseBoolean(toString()), "BULK-TODO implement"); - throw Assertions.fail("BULK-TODO implement"); + return requireNonNull( + Mono.from(wrapped.bulkWrite(unwrap(clientSession), clientWriteModels)).contextWrite(CONTEXT).block(TIMEOUT_DURATION)); } @Override @@ -311,8 +308,8 @@ public ClientBulkWriteResult bulkWrite( final ClientSession clientSession, final List clientWriteModels, final ClientBulkWriteOptions options) throws ClientBulkWriteException { - org.junit.jupiter.api.Assumptions.assumeTrue(Boolean.parseBoolean(toString()), "BULK-TODO implement"); - throw Assertions.fail("BULK-TODO implement"); + return requireNonNull(Mono.from(wrapped.bulkWrite(unwrap(clientSession), clientWriteModels, options)).contextWrite(CONTEXT) + .block(TIMEOUT_DURATION)); } private com.mongodb.reactivestreams.client.ClientSession unwrap(final ClientSession clientSession) { diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedReactiveStreamsTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedReactiveStreamsTest.java index 62c1315e240..9d47364da84 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedReactiveStreamsTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedReactiveStreamsTest.java @@ -46,7 +46,8 @@ protected UnifiedReactiveStreamsTest() { @Override protected MongoClient createMongoClient(final MongoClientSettings settings) { - return new SyncMongoClient(MongoClients.create(settings)); + com.mongodb.reactivestreams.client.MongoClient wrapped = MongoClients.create(settings); + return new SyncMongoClient(wrapped); } @Override diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/PublisherApiTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/PublisherApiTest.java index 5839a7efd8d..09f77743cde 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/PublisherApiTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/PublisherApiTest.java @@ -51,7 +51,7 @@ public class PublisherApiTest { List testPublisherApiMatchesSyncApi() { return asList( dynamicTest("Client Session Api", () -> assertApis(com.mongodb.client.ClientSession.class, ClientSession.class)), -// BULK-TODO uncomment dynamicTest("MongoClient Api", () -> assertApis(com.mongodb.client.MongoClient.class, MongoClient.class)), + dynamicTest("MongoClient Api", () -> assertApis(com.mongodb.client.MongoClient.class, MongoClient.class)), dynamicTest("MongoDatabase Api", () -> assertApis(com.mongodb.client.MongoDatabase.class, MongoDatabase.class)), dynamicTest("MongoCollection Api", () -> assertApis(com.mongodb.client.MongoCollection.class, MongoCollection.class)), dynamicTest("Aggregate Api", () -> assertApis(AggregateIterable.class, AggregatePublisher.class)), diff --git a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java index 72e2fdea0e0..20fd3160600 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java @@ -32,8 +32,8 @@ import com.mongodb.client.model.Updates; import com.mongodb.client.model.ValidationOptions; import com.mongodb.client.model.bulk.ClientBulkWriteOptions; -import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.event.CommandStartedEvent; import com.mongodb.internal.connection.TestCommandListener; import org.bson.BsonArray; @@ -171,7 +171,7 @@ void testBulkWriteSplitsWhenExceedingMaxWriteBatchSize() { int maxWriteBatchSize = droppedDatabase(client).runCommand(new Document("hello", 1)).getInteger("maxWriteBatchSize"); ClientBulkWriteResult result = client.bulkWrite(nCopies( maxWriteBatchSize + 1, - ClientNamespacedWriteModel.insertOne(NAMESPACE, new Document("a", "b")))); + insertOne(NAMESPACE, new Document("a", "b")))); assertEquals(maxWriteBatchSize + 1, result.getInsertedCount()); List startedBulkWriteCommandEvents = commandListener.getCommandStartedEvents("bulkWrite"); assertEquals(2, startedBulkWriteCommandEvents.size()); @@ -193,7 +193,7 @@ void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytes() { Document helloResponse = droppedDatabase(client).runCommand(new Document("hello", 1)); int maxBsonObjectSize = helloResponse.getInteger("maxBsonObjectSize"); int maxMessageSizeBytes = helloResponse.getInteger("maxMessageSizeBytes"); - ClientNamespacedWriteModel model = ClientNamespacedWriteModel.insertOne( + ClientNamespacedWriteModel model = insertOne( NAMESPACE, new Document("a", join("", nCopies(maxBsonObjectSize - 500, "b")))); int numModels = maxMessageSizeBytes / maxBsonObjectSize + 1; @@ -227,7 +227,7 @@ protected void testBulkWriteCollectsWriteConcernErrorsAcrossBatches() throws Int .addCommandListener(commandListener)); FailPoint ignored = FailPoint.enable(failPointDocument, getPrimary())) { int maxWriteBatchSize = droppedDatabase(client).runCommand(new Document("hello", 1)).getInteger("maxWriteBatchSize"); - ClientNamespacedWriteModel model = ClientNamespacedWriteModel.insertOne(NAMESPACE, new Document("a", "b")); + ClientNamespacedWriteModel model = insertOne(NAMESPACE, new Document("a", "b")); int numModels = maxWriteBatchSize + 1; ClientBulkWriteException error = assertThrows(ClientBulkWriteException.class, () -> client.bulkWrite(nCopies(numModels, model))); @@ -253,7 +253,7 @@ protected void testBulkWriteHandlesWriteErrorsAcrossBatches(final boolean ordere Document document = new Document("_id", 1); MongoCollection collection = droppedCollection(client, Document.class); collection.insertOne(document); - ClientNamespacedWriteModel model = ClientNamespacedWriteModel.insertOne(collection.getNamespace(), document); + ClientNamespacedWriteModel model = insertOne(collection.getNamespace(), document); int numModels = maxWriteBatchSize + 1; ClientBulkWriteException error = assertThrows(ClientBulkWriteException.class, () -> client.bulkWrite(nCopies(numModels, model), clientBulkWriteOptions().ordered(ordered))); @@ -279,6 +279,8 @@ protected void testBulkWriteHandlesCursorRequiringGetMoreWithinTransaction() { assumeFalse(isServerlessTest()); assumeFalse(isStandalone()); assertBulkWriteHandlesCursorRequiringGetMore(true); + + //TODO consider removing withTransaction and use start and end transaction. } private void assertBulkWriteHandlesCursorRequiringGetMore(final boolean transaction) { @@ -305,7 +307,8 @@ private void assertBulkWriteHandlesCursorRequiringGetMore(final boolean transact clientUpdateOptions().upsert(true))), clientBulkWriteOptions().verboseResults(true) ); - ClientBulkWriteResult result = transaction ? session.withTransaction(action::get) : action.get(); + + ClientBulkWriteResult result = transaction ? runInTransaction(session, action) : action.get(); assertEquals(2, result.getUpsertedCount()); assertEquals(2, result.getVerboseResults().orElseThrow(Assertions::fail).getUpdateResults().size()); assertEquals(1, commandListener.getCommandStartedEvents("bulkWrite").size()); @@ -322,7 +325,7 @@ protected void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo() () -> { // Case 1: No batch-splitting required testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo((client, models, commandListener) -> { - models.add(ClientNamespacedWriteModel.insertOne(NAMESPACE, new Document("a", "b"))); + models.add(insertOne(NAMESPACE, new Document("a", "b"))); ClientBulkWriteResult result = client.bulkWrite(models); assertEquals(models.size(), result.getInsertedCount()); List startedBulkWriteCommandEvents = commandListener.getCommandStartedEvents("bulkWrite"); @@ -339,7 +342,7 @@ protected void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo() // Case 2: Batch-splitting required testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo((client, models, commandListener) -> { MongoNamespace namespace = new MongoNamespace(NAMESPACE.getDatabaseName(), join("", nCopies(200, "c"))); - models.add(ClientNamespacedWriteModel.insertOne(namespace, new Document("a", "b"))); + models.add(insertOne(namespace, new Document("a", "b"))); ClientBulkWriteResult result = client.bulkWrite(models); assertEquals(models.size(), result.getInsertedCount()); List startedBulkWriteCommandEvents = commandListener.getCommandStartedEvents("bulkWrite"); @@ -371,11 +374,11 @@ private void testBulkWriteSplitsWhenExceedingMaxMessageSizeBytesDueToNsInfo( int remainderBytes = opsBytes % maxBsonObjectSize; List models = new ArrayList<>(nCopies( numModels, - ClientNamespacedWriteModel.insertOne( + insertOne( NAMESPACE, new Document("a", join("", nCopies(maxBsonObjectSize - 57, "b")))))); if (remainderBytes >= 217) { - models.add(ClientNamespacedWriteModel.insertOne( + models.add(insertOne( NAMESPACE, new Document("a", join("", nCopies(remainderBytes - 57, "b"))))); } @@ -394,13 +397,13 @@ protected void testBulkWriteSplitsErrorsForTooLargeOpsOrNsInfo(final String tooL ClientNamespacedWriteModel model; switch (tooLarge) { case "document": { - model = ClientNamespacedWriteModel.insertOne( + model = insertOne( NAMESPACE, new Document("a", join("", nCopies(maxMessageSizeBytes, "b")))); break; } case "namespace": { - model = ClientNamespacedWriteModel.insertOne( + model = insertOne( new MongoNamespace(NAMESPACE.getDatabaseName(), join("", nCopies(maxMessageSizeBytes, "b"))), new Document("a", "b")); break; @@ -429,8 +432,8 @@ protected void testBulkWriteErrorsForAutoEncryption() { assertTrue( assertThrows( IllegalStateException.class, - () -> client.bulkWrite(singletonList(ClientNamespacedWriteModel.insertOne(NAMESPACE, new Document("a", "b"))))) - .getMessage().contains("bulkWrite does not currently support automatic encryption")); + () -> client.bulkWrite(singletonList(insertOne(NAMESPACE, new Document("a", "b"))))) + .getMessage().contains("bulkWrite does not currently support automatic encryption")); } } @@ -447,7 +450,7 @@ protected void testWriteConcernOfAllBatchesWhenUnacknowledgedRequested() { Document helloResponse = database.runCommand(new Document("hello", 1)); int maxBsonObjectSize = helloResponse.getInteger("maxBsonObjectSize"); int maxMessageSizeBytes = helloResponse.getInteger("maxMessageSizeBytes"); - ClientNamespacedWriteModel model = ClientNamespacedWriteModel.insertOne( + ClientNamespacedWriteModel model = insertOne( NAMESPACE, new Document("a", join("", nCopies(maxBsonObjectSize - 500, "b")))); int numModels = maxMessageSizeBytes / maxBsonObjectSize + 1; @@ -594,4 +597,21 @@ public int getV() { private interface TriConsumer { void accept(A1 a1, A2 a2, A3 a3); } + + /** + * This method is used instead of {@link ClientSession#withTransaction(TransactionBody)} + * because reactive {@link com.mongodb.reactivestreams.client;.ClientSession} do not support it. + */ + private static ClientBulkWriteResult runInTransaction(final ClientSession session, + final Supplier action) { + session.startTransaction(); + try { + ClientBulkWriteResult result = action.get(); + session.commitTransaction(); + return result; + } catch (Throwable throwable) { + session.abortTransaction(); + throw throwable; + } + } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java index 27ebf2a76bd..8141af36918 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java @@ -21,10 +21,6 @@ import com.mongodb.MongoNamespace; import com.mongodb.ReadPreference; import com.mongodb.UnixServerAddress; -import com.mongodb.client.unified.UnifiedTestModifications.TestDef; -import com.mongodb.event.TestServerMonitorListener; -import com.mongodb.internal.logging.LogMessage; -import com.mongodb.logging.TestLoggingInterceptor; import com.mongodb.WriteConcern; import com.mongodb.client.ClientSession; import com.mongodb.client.MongoClient; @@ -32,16 +28,20 @@ import com.mongodb.client.gridfs.GridFSBucket; import com.mongodb.client.model.Filters; import com.mongodb.client.test.CollectionHelper; +import com.mongodb.client.unified.UnifiedTestModifications.TestDef; import com.mongodb.client.vault.ClientEncryption; import com.mongodb.connection.ClusterDescription; import com.mongodb.connection.ClusterType; import com.mongodb.connection.ServerDescription; import com.mongodb.event.CommandEvent; import com.mongodb.event.CommandStartedEvent; +import com.mongodb.event.TestServerMonitorListener; import com.mongodb.internal.connection.TestCommandListener; import com.mongodb.internal.connection.TestConnectionPoolListener; +import com.mongodb.internal.logging.LogMessage; import com.mongodb.lang.NonNull; import com.mongodb.lang.Nullable; +import com.mongodb.logging.TestLoggingInterceptor; import com.mongodb.test.AfterBeforeParameterResolver; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -279,16 +279,7 @@ protected void postSetUp(final TestDef def) { @AfterEach public void cleanUp() { for (FailPoint failPoint : failPoints) { - try { - // BULK-TODO remove the try-catch block - failPoint.disableFailPoint(); - } catch (Throwable e) { - for (Throwable suppressed : e.getSuppressed()) { - if (suppressed instanceof TestAbortedException) { - throw (TestAbortedException) suppressed; - } - } - } + failPoint.disableFailPoint(); } entities.close(); postCleanUp(testDef); @@ -412,14 +403,6 @@ private void assertOperation(final UnifiedTestContext context, final BsonDocumen private static void assertOperationResult(final UnifiedTestContext context, final BsonDocument operation, final int operationIndex, final OperationResult result) { - if (result.getException() instanceof org.opentest4j.TestAbortedException) { - // BULK-TODO remove - throw (org.opentest4j.TestAbortedException) result.getException(); - } - if (result.getException() instanceof org.junit.AssumptionViolatedException) { - // BULK-TODO remove - throw (org.junit.AssumptionViolatedException) result.getException(); - } context.getAssertionContext().push(ContextElement.ofCompletedOperation(operation, result, operationIndex)); if (!operation.getBoolean("ignoreResultAndError", BsonBoolean.FALSE).getValue()) { diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index 3687f156c42..e7b6a97ded9 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -45,10 +45,9 @@ public static void doSkips(final TestDef def) { .directory("atlas-data-lake-testing"); // change-streams - def.skipNoncompliantReactive("error required from change stream initialization") // TODO reason? .test("change-streams", "change-streams", "Test with document comment - pre 4.4"); - def.skipNoncompliantReactive("event sensitive tests") // TODO reason? + def.skipNoncompliantReactive("event sensitive tests. We can't guarantee the amount of GetMore commands sent in the reactive driver") .test("change-streams", "change-streams", "Test that comment is set on getMore") .test("change-streams", "change-streams", "Test that comment is not set on getMore - pre 4.4"); def.modify(IGNORE_EXTRA_EVENTS) From a05882dc324587e4514bdddba3f553a97db185b0 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 13 Dec 2024 17:37:48 -0800 Subject: [PATCH 02/31] Disable Kotlin tests. JAVA-5530 --- .../mongodb/internal/async/AsyncRunnable.java | 38 ----------- .../com/mongodb/internal/time/TimePoint.java | 3 +- .../async/AsyncFunctionsAbstractTest.java | 64 +++++++++++++++++++ .../async/AsyncFunctionsTestBase.java | 3 +- .../coroutine/syncadapter/SyncMongoCluster.kt | 20 +++--- .../client/syncadapter/SyncMongoCluster.kt | 20 +++--- .../ClientSideOperationTimeoutProseTest.java | 12 ++-- .../internal/MongoOperationPublisherTest.java | 3 +- .../org/mongodb/scala/MongoClientSpec.scala | 7 +- .../mongodb/client/unified/UnifiedTest.java | 12 ++++ 10 files changed, 118 insertions(+), 64 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java index 82db7090d6f..3903e3f4e27 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java @@ -183,27 +183,6 @@ default AsyncRunnable thenRun(final AsyncRunnable runnable) { }; } -// /** -// * @param runnable The async runnable to run after this runnable -// * @return the composition of this runnable and the runnable, a runnable -// */ -// default AsyncSupplier thenSupplyUntil( -// final AsyncSupplier supplier, -// final Predicate condition, -// final Consumer runnable) { -// return (c) -> { -// this.unsafeFinish((r, e) -> { -// if (e == null) { -// /* If 'runnable' is executed on a different thread from the one that executed the initial 'finish()', -// then invoking 'finish()' within 'runnable' will catch and propagate any exceptions to 'c' (the callback). */ -// supplier.finish(c); -// } else { -// c.completeExceptionally(e); -// } -// }); -// }; -// } - /** * The error check checks if the exception is an instance of the provided class. * @see #thenRunTryCatchAsyncBlocks(AsyncRunnable, java.util.function.Predicate, AsyncFunction) @@ -318,23 +297,6 @@ default AsyncRunnable thenRunRetryingWhile( }); } - /** - * In order to break the loop and complete the ongoing iteration, use - * {@link LoopState#breakAndCompleteIf(Supplier, SingleResultCallback)} in the loopBodyRunnable. - * - *

- * This is equivalent to while(true) with break. - * - * @param loopBodyRunnable the loopBodyRunnable to loop - * @return the composition of this, and the looping branch - * @see AsyncCallbackLoop - */ - default AsyncRunnable thenRunWhileLoop(final AsyncRunnable loopBodyRunnable, final LoopState loopState) { - return thenRun(callback -> { - new AsyncCallbackLoop(loopState, loopBodyRunnable::finish).run(callback); - }); - } - /** * This method is equivalent to a do-while loop, where the loop body is executed first and * then the condition is checked to determine whether the loop should continue. diff --git a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java index d0b95970511..811065d13a6 100644 --- a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java +++ b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java @@ -28,6 +28,7 @@ import static com.mongodb.assertions.Assertions.assertNotNull; import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; /** @@ -234,7 +235,7 @@ public int hashCode() { public String toString() { String remainingMs = isInfinite() ? "infinite" - : "" + TimeUnit.MILLISECONDS.convert(currentNanos() - assertNotNull(nanos), NANOSECONDS); + : "" + remaining(MILLISECONDS); return "TimePoint{" + "nanos=" + nanos + ", remainingMs=" + remainingMs diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java index 65636e2f842..0731951edfe 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java @@ -748,6 +748,26 @@ void testRetryLoop() { }); } + @Test + void testDoWhileLoop() { + assertBehavesSameVariations(67, + () -> { + do { + plain(0); + sync(1); + } while (plainTest(2)); + }, + (finalCallback) -> { + beginAsync().thenRunDoWhileLoop( + callback -> { + plain(0); + async(1, callback); + }, + () -> plainTest(2) + ).finish(finalCallback); + }); + } + @Test void testFinallyWithPlainInsideTry() { // (in try: normal flow + exception + exception) * (in finally: normal + exception) = 6 @@ -770,6 +790,50 @@ void testFinallyWithPlainInsideTry() { }); } + @Test + void testSupplyFinallyWithPlainInsideTry() { + assertBehavesSameVariations(6, + () -> { + try { + plain(1); + return syncReturns(2); + } finally { + plain(3); + } + }, + (callback) -> { + beginAsync().thenSupply(c -> { + plain(1); + asyncReturns(2, c); + }).thenAlwaysRunAndFinish(() -> { + plain(3); + }, callback); + }); + } + + @Test + void testSupplyFinallyWithPlainOutsideTry() { + assertBehavesSameVariations(5, + () -> { + plain(1); + try { + return syncReturns(2); + } finally { + plain(3); + } + }, + (callback) -> { + beginAsync().thenSupply(c -> { + plain(1); + beginAsync().thenSupply(c2 -> { + asyncReturns(2, c2); + }).thenAlwaysRunAndFinish(() -> { + plain(3); + }, c); + }).finish(callback); + }); + } + @Test void testFinallyWithPlainOutsideTry() { assertBehavesSameVariations(5, diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsTestBase.java b/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsTestBase.java index 1229dbcfcad..10a58152d9f 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsTestBase.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsTestBase.java @@ -256,8 +256,9 @@ private void assertBehavesSame(final Supplier sync, final Runnable betwee await(wasCalledFuture, "Callback should have been called"); // The following code can be used to debug variations: -// System.out.println("===VARIATION START"); +// System.out.println("===VARIATION START: " + invocationTracker.getVariationCount()); // System.out.println("sync: " + expectedEvents); +// System.out.println("sync size: " + expectedEvents.size()); // System.out.println("callback called?: " + wasCalledFuture.isDone()); // System.out.println("value -- sync: " + expectedValue + " -- async: " + actualValue.get()); // System.out.println("excep -- sync: " + expectedException + " -- async: " + actualException.get()); diff --git a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCluster.kt b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCluster.kt index 4fcb4a8852a..2c377e41d41 100644 --- a/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCluster.kt +++ b/driver-kotlin-coroutine/src/integration/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoCluster.kt @@ -115,24 +115,27 @@ internal open class SyncMongoCluster(open val wrapped: MongoCluster) : JMongoClu SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), pipeline, resultClass)) override fun bulkWrite(models: MutableList): ClientBulkWriteResult { - org.junit.jupiter.api.Assumptions.assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement") - TODO("BULK-TODO implement") + org.junit.jupiter.api.Assumptions.assumeTrue( + java.lang.Boolean.parseBoolean(toString()), "BULK-TODO Kotlin implement") + TODO("BULK-TODO Kotlin implement") } override fun bulkWrite( models: MutableList, options: ClientBulkWriteOptions ): ClientBulkWriteResult { - org.junit.jupiter.api.Assumptions.assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement") - TODO("BULK-TODO implement") + org.junit.jupiter.api.Assumptions.assumeTrue( + java.lang.Boolean.parseBoolean(toString()), "BULK-TODO Kotlin implement") + TODO("BULK-TODO Kotlin implement") } override fun bulkWrite( clientSession: ClientSession, models: MutableList ): ClientBulkWriteResult { - org.junit.jupiter.api.Assumptions.assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement") - TODO("BULK-TODO implement") + org.junit.jupiter.api.Assumptions.assumeTrue( + java.lang.Boolean.parseBoolean(toString()), "BULK-TODO Kotlin implement") + TODO("BULK-TODO Kotlin implement") } override fun bulkWrite( @@ -140,8 +143,9 @@ internal open class SyncMongoCluster(open val wrapped: MongoCluster) : JMongoClu models: MutableList, options: ClientBulkWriteOptions ): ClientBulkWriteResult { - org.junit.jupiter.api.Assumptions.assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement") - TODO("BULK-TODO implement") + org.junit.jupiter.api.Assumptions.assumeTrue( + java.lang.Boolean.parseBoolean(toString()), "BULK-TODO Kotlin implement") + TODO("BULK-TODO Kotlin implement") } private fun ClientSession.unwrapped() = (this as SyncClientSession).wrapped diff --git a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCluster.kt b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCluster.kt index b7235d80479..a4ad9bd1418 100644 --- a/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCluster.kt +++ b/driver-kotlin-sync/src/integration/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoCluster.kt @@ -114,24 +114,27 @@ internal open class SyncMongoCluster(open val wrapped: MongoCluster) : JMongoClu SyncChangeStreamIterable(wrapped.watch(clientSession.unwrapped(), pipeline, resultClass)) override fun bulkWrite(models: MutableList): ClientBulkWriteResult { - org.junit.jupiter.api.Assumptions.assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement") - TODO("BULK-TODO implement") + org.junit.jupiter.api.Assumptions.assumeTrue( + java.lang.Boolean.parseBoolean(toString()), "BULK-TODO Kotlin implement") + TODO("BULK-TODO Kotlin implement") } override fun bulkWrite( models: MutableList, options: ClientBulkWriteOptions ): ClientBulkWriteResult { - org.junit.jupiter.api.Assumptions.assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement") - TODO("BULK-TODO implement") + org.junit.jupiter.api.Assumptions.assumeTrue( + java.lang.Boolean.parseBoolean(toString()), "BULK-TODO Kotlin implement") + TODO("BULK-TODO Kotlin implement") } override fun bulkWrite( clientSession: ClientSession, models: MutableList ): ClientBulkWriteResult { - org.junit.jupiter.api.Assumptions.assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement") - TODO("BULK-TODO implement") + org.junit.jupiter.api.Assumptions.assumeTrue( + java.lang.Boolean.parseBoolean(toString()), "BULK-TODO Kotlin implement") + TODO("BULK-TODO Kotlin implement") } override fun bulkWrite( @@ -139,8 +142,9 @@ internal open class SyncMongoCluster(open val wrapped: MongoCluster) : JMongoClu models: MutableList, options: ClientBulkWriteOptions ): ClientBulkWriteResult { - org.junit.jupiter.api.Assumptions.assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement") - TODO("BULK-TODO implement") + org.junit.jupiter.api.Assumptions.assumeTrue( + java.lang.Boolean.parseBoolean(toString()), "BULK-TODO Kotlin implement") + TODO("BULK-TODO Kotlin implement") } private fun ClientSession.unwrapped() = (this as SyncClientSession).wrapped diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java index fbeffd7b369..0209ed3404d 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java @@ -489,12 +489,12 @@ public void testTimeoutMsISHonoredForNnextOperationWhenSeveralGetMoreExecutedInt } } - @DisplayName("11. Multi-batch bulkWrites") - @Test - @Override - protected void test11MultiBatchBulkWrites() { - assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); - } +// @DisplayName("11. Multi-batch bulkWrites") +// @Test +// @Override +// protected void test11MultiBatchBulkWrites() { +// assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); +// } private static void assertCommandStartedEventsInOder(final List expectedCommandNames, final List commandStartedEvents) { diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoOperationPublisherTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoOperationPublisherTest.java index 42d6bb14c5c..1c096748c11 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoOperationPublisherTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoOperationPublisherTest.java @@ -31,6 +31,7 @@ import java.util.concurrent.TimeUnit; +import static com.mongodb.ClusterFixture.TIMEOUT; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS_WITH_TIMEOUT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -113,7 +114,7 @@ public void withReadPreference() { @Test public void withTimeout() { - assertEquals(DEFAULT_MOP, DEFAULT_MOP.withTimeout(60_000, TimeUnit.MILLISECONDS)); + assertEquals(DEFAULT_MOP, DEFAULT_MOP.withTimeout(TIMEOUT, TimeUnit.SECONDS)); assertEquals(1000, DEFAULT_MOP.withTimeout(1000, TimeUnit.MILLISECONDS).getTimeoutMS()); assertThrows(IllegalArgumentException.class, () -> DEFAULT_MOP.withTimeout(500, TimeUnit.NANOSECONDS)); } diff --git a/driver-scala/src/test/scala/org/mongodb/scala/MongoClientSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/MongoClientSpec.scala index 4c721ed8774..d5516b984ae 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/MongoClientSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/MongoClientSpec.scala @@ -35,7 +35,12 @@ class MongoClientSpec extends BaseSpec with MockitoSugar { wrapped.foreach((name: String) => { val cleanedName = name.stripPrefix("get") - assert(local.contains(name) | local.contains(cleanedName.head.toLower + cleanedName.tail), s"Missing: $name") + + // TODO("BULK-TODO remove this if when bulkWrite is implemented and uncomment line 43") + if (!cleanedName.contains("bulkWrite")) { + assert(local.contains(name) | local.contains(cleanedName.head.toLower + cleanedName.tail), s"Missing: $name") + } + // assert(local.contains(name) | local.contains(cleanedName.head.toLower + cleanedName.tail), s"Missing: $name") }) } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java index 8141af36918..1b7c3a40716 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java @@ -403,6 +403,18 @@ private void assertOperation(final UnifiedTestContext context, final BsonDocumen private static void assertOperationResult(final UnifiedTestContext context, final BsonDocument operation, final int operationIndex, final OperationResult result) { + if (result.getException() instanceof org.opentest4j.TestAbortedException) { + // BULK-TODO remove + if (result.getException().getMessage().contains("BULK-TODO Kotlin implement")) { + throw (org.opentest4j.TestAbortedException) result.getException(); + } + } + if (result.getException() instanceof org.junit.AssumptionViolatedException) { + // BULK-TODO remove + if (result.getException().getMessage().contains("BULK-TODO Kotlin implement")) { + throw (org.junit.AssumptionViolatedException) result.getException(); + } + } context.getAssertionContext().push(ContextElement.ofCompletedOperation(operation, result, operationIndex)); if (!operation.getBoolean("ignoreResultAndError", BsonBoolean.FALSE).getValue()) { From 78760c0123dcd4bd893a2efafc1fef8784516633 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 13 Dec 2024 19:26:33 -0800 Subject: [PATCH 03/31] Remove redundant methods. JAVA-5530 --- .../com/mongodb/internal/async/AsyncRunnable.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java index 3903e3f4e27..ad39ccd1f43 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java @@ -219,18 +219,6 @@ default AsyncRunnable thenRunTryCatchAsyncBlocks( }); } - default AsyncSupplier thenSupplyTryCatchAsyncBlocks( - final AsyncSupplier supplier, - final Predicate errorCheck, - final AsyncFunction errorFunction) { - return this.thenSupply(c -> { - beginAsync() - .thenSupply(supplier) - .onErrorIf(errorCheck, errorFunction) - .finish(c); - }); - } - /** * @param condition the condition to check * @param runnable The async runnable to run after this runnable, From cb0d596a410c1b89a8cba55045dcb2fe50569ba7 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 13 Dec 2024 19:31:46 -0800 Subject: [PATCH 04/31] Remove TODOs. JAVA-5530 --- .../mongodb/internal/operation/ClientBulkWriteOperation.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 29ab99ec714..2b00a5161d4 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -685,8 +685,6 @@ private static final class ExhaustiveClientBulkWriteCommandOkResponse { } else if (cursorExhaustBatches.size() == 1) { cursorExhaust = cursorExhaustBatches.get(0); } else { - //TODO-VALENTIN-question can we do this flatmap in cursor exasust method to simplify the flow? - // we can do either flat map above or change getCursorExhaust to return List of Lists (if any perf benefit) cursorExhaust = cursorExhaustBatches.stream().flatMap(Collection::stream).collect(toList()); } } From 9e9f33dd43935bf9b9323fc66d6db710ab513b6c Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 13 Dec 2024 22:15:13 -0800 Subject: [PATCH 05/31] Fix retryable wrapping. JAVA-5530 --- .../internal/operation/ClientBulkWriteOperation.java | 4 ++-- .../com/mongodb/reactivestreams/client/MongoCluster.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 2b00a5161d4..8f7c0edb3b1 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -125,7 +125,7 @@ import static com.mongodb.internal.connection.DualMessageSequences.WritersProviderAndLimitsChecker.WriteResult.FAIL_LIMIT_EXCEEDED; import static com.mongodb.internal.connection.DualMessageSequences.WritersProviderAndLimitsChecker.WriteResult.OK_LIMIT_NOT_REACHED; import static com.mongodb.internal.operation.AsyncOperationHelper.cursorDocumentToAsyncBatchCursor; -import static com.mongodb.internal.operation.AsyncOperationHelper.decorateReadWithRetriesAsync; +import static com.mongodb.internal.operation.AsyncOperationHelper.decorateWriteWithRetriesAsync; import static com.mongodb.internal.operation.AsyncOperationHelper.withAsyncSourceAndConnection; import static com.mongodb.internal.operation.BulkWriteBatch.logWriteModelDoesNotSupportRetries; import static com.mongodb.internal.operation.CommandOperationHelper.commandWriteConcern; @@ -349,7 +349,7 @@ private void executeBatchAsync( RetryState retryState = initialRetryState(retryWritesSetting, timeoutContext); BatchEncoder batchEncoder = new BatchEncoder(); - AsyncCallbackSupplier retryingBatchExecutor = decorateReadWithRetriesAsync( + AsyncCallbackSupplier retryingBatchExecutor = decorateWriteWithRetriesAsync( retryState, operationContext, // Each batch re-selects a server and re-checks out a connection because this is simpler, // and it is allowed by https://jira.mongodb.org/browse/DRIVERS-2502. diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java index 1e2eede9aeb..a0fe0fb3456 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java @@ -377,7 +377,7 @@ public interface MongoCluster { * * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. - * The {@link ClientBulkWriteException} If and only if the operation is unsuccessful or partially unsuccessful, + * The {@link ClientBulkWriteException} if and only if the operation is unsuccessful or partially unsuccessful, * and there is at least one of the following pieces of information to report: * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. @@ -401,7 +401,7 @@ public interface MongoCluster { * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. * @param options The options. * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. - * The {@link ClientBulkWriteException} If and only if the operation is unsuccessful or partially unsuccessful, + * The {@link ClientBulkWriteException} if and only if the operation is unsuccessful or partially unsuccessful, * and there is at least one of the following pieces of information to report: * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. @@ -429,7 +429,7 @@ Publisher bulkWrite( * @param clientSession The {@linkplain ClientSession client session} with which to associate this operation. * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. - * The {@link ClientBulkWriteException} If and only if the operation is unsuccessful or partially unsuccessful, + * The {@link ClientBulkWriteException} if and only if the operation is unsuccessful or partially unsuccessful, * and there is at least one of the following pieces of information to report: * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. @@ -456,7 +456,7 @@ Publisher bulkWrite( * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. * @param options The options. * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. - * The {@link ClientBulkWriteException} If and only if the operation is unsuccessful or partially unsuccessful, + * The {@link ClientBulkWriteException} if and only if the operation is unsuccessful or partially unsuccessful, * and there is at least one of the following pieces of information to report: * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. From 9ca1fe70f5a19440d1a3bfa4ac5797a1b200258f Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 13 Dec 2024 22:25:12 -0800 Subject: [PATCH 06/31] Remove comments. JAVA-5530 --- .../internal/operation/ClientBulkWriteOperation.java | 4 ++-- .../client/ClientSideOperationTimeoutProseTest.java | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 8f7c0edb3b1..91569b35908 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -244,7 +244,7 @@ private void executeAllBatches( *

  • consume the cursor, which may involve executing `getMore` commands.
  • * * - * @throws MongoException When a {@linkplain ClientBulkWriteException#getCause() top-level error} happens. + * {@link SingleResultCallback} propagates {@link MongoException} when a {@linkplain ClientBulkWriteException#getCause() top-level error} happens. */ private void executeAllBatchesAsync( final WriteConcern effectiveWriteConcern, @@ -330,7 +330,7 @@ private Integer executeBatch( } /** - * @return The start model index of the next batch, provided that the operation + * @param finalCallback A callback that accepts the start model index of the next batch, provided that the operation * {@linkplain ExhaustiveClientBulkWriteCommandOkResponse#operationMayContinue(ConcreteClientBulkWriteOptions) may continue} * and there are unexecuted {@linkplain ClientNamespacedWriteModel models} left. */ diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java index 0209ed3404d..75a19536cb7 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/ClientSideOperationTimeoutProseTest.java @@ -489,13 +489,6 @@ public void testTimeoutMsISHonoredForNnextOperationWhenSeveralGetMoreExecutedInt } } -// @DisplayName("11. Multi-batch bulkWrites") -// @Test -// @Override -// protected void test11MultiBatchBulkWrites() { -// assumeTrue(java.lang.Boolean.parseBoolean(toString()), "BULK-TODO implement"); -// } - private static void assertCommandStartedEventsInOder(final List expectedCommandNames, final List commandStartedEvents) { assertEquals(expectedCommandNames.size(), commandStartedEvents.size(), "Expected: " + expectedCommandNames + ". Actual: " From 9a7d9ba9f93e14be1832124578992a8c093c8ff8 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 16 Dec 2024 12:18:21 -0800 Subject: [PATCH 07/31] Remove test skips. JAVA-5530 --- .../client/unified/UnifiedTestModifications.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index e7b6a97ded9..ab3db65f8c5 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -203,18 +203,6 @@ public static void doSkips(final TestDef def) { def.skipJira("https://jira.mongodb.org/browse/JAVA-5341") .when(() -> isDiscoverableReplicaSet() && serverVersionLessThan(4, 4)) .test("retryable-writes", "retryable-writes insertOne serverErrors", "RetryableWriteError label is added based on writeConcernError in pre-4.4 mongod response"); - def.skipJira("https://jira.mongodb.org/browse/JAVA-4586") - //.testContains("retryable-writes", "client bulkWrite") - .test("retryable-writes", "client bulkWrite retryable writes", "client bulkWrite with no multi: true operations succeeds after retryable top-level error") - .test("retryable-writes", "client bulkWrite retryable writes", "client bulkWrite with multi: true operations fails after retryable top-level error") - .test("retryable-writes", "client bulkWrite retryable writes", "client bulkWrite with no multi: true operations succeeds after retryable writeConcernError") - .test("retryable-writes", "client bulkWrite retryable writes", "client bulkWrite with multi: true operations fails after retryable writeConcernError") - .test("retryable-writes", "client bulkWrite retryable writes", "client bulkWrite with retryWrites: false does not retry") - .test("retryable-writes", "client bulkWrite retryable writes with client errors", "client bulkWrite with one network error succeeds after retry") - .test("retryable-writes", "client bulkWrite retryable writes with client errors", "client bulkWrite with two network errors fails after retry") - //.testContains("retryable-writes", "client.clientBulkWrite") - .test("retryable-writes", "retryable writes handshake failures", "client.clientBulkWrite succeeds after retryable handshake network error") - .test("retryable-writes", "retryable writes handshake failures", "client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)"); // server-discovery-and-monitoring (SDAM) From 0ca88599a3784858cd2aa7d66d35c1e3a0649cca Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 14:26:39 -0800 Subject: [PATCH 08/31] Update driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java Co-authored-by: Valentin Kovalenko --- .../src/test/functional/com/mongodb/client/CrudProseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java index 20fd3160600..4bb6b16cb67 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java @@ -600,7 +600,7 @@ private interface TriConsumer { /** * This method is used instead of {@link ClientSession#withTransaction(TransactionBody)} - * because reactive {@link com.mongodb.reactivestreams.client;.ClientSession} do not support it. + * because reactive {@link com.mongodb.reactivestreams.client.ClientSession} do not support it. */ private static ClientBulkWriteResult runInTransaction(final ClientSession session, final Supplier action) { From 99a460a86a77140dcff1a92541dd1337f96525d8 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 14:26:48 -0800 Subject: [PATCH 09/31] Update driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java Co-authored-by: Valentin Kovalenko --- .../src/test/functional/com/mongodb/client/CrudProseTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java index 4bb6b16cb67..67658e938fe 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java @@ -279,8 +279,6 @@ protected void testBulkWriteHandlesCursorRequiringGetMoreWithinTransaction() { assumeFalse(isServerlessTest()); assumeFalse(isStandalone()); assertBulkWriteHandlesCursorRequiringGetMore(true); - - //TODO consider removing withTransaction and use start and end transaction. } private void assertBulkWriteHandlesCursorRequiringGetMore(final boolean transaction) { From ae8763fd5d9419cddf9221c10b30ee61d96e64fa Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 14:27:34 -0800 Subject: [PATCH 10/31] Update driver-core/src/main/com/mongodb/internal/async/MutableValue.java Co-authored-by: Valentin Kovalenko --- .../src/main/com/mongodb/internal/async/MutableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/MutableValue.java b/driver-core/src/main/com/mongodb/internal/async/MutableValue.java index 68295ddc19f..ab77f67e7ae 100644 --- a/driver-core/src/main/com/mongodb/internal/async/MutableValue.java +++ b/driver-core/src/main/com/mongodb/internal/async/MutableValue.java @@ -20,7 +20,7 @@ import com.mongodb.lang.Nullable; @NotThreadSafe -public class MutableValue { +public final class MutableValue { private T value; public MutableValue(@Nullable final T value) { From 7001d9eea4e10aecbb6ca68305daf3519ae13543 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 14:29:22 -0800 Subject: [PATCH 11/31] Update driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java Co-authored-by: Valentin Kovalenko --- .../reactivestreams/client/MongoCluster.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java index a0fe0fb3456..61cf079c437 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java @@ -376,11 +376,17 @@ public interface MongoCluster { * This operation is not supported by MongoDB Atlas Serverless instances.

    * * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. - * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. - * The {@link ClientBulkWriteException} if and only if the operation is unsuccessful or partially unsuccessful, - * and there is at least one of the following pieces of information to report: - * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, - * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. + * @return The {@link Publisher} signalling at most one element {@link ClientBulkWriteResult} if the operation is successful, + * or the following errors: + *
      + *
    • + * {@link ClientBulkWriteException} - If and only if the operation is unsuccessful or partially unsuccessful, + * and there is at least one of the following pieces of information to report: + * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, + * {@link ClientBulkWriteException#getPartialResult()}.
    • + *
    • + * {@link MongoException} - Only if the operation is unsuccessful.
    • + *
    * @since 5.3 * @mongodb.server.release 8.0 * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite From bee199ef5d62be36479bf33c567e78c4364e3450 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 14:29:34 -0800 Subject: [PATCH 12/31] Update driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java Co-authored-by: Valentin Kovalenko --- .../src/main/com/mongodb/internal/async/AsyncRunnable.java | 1 - 1 file changed, 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java index ad39ccd1f43..eb141fecac7 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java @@ -294,7 +294,6 @@ default AsyncRunnable thenRunRetryingWhile( * @return the composition of this and the looping branch * @see AsyncCallbackLoop */ - default AsyncRunnable thenRunDoWhileLoop(final AsyncRunnable loopBodyRunnable, final Supplier whileCheck) { return thenRun(finalCallback -> { LoopState loopState = new LoopState(); From e6a0a7e6ed36e86985311387eaf4170f7f3ca666 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 14:46:47 -0800 Subject: [PATCH 13/31] Apply suggestions from code review Co-authored-by: Valentin Kovalenko --- .../client/internal/MongoClusterImpl.java | 10 +++++++--- .../client/internal/MongoOperationPublisher.java | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java index b0e8a0b40fb..1c907b5620c 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java @@ -269,9 +269,13 @@ public Publisher bulkWrite(final ClientSession clientSess @Override public Publisher bulkWrite(final ClientSession clientSession, - final List models, - final ClientBulkWriteOptions options) throws ClientBulkWriteException { - return mongoOperationPublisher.clientBulkWrite(clientSession, models, options); + final List clientWriteModels, + final ClientBulkWriteOptions options) { + notNull("clientSession", clientSession); + notNull("clientWriteModels", clientWriteModels); + isTrueArgument("`clientWriteModels` must not be empty", !clientWriteModels.isEmpty()); + notNull("options", options); + return mongoOperationPublisher.clientBulkWrite(clientSession, clientWriteModels, options); } } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java index 3da4b885bdf..4bee980c313 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java @@ -292,6 +292,7 @@ Publisher bulkWrite( operations::getTimeoutSettings, () -> operations.bulkWrite(notNull("requests", requests), notNull("options", options)), clientSession); } + Publisher clientBulkWrite( @Nullable final ClientSession clientSession, final List clientWriteModels, From df2cb8876e896898683ecf4b8434d4893ffbc6c3 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 16 Dec 2024 15:04:38 -0800 Subject: [PATCH 14/31] Update javadoc. Remove redundant methods. JAVA-5530 --- .../mongodb/internal/async/AsyncRunnable.java | 43 -------------- .../mongodb/internal/async/AsyncSupplier.java | 25 ++++++++- .../mongodb/internal/async/MutableValue.java | 5 +- .../reactivestreams/client/MongoCluster.java | 56 ++++++++++++------- .../client/internal/MongoClientImpl.java | 12 ++-- .../internal/MongoOperationPublisher.java | 8 +-- .../unified/UnifiedReactiveStreamsTest.java | 3 +- .../com/mongodb/client/CrudProseTest.java | 5 +- .../unified/UnifiedTestModifications.java | 3 +- 9 files changed, 75 insertions(+), 85 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java index eb141fecac7..94febd75c53 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java @@ -122,49 +122,6 @@ static AsyncRunnable beginAsync() { return (c) -> c.complete(c); } - /** - * Must be invoked at end of async chain - * @param runnable the sync code to invoke (under non-exceptional flow) - * prior to the callback - * @param callback the callback provided by the method the chain is used in - */ - default void thenRunAndFinish(final Runnable runnable, final SingleResultCallback callback) { - this.finish((r, e) -> { - if (e != null) { - callback.completeExceptionally(e); - return; - } - try { - runnable.run(); - } catch (Throwable t) { - callback.completeExceptionally(t); - return; - } - callback.complete(callback); - }); - } - - /** - * See {@link #thenRunAndFinish(Runnable, SingleResultCallback)}, but the runnable - * will always be executed, including on the exceptional path. - * @param runnable the runnable - * @param callback the callback - */ - default void thenAlwaysRunAndFinish(final Runnable runnable, final SingleResultCallback callback) { - this.finish((r, e) -> { - try { - runnable.run(); - } catch (Throwable t) { - if (e != null) { - t.addSuppressed(e); - } - callback.completeExceptionally(t); - return; - } - callback.onResult(r, e); - }); - } - /** * @param runnable The async runnable to run after this runnable * @return the composition of this runnable and the runnable, a runnable diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncSupplier.java b/driver-core/src/main/com/mongodb/internal/async/AsyncSupplier.java index f2300d3f00d..6dd89e4d9b0 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncSupplier.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncSupplier.java @@ -82,7 +82,30 @@ default void finish(final SingleResultCallback callback) { } /** - * The runnable will always be executed, including on the exceptional path. + * Must be invoked at end of async chain + * @param runnable the sync code to invoke (under non-exceptional flow) + * prior to the callback + * @param callback the callback provided by the method the chain is used in + */ + default void thenRunAndFinish(final Runnable runnable, final SingleResultCallback callback) { + this.finish((r, e) -> { + if (e != null) { + callback.completeExceptionally(e); + return; + } + try { + runnable.run(); + } catch (Throwable t) { + callback.completeExceptionally(t); + return; + } + callback.onResult(r, null); + }); + } + + /** + * See {@link #thenRunAndFinish(Runnable, SingleResultCallback)}, but the runnable + * will always be executed, including on the exceptional path. * @param runnable the runnable * @param callback the callback */ diff --git a/driver-core/src/main/com/mongodb/internal/async/MutableValue.java b/driver-core/src/main/com/mongodb/internal/async/MutableValue.java index ab77f67e7ae..0ee793788ea 100644 --- a/driver-core/src/main/com/mongodb/internal/async/MutableValue.java +++ b/driver-core/src/main/com/mongodb/internal/async/MutableValue.java @@ -16,9 +16,10 @@ package com.mongodb.internal.async; import com.mongodb.annotations.NotThreadSafe; -import com.mongodb.assertions.Assertions; import com.mongodb.lang.Nullable; +import static com.mongodb.assertions.Assertions.assertNotNull; + @NotThreadSafe public final class MutableValue { private T value; @@ -32,7 +33,7 @@ public MutableValue() { } public T get() { - return Assertions.assertNotNull(value); + return assertNotNull(value); } @Nullable diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java index 61cf079c437..edcc8f29408 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoCluster.java @@ -391,7 +391,7 @@ public interface MongoCluster { * @mongodb.server.release 8.0 * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite */ - Publisher bulkWrite(List models) throws ClientBulkWriteException; + Publisher bulkWrite(List models); /** * Executes a client-level bulk write operation. @@ -406,18 +406,24 @@ public interface MongoCluster { * * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. * @param options The options. - * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. - * The {@link ClientBulkWriteException} if and only if the operation is unsuccessful or partially unsuccessful, - * and there is at least one of the following pieces of information to report: - * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, - * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. + * @return The {@link Publisher} signalling at most one element {@link ClientBulkWriteResult} if the operation is successful, + * or the following errors: + *
      + *
    • + * {@link ClientBulkWriteException} - If and only if the operation is unsuccessful or partially unsuccessful, + * and there is at least one of the following pieces of information to report: + * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, + * {@link ClientBulkWriteException#getPartialResult()}.
    • + *
    • + * {@link MongoException} - Only if the operation is unsuccessful.
    • + *
    * @since 5.3 * @mongodb.server.release 8.0 * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite */ Publisher bulkWrite( List models, - ClientBulkWriteOptions options) throws ClientBulkWriteException; + ClientBulkWriteOptions options); /** * Executes a client-level bulk write operation. @@ -434,18 +440,24 @@ Publisher bulkWrite( * * @param clientSession The {@linkplain ClientSession client session} with which to associate this operation. * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. - * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. - * The {@link ClientBulkWriteException} if and only if the operation is unsuccessful or partially unsuccessful, - * and there is at least one of the following pieces of information to report: - * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, - * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. + * @return The {@link Publisher} signalling at most one element {@link ClientBulkWriteResult} if the operation is successful, + * or the following errors: + *
      + *
    • + * {@link ClientBulkWriteException} - If and only if the operation is unsuccessful or partially unsuccessful, + * and there is at least one of the following pieces of information to report: + * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, + * {@link ClientBulkWriteException#getPartialResult()}.
    • + *
    • + * {@link MongoException} - Only if the operation is unsuccessful.
    • + *
    * @since 5.3 * @mongodb.server.release 8.0 * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite */ Publisher bulkWrite( ClientSession clientSession, - List models) throws ClientBulkWriteException; + List models); /** * Executes a client-level bulk write operation. @@ -461,11 +473,17 @@ Publisher bulkWrite( * @param clientSession The {@linkplain ClientSession client session} with which to associate this operation. * @param models The {@linkplain ClientNamespacedWriteModel individual write operations}. * @param options The options. - * @return The {@link Publisher} with a single element which could be: The {@link ClientBulkWriteResult} if the operation is successful. - * The {@link ClientBulkWriteException} if and only if the operation is unsuccessful or partially unsuccessful, - * and there is at least one of the following pieces of information to report: - * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, - * {@link ClientBulkWriteException#getPartialResult()}. The {@link MongoException} only if the operation is unsuccessful. + * @return The {@link Publisher} signalling at most one element {@link ClientBulkWriteResult} if the operation is successful, + * or the following errors: + *
      + *
    • + * {@link ClientBulkWriteException} - If and only if the operation is unsuccessful or partially unsuccessful, + * and there is at least one of the following pieces of information to report: + * {@link ClientBulkWriteException#getWriteConcernErrors()}, {@link ClientBulkWriteException#getWriteErrors()}, + * {@link ClientBulkWriteException#getPartialResult()}.
    • + *
    • + * {@link MongoException} - Only if the operation is unsuccessful.
    • + *
    * @since 5.3 * @mongodb.server.release 8.0 * @mongodb.driver.manual reference/command/bulkWrite/ bulkWrite @@ -473,5 +491,5 @@ Publisher bulkWrite( Publisher bulkWrite( ClientSession clientSession, List models, - ClientBulkWriteOptions options) throws ClientBulkWriteException; + ClientBulkWriteOptions options); } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java index dd4cb79a06f..3d4822eb7e3 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java @@ -17,7 +17,6 @@ package com.mongodb.reactivestreams.client.internal; import com.mongodb.AutoEncryptionSettings; -import com.mongodb.ClientBulkWriteException; import com.mongodb.ClientSessionOptions; import com.mongodb.ContextProvider; import com.mongodb.MongoClientSettings; @@ -234,29 +233,26 @@ public ChangeStreamPublisher watch( } @Override - public Publisher bulkWrite(final List models) - throws ClientBulkWriteException { + public Publisher bulkWrite(final List models) { return delegate.bulkWrite(models); } @Override public Publisher bulkWrite(final List models, - final ClientBulkWriteOptions options) - throws ClientBulkWriteException { + final ClientBulkWriteOptions options) { return delegate.bulkWrite(models, options); } @Override public Publisher bulkWrite(final ClientSession clientSession, - final List models) - throws ClientBulkWriteException { + final List models) { return delegate.bulkWrite(clientSession, models); } @Override public Publisher bulkWrite(final ClientSession clientSession, final List models, - final ClientBulkWriteOptions options) throws ClientBulkWriteException { + final ClientBulkWriteOptions options) { return delegate.bulkWrite(clientSession, models, options); } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java index 4bee980c313..58030f75fa9 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java @@ -95,6 +95,7 @@ public final class MongoOperationPublisher { private final AsyncOperations operations; private final UuidRepresentation uuidRepresentation; + @Nullable private final AutoEncryptionSettings autoEncryptionSettings; private final OperationExecutor executor; @@ -249,7 +250,7 @@ Publisher createCollection( @Nullable final ClientSession clientSession, final String collectionName, final CreateCollectionOptions options) { return createWriteOperationMono( operations::getTimeoutSettings, - operations.createCollection(collectionName, options, autoEncryptionSettings), clientSession); + () -> operations.createCollection(collectionName, options, autoEncryptionSettings), clientSession); } Publisher createView( @@ -522,11 +523,6 @@ Mono createWriteOperationMono(final Supplier timeoutSett return getExecutor(timeoutSettingsSupplier.get()) .execute(writeOperation, getReadConcern(), clientSession); } - Mono createWriteOperationMono(final Supplier timeoutSettingsSupplier, - final AsyncWriteOperation writeOperation, @Nullable final ClientSession clientSession) { - return getExecutor(timeoutSettingsSupplier.get()) - .execute(writeOperation, getReadConcern(), clientSession); - } private Mono createSingleWriteRequestMono( final Supplier> operation, diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedReactiveStreamsTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedReactiveStreamsTest.java index 9d47364da84..62c1315e240 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedReactiveStreamsTest.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedReactiveStreamsTest.java @@ -46,8 +46,7 @@ protected UnifiedReactiveStreamsTest() { @Override protected MongoClient createMongoClient(final MongoClientSettings settings) { - com.mongodb.reactivestreams.client.MongoClient wrapped = MongoClients.create(settings); - return new SyncMongoClient(wrapped); + return new SyncMongoClient(MongoClients.create(settings)); } @Override diff --git a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java index 67658e938fe..81b59451478 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java @@ -430,8 +430,9 @@ protected void testBulkWriteErrorsForAutoEncryption() { assertTrue( assertThrows( IllegalStateException.class, - () -> client.bulkWrite(singletonList(insertOne(NAMESPACE, new Document("a", "b"))))) - .getMessage().contains("bulkWrite does not currently support automatic encryption")); + () -> client.bulkWrite(singletonList(insertOne(NAMESPACE, new Document("a", "b")))) + ).getMessage().contains("bulkWrite does not currently support automatic encryption") + ); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index ab3db65f8c5..67bf394d6cb 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -47,7 +47,7 @@ public static void doSkips(final TestDef def) { // change-streams def.skipNoncompliantReactive("error required from change stream initialization") // TODO reason? .test("change-streams", "change-streams", "Test with document comment - pre 4.4"); - def.skipNoncompliantReactive("event sensitive tests. We can't guarantee the amount of GetMore commands sent in the reactive driver") + def.skipNoncompliantReactive("event sensitive tests") // TODO reason? .test("change-streams", "change-streams", "Test that comment is set on getMore") .test("change-streams", "change-streams", "Test that comment is not set on getMore - pre 4.4"); def.modify(IGNORE_EXTRA_EVENTS) @@ -197,7 +197,6 @@ public static void doSkips(final TestDef def) { .test("retryable-writes", "findOneAndDelete-errorLabels", "FindOneAndDelete succeeds after WriteConcernError ShutdownInProgress") .test("retryable-writes", "findOneAndReplace-errorLabels", "FindOneAndReplace succeeds after WriteConcernError ShutdownInProgress") //.testContains("retryable-writes", "succeeds after retryable writeConcernError") - .test("retryable-writes", "client bulkWrite retryable writes", "client bulkWrite with no multi: true operations succeeds after retryable writeConcernError") .test("retryable-writes", "retryable-writes insertOne serverErrors", "InsertOne succeeds after retryable writeConcernError") .test("retryable-writes", "retryable-writes bulkWrite serverErrors", "BulkWrite succeeds after retryable writeConcernError in first batch"); def.skipJira("https://jira.mongodb.org/browse/JAVA-5341") From f43fc490a90247b7deb7abfc0f9532ed5d515637 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 16 Dec 2024 16:36:06 -0800 Subject: [PATCH 15/31] Make CursorHelper generic. JAVA-5530 --- .../main/com/mongodb/internal/operation/CursorHelper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java b/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java index caf0b13ad38..2d5ea9ed412 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java @@ -33,8 +33,8 @@ static BsonDocument getCursorDocumentFromBatchSize(@Nullable final Integer batch return batchSize == null ? new BsonDocument() : new BsonDocument("batchSize", new BsonInt32(batchSize)); } - public static void exhaustCursorAsync(final AsyncBatchCursor cursor, final SingleResultCallback>> finalCallback) { - List> results = new ArrayList<>(); + public static void exhaustCursorAsync(final AsyncBatchCursor cursor, final SingleResultCallback>> finalCallback) { + List> results = new ArrayList<>(); beginAsync().thenRunDoWhileLoop(iterationCallback -> { beginAsync(). @@ -46,7 +46,7 @@ public static void exhaustCursorAsync(final AsyncBatchCursor curso callback.complete(callback); }).finish(iterationCallback); }, () -> !cursor.isClosed()) - .>>thenSupply(callback -> { + .>>thenSupply(callback -> { callback.complete(results); }).finish(finalCallback); } From b7c5bfa0be1182eecc3468397d345dbe5aba5901 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 16 Dec 2024 17:35:32 -0800 Subject: [PATCH 16/31] Move exhaustCursor method to cursor interface. JAVA-5530 --- .../internal/async/AsyncBatchCursor.java | 21 +++++ .../internal/operation/BatchCursor.java | 11 +++ .../operation/ClientBulkWriteOperation.java | 9 +- .../internal/operation/CursorHelper.java | 25 ----- .../mongodb/client/test/CollectionHelper.java | 7 ++ ...AsyncCommandBatchCursorFunctionalTest.java | 92 +++++++++++++++++++ .../CommandBatchCursorFunctionalTest.java | 50 ++++++++++ 7 files changed, 183 insertions(+), 32 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java b/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java index 89260ac7b52..3c468a6711a 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java @@ -19,8 +19,11 @@ import com.mongodb.internal.operation.BatchCursor; import java.io.Closeable; +import java.util.ArrayList; import java.util.List; +import static com.mongodb.internal.async.AsyncRunnable.beginAsync; + /** * MongoDB returns query results as batches, and this interface provides an asynchronous iterator over those batches. The first call to * the {@code next} method will return the first batch, and subsequent calls will trigger an asynchronous request to get the next batch @@ -72,4 +75,22 @@ public interface AsyncBatchCursor extends Closeable { */ @Override void close(); + + default void exhaustCursor(final SingleResultCallback>> finalCallback) { + List> results = new ArrayList<>(); + + beginAsync().thenRunDoWhileLoop(iterationCallback -> { + beginAsync(). + thenSupply(this::next) + .thenConsume((batch, callback) -> { + if (batch != null && !batch.isEmpty()) { + results.add(batch); + } + callback.complete(callback); + }).finish(iterationCallback); + }, () -> !this.isClosed()) + .>>thenSupply(callback -> { + callback.complete(results); + }).finish(finalCallback); + } } diff --git a/driver-core/src/main/com/mongodb/internal/operation/BatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/BatchCursor.java index 5f86eb1f8fb..fc697dfdfb7 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/BatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/operation/BatchCursor.java @@ -25,6 +25,12 @@ import java.util.Iterator; import java.util.List; +import static java.util.Spliterator.IMMUTABLE; +import static java.util.Spliterator.ORDERED; +import static java.util.Spliterators.spliteratorUnknownSize; +import static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; + /** * MongoDB returns query results as batches, and this interface provideds an iterator over those batches. The first call to * the {@code next} method will return the first batch, and subsequent calls will trigger a request to get the next batch @@ -98,4 +104,9 @@ public interface BatchCursor extends Iterator>, Closeable { ServerCursor getServerCursor(); ServerAddress getServerAddress(); + + default List> exhaustCursor() { + return stream(spliteratorUnknownSize(this, ORDERED | IMMUTABLE), false) + .collect(toList()); + } } diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 91569b35908..4d75b1a4309 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -132,7 +132,6 @@ import static com.mongodb.internal.operation.CommandOperationHelper.initialRetryState; import static com.mongodb.internal.operation.CommandOperationHelper.shouldAttemptToRetryWriteAndAddRetryableLabel; import static com.mongodb.internal.operation.CommandOperationHelper.transformWriteException; -import static com.mongodb.internal.operation.CursorHelper.exhaustCursorAsync; import static com.mongodb.internal.operation.OperationHelper.isRetryableWrite; import static com.mongodb.internal.operation.SyncOperationHelper.cursorDocumentToBatchCursor; import static com.mongodb.internal.operation.SyncOperationHelper.decorateWriteWithRetries; @@ -141,12 +140,8 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Optional.ofNullable; -import static java.util.Spliterator.IMMUTABLE; -import static java.util.Spliterator.ORDERED; -import static java.util.Spliterators.spliteratorUnknownSize; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; -import static java.util.stream.StreamSupport.stream; /** * This class is not part of the public API and may be removed or changed at any time. @@ -544,7 +539,7 @@ private List> exhaustBulkWriteCommandOkResponseCursor( options.getComment().orElse(null), connectionSource, connection)) { - return stream(spliteratorUnknownSize(cursor, ORDERED | IMMUTABLE), false).collect(toList()); + return cursor.exhaustCursor(); } } @@ -562,7 +557,7 @@ private void exhaustBulkWriteCommandOkResponseCursorAsync(final AsyncConnectionS connection); beginAsync().>>thenSupply(callback -> { - exhaustCursorAsync(cursor, callback); + cursor.exhaustCursor(callback); }).thenAlwaysRunAndFinish(() -> { if (!cursor.isClosed()) { cursor.close(); diff --git a/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java b/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java index 2d5ea9ed412..26511c86885 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/CursorHelper.java @@ -16,41 +16,16 @@ package com.mongodb.internal.operation; -import com.mongodb.internal.async.AsyncBatchCursor; -import com.mongodb.internal.async.SingleResultCallback; import com.mongodb.lang.Nullable; import org.bson.BsonDocument; import org.bson.BsonInt32; -import java.util.ArrayList; -import java.util.List; - -import static com.mongodb.internal.async.AsyncRunnable.beginAsync; - final class CursorHelper { static BsonDocument getCursorDocumentFromBatchSize(@Nullable final Integer batchSize) { return batchSize == null ? new BsonDocument() : new BsonDocument("batchSize", new BsonInt32(batchSize)); } - public static void exhaustCursorAsync(final AsyncBatchCursor cursor, final SingleResultCallback>> finalCallback) { - List> results = new ArrayList<>(); - - beginAsync().thenRunDoWhileLoop(iterationCallback -> { - beginAsync(). - thenSupply(cursor::next) - .thenConsume((batch, callback) -> { - if (batch != null && !batch.isEmpty()) { - results.add(batch); - } - callback.complete(callback); - }).finish(iterationCallback); - }, () -> !cursor.isClosed()) - .>>thenSupply(callback -> { - callback.complete(results); - }).finish(finalCallback); - } - private CursorHelper() { } } diff --git a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java index adce165ee51..67091939293 100644 --- a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java +++ b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java @@ -363,6 +363,13 @@ public void deleteOne(final Bson filter) { .execute(getBinding()); } + public void deleteMany(final Bson filter) { + new MixedBulkWriteOperation(namespace, + singletonList(new DeleteRequest(filter.toBsonDocument(Document.class, registry)).multi(true)), + true, WriteConcern.ACKNOWLEDGED, false) + .execute(getBinding()); + } + public List find(final Bson filter) { return find(filter, null); } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java index a272f8b0f67..f8e8b64b432 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java @@ -23,6 +23,7 @@ import com.mongodb.ServerCursor; import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.model.CreateCollectionOptions; +import com.mongodb.client.model.Filters; import com.mongodb.client.model.OperationTest; import com.mongodb.internal.binding.AsyncConnectionSource; import com.mongodb.internal.connection.AsyncConnection; @@ -46,7 +47,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -103,6 +106,95 @@ void cleanup() { }); } + @Test + @DisplayName("should exhaust cursor with multiple batches") + void shouldExhaustCursorAsyncWithMultipleBatches() { + // given + BsonDocument commandResult = executeFindCommand(0, 3); // Fetch in batches of size 3 + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, + null, connectionSource, connection); + + CompletableFuture>> futureResult = new CompletableFuture<>(); + + // when + cursor.exhaustCursor((result, throwable) -> { + if (throwable != null) { + futureResult.completeExceptionally(throwable); + } else { + futureResult.complete(result); + } + }); + + // then + assertDoesNotThrow(() -> { + List> resultBatches = futureResult.get(5, TimeUnit.SECONDS); + + assertEquals(4, resultBatches.size(), "Expected 4 batches for 10 documents with batch size of 3."); + + int totalDocuments = resultBatches.stream().mapToInt(List::size).sum(); + assertEquals(10, totalDocuments, "Expected a total of 10 documents."); + }); + } + + @Test + @DisplayName("should exhaust cursor with closed cursor") + void shouldExhaustCursorAsyncWithClosedCursor() { + // given + BsonDocument commandResult = executeFindCommand(0, 3); + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, + null, connectionSource, connection); + + cursor.close(); + + CompletableFuture>> futureResult = new CompletableFuture<>(); + + // when + cursor.exhaustCursor((result, throwable) -> { + if (throwable != null) { + futureResult.completeExceptionally(throwable); + } else { + futureResult.complete(result); + } + }); + + //then + ExecutionException executionException = assertThrows(ExecutionException.class, () -> { + futureResult.get(5, TimeUnit.SECONDS); + }, "Expected an exception when operating on a closed cursor."); + + IllegalStateException illegalStateException = (IllegalStateException) executionException.getCause(); + assertEquals("Cursor has been closed", illegalStateException.getMessage()); + } + + @Test + @DisplayName("should exhaust cursor with empty cursor") + void shouldExhaustCursorAsyncWithEmptyCursor() { + // given + getCollectionHelper().deleteMany(Filters.empty()); + + BsonDocument commandResult = executeFindCommand(0, 3); // No documents to fetch + cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, + null, connectionSource, connection); + + CompletableFuture>> futureResult = new CompletableFuture<>(); + + // when + cursor.exhaustCursor((result, throwable) -> { + if (throwable != null) { + futureResult.completeExceptionally(throwable); + } else { + futureResult.complete(result); + } + }); + + // then + assertDoesNotThrow(() -> { + List> resultBatches = futureResult.get(5, TimeUnit.SECONDS); + + assertTrue(resultBatches.isEmpty(), "Expected no batches for an empty cursor."); + }); + } + @Test @DisplayName("server cursor should not be null") void theServerCursorShouldNotBeNull() { diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java index 57caf3bdbfc..77468c10310 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java @@ -22,6 +22,7 @@ import com.mongodb.ServerCursor; import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.model.CreateCollectionOptions; +import com.mongodb.client.model.Filters; import com.mongodb.client.model.OperationTest; import com.mongodb.internal.binding.ConnectionSource; import com.mongodb.internal.connection.Connection; @@ -101,6 +102,55 @@ void cleanup() { }); } + @Test + @DisplayName("should exhaust cursor with multiple batches") + void shouldExhaustCursorAsyncWithMultipleBatches() { + // given + BsonDocument commandResult = executeFindCommand(0, 3); // Fetch in batches of size 3 + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, + null, connectionSource, connection); + + // when + List> result = cursor.exhaustCursor(); + + // then + assertEquals(4, result.size(), "Expected 4 batches for 10 documents with batch size of 3."); + + int totalDocuments = result.stream().mapToInt(List::size).sum(); + assertEquals(10, totalDocuments, "Expected a total of 10 documents."); + } + + @Test + @DisplayName("should exhaust cursor with closed cursor") + void shouldExhaustCursorAsyncWithClosedCursor() { + // given + BsonDocument commandResult = executeFindCommand(0, 3); + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, + null, connectionSource, connection); + cursor.close(); + + // when & then + IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, cursor::exhaustCursor); + assertEquals("Cursor has been closed", illegalStateException.getMessage()); + } + + @Test + @DisplayName("should exhaust cursor async with empty cursor") + void shouldExhaustCursorAsyncWithEmptyCursor() { + // given + getCollectionHelper().deleteMany(Filters.empty()); + + BsonDocument commandResult = executeFindCommand(0, 3); // No documents to fetch + cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, + null, connectionSource, connection); + + // when + List> result = cursor.exhaustCursor(); + + // then + assertTrue(result.isEmpty(), "Expected no batches for an empty cursor."); + } + @Test @DisplayName("server cursor should not be null") void theServerCursorShouldNotBeNull() { From c2d9b07135732bf506ddcddad2076a731a7b0d6e Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 16 Dec 2024 17:51:57 -0800 Subject: [PATCH 17/31] Add assertions. JAVA-5530 --- .../com/mongodb/client/test/CollectionHelper.java | 14 +++++++------- .../AsyncCommandBatchCursorFunctionalTest.java | 3 ++- .../CommandBatchCursorFunctionalTest.java | 2 ++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java index 67091939293..f1ef6fe6a95 100644 --- a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java +++ b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java @@ -357,17 +357,17 @@ public void replaceOne(final Bson filter, final Bson update, final boolean isUps } public void deleteOne(final Bson filter) { - new MixedBulkWriteOperation(namespace, - singletonList(new DeleteRequest(filter.toBsonDocument(Document.class, registry))), - true, WriteConcern.ACKNOWLEDGED, false) - .execute(getBinding()); + delete(filter, false); } public void deleteMany(final Bson filter) { + delete(filter, true); + } + + private void delete(final Bson filter, final boolean multi) { new MixedBulkWriteOperation(namespace, - singletonList(new DeleteRequest(filter.toBsonDocument(Document.class, registry)).multi(true)), - true, WriteConcern.ACKNOWLEDGED, false) - .execute(getBinding()); + singletonList(new DeleteRequest(filter.toBsonDocument(Document.class, registry))), + true, WriteConcern.ACKNOWLEDGED, false); } public List find(final Bson filter) { diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java index f8e8b64b432..1c7eefd56c5 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java @@ -133,6 +133,7 @@ void shouldExhaustCursorAsyncWithMultipleBatches() { int totalDocuments = resultBatches.stream().mapToInt(List::size).sum(); assertEquals(10, totalDocuments, "Expected a total of 10 documents."); + assertTrue(cursor.isClosed(), "Expected cursor to be closed."); }); } @@ -190,8 +191,8 @@ void shouldExhaustCursorAsyncWithEmptyCursor() { // then assertDoesNotThrow(() -> { List> resultBatches = futureResult.get(5, TimeUnit.SECONDS); - assertTrue(resultBatches.isEmpty(), "Expected no batches for an empty cursor."); + assertTrue(cursor.isClosed(), "Expected cursor to be closed."); }); } diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java index 77468c10310..2569acc943d 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java @@ -118,6 +118,7 @@ void shouldExhaustCursorAsyncWithMultipleBatches() { int totalDocuments = result.stream().mapToInt(List::size).sum(); assertEquals(10, totalDocuments, "Expected a total of 10 documents."); + assertTrue(cursor.isClosed(), "Expected cursor to be closed."); } @Test @@ -149,6 +150,7 @@ void shouldExhaustCursorAsyncWithEmptyCursor() { // then assertTrue(result.isEmpty(), "Expected no batches for an empty cursor."); + assertTrue(cursor.isClosed(), "Expected cursor to be closed."); } @Test From 9c237369bd41c353fbeaccef85804ee1116fbbca Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 22:58:03 -0800 Subject: [PATCH 18/31] Update driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java Co-authored-by: Valentin Kovalenko --- .../mongodb/internal/operation/ClientBulkWriteOperation.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 4d75b1a4309..f139555c2ac 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -325,11 +325,8 @@ private Integer executeBatch( } /** - * @param finalCallback A callback that accepts the start model index of the next batch, provided that the operation - * {@linkplain ExhaustiveClientBulkWriteCommandOkResponse#operationMayContinue(ConcreteClientBulkWriteOptions) may continue} - * and there are unexecuted {@linkplain ClientNamespacedWriteModel models} left. + * @see #executeBatch(int, WriteConcern, WriteBinding, ResultAccumulator) */ - @Nullable private void executeBatchAsync( final int batchStartModelIndex, final WriteConcern effectiveWriteConcern, From f09156171543f3192d903f805eaf242bf8440438 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 22:59:56 -0800 Subject: [PATCH 19/31] Update driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java Co-authored-by: Valentin Kovalenko --- .../operation/ClientBulkWriteOperation.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index f139555c2ac..ffdf94a40b0 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -354,18 +354,13 @@ private void executeBatchAsync( retryWritesSetting, effectiveWriteConcern, connectionDescription, sessionContext); retryState.breakAndThrowIfRetryAnd(() -> !effectiveRetryWrites); resultAccumulator.onNewServerAddress(connectionDescription.getServerAddress()); - retryState.attach(AttachmentKeys.maxWireVersion(), connectionDescription.getMaxWireVersion(), - true) - .attach(AttachmentKeys.commandDescriptionSupplier(), () -> BULK_WRITE_COMMAND_NAME, - false); + retryState.attach(AttachmentKeys.maxWireVersion(), connectionDescription.getMaxWireVersion(), true) + .attach(AttachmentKeys.commandDescriptionSupplier(), () -> BULK_WRITE_COMMAND_NAME, false); ClientBulkWriteCommand bulkWriteCommand = createBulkWriteCommand( - retryState, effectiveRetryWrites, effectiveWriteConcern, sessionContext, - unexecutedModels, batchEncoder, + retryState, effectiveRetryWrites, effectiveWriteConcern, sessionContext, unexecutedModels, batchEncoder, () -> retryState.attach(AttachmentKeys.retryableCommandFlag(), true, true)); executeBulkWriteCommandAndExhaustOkResponseAsync( - retryState, connectionSource, connection, bulkWriteCommand, effectiveWriteConcern, - operationContext, - resultCallback); + retryState, connectionSource, connection, bulkWriteCommand, effectiveWriteConcern, operationContext, resultCallback); })); beginAsync() From 91030ab2f7d4ae1a14e138d099ea59e60fdf47cc Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 23:00:21 -0800 Subject: [PATCH 20/31] Update driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java Co-authored-by: Valentin Kovalenko --- .../internal/async/AsyncBatchCursor.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java b/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java index 3c468a6711a..6e6aba5c6ad 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java @@ -80,17 +80,17 @@ default void exhaustCursor(final SingleResultCallback>> finalCallba List> results = new ArrayList<>(); beginAsync().thenRunDoWhileLoop(iterationCallback -> { - beginAsync(). - thenSupply(this::next) - .thenConsume((batch, callback) -> { - if (batch != null && !batch.isEmpty()) { - results.add(batch); - } - callback.complete(callback); - }).finish(iterationCallback); - }, () -> !this.isClosed()) - .>>thenSupply(callback -> { - callback.complete(results); - }).finish(finalCallback); + beginAsync().>thenSupply(c -> { + next(c); + }).thenConsume((batch, c) -> { + if (batch != null && !batch.isEmpty()) { + results.add(batch); + } + c.complete(c); + }).finish(iterationCallback); + }, () -> !this.isClosed() + ).>>thenSupply(c -> { + c.complete(results); + }).finish(finalCallback); } } From 24ce23d33f03e6adaee75dffbc851e6521ccf68b Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 23:02:52 -0800 Subject: [PATCH 21/31] Update driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java Co-authored-by: Valentin Kovalenko --- .../operation/ClientBulkWriteOperation.java | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index ffdf94a40b0..62b0fc4850c 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -363,31 +363,29 @@ private void executeBatchAsync( retryState, connectionSource, connection, bulkWriteCommand, effectiveWriteConcern, operationContext, resultCallback); })); - beginAsync() - .thenSupply(callback -> { - retryingBatchExecutor.get(callback); - }) - .thenApply((bulkWriteCommandOkResponse, callback) -> { - callback.complete(resultAccumulator.onBulkWriteCommandOkResponseOrNoResponse( - batchStartModelIndex, bulkWriteCommandOkResponse, batchEncoder.intoEncodedBatchInfo())); - }).onErrorIf(throwable -> true, (t, callback) -> { - if (t instanceof MongoWriteConcernWithResponseException) { - callback.complete(resultAccumulator.onBulkWriteCommandOkResponseWithWriteConcernError( - batchStartModelIndex, (MongoWriteConcernWithResponseException) t, batchEncoder.intoEncodedBatchInfo())); - } else if (t instanceof MongoCommandException) { - resultAccumulator.onBulkWriteCommandErrorResponse((MongoCommandException) t); - callback.completeExceptionally(t); - } else if (t instanceof MongoException) { - // The server does not have a chance to add "RetryableWriteError" label to `e`, - // and if it is the last attempt failure, `RetryingSyncSupplier` also may not have a chance - // to add the label. So we do that explicitly. - shouldAttemptToRetryWriteAndAddRetryableLabel(retryState, t); - resultAccumulator.onBulkWriteCommandErrorWithoutResponse((MongoException) t); - callback.completeExceptionally(t); - } else { - callback.completeExceptionally(t); - } - }).finish(finalCallback); + beginAsync().thenSupply(callback -> { + retryingBatchExecutor.get(callback); + }).thenApply((bulkWriteCommandOkResponse, callback) -> { + callback.complete(resultAccumulator.onBulkWriteCommandOkResponseOrNoResponse( + batchStartModelIndex, bulkWriteCommandOkResponse, batchEncoder.intoEncodedBatchInfo())); + }).onErrorIf(throwable -> true, (t, callback) -> { + if (t instanceof MongoWriteConcernWithResponseException) { + callback.complete(resultAccumulator.onBulkWriteCommandOkResponseWithWriteConcernError( + batchStartModelIndex, (MongoWriteConcernWithResponseException) t, batchEncoder.intoEncodedBatchInfo())); + } else if (t instanceof MongoCommandException) { + resultAccumulator.onBulkWriteCommandErrorResponse((MongoCommandException) t); + callback.completeExceptionally(t); + } else if (t instanceof MongoException) { + // The server does not have a chance to add "RetryableWriteError" label to `e`, + // and if it is the last attempt failure, `RetryingSyncSupplier` also may not have a chance + // to add the label. So we do that explicitly. + shouldAttemptToRetryWriteAndAddRetryableLabel(retryState, t); + resultAccumulator.onBulkWriteCommandErrorWithoutResponse((MongoException) t); + callback.completeExceptionally(t); + } else { + callback.completeExceptionally(t); + } + }).finish(finalCallback); } /** From b775ea40c90708dfafa85bfc76923f39f79a2d21 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 23:07:54 -0800 Subject: [PATCH 22/31] Update driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java Co-authored-by: Valentin Kovalenko --- .../internal/operation/ClientBulkWriteOperation.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 62b0fc4850c..cee4a27804d 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -233,13 +233,7 @@ private void executeAllBatches( } /** - * To execute a batch means: - *
      - *
    • execute a `bulkWrite` command, which creates a cursor;
    • - *
    • consume the cursor, which may involve executing `getMore` commands.
    • - *
    - * - * {@link SingleResultCallback} propagates {@link MongoException} when a {@linkplain ClientBulkWriteException#getCause() top-level error} happens. + * @see #executeAllBatches(WriteConcern, WriteBinding, ResultAccumulator) */ private void executeAllBatchesAsync( final WriteConcern effectiveWriteConcern, From 8110a09f14406fa135326d60331172bab1ca922b Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 23:08:50 -0800 Subject: [PATCH 23/31] Update driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java Co-authored-by: Valentin Kovalenko --- .../operation/ClientBulkWriteOperation.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index cee4a27804d..69b12df6747 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -242,16 +242,15 @@ private void executeAllBatchesAsync( final SingleResultCallback finalCallback) { MutableValue nextBatchStartModelIndex = new MutableValue<>(INITIAL_BATCH_MODEL_START_INDEX); - beginAsync() - .thenRunDoWhileLoop(iterationCallback -> { - beginAsync().thenSupply(c -> { - executeBatchAsync(nextBatchStartModelIndex.get(), effectiveWriteConcern, binding, resultAccumulator, c); - }).thenApply((nextBatchStartModelIdx, c) -> { - nextBatchStartModelIndex.set(nextBatchStartModelIdx); - c.complete(c); - }).finish(iterationCallback); - }, () -> shouldExecuteNextBatch(nextBatchStartModelIndex.getNullable())) - .finish(finalCallback); + beginAsync().thenRunDoWhileLoop(iterationCallback -> { + beginAsync().thenSupply(c -> { + executeBatchAsync(nextBatchStartModelIndex.get(), effectiveWriteConcern, binding, resultAccumulator, c); + }).thenApply((nextBatchStartModelIdx, c) -> { + nextBatchStartModelIndex.set(nextBatchStartModelIdx); + c.complete(c); + }).finish(iterationCallback); + }, () -> shouldExecuteNextBatch(nextBatchStartModelIndex.getNullable()) + ).finish(finalCallback); } private static boolean shouldExecuteNextBatch(@Nullable final Integer nextBatchStartModelIndex) { From e8c1520747c5de699caad4f6f7e1b72b248e7055 Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Mon, 16 Dec 2024 23:09:16 -0800 Subject: [PATCH 24/31] Update driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java Co-authored-by: Valentin Kovalenko --- .../com/mongodb/internal/operation/ClientBulkWriteOperation.java | 1 - 1 file changed, 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 69b12df6747..8da07f353f7 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -241,7 +241,6 @@ private void executeAllBatchesAsync( final ResultAccumulator resultAccumulator, final SingleResultCallback finalCallback) { MutableValue nextBatchStartModelIndex = new MutableValue<>(INITIAL_BATCH_MODEL_START_INDEX); - beginAsync().thenRunDoWhileLoop(iterationCallback -> { beginAsync().thenSupply(c -> { executeBatchAsync(nextBatchStartModelIndex.get(), effectiveWriteConcern, binding, resultAccumulator, c); From 8f3e522768965ad75b2b15470b0e069edc7a1ba2 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 17 Dec 2024 00:08:05 -0800 Subject: [PATCH 25/31] - Adress formatting issues. - Remove redundunt checks. - Rename test methods. JAVA-5530 --- .../internal/async/AsyncBatchCursor.java | 4 +- .../mongodb/internal/async/AsyncFunction.java | 2 +- .../mongodb/internal/async/AsyncRunnable.java | 5 +- .../operation/AsyncOperationHelper.java | 2 +- .../internal/operation/BatchCursor.java | 2 +- .../operation/ClientBulkWriteOperation.java | 124 +++++++++--------- .../mongodb/client/test/CollectionHelper.java | 5 +- ...AsyncCommandBatchCursorFunctionalTest.java | 56 +++----- .../CommandBatchCursorFunctionalTest.java | 16 +-- .../async/AsyncFunctionsAbstractTest.java | 47 +++---- .../client/internal/MongoClusterImpl.java | 4 +- 11 files changed, 121 insertions(+), 146 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java b/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java index 6e6aba5c6ad..bd8d6c64a3f 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncBatchCursor.java @@ -76,14 +76,14 @@ public interface AsyncBatchCursor extends Closeable { @Override void close(); - default void exhaustCursor(final SingleResultCallback>> finalCallback) { + default void exhaust(final SingleResultCallback>> finalCallback) { List> results = new ArrayList<>(); beginAsync().thenRunDoWhileLoop(iterationCallback -> { beginAsync().>thenSupply(c -> { next(c); }).thenConsume((batch, c) -> { - if (batch != null && !batch.isEmpty()) { + if (!batch.isEmpty()) { results.add(batch); } c.complete(c); diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncFunction.java b/driver-core/src/main/com/mongodb/internal/async/AsyncFunction.java index ad44d52f0d1..7203d3a4945 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncFunction.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncFunction.java @@ -34,7 +34,7 @@ public interface AsyncFunction { * @param value A {@code @}{@link Nullable} argument of the asynchronous function. * @param callback the callback */ - void unsafeFinish(@Nullable T value, SingleResultCallback callback); + void unsafeFinish(T value, SingleResultCallback callback); /** * Must be invoked at end of async chain or when executing a callback handler supplied by the caller. diff --git a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java index 94febd75c53..e404e2b8152 100644 --- a/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java +++ b/driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java @@ -22,6 +22,7 @@ import com.mongodb.internal.async.function.RetryState; import com.mongodb.internal.async.function.RetryingAsyncCallbackSupplier; +import java.util.function.BooleanSupplier; import java.util.function.Predicate; import java.util.function.Supplier; @@ -251,7 +252,7 @@ default AsyncRunnable thenRunRetryingWhile( * @return the composition of this and the looping branch * @see AsyncCallbackLoop */ - default AsyncRunnable thenRunDoWhileLoop(final AsyncRunnable loopBodyRunnable, final Supplier whileCheck) { + default AsyncRunnable thenRunDoWhileLoop(final AsyncRunnable loopBodyRunnable, final BooleanSupplier whileCheck) { return thenRun(finalCallback -> { LoopState loopState = new LoopState(); new AsyncCallbackLoop(loopState, iterationCallback -> { @@ -261,7 +262,7 @@ default AsyncRunnable thenRunDoWhileLoop(final AsyncRunnable loopBodyRunnable, f iterationCallback.completeExceptionally(t); return; } - if (loopState.breakAndCompleteIf(() -> !whileCheck.get(), iterationCallback)) { + if (loopState.breakAndCompleteIf(() -> !whileCheck.getAsBoolean(), iterationCallback)) { return; } iterationCallback.complete(iterationCallback); diff --git a/driver-core/src/main/com/mongodb/internal/operation/AsyncOperationHelper.java b/driver-core/src/main/com/mongodb/internal/operation/AsyncOperationHelper.java index 072ae8e0d9f..f158b3944ae 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/AsyncOperationHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/AsyncOperationHelper.java @@ -344,7 +344,7 @@ static CommandReadTransformerAsync> asyncS } static AsyncBatchCursor cursorDocumentToAsyncBatchCursor(final TimeoutMode timeoutMode, final BsonDocument cursorDocument, - final int batchSize, final Decoder decoder, final BsonValue comment, final AsyncConnectionSource source, + final int batchSize, final Decoder decoder, @Nullable final BsonValue comment, final AsyncConnectionSource source, final AsyncConnection connection) { return new AsyncCommandBatchCursor<>(timeoutMode, cursorDocument, batchSize, 0, decoder, comment, source, connection); } diff --git a/driver-core/src/main/com/mongodb/internal/operation/BatchCursor.java b/driver-core/src/main/com/mongodb/internal/operation/BatchCursor.java index fc697dfdfb7..1463798ef64 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/BatchCursor.java +++ b/driver-core/src/main/com/mongodb/internal/operation/BatchCursor.java @@ -105,7 +105,7 @@ public interface BatchCursor extends Iterator>, Closeable { ServerAddress getServerAddress(); - default List> exhaustCursor() { + default List> exhaust() { return stream(spliteratorUnknownSize(this, ORDERED | IMMUTABLE), false) .collect(toList()); } diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index 8da07f353f7..d5a9dd9f4c6 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -153,6 +153,7 @@ public final class ClientBulkWriteOperation implements WriteOperation models; private final ConcreteClientBulkWriteOptions options; @@ -181,6 +182,7 @@ public ClientBulkWriteResult execute(final WriteBinding binding) throws ClientBu WriteConcern effectiveWriteConcern = validateAndGetEffectiveWriteConcern(binding.getOperationContext().getSessionContext()); ResultAccumulator resultAccumulator = new ResultAccumulator(); MongoException transformedTopLevelError = null; + try { executeAllBatches(effectiveWriteConcern, binding, resultAccumulator); } catch (MongoException topLevelError) { @@ -189,27 +191,21 @@ public ClientBulkWriteResult execute(final WriteBinding binding) throws ClientBu return resultAccumulator.build(transformedTopLevelError, effectiveWriteConcern); } + @Override public void executeAsync(final AsyncWriteBinding binding, final SingleResultCallback finalCallback) { WriteConcern effectiveWriteConcern = validateAndGetEffectiveWriteConcern(binding.getOperationContext().getSessionContext()); ResultAccumulator resultAccumulator = new ResultAccumulator(); - - beginAsync().thenSupply(callback -> { - executeAllBatchesAsync(effectiveWriteConcern, binding, resultAccumulator, (result, t) -> { - MongoException transformedTopLevelError = null; - if (t != null) { - if (t instanceof MongoException) { - transformedTopLevelError = transformWriteException((MongoException) t); - } else { - callback.completeExceptionally(t); - return; - } - } - callback.onResult(transformedTopLevelError, null); - }); - }).thenApply((transformedTopLevelError, callback1) -> { - callback1.onResult(resultAccumulator.build(transformedTopLevelError, effectiveWriteConcern), null); + MutableValue transformedTopLevelError = new MutableValue<>(); + + beginAsync().thenSupply(c -> { + executeAllBatchesAsync(effectiveWriteConcern, binding, resultAccumulator, c); + }).onErrorIf(topLevelError -> topLevelError instanceof MongoException, (topLevelError, c) -> { + transformedTopLevelError.set(transformWriteException((MongoException) topLevelError)); + c.complete(c); + }).thenApply((ignored, c) -> { + c.complete(resultAccumulator.build(transformedTopLevelError.getNullable(), effectiveWriteConcern)); }).finish(finalCallback); } @@ -227,9 +223,10 @@ private void executeAllBatches( final WriteBinding binding, final ResultAccumulator resultAccumulator) throws MongoException { Integer nextBatchStartModelIndex = INITIAL_BATCH_MODEL_START_INDEX; + do { nextBatchStartModelIndex = executeBatch(nextBatchStartModelIndex, effectiveWriteConcern, binding, resultAccumulator); - } while (shouldExecuteNextBatch(nextBatchStartModelIndex)); + } while (nextBatchStartModelIndex != null); } /** @@ -241,6 +238,7 @@ private void executeAllBatchesAsync( final ResultAccumulator resultAccumulator, final SingleResultCallback finalCallback) { MutableValue nextBatchStartModelIndex = new MutableValue<>(INITIAL_BATCH_MODEL_START_INDEX); + beginAsync().thenRunDoWhileLoop(iterationCallback -> { beginAsync().thenSupply(c -> { executeBatchAsync(nextBatchStartModelIndex.get(), effectiveWriteConcern, binding, resultAccumulator, c); @@ -274,26 +272,28 @@ private Integer executeBatch( TimeoutContext timeoutContext = operationContext.getTimeoutContext(); RetryState retryState = initialRetryState(retryWritesSetting, timeoutContext); BatchEncoder batchEncoder = new BatchEncoder(); + Supplier retryingBatchExecutor = decorateWriteWithRetries( retryState, operationContext, // Each batch re-selects a server and re-checks out a connection because this is simpler, // and it is allowed by https://jira.mongodb.org/browse/DRIVERS-2502. - // If connection pinning is required, {@code binding} handles that, + // If connection pinning is required, binding handles that, // and `ClientSession`, `TransactionContext` are aware of that. - () -> withSourceAndConnection(binding::getWriteConnectionSource, true, (connectionSource, connection) -> { - ConnectionDescription connectionDescription = connection.getDescription(); - boolean effectiveRetryWrites = isRetryableWrite( - retryWritesSetting, effectiveWriteConcern, connectionDescription, sessionContext); - retryState.breakAndThrowIfRetryAnd(() -> !effectiveRetryWrites); - resultAccumulator.onNewServerAddress(connectionDescription.getServerAddress()); - retryState.attach(AttachmentKeys.maxWireVersion(), connectionDescription.getMaxWireVersion(), true) - .attach(AttachmentKeys.commandDescriptionSupplier(), () -> BULK_WRITE_COMMAND_NAME, false); - ClientBulkWriteCommand bulkWriteCommand = createBulkWriteCommand( - retryState, effectiveRetryWrites, effectiveWriteConcern, sessionContext, unexecutedModels, batchEncoder, - () -> retryState.attach(AttachmentKeys.retryableCommandFlag(), true, true)); - return executeBulkWriteCommandAndExhaustOkResponse( - retryState, connectionSource, connection, bulkWriteCommand, effectiveWriteConcern, operationContext); - }) + () -> withSourceAndConnection(binding::getWriteConnectionSource, true, + (connectionSource, connection) -> { + ConnectionDescription connectionDescription = connection.getDescription(); + boolean effectiveRetryWrites = isRetryableWrite( + retryWritesSetting, effectiveWriteConcern, connectionDescription, sessionContext); + retryState.breakAndThrowIfRetryAnd(() -> !effectiveRetryWrites); + resultAccumulator.onNewServerAddress(connectionDescription.getServerAddress()); + retryState.attach(AttachmentKeys.maxWireVersion(), connectionDescription.getMaxWireVersion(), true) + .attach(AttachmentKeys.commandDescriptionSupplier(), () -> BULK_WRITE_COMMAND_NAME, false); + ClientBulkWriteCommand bulkWriteCommand = createBulkWriteCommand( + retryState, effectiveRetryWrites, effectiveWriteConcern, sessionContext, unexecutedModels, batchEncoder, + () -> retryState.attach(AttachmentKeys.retryableCommandFlag(), true, true)); + return executeBulkWriteCommandAndExhaustOkResponse( + retryState, connectionSource, connection, bulkWriteCommand, effectiveWriteConcern, operationContext); + }) ); try { @@ -306,13 +306,13 @@ private Integer executeBatch( } catch (MongoCommandException bulkWriteCommandException) { resultAccumulator.onBulkWriteCommandErrorResponse(bulkWriteCommandException); throw bulkWriteCommandException; - } catch (MongoException e) { + } catch (MongoException mongoException) { // The server does not have a chance to add "RetryableWriteError" label to `e`, // and if it is the last attempt failure, `RetryingSyncSupplier` also may not have a chance // to add the label. So we do that explicitly. - shouldAttemptToRetryWriteAndAddRetryableLabel(retryState, e); - resultAccumulator.onBulkWriteCommandErrorWithoutResponse(e); - throw e; + shouldAttemptToRetryWriteAndAddRetryableLabel(retryState, mongoException); + resultAccumulator.onBulkWriteCommandErrorWithoutResponse(mongoException); + throw mongoException; } } @@ -337,7 +337,7 @@ private void executeBatchAsync( retryState, operationContext, // Each batch re-selects a server and re-checks out a connection because this is simpler, // and it is allowed by https://jira.mongodb.org/browse/DRIVERS-2502. - // If connection pinning is required, {@code binding} handles that, + // If connection pinning is required, binding handles that, // and `ClientSession`, `TransactionContext` are aware of that. funcCallback -> withAsyncSourceAndConnection(binding::getWriteConnectionSource, true, funcCallback, (connectionSource, connection, resultCallback) -> { @@ -362,18 +362,21 @@ private void executeBatchAsync( batchStartModelIndex, bulkWriteCommandOkResponse, batchEncoder.intoEncodedBatchInfo())); }).onErrorIf(throwable -> true, (t, callback) -> { if (t instanceof MongoWriteConcernWithResponseException) { + MongoWriteConcernWithResponseException mongoWriteConcernWithOkResponseException = (MongoWriteConcernWithResponseException) t; callback.complete(resultAccumulator.onBulkWriteCommandOkResponseWithWriteConcernError( - batchStartModelIndex, (MongoWriteConcernWithResponseException) t, batchEncoder.intoEncodedBatchInfo())); + batchStartModelIndex, mongoWriteConcernWithOkResponseException, batchEncoder.intoEncodedBatchInfo())); } else if (t instanceof MongoCommandException) { - resultAccumulator.onBulkWriteCommandErrorResponse((MongoCommandException) t); + MongoCommandException bulkWriteCommandException = (MongoCommandException) t; + resultAccumulator.onBulkWriteCommandErrorResponse(bulkWriteCommandException); callback.completeExceptionally(t); } else if (t instanceof MongoException) { + MongoException mongoException = (MongoException) t; // The server does not have a chance to add "RetryableWriteError" label to `e`, // and if it is the last attempt failure, `RetryingSyncSupplier` also may not have a chance // to add the label. So we do that explicitly. - shouldAttemptToRetryWriteAndAddRetryableLabel(retryState, t); - resultAccumulator.onBulkWriteCommandErrorWithoutResponse((MongoException) t); - callback.completeExceptionally(t); + shouldAttemptToRetryWriteAndAddRetryableLabel(retryState, mongoException); + resultAccumulator.onBulkWriteCommandErrorWithoutResponse(mongoException); + callback.completeExceptionally(mongoException); } else { callback.completeExceptionally(t); } @@ -410,13 +413,12 @@ private ExhaustiveClientBulkWriteCommandOkResponse executeBulkWriteCommandAndExh List> cursorExhaustBatches = doWithRetriesDisabledForCommand(retryState, "getMore", () -> exhaustBulkWriteCommandOkResponseCursor(connectionSource, connection, bulkWriteCommandOkResponse)); - return getExhaustiveClientBulkWriteCommandOkResponse( + return createExhaustiveClientBulkWriteCommandOkResponse( bulkWriteCommandOkResponse, cursorExhaustBatches, connection.getDescription()); } - @Nullable private void executeBulkWriteCommandAndExhaustOkResponseAsync( final RetryState retryState, final AsyncConnectionSource connectionSource, @@ -424,8 +426,7 @@ private void executeBulkWriteCommandAndExhaustOkResponseAsync( final ClientBulkWriteCommand bulkWriteCommand, final WriteConcern effectiveWriteConcern, final OperationContext operationContext, - final SingleResultCallback finalCallback) - throws MongoWriteConcernWithResponseException { + final SingleResultCallback finalCallback) { beginAsync().thenSupply(callback -> { connection.commandAsync( "admin", @@ -446,26 +447,24 @@ private void executeBulkWriteCommandAndExhaustOkResponseAsync( exhaustBulkWriteCommandOkResponseCursorAsync(connectionSource, connection, bulkWriteCommandOkResponse, c1); }, c); }).thenApply((cursorExhaustBatches, c) -> { - ExhaustiveClientBulkWriteCommandOkResponse exhaustiveBulkWriteCommandOkResponse = - getExhaustiveClientBulkWriteCommandOkResponse( - bulkWriteCommandOkResponse, - cursorExhaustBatches, - connection.getDescription()); - c.complete(exhaustiveBulkWriteCommandOkResponse); + c.complete(createExhaustiveClientBulkWriteCommandOkResponse( + bulkWriteCommandOkResponse, + cursorExhaustBatches, + connection.getDescription())); }).finish(callback); }).finish(finalCallback); } - private static ExhaustiveClientBulkWriteCommandOkResponse getExhaustiveClientBulkWriteCommandOkResponse( + private static ExhaustiveClientBulkWriteCommandOkResponse createExhaustiveClientBulkWriteCommandOkResponse( final BsonDocument bulkWriteCommandOkResponse, final List> cursorExhaustBatches, - final ConnectionDescription connection) { + final ConnectionDescription connectionDescription) { ExhaustiveClientBulkWriteCommandOkResponse exhaustiveBulkWriteCommandOkResponse = new ExhaustiveClientBulkWriteCommandOkResponse( bulkWriteCommandOkResponse, cursorExhaustBatches); // `Connection.command` does not throw `MongoWriteConcernException`, so we have to construct it ourselves MongoWriteConcernException writeConcernException = Exceptions.createWriteConcernException( - bulkWriteCommandOkResponse, connection.getServerAddress()); + bulkWriteCommandOkResponse, connectionDescription.getServerAddress()); if (writeConcernException != null) { throw new MongoWriteConcernWithResponseException(writeConcernException, exhaustiveBulkWriteCommandOkResponse); } @@ -479,6 +478,7 @@ private R doWithRetriesDisabledForCommand( Optional originalRetryableCommandFlag = retryState.attachment(AttachmentKeys.retryableCommandFlag()); Supplier originalCommandDescriptionSupplier = retryState.attachment(AttachmentKeys.commandDescriptionSupplier()) .orElseThrow(Assertions::fail); + try { retryState.attach(AttachmentKeys.retryableCommandFlag(), false, true) .attach(AttachmentKeys.commandDescriptionSupplier(), () -> commandDescription, false); @@ -512,16 +512,15 @@ private List> exhaustBulkWriteCommandOkResponseCursor( final ConnectionSource connectionSource, final Connection connection, final BsonDocument response) { - int serverDefaultCursorBatchSize = 0; try (CommandBatchCursor cursor = cursorDocumentToBatchCursor( TimeoutMode.CURSOR_LIFETIME, response, - serverDefaultCursorBatchSize, + SERVER_DEFAULT_CURSOR_BATCH_SIZE, codecRegistry.get(BsonDocument.class), options.getComment().orElse(null), connectionSource, connection)) { - return cursor.exhaustCursor(); + return cursor.exhaust(); } } @@ -532,18 +531,17 @@ private void exhaustBulkWriteCommandOkResponseCursorAsync(final AsyncConnectionS AsyncBatchCursor cursor = cursorDocumentToAsyncBatchCursor( TimeoutMode.CURSOR_LIFETIME, bulkWriteCommandOkResponse, - 0, + SERVER_DEFAULT_CURSOR_BATCH_SIZE, codecRegistry.get(BsonDocument.class), options.getComment().orElse(null), connectionSource, connection); beginAsync().>>thenSupply(callback -> { - cursor.exhaustCursor(callback); + cursor.exhaust(callback); }).thenAlwaysRunAndFinish(() -> { - if (!cursor.isClosed()) { - cursor.close(); - }}, finalCallback); + cursor.close(); + }, finalCallback); } diff --git a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java index f1ef6fe6a95..3e58712ca9c 100644 --- a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java +++ b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java @@ -366,8 +366,9 @@ public void deleteMany(final Bson filter) { private void delete(final Bson filter, final boolean multi) { new MixedBulkWriteOperation(namespace, - singletonList(new DeleteRequest(filter.toBsonDocument(Document.class, registry))), - true, WriteConcern.ACKNOWLEDGED, false); + singletonList(new DeleteRequest(filter.toBsonDocument(Document.class, registry)).multi(multi)), + true, WriteConcern.ACKNOWLEDGED, false) + .execute(getBinding()); } public List find(final Bson filter) { diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java index 1c7eefd56c5..7d2e6c9e3f7 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java @@ -21,6 +21,7 @@ import com.mongodb.MongoQueryException; import com.mongodb.ReadPreference; import com.mongodb.ServerCursor; +import com.mongodb.async.FutureResultCallback; import com.mongodb.client.cursor.TimeoutMode; import com.mongodb.client.model.CreateCollectionOptions; import com.mongodb.client.model.Filters; @@ -114,27 +115,18 @@ void shouldExhaustCursorAsyncWithMultipleBatches() { cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, null, connectionSource, connection); - CompletableFuture>> futureResult = new CompletableFuture<>(); - // when - cursor.exhaustCursor((result, throwable) -> { - if (throwable != null) { - futureResult.completeExceptionally(throwable); - } else { - futureResult.complete(result); - } - }); + FutureResultCallback>> futureCallback = new FutureResultCallback<>(); + cursor.exhaust(futureCallback); // then - assertDoesNotThrow(() -> { - List> resultBatches = futureResult.get(5, TimeUnit.SECONDS); + List> resultBatches = futureCallback.get(5, TimeUnit.SECONDS); - assertEquals(4, resultBatches.size(), "Expected 4 batches for 10 documents with batch size of 3."); + assertTrue(cursor.isClosed(), "Expected cursor to be closed."); + assertEquals(4, resultBatches.size(), "Expected 4 batches for 10 documents with batch size of 3."); - int totalDocuments = resultBatches.stream().mapToInt(List::size).sum(); - assertEquals(10, totalDocuments, "Expected a total of 10 documents."); - assertTrue(cursor.isClosed(), "Expected cursor to be closed."); - }); + int totalDocuments = resultBatches.stream().mapToInt(List::size).sum(); + assertEquals(10, totalDocuments, "Expected a total of 10 documents."); } @Test @@ -147,20 +139,13 @@ void shouldExhaustCursorAsyncWithClosedCursor() { cursor.close(); - CompletableFuture>> futureResult = new CompletableFuture<>(); - // when - cursor.exhaustCursor((result, throwable) -> { - if (throwable != null) { - futureResult.completeExceptionally(throwable); - } else { - futureResult.complete(result); - } - }); + FutureResultCallback>> futureCallback = new FutureResultCallback<>(); + cursor.exhaust(futureCallback); //then ExecutionException executionException = assertThrows(ExecutionException.class, () -> { - futureResult.get(5, TimeUnit.SECONDS); + futureCallback.get(5, TimeUnit.SECONDS); }, "Expected an exception when operating on a closed cursor."); IllegalStateException illegalStateException = (IllegalStateException) executionException.getCause(); @@ -177,23 +162,14 @@ void shouldExhaustCursorAsyncWithEmptyCursor() { cursor = new AsyncCommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, null, connectionSource, connection); - CompletableFuture>> futureResult = new CompletableFuture<>(); - // when - cursor.exhaustCursor((result, throwable) -> { - if (throwable != null) { - futureResult.completeExceptionally(throwable); - } else { - futureResult.complete(result); - } - }); + FutureResultCallback>> futureCallback = new FutureResultCallback<>(); + cursor.exhaust(futureCallback); // then - assertDoesNotThrow(() -> { - List> resultBatches = futureResult.get(5, TimeUnit.SECONDS); - assertTrue(resultBatches.isEmpty(), "Expected no batches for an empty cursor."); - assertTrue(cursor.isClosed(), "Expected cursor to be closed."); - }); + List> resultBatches = futureCallback.get(5, TimeUnit.SECONDS); + assertTrue(resultBatches.isEmpty(), "Expected no batches for an empty cursor."); + assertTrue(cursor.isClosed(), "Expected cursor to be closed."); } @Test diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java index 2569acc943d..d9861c71659 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/CommandBatchCursorFunctionalTest.java @@ -104,26 +104,25 @@ void cleanup() { @Test @DisplayName("should exhaust cursor with multiple batches") - void shouldExhaustCursorAsyncWithMultipleBatches() { + void shouldExhaustCursorWithMultipleBatches() { // given BsonDocument commandResult = executeFindCommand(0, 3); // Fetch in batches of size 3 cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, null, connectionSource, connection); // when - List> result = cursor.exhaustCursor(); + List> result = cursor.exhaust(); // then assertEquals(4, result.size(), "Expected 4 batches for 10 documents with batch size of 3."); int totalDocuments = result.stream().mapToInt(List::size).sum(); assertEquals(10, totalDocuments, "Expected a total of 10 documents."); - assertTrue(cursor.isClosed(), "Expected cursor to be closed."); } @Test @DisplayName("should exhaust cursor with closed cursor") - void shouldExhaustCursorAsyncWithClosedCursor() { + void shouldExhaustCursorWithClosedCursor() { // given BsonDocument commandResult = executeFindCommand(0, 3); cursor = new CommandBatchCursor<>(TimeoutMode.CURSOR_LIFETIME, commandResult, 3, 0, DOCUMENT_DECODER, @@ -131,13 +130,13 @@ void shouldExhaustCursorAsyncWithClosedCursor() { cursor.close(); // when & then - IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, cursor::exhaustCursor); + IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, cursor::exhaust); assertEquals("Cursor has been closed", illegalStateException.getMessage()); } @Test - @DisplayName("should exhaust cursor async with empty cursor") - void shouldExhaustCursorAsyncWithEmptyCursor() { + @DisplayName("should exhaust cursor with empty cursor") + void shouldExhaustCursorWithEmptyCursor() { // given getCollectionHelper().deleteMany(Filters.empty()); @@ -146,11 +145,10 @@ void shouldExhaustCursorAsyncWithEmptyCursor() { null, connectionSource, connection); // when - List> result = cursor.exhaustCursor(); + List> result = cursor.exhaust(); // then assertTrue(result.isEmpty(), "Expected no batches for an empty cursor."); - assertTrue(cursor.isClosed(), "Expected cursor to be closed."); } @Test diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java index 0731951edfe..9a9b7552d3e 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java @@ -790,6 +790,29 @@ void testFinallyWithPlainInsideTry() { }); } + @Test + void testFinallyWithPlainOutsideTry() { + assertBehavesSameVariations(5, + () -> { + plain(1); + try { + sync(2); + } finally { + plain(3); + } + }, + (callback) -> { + beginAsync().thenRun(c -> { + plain(1); + beginAsync().thenRun(c2 -> { + async(2, c2); + }).thenAlwaysRunAndFinish(() -> { + plain(3); + }, c); + }).finish(callback); + }); + } + @Test void testSupplyFinallyWithPlainInsideTry() { assertBehavesSameVariations(6, @@ -817,7 +840,7 @@ void testSupplyFinallyWithPlainOutsideTry() { () -> { plain(1); try { - return syncReturns(2); + return syncReturns(2); } finally { plain(3); } @@ -834,28 +857,6 @@ void testSupplyFinallyWithPlainOutsideTry() { }); } - @Test - void testFinallyWithPlainOutsideTry() { - assertBehavesSameVariations(5, - () -> { - plain(1); - try { - sync(2); - } finally { - plain(3); - } - }, - (callback) -> { - beginAsync().thenRun(c -> { - plain(1); - beginAsync().thenRun(c2 -> { - async(2, c2); - }).thenAlwaysRunAndFinish(() -> { - plain(3); - }, c); - }).finish(callback); - }); - } @Test void testUsedAsLambda() { diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java index 1c907b5620c..3421b28eb4e 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java @@ -243,7 +243,7 @@ public ChangeStreamPublisher watch(final ClientSession clientSession, fin } @Override - public Publisher bulkWrite(final List clientWriteModels) throws ClientBulkWriteException { + public Publisher bulkWrite(final List clientWriteModels) { notNull("clientWriteModels", clientWriteModels); isTrueArgument("`clientWriteModels` must not be empty", !clientWriteModels.isEmpty()); return mongoOperationPublisher.clientBulkWrite(null, clientWriteModels, null); @@ -260,7 +260,7 @@ public Publisher bulkWrite(final List bulkWrite(final ClientSession clientSession, - final List clientWriteModels) throws ClientBulkWriteException { + final List clientWriteModels) { notNull("clientSession", clientSession); notNull("clientWriteModels", clientWriteModels); isTrueArgument("`clientWriteModels` must not be empty", !clientWriteModels.isEmpty()); From dc85f963c954a2777638ae4ebd7c26882a6815cc Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 17 Dec 2024 00:14:12 -0800 Subject: [PATCH 26/31] Fix static checks. JAVA-5530 --- .../operation/AsyncCommandBatchCursorFunctionalTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java index 7d2e6c9e3f7..a180fe4c271 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java @@ -48,7 +48,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; From c0b7292a9395fa372f02cec17dc71226c0eb9a0d Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 17 Dec 2024 11:58:23 -0800 Subject: [PATCH 27/31] Fix test. JAVA-5530 --- .../operation/AsyncCommandBatchCursorFunctionalTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java index a180fe4c271..88dc199ee29 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/operation/AsyncCommandBatchCursorFunctionalTest.java @@ -49,7 +49,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -143,11 +142,9 @@ void shouldExhaustCursorAsyncWithClosedCursor() { cursor.exhaust(futureCallback); //then - ExecutionException executionException = assertThrows(ExecutionException.class, () -> { + IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, () -> { futureCallback.get(5, TimeUnit.SECONDS); }, "Expected an exception when operating on a closed cursor."); - - IllegalStateException illegalStateException = (IllegalStateException) executionException.getCause(); assertEquals("Cursor has been closed", illegalStateException.getMessage()); } From fc02c9ab3954d6fa31954ea164b42e9348a76232 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 17 Dec 2024 12:25:44 -0800 Subject: [PATCH 28/31] Remove throws. JAVA-5530 --- .../reactivestreams/client/internal/MongoClusterImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java index 3421b28eb4e..14f313f925d 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java @@ -251,7 +251,7 @@ public Publisher bulkWrite(final List bulkWrite(final List clientWriteModels, - final ClientBulkWriteOptions options) throws ClientBulkWriteException { + final ClientBulkWriteOptions options) { notNull("clientWriteModels", clientWriteModels); isTrueArgument("`clientWriteModels` must not be empty", !clientWriteModels.isEmpty()); notNull("options", options); From 3f36b2192492f87af27db5469dfe13483e7c4a4f Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Tue, 17 Dec 2024 12:42:05 -0800 Subject: [PATCH 29/31] Remove redundant method. JAVA-5530 --- .../internal/operation/ClientBulkWriteOperation.java | 12 ++++++------ .../client/internal/MongoClusterImpl.java | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index d5a9dd9f4c6..b317ba1b6e6 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -246,12 +246,8 @@ private void executeAllBatchesAsync( nextBatchStartModelIndex.set(nextBatchStartModelIdx); c.complete(c); }).finish(iterationCallback); - }, () -> shouldExecuteNextBatch(nextBatchStartModelIndex.getNullable()) - ).finish(finalCallback); - } - - private static boolean shouldExecuteNextBatch(@Nullable final Integer nextBatchStartModelIndex) { - return nextBatchStartModelIndex != null; + }, () -> nextBatchStartModelIndex.getNullable() != null) + .finish(finalCallback); } /** @@ -419,6 +415,9 @@ private ExhaustiveClientBulkWriteCommandOkResponse executeBulkWriteCommandAndExh connection.getDescription()); } + /** + * @see #executeBulkWriteCommandAndExhaustOkResponse(RetryState, ConnectionSource, Connection, ClientBulkWriteCommand, WriteConcern, OperationContext) + */ private void executeBulkWriteCommandAndExhaustOkResponseAsync( final RetryState retryState, final AsyncConnectionSource connectionSource, @@ -520,6 +519,7 @@ private List> exhaustBulkWriteCommandOkResponseCursor( options.getComment().orElse(null), connectionSource, connection)) { + return cursor.exhaust(); } } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java index 14f313f925d..04028ecc684 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClusterImpl.java @@ -16,7 +16,6 @@ package com.mongodb.reactivestreams.client.internal; -import com.mongodb.ClientBulkWriteException; import com.mongodb.ClientSessionOptions; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; From 190a06711a4bde16622fc0be1e725889d4bc4db4 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 18 Dec 2024 20:08:15 -0800 Subject: [PATCH 30/31] Fix typo. JAVA-5530 --- .../mongodb/internal/operation/ClientBulkWriteOperation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index b317ba1b6e6..a6c944e8fa6 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -273,7 +273,7 @@ private Integer executeBatch( retryState, operationContext, // Each batch re-selects a server and re-checks out a connection because this is simpler, // and it is allowed by https://jira.mongodb.org/browse/DRIVERS-2502. - // If connection pinning is required, binding handles that, + // If connection pinning is required, `binding` handles that, // and `ClientSession`, `TransactionContext` are aware of that. () -> withSourceAndConnection(binding::getWriteConnectionSource, true, (connectionSource, connection) -> { @@ -333,7 +333,7 @@ private void executeBatchAsync( retryState, operationContext, // Each batch re-selects a server and re-checks out a connection because this is simpler, // and it is allowed by https://jira.mongodb.org/browse/DRIVERS-2502. - // If connection pinning is required, binding handles that, + // If connection pinning is required, `binding` handles that, // and `ClientSession`, `TransactionContext` are aware of that. funcCallback -> withAsyncSourceAndConnection(binding::getWriteConnectionSource, true, funcCallback, (connectionSource, connection, resultCallback) -> { From 3019e074c723b5c4606b6a19faf4ed1bc908fbef Mon Sep 17 00:00:00 2001 From: Viacheslav Babanin Date: Fri, 20 Dec 2024 10:00:10 -0800 Subject: [PATCH 31/31] Apply suggestions from code review Co-authored-by: Valentin Kovalenko --- .../internal/operation/ClientBulkWriteOperation.java | 8 ++++---- .../test/functional/com/mongodb/client/CrudProseTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java index a6c944e8fa6..6592fcbaed3 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java +++ b/driver-core/src/main/com/mongodb/internal/operation/ClientBulkWriteOperation.java @@ -246,8 +246,8 @@ private void executeAllBatchesAsync( nextBatchStartModelIndex.set(nextBatchStartModelIdx); c.complete(c); }).finish(iterationCallback); - }, () -> nextBatchStartModelIndex.getNullable() != null) - .finish(finalCallback); + }, () -> nextBatchStartModelIndex.getNullable() != null + ).finish(finalCallback); } /** @@ -349,7 +349,8 @@ private void executeBatchAsync( () -> retryState.attach(AttachmentKeys.retryableCommandFlag(), true, true)); executeBulkWriteCommandAndExhaustOkResponseAsync( retryState, connectionSource, connection, bulkWriteCommand, effectiveWriteConcern, operationContext, resultCallback); - })); + }) + ); beginAsync().thenSupply(callback -> { retryingBatchExecutor.get(callback); @@ -408,7 +409,6 @@ private ExhaustiveClientBulkWriteCommandOkResponse executeBulkWriteCommandAndExh } List> cursorExhaustBatches = doWithRetriesDisabledForCommand(retryState, "getMore", () -> exhaustBulkWriteCommandOkResponseCursor(connectionSource, connection, bulkWriteCommandOkResponse)); - return createExhaustiveClientBulkWriteCommandOkResponse( bulkWriteCommandOkResponse, cursorExhaustBatches, diff --git a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java index 81b59451478..101865079e0 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/CrudProseTest.java @@ -599,7 +599,7 @@ private interface TriConsumer { /** * This method is used instead of {@link ClientSession#withTransaction(TransactionBody)} - * because reactive {@link com.mongodb.reactivestreams.client.ClientSession} do not support it. + * because reactive {@code com.mongodb.reactivestreams.client.ClientSession} do not support it. */ private static ClientBulkWriteResult runInTransaction(final ClientSession session, final Supplier action) {