diff --git a/src/MongoDB.Driver/AggregateHelper.cs b/src/MongoDB.Driver/AggregateHelper.cs new file mode 100644 index 00000000000..c0b1313411d --- /dev/null +++ b/src/MongoDB.Driver/AggregateHelper.cs @@ -0,0 +1,96 @@ +/* Copyright 2010-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. + */ + +using System; +using System.Linq; +using MongoDB.Bson; + +namespace MongoDB.Driver +{ + internal static class AggregateHelper + { + public static RenderedPipelineDefinition RenderAggregatePipeline(PipelineDefinition pipeline, RenderArgs renderArgs, out bool isAggregateToCollection) + { + var renderedPipeline = pipeline.Render(renderArgs); + + var lastStage = renderedPipeline.Documents.LastOrDefault(); + var lastStageName = lastStage?.GetElement(0).Name; + isAggregateToCollection = lastStage != null && (lastStageName == "$out" || lastStageName == "$merge"); + + return renderedPipeline; + } + + public static CollectionNamespace GetOutCollection(BsonDocument outStage, DatabaseNamespace defaultDatabaseNamespace) + { + var stageName = outStage.GetElement(0).Name; + switch (stageName) + { + case "$out": + { + var outValue = outStage[0]; + DatabaseNamespace outputDatabaseNamespace; + string outputCollectionName; + if (outValue.IsString) + { + outputDatabaseNamespace = defaultDatabaseNamespace; + outputCollectionName = outValue.AsString; + } + else + { + outputDatabaseNamespace = new DatabaseNamespace(outValue["db"].AsString); + outputCollectionName = outValue["coll"].AsString; + } + return new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); + } + case "$merge": + { + var mergeArguments = outStage[0]; + DatabaseNamespace outputDatabaseNamespace; + string outputCollectionName; + if (mergeArguments.IsString) + { + outputDatabaseNamespace = defaultDatabaseNamespace; + outputCollectionName = mergeArguments.AsString; + } + else + { + var into = mergeArguments.AsBsonDocument["into"]; + if (into.IsString) + { + outputDatabaseNamespace = defaultDatabaseNamespace; + outputCollectionName = into.AsString; + } + else + { + if (into.AsBsonDocument.Contains("db")) + { + outputDatabaseNamespace = new DatabaseNamespace(into["db"].AsString); + } + else + { + outputDatabaseNamespace = defaultDatabaseNamespace; + } + outputCollectionName = into["coll"].AsString; + } + } + return new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); + } + default: + throw new ArgumentException($"Unexpected stage name: {stageName}."); + } + } + } +} + diff --git a/src/MongoDB.Driver/IOperationExecutor.cs b/src/MongoDB.Driver/IOperationExecutor.cs index 1af86d6ff62..bad44c4effe 100644 --- a/src/MongoDB.Driver/IOperationExecutor.cs +++ b/src/MongoDB.Driver/IOperationExecutor.cs @@ -13,23 +13,40 @@ * limitations under the License. */ -using System; using System.Threading; using System.Threading.Tasks; -using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Operations; namespace MongoDB.Driver { internal interface IOperationExecutor { - TResult ExecuteReadOperation(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken); - Task ExecuteReadOperationAsync(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken); + TResult ExecuteReadOperation( + IReadOperation operation, + ReadOperationOptions options, + IClientSessionHandle session = null, + CancellationToken cancellationToken = default); - TResult ExecuteWriteOperation(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken); - Task ExecuteWriteOperationAsync(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken); + Task ExecuteReadOperationAsync( + IReadOperation operation, + ReadOperationOptions options, + IClientSessionHandle session = null, + CancellationToken cancellationToken = default); + + TResult ExecuteWriteOperation( + IWriteOperation operation, + WriteOperationOptions options, + IClientSessionHandle session = null, + CancellationToken cancellationToken = default); + + Task ExecuteWriteOperationAsync( + IWriteOperation operation, + WriteOperationOptions options, + IClientSessionHandle session = null, + CancellationToken cancellationToken = default); IClientSessionHandle StartImplicitSession(CancellationToken cancellationToken); + Task StartImplicitSessionAsync(CancellationToken cancellationToken); } } diff --git a/src/MongoDB.Driver/MongoClient.cs b/src/MongoDB.Driver/MongoClient.cs index 0fa1b4eb8d2..1aec659d044 100644 --- a/src/MongoDB.Driver/MongoClient.cs +++ b/src/MongoDB.Driver/MongoClient.cs @@ -23,7 +23,6 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; -using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Clusters; using MongoDB.Driver.Core.Logging; using MongoDB.Driver.Core.Misc; @@ -45,6 +44,8 @@ public sealed class MongoClient : IMongoClient private readonly IOperationExecutor _operationExecutor; private readonly MongoClientSettings _settings; private readonly ILogger _logger; + private readonly ReadOperationOptions _readOperationOptions; + private readonly WriteOperationOptions _writeOperationOptions; // constructors /// @@ -66,6 +67,9 @@ public MongoClient(MongoClientSettings settings) _cluster = _settings.ClusterSource.Get(_settings.ToClusterKey()); _operationExecutor = new OperationExecutor(this); + _readOperationOptions = new ReadOperationOptions(DefaultReadPreference: _settings.ReadPreference); + _writeOperationOptions = new WriteOperationOptions(); + if (settings.AutoEncryptionOptions != null) { _libMongoCryptController = @@ -117,8 +121,6 @@ internal MongoClient(IOperationExecutor operationExecutor, MongoClientSettings s // internal methods internal void ConfigureAutoEncryptionMessageEncoderSettings(MessageEncoderSettings messageEncoderSettings) { - ThrowIfDisposed(); - var autoEncryptionOptions = _settings.AutoEncryptionOptions; if (autoEncryptionOptions != null) { @@ -133,33 +135,33 @@ internal void ConfigureAutoEncryptionMessageEncoderSettings(MessageEncoderSettin // public methods /// public ClientBulkWriteResult BulkWrite(IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) - => UsingImplicitSession(session => BulkWrite(session, models, options, cancellationToken), cancellationToken); + => OperationExecutor.ExecuteWriteOperation( + CreateClientBulkWriteOperation(models, options), + _writeOperationOptions, + cancellationToken: cancellationToken); /// public ClientBulkWriteResult BulkWrite(IClientSessionHandle session, IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) - { - var operation = CreateClientBulkWriteOperation(models, options); - return ExecuteWriteOperation(session, operation, cancellationToken); - } + => OperationExecutor.ExecuteWriteOperation( + CreateClientBulkWriteOperation(models, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// public Task BulkWriteAsync(IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) - => UsingImplicitSession(session => BulkWriteAsync(session, models, options, cancellationToken), cancellationToken); + => OperationExecutor.ExecuteWriteOperationAsync( + CreateClientBulkWriteOperation(models, options), + _writeOperationOptions, + cancellationToken: cancellationToken); /// public Task BulkWriteAsync(IClientSessionHandle session, IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) - { - var operation = CreateClientBulkWriteOperation(models, options); - return ExecuteWriteOperationAsync(session, operation, cancellationToken); - } - - /// - public void DropDatabase(string name, CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - UsingImplicitSession(session => DropDatabase(session, name, cancellationToken), cancellationToken); - } + => OperationExecutor.ExecuteWriteOperationAsync( + CreateClientBulkWriteOperation(models, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// public void Dispose() @@ -191,40 +193,34 @@ public void Dispose(bool disposing) } /// - public void DropDatabase(IClientSessionHandle session, string name, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - ThrowIfDisposed(); - - var messageEncoderSettings = GetMessageEncoderSettings(); - var operation = new DropDatabaseOperation(new DatabaseNamespace(name), messageEncoderSettings) - { - WriteConcern = _settings.WriteConcern - }; - ExecuteWriteOperation(session, operation, cancellationToken); - } + public void DropDatabase(string name, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateDropDatabaseOperation(name), + _writeOperationOptions, + cancellationToken: cancellationToken); /// - public Task DropDatabaseAsync(string name, CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => DropDatabaseAsync(session, name, cancellationToken), cancellationToken); - } + public void DropDatabase(IClientSessionHandle session, string name, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateDropDatabaseOperation(name), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// - public Task DropDatabaseAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); + public Task DropDatabaseAsync(string name, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateDropDatabaseOperation(name), + _writeOperationOptions, + cancellationToken: cancellationToken); - Ensure.IsNotNull(session, nameof(session)); - var messageEncoderSettings = GetMessageEncoderSettings(); - var operation = new DropDatabaseOperation(new DatabaseNamespace(name), messageEncoderSettings) - { - WriteConcern = _settings.WriteConcern - }; - return ExecuteWriteOperationAsync(session, operation, cancellationToken); - } + /// + public Task DropDatabaseAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateDropDatabaseOperation(name), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// public IMongoDatabase GetDatabase(string name, MongoDatabaseSettings settings = null) @@ -241,205 +237,143 @@ public IMongoDatabase GetDatabase(string name, MongoDatabaseSettings settings = } /// - public IAsyncCursor ListDatabaseNames( - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabaseNames(options: null, cancellationToken); - } + public IAsyncCursor ListDatabaseNames(CancellationToken cancellationToken = default) + => ListDatabaseNames(options: null, cancellationToken); /// public IAsyncCursor ListDatabaseNames( ListDatabaseNamesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - ThrowIfDisposed(); - - return UsingImplicitSession(session => ListDatabaseNames(session, options, cancellationToken), cancellationToken); + var listDatabasesOptions = CreateListDatabasesOptionsFromListDatabaseNamesOptions(options); + var databases = ListDatabases(listDatabasesOptions, cancellationToken); + return CreateDatabaseNamesCursor(databases); } /// public IAsyncCursor ListDatabaseNames( IClientSessionHandle session, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabaseNames(session, options: null, cancellationToken); - } + CancellationToken cancellationToken = default) + => ListDatabaseNames(session, options: null, cancellationToken); /// public IAsyncCursor ListDatabaseNames( IClientSessionHandle session, ListDatabaseNamesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - ThrowIfDisposed(); - var listDatabasesOptions = CreateListDatabasesOptionsFromListDatabaseNamesOptions(options); var databases = ListDatabases(session, listDatabasesOptions, cancellationToken); - return CreateDatabaseNamesCursor(databases); } /// - public Task> ListDatabaseNamesAsync( - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabaseNamesAsync(options: null, cancellationToken); - } + public Task> ListDatabaseNamesAsync(CancellationToken cancellationToken = default) + => ListDatabaseNamesAsync(options: null, cancellationToken); /// - public Task> ListDatabaseNamesAsync( + public async Task> ListDatabaseNamesAsync( ListDatabaseNamesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => ListDatabaseNamesAsync(session, options, cancellationToken), cancellationToken); + var listDatabasesOptions = CreateListDatabasesOptionsFromListDatabaseNamesOptions(options); + var databases = await ListDatabasesAsync(listDatabasesOptions, cancellationToken).ConfigureAwait(false); + return CreateDatabaseNamesCursor(databases); } /// public Task> ListDatabaseNamesAsync( IClientSessionHandle session, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabaseNamesAsync(session, options: null, cancellationToken); - } + CancellationToken cancellationToken = default) + => ListDatabaseNamesAsync(session, options: null, cancellationToken); /// public async Task> ListDatabaseNamesAsync( IClientSessionHandle session, ListDatabaseNamesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - ThrowIfDisposed(); - var listDatabasesOptions = CreateListDatabasesOptionsFromListDatabaseNamesOptions(options); var databases = await ListDatabasesAsync(session, listDatabasesOptions, cancellationToken).ConfigureAwait(false); - return CreateDatabaseNamesCursor(databases); } /// - public IAsyncCursor ListDatabases( - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return UsingImplicitSession(session => ListDatabases(session, cancellationToken), cancellationToken); - } + public IAsyncCursor ListDatabases(CancellationToken cancellationToken) + => OperationExecutor.ExecuteReadOperation( + CreateListDatabaseOperation(null), + _readOperationOptions, + cancellationToken: cancellationToken); /// public IAsyncCursor ListDatabases( ListDatabasesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return UsingImplicitSession(session => ListDatabases(session, options, cancellationToken), cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateListDatabaseOperation(options), + _readOperationOptions, + cancellationToken: cancellationToken); /// public IAsyncCursor ListDatabases( IClientSessionHandle session, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabases(session, null, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateListDatabaseOperation(null), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// public IAsyncCursor ListDatabases( IClientSessionHandle session, ListDatabasesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - Ensure.IsNotNull(session, nameof(session)); - options = options ?? new ListDatabasesOptions(); - var messageEncoderSettings = GetMessageEncoderSettings(); - var translationOptions = _settings.TranslationOptions; - var operation = CreateListDatabaseOperation(options, messageEncoderSettings, translationOptions); - return ExecuteReadOperation(session, operation, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateListDatabaseOperation(options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// - public Task> ListDatabasesAsync( - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => ListDatabasesAsync(session, null, cancellationToken), cancellationToken); - } + public Task> ListDatabasesAsync(CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateListDatabaseOperation(null), + _readOperationOptions, + cancellationToken: cancellationToken); /// public Task> ListDatabasesAsync( ListDatabasesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => ListDatabasesAsync(session, options, cancellationToken), cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateListDatabaseOperation(options), + _readOperationOptions, + cancellationToken: cancellationToken); /// public Task> ListDatabasesAsync( IClientSessionHandle session, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabasesAsync(session, null, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateListDatabaseOperation(null), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// public Task> ListDatabasesAsync( IClientSessionHandle session, ListDatabasesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - ThrowIfDisposed(); - - options = options ?? new ListDatabasesOptions(); - var messageEncoderSettings = GetMessageEncoderSettings(); - var translationOptions = _settings.TranslationOptions; - var operation = CreateListDatabaseOperation(options, messageEncoderSettings, translationOptions); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } - - /// - /// Starts an implicit session. - /// - /// A session. - internal IClientSessionHandle StartImplicitSession(CancellationToken cancellationToken) - { - ThrowIfDisposed(); - - return StartImplicitSession(); - } - - /// - /// Starts an implicit session. - /// - /// A Task whose result is a session. - internal Task StartImplicitSessionAsync(CancellationToken cancellationToken) - { - ThrowIfDisposed(); - - return Task.FromResult(StartImplicitSession()); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateListDatabaseOperation(options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// - public IClientSessionHandle StartSession(ClientSessionOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public IClientSessionHandle StartSession(ClientSessionOptions options = null, CancellationToken cancellationToken = default) { ThrowIfDisposed(); @@ -447,7 +381,7 @@ internal Task StartImplicitSessionAsync(CancellationToken } /// - public Task StartSessionAsync(ClientSessionOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task StartSessionAsync(ClientSessionOptions options = null, CancellationToken cancellationToken = default) { ThrowIfDisposed(); @@ -458,56 +392,45 @@ internal Task StartImplicitSessionAsync(CancellationToken public IChangeStreamCursor Watch( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return UsingImplicitSession(session => Watch(session, pipeline, options, cancellationToken), cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + cancellationToken: cancellationToken); /// public IChangeStreamCursor Watch( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(pipeline, nameof(pipeline)); - ThrowIfDisposed(); - - var translationOptions = _settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); - return ExecuteReadOperation(session, operation, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// public Task> WatchAsync( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => WatchAsync(session, pipeline, options, cancellationToken), cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + cancellationToken: cancellationToken); /// public Task> WatchAsync( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(pipeline, nameof(pipeline)); - - ThrowIfDisposed(); - - var translationOptions = _settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); /// public IMongoClient WithReadConcern(ReadConcern readConcern) @@ -546,7 +469,7 @@ public IMongoClient WithWriteConcern(WriteConcern writeConcern) } // private methods - private ClientBulkWriteOperation CreateClientBulkWriteOperation(IReadOnlyList models, ClientBulkWriteOptions options = null) + private ClientBulkWriteOperation CreateClientBulkWriteOperation(IReadOnlyList models, ClientBulkWriteOptions options) { if (_settings.AutoEncryptionOptions != null) { @@ -578,17 +501,22 @@ private ClientBulkWriteOperation CreateClientBulkWriteOperation(IReadOnlyList CreateDatabaseNamesCursor(IAsyncCursor cursor) - { - return new BatchTransformingAsyncCursor( + => new BatchTransformingAsyncCursor( cursor, databases => databases.Select(database => database["name"].AsString)); - } - private ListDatabasesOperation CreateListDatabaseOperation( - ListDatabasesOptions options, - MessageEncoderSettings messageEncoderSettings, - ExpressionTranslationOptions translationOptions) + private DropDatabaseOperation CreateDropDatabaseOperation(string name) + => new(new DatabaseNamespace(name), GetMessageEncoderSettings()) + { + WriteConcern = _settings.WriteConcern + }; + + private ListDatabasesOperation CreateListDatabaseOperation(ListDatabasesOptions options) { + options ??= new ListDatabasesOptions(); + var messageEncoderSettings = GetMessageEncoderSettings(); + var translationOptions = _settings.TranslationOptions; + return new ListDatabasesOperation(messageEncoderSettings) { AuthorizedDatabases = options.AuthorizedDatabases, @@ -612,69 +540,16 @@ private ListDatabasesOptions CreateListDatabasesOptionsFromListDatabaseNamesOpti return listDatabasesOptions; } - private IReadBindingHandle CreateReadBinding(IClientSessionHandle session) - { - var readPreference = _settings.ReadPreference; - if (session.IsInTransaction && readPreference.ReadPreferenceMode != ReadPreferenceMode.Primary) - { - throw new InvalidOperationException("Read preference in a transaction must be primary."); - } - - var binding = new ReadPreferenceBinding(_cluster, readPreference, session.WrappedCoreSession.Fork()); - return new ReadBindingHandle(binding); - } - - private IReadWriteBindingHandle CreateReadWriteBinding(IClientSessionHandle session) - { - var binding = new WritableServerBinding(_cluster, session.WrappedCoreSession.Fork()); - return new ReadWriteBindingHandle(binding); - } - private ChangeStreamOperation CreateChangeStreamOperation( PipelineDefinition, TResult> pipeline, - ChangeStreamOptions options, - ExpressionTranslationOptions translationOptions) - { - return ChangeStreamHelper.CreateChangeStreamOperation( - pipeline, + ChangeStreamOptions options) + => ChangeStreamHelper.CreateChangeStreamOperation( + Ensure.IsNotNull(pipeline, nameof(pipeline)), options, _settings.ReadConcern, GetMessageEncoderSettings(), _settings.RetryReads, - translationOptions); - } - - private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadBinding(session)) - { - return _operationExecutor.ExecuteReadOperation(binding, operation, cancellationToken); - } - } - - private async Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadBinding(session)) - { - return await _operationExecutor.ExecuteReadOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } - - private TResult ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadWriteBinding(session)) - { - return _operationExecutor.ExecuteWriteOperation(binding, operation, cancellationToken); - } - } - - private async Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadWriteBinding(session)) - { - return await _operationExecutor.ExecuteWriteOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } + _settings.TranslationOptions); private MessageEncoderSettings GetMessageEncoderSettings() { @@ -696,13 +571,6 @@ private RenderArgs GetRenderArgs() return new RenderArgs(BsonDocumentSerializer.Instance, serializerRegistry, translationOptions: translationOptions); } - private IClientSessionHandle StartImplicitSession() - { - var options = new ClientSessionOptions { CausalConsistency = false, Snapshot = false }; - ICoreSessionHandle coreSession = _cluster.StartSession(options.ToCore(isImplicit: true)); - return new ClientSessionHandle(this, options, coreSession); - } - private IClientSessionHandle StartSession(ClientSessionOptions options) { if (options != null && options.Snapshot && options.CausalConsistency == true) @@ -718,37 +586,5 @@ private IClientSessionHandle StartSession(ClientSessionOptions options) private void ThrowIfDisposed() => ThrowIfDisposed(string.Empty); private T ThrowIfDisposed(T value) => _disposed ? throw new ObjectDisposedException(GetType().Name) : value; - - private void UsingImplicitSession(Action func, CancellationToken cancellationToken) - { - using (var session = StartImplicitSession(cancellationToken)) - { - func(session); - } - } - - private TResult UsingImplicitSession(Func func, CancellationToken cancellationToken) - { - using (var session = StartImplicitSession(cancellationToken)) - { - return func(session); - } - } - - private async Task UsingImplicitSessionAsync(Func funcAsync, CancellationToken cancellationToken) - { - using (var session = await StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - await funcAsync(session).ConfigureAwait(false); - } - } - - private async Task UsingImplicitSessionAsync(Func> funcAsync, CancellationToken cancellationToken) - { - using (var session = await StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - return await funcAsync(session).ConfigureAwait(false); - } - } } } diff --git a/src/MongoDB.Driver/MongoCollectionImpl.cs b/src/MongoDB.Driver/MongoCollectionImpl.cs index 51f03dc0a04..47d61cfc812 100644 --- a/src/MongoDB.Driver/MongoCollectionImpl.cs +++ b/src/MongoDB.Driver/MongoCollectionImpl.cs @@ -21,8 +21,6 @@ using MongoDB.Bson; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; -using MongoDB.Driver.Core; -using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Clusters; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.Operations; @@ -41,6 +39,8 @@ internal sealed class MongoCollectionImpl : MongoCollectionBase _documentSerializer; private readonly MongoCollectionSettings _settings; + private readonly ReadOperationOptions _readOperationOptions; + private readonly WriteOperationOptions _writeOperationOptions; // constructors public MongoCollectionImpl(IMongoDatabase database, CollectionNamespace collectionNamespace, MongoCollectionSettings settings, IClusterInternal cluster, IOperationExecutor operationExecutor) @@ -58,6 +58,8 @@ private MongoCollectionImpl(IMongoDatabase database, CollectionNamespace collect _documentSerializer = Ensure.IsNotNull(documentSerializer, nameof(documentSerializer)); _messageEncoderSettings = GetMessageEncoderSettings(); + _readOperationOptions = new ReadOperationOptions(DefaultReadPreference: _settings.ReadPreference); + _writeOperationOptions = new WriteOperationOptions(); } // properties @@ -83,6 +85,8 @@ public override IMongoIndexManager Indexes get { return new MongoIndexManager(this); } } + private IOperationExecutor OperationExecutor => _operationExecutor; + public override IMongoSearchIndexManager SearchIndexes { get { return new MongoSearchIndexManager(this); } @@ -94,160 +98,117 @@ public override MongoCollectionSettings Settings } // public methods - public override IAsyncCursor Aggregate(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor Aggregate(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { return UsingImplicitSession(session => Aggregate(session, pipeline, options, cancellationToken), cancellationToken); } - public override IAsyncCursor Aggregate(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor Aggregate(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new AggregateOptions(); + options ??= new AggregateOptions(); var renderArgs = GetRenderArgs(options.TranslationOptions); - var renderedPipeline = pipeline.Render(renderArgs); - - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage != null && (lastStageName == "$out" || lastStageName == "$merge")) + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out bool isAggregateToCollection); + if (isAggregateToCollection) { var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - ExecuteWriteOperation(session, aggregateOperation, cancellationToken); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateAggregateToCollectionFindOperation(lastStage, renderedPipeline.OutputSerializer, options); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return deferredCursor; + OperationExecutor.ExecuteWriteOperation(aggregateOperation, _writeOperationOptions, session, cancellationToken); + return CreateAggregateToCollectionResultCursor(session, renderedPipeline, options); } else { var aggregateOperation = CreateAggregateOperation(renderedPipeline, options); - return ExecuteReadOperation(session, aggregateOperation, cancellationToken); + return OperationExecutor.ExecuteReadOperation(aggregateOperation, _readOperationOptions, session, cancellationToken); } } - public override Task> AggregateAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> AggregateAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { return UsingImplicitSessionAsync(session => AggregateAsync(session, pipeline, options, cancellationToken), cancellationToken); } - public override async Task> AggregateAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> AggregateAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new AggregateOptions(); + options ??= new AggregateOptions(); var renderArgs = GetRenderArgs(options.TranslationOptions); - var renderedPipeline = pipeline.Render(renderArgs); - - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage != null && (lastStageName == "$out" || lastStageName == "$merge")) + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out bool isAggregateToCollection); + if (isAggregateToCollection) { var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - await ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateAggregateToCollectionFindOperation(lastStage, renderedPipeline.OutputSerializer, options); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return await Task.FromResult>(deferredCursor).ConfigureAwait(false); + await OperationExecutor.ExecuteWriteOperationAsync(aggregateOperation, _writeOperationOptions, session, cancellationToken).ConfigureAwait(false); + return CreateAggregateToCollectionResultCursor(session, renderedPipeline, options); } else { var aggregateOperation = CreateAggregateOperation(renderedPipeline, options); - return await ExecuteReadOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); + return await OperationExecutor.ExecuteReadOperationAsync(aggregateOperation, _readOperationOptions, session, cancellationToken).ConfigureAwait(false); } } - public override void AggregateToCollection(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override void AggregateToCollection(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { UsingImplicitSession(session => AggregateToCollection(session, pipeline, options, cancellationToken), cancellationToken); } - public override void AggregateToCollection(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override void AggregateToCollection(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new AggregateOptions(); + options ??= new AggregateOptions(); var renderArgs = GetRenderArgs(options.TranslationOptions); - var renderedPipeline = pipeline.Render(renderArgs); - - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage == null || (lastStageName != "$out" && lastStageName != "$merge")) + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out bool isAggregateToCollection); + if (renderedPipeline.Documents.Count == 0 || !isAggregateToCollection) { throw new InvalidOperationException("AggregateToCollection requires that the last stage be $out or $merge."); } var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - ExecuteWriteOperation(session, aggregateOperation, cancellationToken); + OperationExecutor.ExecuteWriteOperation(aggregateOperation, _writeOperationOptions, session, cancellationToken); } - public override Task AggregateToCollectionAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task AggregateToCollectionAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { return UsingImplicitSessionAsync(session => AggregateToCollectionAsync(session, pipeline, options, cancellationToken), cancellationToken); } - public override async Task AggregateToCollectionAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task AggregateToCollectionAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new AggregateOptions(); + options ??= new AggregateOptions(); var renderArgs = GetRenderArgs(options.TranslationOptions); - var renderedPipeline = pipeline.Render(renderArgs); - - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage == null || (lastStageName != "$out" && lastStageName != "$merge")) + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out bool isAggregateToCollection); + if (renderedPipeline.Documents.Count == 0 || !isAggregateToCollection) { throw new InvalidOperationException("AggregateToCollectionAsync requires that the last stage be $out or $merge."); } var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - await ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); + await OperationExecutor.ExecuteWriteOperationAsync(aggregateOperation, _writeOperationOptions, session, cancellationToken).ConfigureAwait(false); } - public override BulkWriteResult BulkWrite(IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override BulkWriteResult BulkWrite(IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => BulkWrite(session, requests, options, cancellationToken), cancellationToken); + using var session = OperationExecutor.StartImplicitSession(cancellationToken); + return BulkWrite(session, requests, options, cancellationToken); } - public override BulkWriteResult BulkWrite(IClientSessionHandle session, IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override BulkWriteResult BulkWrite(IClientSessionHandle session, IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull((object)requests, nameof(requests)); - var requestsArray = requests.ToArray(); - if (requestsArray.Length == 0) - { - throw new ArgumentException("Must contain at least 1 request.", nameof(requests)); - } - - foreach (var request in requestsArray) - { - request.ThrowIfNotValid(); - } - - options = options ?? new BulkWriteOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateBulkWriteOperation(session, requestsArray, options, renderArgs); + var operation = CreateBulkWriteOperation(session, requestsArray, options); try { - var result = ExecuteWriteOperation(session, operation, cancellationToken); + var result = OperationExecutor.ExecuteWriteOperation(operation, _writeOperationOptions, session, cancellationToken); return BulkWriteResult.FromCore(result, requestsArray); } catch (MongoBulkWriteOperationException ex) @@ -256,34 +217,21 @@ public override MongoCollectionSettings Settings } } - public override Task> BulkWriteAsync(IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> BulkWriteAsync(IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => BulkWriteAsync(session, requests, options, cancellationToken), cancellationToken); + using var session = await OperationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false); + return await BulkWriteAsync(session, requests, options, cancellationToken).ConfigureAwait(false); } - public override async Task> BulkWriteAsync(IClientSessionHandle session, IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> BulkWriteAsync(IClientSessionHandle session, IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull((object)requests, nameof(requests)); - var requestsArray = requests.ToArray(); - if (requestsArray.Length == 0) - { - throw new ArgumentException("Must contain at least 1 request.", nameof(requests)); - } - foreach (var request in requestsArray) - { - request.ThrowIfNotValid(); - } - - options = options ?? new BulkWriteOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateBulkWriteOperation(session, requestsArray, options, renderArgs); + var operation = CreateBulkWriteOperation(session, requestsArray, options); try { - var result = await ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); + var result = await OperationExecutor.ExecuteWriteOperationAsync(operation, _writeOperationOptions, session, cancellationToken).ConfigureAwait(false); return BulkWriteResult.FromCore(result, requestsArray); } catch (MongoBulkWriteOperationException ex) @@ -293,314 +241,242 @@ public override MongoCollectionSettings Settings } [Obsolete("Use CountDocuments or EstimatedDocumentCount instead.")] - public override long Count(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => Count(session, filter, options, cancellationToken), cancellationToken); - } + public override long Count(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateCountOperation(filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); [Obsolete("Use CountDocuments or EstimatedDocumentCount instead.")] - public override long Count(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new CountOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateCountOperation(filter, options, renderArgs); - return ExecuteReadOperation(session, operation, cancellationToken); - } + public override long Count(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateCountOperation(filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); [Obsolete("Use CountDocumentsAsync or EstimatedDocumentCountAsync instead.")] - public override Task CountAsync(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => CountAsync(session, filter, options, cancellationToken), cancellationToken); - } + public override Task CountAsync(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateCountOperation(filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); [Obsolete("Use CountDocumentsAsync or EstimatedDocumentCountAsync instead.")] - public override Task CountAsync(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new CountOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateCountOperation(filter, options, renderArgs); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } - - public override long CountDocuments(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => CountDocuments(session, filter, options, cancellationToken), cancellationToken); - } - - public override long CountDocuments(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new CountOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateCountDocumentsOperation(filter, options, renderArgs); - return ExecuteReadOperation(session, operation, cancellationToken); - } - - public override Task CountDocumentsAsync(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => CountDocumentsAsync(session, filter, options, cancellationToken), cancellationToken); - } - - public override Task CountDocumentsAsync(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new CountOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateCountDocumentsOperation(filter, options, renderArgs); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } - - public override IAsyncCursor Distinct(FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => Distinct(session, field, filter, options, cancellationToken), cancellationToken); - } - - public override IAsyncCursor Distinct(IClientSessionHandle session, FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(field, nameof(field)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new DistinctOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateDistinctOperation(field, filter, options, renderArgs); - return ExecuteReadOperation(session, operation, cancellationToken); - } - - public override Task> DistinctAsync(FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => DistinctAsync(session, field, filter, options, cancellationToken), cancellationToken); - } - - public override Task> DistinctAsync(IClientSessionHandle session, FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(field, nameof(field)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new DistinctOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateDistinctOperation(field, filter, options, renderArgs); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } - - public override IAsyncCursor DistinctMany(FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => DistinctMany(session, field, filter, options, cancellationToken), cancellationToken); - } - - public override IAsyncCursor DistinctMany(IClientSessionHandle session, FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(field, nameof(field)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new DistinctOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateDistinctManyOperation(field, filter, options, renderArgs); - return ExecuteReadOperation(session, operation, cancellationToken); - } - - public override Task> DistinctManyAsync(FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => DistinctManyAsync(session, field, filter, options, cancellationToken), cancellationToken); - } - - public override Task> DistinctManyAsync(IClientSessionHandle session, FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(field, nameof(field)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new DistinctOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateDistinctManyOperation(field, filter, options, renderArgs); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } - - public override long EstimatedDocumentCount(EstimatedDocumentCountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => - { - var operation = CreateEstimatedDocumentCountOperation(options); - return ExecuteReadOperation(session, operation, cancellationToken); - }); - } - - public override Task EstimatedDocumentCountAsync(EstimatedDocumentCountOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => - { - var operation = CreateEstimatedDocumentCountOperation(options); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - }); - } - - public override IAsyncCursor FindSync(FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => FindSync(session, filter, options, cancellationToken), cancellationToken); - } - - public override IAsyncCursor FindSync(IClientSessionHandle session, FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new FindOptions(); - - var renderArgs = GetRenderArgs(options.TranslationOptions); - var operation = CreateFindOperation(filter, options, renderArgs); - return ExecuteReadOperation(session, operation, cancellationToken); - } - - public override Task> FindAsync(FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => FindAsync(session, filter, options, cancellationToken), cancellationToken); - } - - public override Task> FindAsync(IClientSessionHandle session, FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new FindOptions(); - - var renderArgs = GetRenderArgs(options.TranslationOptions); - var operation = CreateFindOperation(filter, options, renderArgs); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } - - public override TProjection FindOneAndDelete(FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => FindOneAndDelete(session, filter, options, cancellationToken), cancellationToken); - } - - public override TProjection FindOneAndDelete(IClientSessionHandle session, FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new FindOneAndDeleteOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndDeleteOperation(filter, options, renderArgs); - return ExecuteWriteOperation(session, operation, cancellationToken); - } - - public override Task FindOneAndDeleteAsync(FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => FindOneAndDeleteAsync(session, filter, options, cancellationToken), cancellationToken); - } - - public override Task FindOneAndDeleteAsync(IClientSessionHandle session, FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new FindOneAndDeleteOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndDeleteOperation(filter, options, renderArgs); - return ExecuteWriteOperationAsync(session, operation, cancellationToken); - } - - public override TProjection FindOneAndReplace(FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => FindOneAndReplace(session, filter, replacement, options, cancellationToken), cancellationToken); - } - - public override TProjection FindOneAndReplace(IClientSessionHandle session, FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - var replacementObject = Ensure.IsNotNull((object)replacement, nameof(replacement)); // only box once if it's a struct - options = options ?? new FindOneAndReplaceOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndReplaceOperation(filter, replacementObject, options, renderArgs); - return ExecuteWriteOperation(session, operation, cancellationToken); - } - - public override Task FindOneAndReplaceAsync(FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => FindOneAndReplaceAsync(session, filter, replacement, options, cancellationToken), cancellationToken); - } - - public override Task FindOneAndReplaceAsync(IClientSessionHandle session, FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - var replacementObject = Ensure.IsNotNull((object)replacement, nameof(replacement)); // only box once if it's a struct - options = options ?? new FindOneAndReplaceOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndReplaceOperation(filter, replacementObject, options, renderArgs); - return ExecuteWriteOperationAsync(session, operation, cancellationToken); - } - - public override TProjection FindOneAndUpdate(FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => FindOneAndUpdate(session, filter, update, options, cancellationToken), cancellationToken); - } - - public override TProjection FindOneAndUpdate(IClientSessionHandle session, FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - Ensure.IsNotNull(update, nameof(update)); - options = options ?? new FindOneAndUpdateOptions(); - - if (update is PipelineUpdateDefinition && (options.ArrayFilters != null && options.ArrayFilters.Any())) - { - throw new NotSupportedException("An arrayfilter is not supported in the pipeline-style update."); - } - - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndUpdateOperation(filter, update, options, renderArgs); - return ExecuteWriteOperation(session, operation, cancellationToken); - } - - public override Task FindOneAndUpdateAsync(FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => FindOneAndUpdateAsync(session, filter, update, options, cancellationToken), cancellationToken); - } - - public override Task FindOneAndUpdateAsync(IClientSessionHandle session, FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(filter, nameof(filter)); - Ensure.IsNotNull(update, nameof(update)); - options = options ?? new FindOneAndUpdateOptions(); - - if (update is PipelineUpdateDefinition && (options.ArrayFilters != null && options.ArrayFilters.Any())) - { - throw new NotSupportedException("An arrayfilter is not supported in the pipeline-style update."); - } - - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndUpdateOperation(filter, update, options, renderArgs); - return ExecuteWriteOperationAsync(session, operation, cancellationToken); - } + public override Task CountAsync(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateCountOperation(filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override long CountDocuments(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateCountDocumentsOperation(filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override long CountDocuments(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateCountDocumentsOperation(filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override Task CountDocumentsAsync(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateCountDocumentsOperation(filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override Task CountDocumentsAsync(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateCountDocumentsOperation(filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override IAsyncCursor Distinct(FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateDistinctOperation(field, filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override IAsyncCursor Distinct(IClientSessionHandle session, FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateDistinctOperation(field, filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override Task> DistinctAsync(FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateDistinctOperation(field, filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override Task> DistinctAsync(IClientSessionHandle session, FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateDistinctOperation(field, filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override IAsyncCursor DistinctMany(FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateDistinctManyOperation(field, filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override IAsyncCursor DistinctMany(IClientSessionHandle session, FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateDistinctManyOperation(field, filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override Task> DistinctManyAsync(FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateDistinctManyOperation(field, filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override Task> DistinctManyAsync(IClientSessionHandle session, FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateDistinctManyOperation(field, filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override long EstimatedDocumentCount(EstimatedDocumentCountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateEstimatedDocumentCountOperation(options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override Task EstimatedDocumentCountAsync(EstimatedDocumentCountOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateEstimatedDocumentCountOperation(options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override IAsyncCursor FindSync(FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateFindOperation(filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override IAsyncCursor FindSync(IClientSessionHandle session, FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateFindOperation(filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override Task> FindAsync(FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateFindOperation(filter, options), + _readOperationOptions, + cancellationToken: cancellationToken); + + public override Task> FindAsync(IClientSessionHandle session, FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateFindOperation(filter, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override TProjection FindOneAndDelete(FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateFindOneAndDeleteOperation(filter, options), + _writeOperationOptions, + cancellationToken: cancellationToken); + + public override TProjection FindOneAndDelete(IClientSessionHandle session, FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateFindOneAndDeleteOperation(filter, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override Task FindOneAndDeleteAsync(FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateFindOneAndDeleteOperation(filter, options), + _writeOperationOptions, + cancellationToken: cancellationToken); + + public override Task FindOneAndDeleteAsync(IClientSessionHandle session, FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateFindOneAndDeleteOperation(filter, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override TProjection FindOneAndReplace(FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateFindOneAndReplaceOperation(filter, replacement, options), + _writeOperationOptions, + cancellationToken: cancellationToken); + + public override TProjection FindOneAndReplace(IClientSessionHandle session, FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateFindOneAndReplaceOperation(filter, replacement, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override Task FindOneAndReplaceAsync(FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateFindOneAndReplaceOperation(filter, replacement, options), + _writeOperationOptions, + cancellationToken: cancellationToken); + + public override Task FindOneAndReplaceAsync(IClientSessionHandle session, FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateFindOneAndReplaceOperation(filter, replacement, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override TProjection FindOneAndUpdate(FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateFindOneAndUpdateOperation(filter, update, options), + _writeOperationOptions, + cancellationToken: cancellationToken); + + public override TProjection FindOneAndUpdate(IClientSessionHandle session, FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateFindOneAndUpdateOperation(filter, update, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public override Task FindOneAndUpdateAsync(FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateFindOneAndUpdateOperation(filter, update, options), + _writeOperationOptions, + cancellationToken: cancellationToken); + + public override Task FindOneAndUpdateAsync(IClientSessionHandle session, FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateFindOneAndUpdateOperation(filter, update, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); [Obsolete("Use Aggregation pipeline instead.")] - public override IAsyncCursor MapReduce(BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor MapReduce(BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default) { return UsingImplicitSession(session => MapReduce(session, map, reduce, options, cancellationToken), cancellationToken); } [Obsolete("Use Aggregation pipeline instead.")] - public override IAsyncCursor MapReduce(IClientSessionHandle session, BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor MapReduce(IClientSessionHandle session, BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(map, nameof(map)); Ensure.IsNotNull(reduce, nameof(reduce)); - options = options ?? new MapReduceOptions(); + options ??= new MapReduceOptions(); var outputOptions = options.OutputOptions ?? MapReduceOutputOptions.Inline; var resultSerializer = ResolveResultSerializer(options.ResultSerializer); @@ -609,38 +485,29 @@ public override MongoCollectionSettings Settings if (outputOptions == MapReduceOutputOptions.Inline) { var operation = CreateMapReduceOperation(map, reduce, options, resultSerializer, renderArgs); - return ExecuteReadOperation(session, operation, cancellationToken); + return OperationExecutor.ExecuteReadOperation(operation, _readOperationOptions, session, cancellationToken); } else { var mapReduceOperation = CreateMapReduceOutputToCollectionOperation(map, reduce, options, outputOptions, renderArgs); - ExecuteWriteOperation(session, mapReduceOperation, cancellationToken); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateMapReduceOutputToCollectionFindOperation(options, mapReduceOperation.OutputCollectionNamespace, resultSerializer); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return deferredCursor; + OperationExecutor.ExecuteWriteOperation(mapReduceOperation, _writeOperationOptions, session, cancellationToken); + return CreateMapReduceOutputToCollectionResultCursor(session, options, mapReduceOperation.OutputCollectionNamespace, resultSerializer); } } [Obsolete("Use Aggregation pipeline instead.")] - public override Task> MapReduceAsync(BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> MapReduceAsync(BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default) { return UsingImplicitSessionAsync(session => MapReduceAsync(session, map, reduce, options, cancellationToken), cancellationToken); } [Obsolete("Use Aggregation pipeline instead.")] - public override async Task> MapReduceAsync(IClientSessionHandle session, BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> MapReduceAsync(IClientSessionHandle session, BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(map, nameof(map)); Ensure.IsNotNull(reduce, nameof(reduce)); - options = options ?? new MapReduceOptions(); + options ??= new MapReduceOptions(); var outputOptions = options.OutputOptions ?? MapReduceOutputOptions.Inline; var resultSerializer = ResolveResultSerializer(options.ResultSerializer); @@ -649,22 +516,13 @@ public override MongoCollectionSettings Settings if (outputOptions == MapReduceOutputOptions.Inline) { var operation = CreateMapReduceOperation(map, reduce, options, resultSerializer, renderArgs); - return await ExecuteReadOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); + return await OperationExecutor.ExecuteReadOperationAsync(operation, _readOperationOptions, session, cancellationToken).ConfigureAwait(false); } else { var mapReduceOperation = CreateMapReduceOutputToCollectionOperation(map, reduce, options, outputOptions, renderArgs); - await ExecuteWriteOperationAsync(session, mapReduceOperation, cancellationToken).ConfigureAwait(false); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateMapReduceOutputToCollectionFindOperation(options, mapReduceOperation.OutputCollectionNamespace, resultSerializer); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return await Task.FromResult(deferredCursor).ConfigureAwait(false); + await OperationExecutor.ExecuteWriteOperationAsync(mapReduceOperation, _writeOperationOptions, session, cancellationToken).ConfigureAwait(false); + return CreateMapReduceOutputToCollectionResultCursor(session, options, mapReduceOperation.OutputCollectionNamespace, resultSerializer); } } @@ -685,44 +543,42 @@ public override IFilteredMongoCollection OfType Watch( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => Watch(session, pipeline, options, cancellationToken), cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + cancellationToken: cancellationToken); public override IChangeStreamCursor Watch( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(pipeline, nameof(pipeline)); - var translationOptions = _database.Client.Settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); - return ExecuteReadOperation(session, operation, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public override Task> WatchAsync( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => WatchAsync(session, pipeline, options, cancellationToken), cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + cancellationToken: cancellationToken); public override Task> WatchAsync( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(pipeline, nameof(pipeline)); - var translationOptions = _database.Client.Settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public override IMongoCollection WithReadConcern(ReadConcern readConcern) { @@ -850,72 +706,11 @@ private AggregateOperation CreateAggregateOperation(RenderedPi }; } - private FindOperation CreateAggregateToCollectionFindOperation(BsonDocument outStage, IBsonSerializer resultSerializer, AggregateOptions options) + private IAsyncCursor CreateAggregateToCollectionResultCursor(IClientSessionHandle session, RenderedPipelineDefinition pipeline, AggregateOptions options) { - CollectionNamespace outputCollectionNamespace; - var stageName = outStage.GetElement(0).Name; - switch (stageName) - { - case "$out": - { - var outValue = outStage[0]; - DatabaseNamespace outputDatabaseNamespace; - string outputCollectionName; - if (outValue.IsString) - { - outputDatabaseNamespace = _collectionNamespace.DatabaseNamespace; - outputCollectionName = outValue.AsString; - } - else - { - outputDatabaseNamespace = new DatabaseNamespace(outValue["db"].AsString); - outputCollectionName = outValue["coll"].AsString; - } - outputCollectionNamespace = new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); - } - break; - case "$merge": - { - var mergeArguments = outStage[0]; - DatabaseNamespace outputDatabaseNamespace; - string outputCollectionName; - if (mergeArguments.IsString) - { - outputDatabaseNamespace = _collectionNamespace.DatabaseNamespace; - outputCollectionName = mergeArguments.AsString; - } - else - { - var into = mergeArguments.AsBsonDocument["into"]; - if (into.IsString) - { - outputDatabaseNamespace = _collectionNamespace.DatabaseNamespace; - outputCollectionName = into.AsString; - } - else - { - if (into.AsBsonDocument.Contains("db")) - { - outputDatabaseNamespace = new DatabaseNamespace(into["db"].AsString); - } - else - { - outputDatabaseNamespace = _collectionNamespace.DatabaseNamespace; - } - outputCollectionName = into["coll"].AsString; - } - } - outputCollectionNamespace = new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); - } - break; - default: - throw new ArgumentException($"Unexpected stage name: {stageName}."); - } + var outputCollectionNamespace = AggregateHelper.GetOutCollection(pipeline.Documents.Last(), _collectionNamespace.DatabaseNamespace); - return new FindOperation( - outputCollectionNamespace, - resultSerializer, - _messageEncoderSettings) + var findOperation = new FindOperation(outputCollectionNamespace, pipeline.OutputSerializer, _messageEncoderSettings) { BatchSize = options.BatchSize, Collation = options.Collation, @@ -923,6 +718,16 @@ private FindOperation CreateAggregateToCollectionFindOperation ReadConcern = _settings.ReadConcern, RetryRequested = _database.Client.Settings.RetryReads }; + + // we want to delay execution of the find because the user may + // not want to iterate the results at all... + var forkedSession = session.Fork(); + var readOperationOptions = _readOperationOptions with { ExplicitReadPreference = ReadPreference.Primary }; + var deferredCursor = new DeferredAsyncCursor( + () => forkedSession.Dispose(), + ct => OperationExecutor.ExecuteReadOperation(findOperation, readOperationOptions, forkedSession, ct), + ct => OperationExecutor.ExecuteReadOperationAsync(findOperation, readOperationOptions, forkedSession, ct)); + return deferredCursor; } private AggregateToCollectionOperation CreateAggregateToCollectionOperation(RenderedPipelineDefinition renderedPipeline, AggregateOptions options) @@ -947,15 +752,28 @@ private AggregateToCollectionOperation CreateAggregateToCollectionOperation> requests, - BulkWriteOptions options, - RenderArgs renderArgs) + IReadOnlyList> requests, + BulkWriteOptions options) { + Ensure.IsNotNull((object)requests, nameof(requests)); + if (requests.Count == 0) + { + throw new ArgumentException("Must contain at least 1 request.", nameof(requests)); + } + + options ??= new BulkWriteOptions(); + var renderArgs = GetRenderArgs(); var effectiveWriteConcern = session.IsInTransaction ? WriteConcern.Acknowledged : _settings.WriteConcern; + var writeModels = requests.Select((model, index) => + { + model.ThrowIfNotValid(); + return ConvertWriteModelToWriteRequest(model, index, renderArgs); + }).ToArray(); + return new BulkMixedWriteOperation( _collectionNamespace, - requests.Select((model, index) => ConvertWriteModelToWriteRequest(model, index, renderArgs)), + writeModels, _messageEncoderSettings) { BypassDocumentValidation = options.BypassDocumentValidation, @@ -969,9 +787,11 @@ private BulkMixedWriteOperation CreateBulkWriteOperation( private ChangeStreamOperation CreateChangeStreamOperation( PipelineDefinition, TResult> pipeline, - ChangeStreamOptions options, - ExpressionTranslationOptions translationOptions) + ChangeStreamOptions options) { + Ensure.IsNotNull(pipeline, nameof(pipeline)); + var translationOptions = _database.Client.Settings.TranslationOptions; + return ChangeStreamHelper.CreateChangeStreamOperation( this, pipeline, @@ -984,9 +804,12 @@ private ChangeStreamOperation CreateChangeStreamOperation( private CountDocumentsOperation CreateCountDocumentsOperation( FilterDefinition filter, - CountOptions options, - RenderArgs renderArgs) + CountOptions options) { + Ensure.IsNotNull(filter, nameof(filter)); + options ??= new CountOptions(); + var renderArgs = GetRenderArgs(); + return new CountDocumentsOperation(_collectionNamespace, _messageEncoderSettings) { Collation = options.Collation, @@ -1003,9 +826,12 @@ private CountDocumentsOperation CreateCountDocumentsOperation( private CountOperation CreateCountOperation( FilterDefinition filter, - CountOptions options, - RenderArgs renderArgs) + CountOptions options) { + Ensure.IsNotNull(filter, nameof(filter)); + options ??= new CountOptions(); + var renderArgs = GetRenderArgs(); + return new CountOperation(_collectionNamespace, _messageEncoderSettings) { Collation = options.Collation, @@ -1023,9 +849,12 @@ private CountOperation CreateCountOperation( private DistinctOperation CreateDistinctOperation( FieldDefinition field, FilterDefinition filter, - DistinctOptions options, - RenderArgs renderArgs) + DistinctOptions options) { + Ensure.IsNotNull(field, nameof(field)); + Ensure.IsNotNull(filter, nameof(filter)); + options ??= new DistinctOptions(); + var renderArgs = GetRenderArgs(); var renderedField = field.Render(renderArgs); var valueSerializer = GetValueSerializerForDistinct(renderedField, _settings.SerializerRegistry); @@ -1047,9 +876,12 @@ private DistinctOperation CreateDistinctOperation( private DistinctOperation CreateDistinctManyOperation( FieldDefinition> field, FilterDefinition filter, - DistinctOptions options, - RenderArgs renderArgs) + DistinctOptions options) { + Ensure.IsNotNull(field, nameof(field)); + Ensure.IsNotNull(filter, nameof(filter)); + options ??= new DistinctOptions(); + var renderArgs = GetRenderArgs(); var renderedField = field.Render(renderArgs); var itemSerializer = GetItemSerializerForDistinctMany(renderedField, _settings.SerializerRegistry); @@ -1080,9 +912,11 @@ private EstimatedDocumentCountOperation CreateEstimatedDocumentCountOperation(Es private FindOneAndDeleteOperation CreateFindOneAndDeleteOperation( FilterDefinition filter, - FindOneAndDeleteOptions options, - RenderArgs renderArgs) + FindOneAndDeleteOptions options) { + Ensure.IsNotNull(filter, nameof(filter)); + options ??= new FindOneAndDeleteOptions(); + var renderArgs = GetRenderArgs(); var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition(); var renderedProjection = projection.Render(renderArgs with { RenderForFind = true }); @@ -1106,17 +940,21 @@ private FindOneAndDeleteOperation CreateFindOneAndDeleteOperation CreateFindOneAndReplaceOperation( FilterDefinition filter, - object replacementObject, - FindOneAndReplaceOptions options, - RenderArgs renderArgs) + object replacement, + FindOneAndReplaceOptions options) { + Ensure.IsNotNull(filter, nameof(filter)); + Ensure.IsNotNull(replacement, nameof(replacement)); + options ??= new FindOneAndReplaceOptions(); + + var renderArgs = GetRenderArgs(); var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition(); var renderedProjection = projection.Render(renderArgs with { RenderForFind = true }); return new FindOneAndReplaceOperation( _collectionNamespace, filter.Render(renderArgs), - new BsonDocumentWrapper(replacementObject, _documentSerializer), + new BsonDocumentWrapper(replacement, _documentSerializer), new FindAndModifyValueDeserializer(renderedProjection.ProjectionSerializer), _messageEncoderSettings) { @@ -1138,9 +976,18 @@ private FindOneAndReplaceOperation CreateFindOneAndReplaceOperation private FindOneAndUpdateOperation CreateFindOneAndUpdateOperation( FilterDefinition filter, UpdateDefinition update, - FindOneAndUpdateOptions options, - RenderArgs renderArgs) + FindOneAndUpdateOptions options) { + Ensure.IsNotNull(filter, nameof(filter)); + Ensure.IsNotNull(update, nameof(update)); + options = options ?? new FindOneAndUpdateOptions(); + + if (update is PipelineUpdateDefinition && (options.ArrayFilters != null && options.ArrayFilters.Any())) + { + throw new NotSupportedException("An arrayfilter is not supported in the pipeline-style update."); + } + + var renderArgs = GetRenderArgs(); var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition(); var renderedProjection = projection.Render(renderArgs with { RenderForFind = true }); @@ -1169,9 +1016,12 @@ private FindOneAndUpdateOperation CreateFindOneAndUpdateOperation CreateFindOperation( FilterDefinition filter, - FindOptions options, - RenderArgs renderArgs) + FindOptions options) { + Ensure.IsNotNull(filter, nameof(filter)); + options ??= new FindOptions(); + + var renderArgs = GetRenderArgs(options.TranslationOptions); var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition(); var renderedProjection = projection.Render(renderArgs with { RenderForFind = true }); @@ -1285,10 +1135,10 @@ private MapReduceOutputToCollectionOperation CreateMapReduceOutputToCollectionOp } #pragma warning disable CS0618 // Type or member is obsolete - private FindOperation CreateMapReduceOutputToCollectionFindOperation(MapReduceOptions options, CollectionNamespace outputCollectionNamespace, IBsonSerializer resultSerializer) + private IAsyncCursor CreateMapReduceOutputToCollectionResultCursor(IClientSessionHandle session, MapReduceOptions options, CollectionNamespace outputCollectionNamespace, IBsonSerializer resultSerializer) #pragma warning restore CS0618 // Type or member is obsolete { - return new FindOperation( + var findOperation = new FindOperation( outputCollectionNamespace, resultSerializer, _messageEncoderSettings) @@ -1298,21 +1148,16 @@ private FindOperation CreateMapReduceOutputToCollectionFindOperation( + () => forkedSession.Dispose(), + ct => OperationExecutor.ExecuteReadOperation(findOperation, readOperationOptions, forkedSession, ct), + ct => OperationExecutor.ExecuteReadOperationAsync(findOperation, readOperationOptions, forkedSession, ct)); + return deferredCursor; } private MessageEncoderSettings GetMessageEncoderSettings() @@ -1389,50 +1234,6 @@ private RenderArgs GetRenderArgs(ExpressionTranslationOptions transla return new RenderArgs(_documentSerializer, _settings.SerializerRegistry, translationOptions: translationOptions); } - private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, _settings.ReadPreference); - return ExecuteReadOperation(session, operation, effectiveReadPreference, cancellationToken); - } - - private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, ReadPreference readPreference, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadBinding(session, readPreference)) - { - return _operationExecutor.ExecuteReadOperation(binding, operation, cancellationToken); - } - } - - private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, _settings.ReadPreference); - return ExecuteReadOperationAsync(session, operation, effectiveReadPreference, cancellationToken); - } - - private async Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, ReadPreference readPreference, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadBinding(session, readPreference)) - { - return await _operationExecutor.ExecuteReadOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } - - private TResult ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadWriteBinding(session)) - { - return _operationExecutor.ExecuteWriteOperation(binding, operation, cancellationToken); - } - } - - private async Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadWriteBinding(session)) - { - return await _operationExecutor.ExecuteWriteOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } - private IEnumerable RenderArrayFilters(IEnumerable arrayFilters) { if (arrayFilters == null) @@ -1465,7 +1266,7 @@ private IBsonSerializer ResolveResultSerializer(IBsonSerialize return _settings.SerializerRegistry.GetSerializer(); } - private void UsingImplicitSession(Action func, CancellationToken cancellationToken = default(CancellationToken)) + private void UsingImplicitSession(Action func, CancellationToken cancellationToken = default) { using (var session = _operationExecutor.StartImplicitSession(cancellationToken)) { @@ -1473,7 +1274,7 @@ private IBsonSerializer ResolveResultSerializer(IBsonSerialize } } - private TResult UsingImplicitSession(Func func, CancellationToken cancellationToken = default(CancellationToken)) + private TResult UsingImplicitSession(Func func, CancellationToken cancellationToken = default) { using (var session = _operationExecutor.StartImplicitSession(cancellationToken)) { @@ -1481,7 +1282,7 @@ private IBsonSerializer ResolveResultSerializer(IBsonSerialize } } - private async Task UsingImplicitSessionAsync(Func funcAsync, CancellationToken cancellationToken = default(CancellationToken)) + private async Task UsingImplicitSessionAsync(Func funcAsync, CancellationToken cancellationToken = default) { using (var session = await _operationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) { @@ -1489,7 +1290,7 @@ private IBsonSerializer ResolveResultSerializer(IBsonSerialize } } - private async Task UsingImplicitSessionAsync(Func> funcAsync, CancellationToken cancellationToken = default(CancellationToken)) + private async Task UsingImplicitSessionAsync(Func> funcAsync, CancellationToken cancellationToken = default) { using (var session = await _operationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) { @@ -1526,231 +1327,192 @@ public override MongoCollectionSettings Settings } // public methods - public override IEnumerable CreateMany(IEnumerable> models, CancellationToken cancellationToken = default(CancellationToken)) - { - return CreateMany(models, null, cancellationToken); - } + public override IEnumerable CreateMany(IEnumerable> models, CancellationToken cancellationToken = default) + => CreateMany(models, null, cancellationToken); public override IEnumerable CreateMany( IEnumerable> models, CreateManyIndexesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - return _collection.UsingImplicitSession(session => CreateMany(session, models, options, cancellationToken), cancellationToken); + var operation = CreateCreateIndexesOperation(models, options); + _collection.OperationExecutor.ExecuteWriteOperation(operation, _collection._writeOperationOptions, null, cancellationToken); + return operation.Requests.Select(x => x.GetIndexName()); } - public override IEnumerable CreateMany(IClientSessionHandle session, IEnumerable> models, CancellationToken cancellationToken = default(CancellationToken)) - { - return CreateMany(session, models, null, cancellationToken); - } + public override IEnumerable CreateMany(IClientSessionHandle session, IEnumerable> models, CancellationToken cancellationToken = default) + => CreateMany(session, models, null, cancellationToken); public override IEnumerable CreateMany( IClientSessionHandle session, IEnumerable> models, CreateManyIndexesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(models, nameof(models)); - var renderArgs = _collection.GetRenderArgs(); - var requests = CreateCreateIndexRequests(models, renderArgs); - var operation = CreateCreateIndexesOperation(requests, options); - _collection.ExecuteWriteOperation(session, operation, cancellationToken); - - return requests.Select(x => x.GetIndexName()); + var operation = CreateCreateIndexesOperation(models, options); + _collection.OperationExecutor.ExecuteWriteOperation(operation, _collection._writeOperationOptions, session, cancellationToken); + return operation.Requests.Select(x => x.GetIndexName()); } - public override Task> CreateManyAsync(IEnumerable> models, CancellationToken cancellationToken = default(CancellationToken)) - { - return CreateManyAsync(models, null, cancellationToken); - } + public override Task> CreateManyAsync(IEnumerable> models, CancellationToken cancellationToken = default) + => CreateManyAsync(models, null, cancellationToken); - public override Task> CreateManyAsync( + public override async Task> CreateManyAsync( IEnumerable> models, CreateManyIndexesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - return _collection.UsingImplicitSessionAsync(session => CreateManyAsync(session, models, options, cancellationToken), cancellationToken); + var operation = CreateCreateIndexesOperation(models, options); + await _collection.OperationExecutor.ExecuteWriteOperationAsync(operation, _collection._writeOperationOptions, null, cancellationToken).ConfigureAwait(false); + return operation.Requests.Select(x => x.GetIndexName()); } - public override Task> CreateManyAsync(IClientSessionHandle session, IEnumerable> models, CancellationToken cancellationToken = default(CancellationToken)) - { - return CreateManyAsync(session, models, null, cancellationToken); - } + public override Task> CreateManyAsync(IClientSessionHandle session, IEnumerable> models, CancellationToken cancellationToken = default) + => CreateManyAsync(session, models, null, cancellationToken); public override async Task> CreateManyAsync( IClientSessionHandle session, IEnumerable> models, CreateManyIndexesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(models, nameof(models)); - var renderArgs = _collection.GetRenderArgs(); - var requests = CreateCreateIndexRequests(models, renderArgs); - var operation = CreateCreateIndexesOperation(requests, options); - await _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); - - return requests.Select(x => x.GetIndexName()); + var operation = CreateCreateIndexesOperation(models, options); + await _collection.OperationExecutor.ExecuteWriteOperationAsync(operation, _collection._writeOperationOptions, session, cancellationToken).ConfigureAwait(false); + return operation.Requests.Select(x => x.GetIndexName()); } public override void DropAll(CancellationToken cancellationToken) - { - _collection.UsingImplicitSession(session => DropAll(session, cancellationToken), cancellationToken); - } + => DropAll(options: null, cancellationToken); - public override void DropAll(DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - _collection.UsingImplicitSession(session => DropAll(session, options, cancellationToken), cancellationToken); - } + public override void DropAll(DropIndexOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteWriteOperation( + CreateDropAllOperation(options), + _collection._writeOperationOptions, + cancellationToken: cancellationToken); - public override void DropAll(IClientSessionHandle session, CancellationToken cancellationToken = default(CancellationToken)) - { - DropAll(session, null, cancellationToken); - } + public override void DropAll(IClientSessionHandle session, CancellationToken cancellationToken = default) + => DropAll(session, null, cancellationToken); - public override void DropAll(IClientSessionHandle session, DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - var operation = CreateDropAllOperation(options); - _collection.ExecuteWriteOperation(session, operation, cancellationToken); - } + public override void DropAll(IClientSessionHandle session, DropIndexOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteWriteOperation( + CreateDropAllOperation(options), + _collection._writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public override Task DropAllAsync(CancellationToken cancellationToken) - { - return _collection.UsingImplicitSessionAsync(session => DropAllAsync(session, cancellationToken), cancellationToken); - } + => DropAllAsync(options: null, cancellationToken); - public override Task DropAllAsync(DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return _collection.UsingImplicitSessionAsync(session => DropAllAsync(session, options, cancellationToken), cancellationToken); - } + public override Task DropAllAsync(DropIndexOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteWriteOperationAsync( + CreateDropAllOperation(options), + _collection._writeOperationOptions, + cancellationToken: cancellationToken); - public override Task DropAllAsync(IClientSessionHandle session, CancellationToken cancellationToken = default(CancellationToken)) - { - return DropAllAsync(session, null, cancellationToken); - } + public override Task DropAllAsync(IClientSessionHandle session, CancellationToken cancellationToken = default) + => DropAllAsync(session, null, cancellationToken); - public override Task DropAllAsync(IClientSessionHandle session, DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - var operation = CreateDropAllOperation(options); - return _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken); - } + public override Task DropAllAsync(IClientSessionHandle session, DropIndexOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteWriteOperationAsync( + CreateDropAllOperation(options), + _collection._writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); - public override void DropOne(string name, CancellationToken cancellationToken = default(CancellationToken)) - { - _collection.UsingImplicitSession(session => DropOne(session, name, cancellationToken), cancellationToken); - } + public override void DropOne(string name, CancellationToken cancellationToken = default) + => DropOne(name, null, cancellationToken); - public override void DropOne(string name, DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - _collection.UsingImplicitSession(session => DropOne(session, name, options, cancellationToken), cancellationToken); - } + public override void DropOne(string name, DropIndexOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteWriteOperation( + CreateDropOneOperation(name, options), + _collection._writeOperationOptions, + cancellationToken: cancellationToken); - public override void DropOne(IClientSessionHandle session, string name, CancellationToken cancellationToken = default(CancellationToken)) - { - DropOne(session, name, null, cancellationToken); - } + public override void DropOne(IClientSessionHandle session, string name, CancellationToken cancellationToken = default) + => DropOne(session, name, null, cancellationToken); public override void DropOne( IClientSessionHandle session, string name, DropIndexOptions options, CancellationToken cancellationToken) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNullOrEmpty(name, nameof(name)); - if (name == "*") - { - throw new ArgumentException("Cannot specify '*' for the index name. Use DropAllAsync to drop all indexes.", "name"); - } + => _collection.OperationExecutor.ExecuteWriteOperation( + CreateDropOneOperation(name, options), + _collection._writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); - var operation = CreateDropOneOperation(name, options); - _collection.ExecuteWriteOperation(session, operation, cancellationToken); - } - - public override Task DropOneAsync(string name, CancellationToken cancellationToken = default(CancellationToken)) - { - return _collection.UsingImplicitSessionAsync(session => DropOneAsync(session, name, cancellationToken), cancellationToken); - } + public override Task DropOneAsync(string name, CancellationToken cancellationToken = default) + => DropOneAsync(name, null, cancellationToken); - public override Task DropOneAsync(string name, DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return _collection.UsingImplicitSessionAsync(session => DropOneAsync(session, name, options, cancellationToken), cancellationToken); - } + public override Task DropOneAsync(string name, DropIndexOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteWriteOperationAsync( + CreateDropOneOperation(name, options), + _collection._writeOperationOptions, + cancellationToken: cancellationToken); - public override Task DropOneAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken = default(CancellationToken)) - { - return DropOneAsync(session, name, null, cancellationToken); - } + public override Task DropOneAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken = default) + => DropOneAsync(session, name, null, cancellationToken); public override Task DropOneAsync( IClientSessionHandle session, string name, DropIndexOptions options, CancellationToken cancellationToken) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNullOrEmpty(name, nameof(name)); - if (name == "*") - { - throw new ArgumentException("Cannot specify '*' for the index name. Use DropAllAsync to drop all indexes.", "name"); - } + => _collection.OperationExecutor.ExecuteWriteOperationAsync( + CreateDropOneOperation(name, options), + _collection._writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); - var operation = CreateDropOneOperation(name, options); - return _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken); - } + public override IAsyncCursor List(CancellationToken cancellationToken = default) + => List(options: null, cancellationToken); - public override IAsyncCursor List(CancellationToken cancellationToken = default(CancellationToken)) - { - return List(options: null, cancellationToken); - } - - public override IAsyncCursor List(ListIndexesOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return _collection.UsingImplicitSession(session => List(session, options, cancellationToken), cancellationToken); - } + public override IAsyncCursor List(ListIndexesOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteReadOperation( + CreateListIndexesOperation(options), + _collection._readOperationOptions, + cancellationToken: cancellationToken); public override IAsyncCursor List(IClientSessionHandle session, CancellationToken cancellationToken = default) - { - return List(session, options: null, cancellationToken); - } + => List(session, options: null, cancellationToken); - public override IAsyncCursor List(IClientSessionHandle session, ListIndexesOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - var operation = CreateListIndexesOperation(options); - return _collection.ExecuteReadOperation(session, operation, ReadPreference.Primary, cancellationToken); - } + public override IAsyncCursor List(IClientSessionHandle session, ListIndexesOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteReadOperation( + CreateListIndexesOperation(options), + _collection._readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); - public override Task> ListAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - return ListAsync(options: null, cancellationToken); - } + public override Task> ListAsync(CancellationToken cancellationToken = default) + => ListAsync(options: null, cancellationToken); - public override Task> ListAsync(ListIndexesOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - return _collection.UsingImplicitSessionAsync(session => ListAsync(session, options, cancellationToken), cancellationToken); - } + public override Task> ListAsync(ListIndexesOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteReadOperationAsync( + CreateListIndexesOperation(options), + _collection._readOperationOptions, + cancellationToken: cancellationToken); public override Task> ListAsync(IClientSessionHandle session, CancellationToken cancellationToken = default) - { - return ListAsync(session, options: null, cancellationToken); - } + => ListAsync(session, options: null, cancellationToken); - public override Task> ListAsync(IClientSessionHandle session, ListIndexesOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - var operation = CreateListIndexesOperation(options); - return _collection.ExecuteReadOperationAsync(session, operation, ReadPreference.Primary, cancellationToken); - } + public override Task> ListAsync(IClientSessionHandle session, ListIndexesOptions options, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteReadOperationAsync( + CreateListIndexesOperation(options), + _collection._readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); // private methods - private CreateIndexesOperation CreateCreateIndexesOperation(IEnumerable requests, CreateManyIndexesOptions options) + private CreateIndexesOperation CreateCreateIndexesOperation(IEnumerable> models, CreateManyIndexesOptions options) { + Ensure.IsNotNull((object)models, nameof(models)); + var requests = CreateCreateIndexRequests(models); + return new CreateIndexesOperation(_collection._collectionNamespace, requests, _collection._messageEncoderSettings) { Comment = options?.Comment, @@ -1760,8 +1522,9 @@ private CreateIndexesOperation CreateCreateIndexesOperation(IEnumerable CreateCreateIndexRequests(IEnumerable> models, RenderArgs renderArgs) + private IEnumerable CreateCreateIndexRequests(IEnumerable> models) { + var renderArgs = _collection.GetRenderArgs(); return models.Select(m => { var options = m.Options ?? new CreateIndexOptions(); @@ -1809,6 +1572,12 @@ private DropIndexOperation CreateDropAllOperation(DropIndexOptions options) private DropIndexOperation CreateDropOneOperation(string name, DropIndexOptions options) { + Ensure.IsNotNullOrEmpty(name, nameof(name)); + if (name == "*") + { + throw new ArgumentException("Cannot specify '*' for the index name. Use DropAllAsync to drop all indexes.", "name"); + } + return new DropIndexOperation(_collection._collectionNamespace, name, _collection._messageEncoderSettings) { Comment = options?.Comment, @@ -1842,19 +1611,15 @@ public MongoSearchIndexManager(MongoCollectionImpl collection) public IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default) { var operation = CreateCreateIndexesOperation(models); - var result = _collection.UsingImplicitSession(session => _collection.ExecuteWriteOperation(session, operation, cancellationToken), cancellationToken); - var indexNames = GetIndexNames(result); - - return indexNames; + var result = _collection.OperationExecutor.ExecuteWriteOperation(operation, _collection._writeOperationOptions, null, cancellationToken); + return GetIndexNames(result); } public async Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default) { var operation = CreateCreateIndexesOperation(models); - var result = await _collection.UsingImplicitSessionAsync(session => _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken), cancellationToken).ConfigureAwait(false); - var indexNames = GetIndexNames(result); - - return indexNames; + var result = await _collection.OperationExecutor.ExecuteWriteOperationAsync(operation, _collection._writeOperationOptions, null, cancellationToken).ConfigureAwait(false); + return GetIndexNames(result); } public string CreateOne(BsonDocument definition, string name = null, CancellationToken cancellationToken = default) => @@ -1876,16 +1641,16 @@ public async Task CreateOneAsync(CreateSearchIndexModel model, Cancellat } public void DropOne(string indexName, CancellationToken cancellationToken = default) - { - var operation = new DropSearchIndexOperation(_collection.CollectionNamespace, indexName, _collection._messageEncoderSettings); - _collection.UsingImplicitSession(session => _collection.ExecuteWriteOperation(session, operation, cancellationToken), cancellationToken); - } + => _collection.OperationExecutor.ExecuteWriteOperation( + new DropSearchIndexOperation(_collection.CollectionNamespace, indexName, _collection._messageEncoderSettings), + _collection._writeOperationOptions, + cancellationToken: cancellationToken); public Task DropOneAsync(string indexName, CancellationToken cancellationToken = default) - { - var operation = new DropSearchIndexOperation(_collection.CollectionNamespace, indexName, _collection._messageEncoderSettings); - return _collection.UsingImplicitSessionAsync(session => _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken), cancellationToken); - } + => _collection.OperationExecutor.ExecuteWriteOperationAsync( + new DropSearchIndexOperation(_collection.CollectionNamespace, indexName, _collection._messageEncoderSettings), + _collection._writeOperationOptions, + cancellationToken: cancellationToken); public IAsyncCursor List(string indexName, AggregateOptions aggregateOptions = null, CancellationToken cancellationToken = default) { @@ -1898,18 +1663,16 @@ public Task> ListAsync(string indexName, AggregateOpt } public void Update(string indexName, BsonDocument definition, CancellationToken cancellationToken = default) - { - var operation = new UpdateSearchIndexOperation(_collection.CollectionNamespace, indexName, definition, _collection._messageEncoderSettings); - - _collection.UsingImplicitSession(session => _collection.ExecuteWriteOperation(session, operation, cancellationToken), cancellationToken); - } - - public async Task UpdateAsync(string indexName, BsonDocument definition, CancellationToken cancellationToken = default) - { - var operation = new UpdateSearchIndexOperation(_collection.CollectionNamespace, indexName, definition, _collection._messageEncoderSettings); - - await _collection.UsingImplicitSessionAsync(session => _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken), cancellationToken).ConfigureAwait(false); - } + => _collection.OperationExecutor.ExecuteWriteOperation( + new UpdateSearchIndexOperation(_collection.CollectionNamespace, indexName, definition, _collection._messageEncoderSettings), + _collection._writeOperationOptions, + cancellationToken: cancellationToken); + + public Task UpdateAsync(string indexName, BsonDocument definition, CancellationToken cancellationToken = default) + => _collection.OperationExecutor.ExecuteWriteOperationAsync( + new UpdateSearchIndexOperation(_collection.CollectionNamespace, indexName, definition, _collection._messageEncoderSettings), + _collection._writeOperationOptions, + cancellationToken: cancellationToken); // private methods private PipelineDefinition CreateListIndexesStage(string indexName) diff --git a/src/MongoDB.Driver/MongoDatabase.cs b/src/MongoDB.Driver/MongoDatabase.cs index a57eec55d94..e4694f28f16 100644 --- a/src/MongoDB.Driver/MongoDatabase.cs +++ b/src/MongoDB.Driver/MongoDatabase.cs @@ -23,8 +23,6 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; -using MongoDB.Driver.Core; -using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Clusters; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.Operations; @@ -42,6 +40,9 @@ internal sealed class MongoDatabase : IMongoDatabase private readonly IOperationExecutor _operationExecutor; private readonly MongoDatabaseSettings _settings; + private readonly ReadOperationOptions _readOperationOptions; + private readonly WriteOperationOptions _writeOperationOptions; + // constructors public MongoDatabase(IMongoClient client, DatabaseNamespace databaseNamespace, MongoDatabaseSettings settings, IClusterInternal cluster, IOperationExecutor operationExecutor) { @@ -50,139 +51,122 @@ public MongoDatabase(IMongoClient client, DatabaseNamespace databaseNamespace, M _settings = Ensure.IsNotNull(settings, nameof(settings)).Freeze(); _cluster = Ensure.IsNotNull(cluster, nameof(cluster)); _operationExecutor = Ensure.IsNotNull(operationExecutor, nameof(operationExecutor)); + + _readOperationOptions = new ReadOperationOptions(DefaultReadPreference: _settings.ReadPreference); + _writeOperationOptions = new WriteOperationOptions(); } // public properties public IMongoClient Client => _client; public DatabaseNamespace DatabaseNamespace => _databaseNamespace; + private IOperationExecutor OperationExecutor => _operationExecutor; public MongoDatabaseSettings Settings => _settings; // public methods - public IAsyncCursor Aggregate(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor Aggregate(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => Aggregate(session, pipeline, options, cancellationToken), cancellationToken); + using var session = OperationExecutor.StartImplicitSession(cancellationToken); + return Aggregate(session, pipeline, options, cancellationToken); } - public IAsyncCursor Aggregate(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor Aggregate(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options?.TranslationOptions); - var renderedPipeline = Ensure.IsNotNull(pipeline, nameof(pipeline)).Render(renderArgs); - options = options ?? new AggregateOptions(); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new AggregateOptions(); - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage != null && (lastStageName == "$out" || lastStageName == "$merge")) + var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options.TranslationOptions); + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out bool isAggregateToCollection); + if (isAggregateToCollection) { var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - ExecuteWriteOperation(session, aggregateOperation, cancellationToken); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateAggregateToCollectionFindOperation(lastStage, renderedPipeline.OutputSerializer, options); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return deferredCursor; + OperationExecutor.ExecuteWriteOperation(aggregateOperation, _writeOperationOptions, session, cancellationToken); + return CreateAggregateToCollectionResultCursor(session, renderedPipeline, options); } else { var aggregateOperation = CreateAggregateOperation(renderedPipeline, options); - return ExecuteReadOperation(session, aggregateOperation, cancellationToken); + return OperationExecutor.ExecuteReadOperation(aggregateOperation, _readOperationOptions, session, cancellationToken); } } - public Task> AggregateAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> AggregateAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => AggregateAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = await OperationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false); + return await AggregateAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } - public async Task> AggregateAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> AggregateAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options?.TranslationOptions); - var renderedPipeline = Ensure.IsNotNull(pipeline, nameof(pipeline)).Render(renderArgs); - options = options ?? new AggregateOptions(); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new AggregateOptions(); - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage != null && (lastStageName == "$out" || lastStageName == "$merge")) + var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options.TranslationOptions); + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out bool isAggregateToCollection); + if (isAggregateToCollection) { var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - await ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateAggregateToCollectionFindOperation(lastStage, renderedPipeline.OutputSerializer, options); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return await Task.FromResult>(deferredCursor).ConfigureAwait(false); + await OperationExecutor.ExecuteWriteOperationAsync(aggregateOperation, _writeOperationOptions, session, cancellationToken).ConfigureAwait(false); + return CreateAggregateToCollectionResultCursor(session, renderedPipeline, options); } else { var aggregateOperation = CreateAggregateOperation(renderedPipeline, options); - return await ExecuteReadOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); + return await OperationExecutor.ExecuteReadOperationAsync(aggregateOperation, _readOperationOptions, session, cancellationToken).ConfigureAwait(false); } } - public void AggregateToCollection(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public void AggregateToCollection(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - UsingImplicitSession(session => AggregateToCollection(session, pipeline, options, cancellationToken), cancellationToken); + using var session = OperationExecutor.StartImplicitSession(cancellationToken); + AggregateToCollection(session, pipeline, options, cancellationToken); } - public void AggregateToCollection(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public void AggregateToCollection(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options?.TranslationOptions); - var renderedPipeline = Ensure.IsNotNull(pipeline, nameof(pipeline)).Render(renderArgs); - options = options ?? new AggregateOptions(); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new AggregateOptions(); - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage == null || (lastStageName != "$out" && lastStageName != "$merge")) + var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options.TranslationOptions); + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out bool isAggregateToCollection); + if (!isAggregateToCollection) { throw new InvalidOperationException("AggregateToCollection requires that the last stage be $out or $merge."); } - else - { - var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - ExecuteWriteOperation(session, aggregateOperation, cancellationToken); - } + + var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); + OperationExecutor.ExecuteWriteOperation(aggregateOperation, _writeOperationOptions, session, cancellationToken); } - public Task AggregateToCollectionAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public async Task AggregateToCollectionAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => AggregateToCollectionAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = await OperationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false); + await AggregateToCollectionAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } - public async Task AggregateToCollectionAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public async Task AggregateToCollectionAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options?.TranslationOptions); - var renderedPipeline = Ensure.IsNotNull(pipeline, nameof(pipeline)).Render(renderArgs); - options = options ?? new AggregateOptions(); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new AggregateOptions(); - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage == null || (lastStageName != "$out" && lastStageName != "$merge")) + var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options.TranslationOptions); + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out bool isAggregateToCollection); + if (!isAggregateToCollection) { throw new InvalidOperationException("AggregateToCollectionAsync requires that the last stage be $out or $merge."); } - else - { - var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - await ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); - } + + var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); + await OperationExecutor.ExecuteWriteOperationAsync(aggregateOperation, _writeOperationOptions, session, cancellationToken).ConfigureAwait(false); } public void CreateCollection(string name, CreateCollectionOptions options, CancellationToken cancellationToken) { - UsingImplicitSession(session => CreateCollection(session, name, options, cancellationToken), cancellationToken); + using var session = OperationExecutor.StartImplicitSession(cancellationToken); + CreateCollection(session, name, options, cancellationToken); } public void CreateCollection(IClientSessionHandle session, string name, CreateCollectionOptions options, CancellationToken cancellationToken) @@ -203,7 +187,7 @@ public void CreateCollection(IClientSessionHandle session, string name, CreateCo return; } - var genericMethodDefinition = typeof(MongoDatabase).GetTypeInfo().GetMethod("CreateCollectionHelper", BindingFlags.NonPublic | BindingFlags.Instance); + var genericMethodDefinition = typeof(MongoDatabase).GetTypeInfo().GetMethod(nameof(CreateCollectionHelper), BindingFlags.NonPublic | BindingFlags.Instance); var documentType = options.GetType().GetTypeInfo().GetGenericArguments()[0]; var methodInfo = genericMethodDefinition.MakeGenericMethod(documentType); try @@ -216,9 +200,10 @@ public void CreateCollection(IClientSessionHandle session, string name, CreateCo } } - public Task CreateCollectionAsync(string name, CreateCollectionOptions options, CancellationToken cancellationToken) + public async Task CreateCollectionAsync(string name, CreateCollectionOptions options, CancellationToken cancellationToken) { - return UsingImplicitSessionAsync(session => CreateCollectionAsync(session, name, options, cancellationToken), cancellationToken); + using var session = await OperationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false); + await CreateCollectionAsync(session, name, options, cancellationToken).ConfigureAwait(false); } public async Task CreateCollectionAsync(IClientSessionHandle session, string name, CreateCollectionOptions options, CancellationToken cancellationToken) @@ -239,7 +224,7 @@ public async Task CreateCollectionAsync(IClientSessionHandle session, string nam return; } - var genericMethodDefinition = typeof(MongoDatabase).GetTypeInfo().GetMethod("CreateCollectionHelperAsync", BindingFlags.NonPublic | BindingFlags.Instance); + var genericMethodDefinition = typeof(MongoDatabase).GetTypeInfo().GetMethod(nameof(CreateCollectionHelperAsync), BindingFlags.NonPublic | BindingFlags.Instance); var documentType = options.GetType().GetTypeInfo().GetGenericArguments()[0]; var methodInfo = genericMethodDefinition.MakeGenericMethod(documentType); try @@ -252,39 +237,31 @@ public async Task CreateCollectionAsync(IClientSessionHandle session, string nam } } - public void CreateView(string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) - { - UsingImplicitSession(session => CreateView(session, viewName, viewOn, pipeline, options, cancellationToken), cancellationToken); - } - - public void CreateView(IClientSessionHandle session, string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(viewName, nameof(viewName)); - Ensure.IsNotNull(viewOn, nameof(viewOn)); - Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new CreateViewOptions(); - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateCreateViewOperation(viewName, viewOn, pipeline, options, translationOptions); - ExecuteWriteOperation(session, operation, cancellationToken); - } - - public Task CreateViewAsync(string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => CreateViewAsync(session, viewName, viewOn, pipeline, options, cancellationToken), cancellationToken); - } - - public Task CreateViewAsync(IClientSessionHandle session, string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(viewName, nameof(viewName)); - Ensure.IsNotNull(viewOn, nameof(viewOn)); - Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new CreateViewOptions(); - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateCreateViewOperation(viewName, viewOn, pipeline, options, translationOptions); - return ExecuteWriteOperationAsync(session, operation, cancellationToken); - } + public void CreateView(string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateCreateViewOperation(viewName, viewOn, pipeline, options), + _writeOperationOptions, + cancellationToken: cancellationToken); + + public void CreateView(IClientSessionHandle session, string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperation( + CreateCreateViewOperation(viewName, viewOn, pipeline, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public Task CreateViewAsync(string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateCreateViewOperation(viewName, viewOn, pipeline, options), + _writeOperationOptions, + cancellationToken: cancellationToken); + + public Task CreateViewAsync(IClientSessionHandle session, string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteWriteOperationAsync( + CreateCreateViewOperation(viewName, viewOn, pipeline, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public void DropCollection(string name, CancellationToken cancellationToken) { @@ -293,7 +270,8 @@ public void DropCollection(string name, CancellationToken cancellationToken) public void DropCollection(string name, DropCollectionOptions options, CancellationToken cancellationToken = default) { - UsingImplicitSession(session => DropCollection(session, name, options, cancellationToken), cancellationToken); + using var session = OperationExecutor.StartImplicitSession(cancellationToken); + DropCollection(session, name, options, cancellationToken); } public void DropCollection(IClientSessionHandle session, string name, CancellationToken cancellationToken) @@ -304,9 +282,8 @@ public void DropCollection(IClientSessionHandle session, string name, Cancellati public void DropCollection(IClientSessionHandle session, string name, DropCollectionOptions options, CancellationToken cancellationToken) { Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNullOrEmpty(name, nameof(name)); var operation = CreateDropCollectionOperation(name, options, session, cancellationToken); - ExecuteWriteOperation(session, operation, cancellationToken); + OperationExecutor.ExecuteWriteOperation(operation, _writeOperationOptions, session, cancellationToken); } public Task DropCollectionAsync(string name, CancellationToken cancellationToken) @@ -314,9 +291,10 @@ public Task DropCollectionAsync(string name, CancellationToken cancellationToken return DropCollectionAsync(name, options: null, cancellationToken); } - public Task DropCollectionAsync(string name, DropCollectionOptions options, CancellationToken cancellationToken) + public async Task DropCollectionAsync(string name, DropCollectionOptions options, CancellationToken cancellationToken) { - return UsingImplicitSessionAsync(session => DropCollectionAsync(session, name, options, cancellationToken), cancellationToken); + using var session = await OperationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false); + await DropCollectionAsync(session, name, options, cancellationToken).ConfigureAwait(false); } public Task DropCollectionAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken) @@ -327,9 +305,9 @@ public Task DropCollectionAsync(IClientSessionHandle session, string name, Cance public async Task DropCollectionAsync(IClientSessionHandle session, string name, DropCollectionOptions options, CancellationToken cancellationToken) { Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNullOrEmpty(name, nameof(name)); + var operation = await CreateDropCollectionOperationAsync(name, options, session, cancellationToken).ConfigureAwait(false); - await ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); + await OperationExecutor.ExecuteWriteOperationAsync(operation, _writeOperationOptions, session, cancellationToken).ConfigureAwait(false); } public IMongoCollection GetCollection(string name, MongoCollectionSettings settings) @@ -345,167 +323,160 @@ public IMongoCollection GetCollection(string name, MongoCo return new MongoCollectionImpl(this, new CollectionNamespace(_databaseNamespace, name), settings, _cluster, _operationExecutor); } - public IAsyncCursor ListCollectionNames(ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor ListCollectionNames(ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => ListCollectionNames(session, options, cancellationToken), cancellationToken); + var cursor = OperationExecutor.ExecuteReadOperation(CreateListCollectionNamesOperation(options), + _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, + null, cancellationToken); + return new BatchTransformingAsyncCursor(cursor, ExtractCollectionNames); } - public IAsyncCursor ListCollectionNames(IClientSessionHandle session, ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor ListCollectionNames(IClientSessionHandle session, ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default) { - Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); - var operation = CreateListCollectionNamesOperation(options, renderArgs); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, ReadPreference.Primary); - var cursor = ExecuteReadOperation(session, operation, effectiveReadPreference, cancellationToken); + var cursor = OperationExecutor.ExecuteReadOperation( + CreateListCollectionNamesOperation(options), + _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); return new BatchTransformingAsyncCursor(cursor, ExtractCollectionNames); } - public Task> ListCollectionNamesAsync(ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> ListCollectionNamesAsync(ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => ListCollectionNamesAsync(session, options, cancellationToken), cancellationToken); + var cursor = await OperationExecutor.ExecuteReadOperationAsync( + CreateListCollectionNamesOperation(options), + _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, + cancellationToken: cancellationToken).ConfigureAwait(false); + return new BatchTransformingAsyncCursor(cursor, ExtractCollectionNames); } - public async Task> ListCollectionNamesAsync(IClientSessionHandle session, ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> ListCollectionNamesAsync(IClientSessionHandle session, ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default) { - Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); - var operation = CreateListCollectionNamesOperation(options, renderArgs); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, ReadPreference.Primary); - var cursor = await ExecuteReadOperationAsync(session, operation, effectiveReadPreference, cancellationToken).ConfigureAwait(false); + var cursor = await OperationExecutor.ExecuteReadOperationAsync( + CreateListCollectionNamesOperation(options), + _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken).ConfigureAwait(false); return new BatchTransformingAsyncCursor(cursor, ExtractCollectionNames); } public IAsyncCursor ListCollections(ListCollectionsOptions options, CancellationToken cancellationToken) - { - return UsingImplicitSession(session => ListCollections(session, options, cancellationToken), cancellationToken); - } + => OperationExecutor.ExecuteReadOperation( + CreateListCollectionsOperation(options), + _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, + cancellationToken: cancellationToken); public IAsyncCursor ListCollections(IClientSessionHandle session, ListCollectionsOptions options, CancellationToken cancellationToken) - { - Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); - var operation = CreateListCollectionsOperation(options, renderArgs); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, ReadPreference.Primary); - return ExecuteReadOperation(session, operation, effectiveReadPreference, cancellationToken); - } + => OperationExecutor.ExecuteReadOperation( + CreateListCollectionsOperation(options), + _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public Task> ListCollectionsAsync(ListCollectionsOptions options, CancellationToken cancellationToken) - { - return UsingImplicitSessionAsync(session => ListCollectionsAsync(session, options, cancellationToken), cancellationToken); - } + => OperationExecutor.ExecuteReadOperationAsync( + CreateListCollectionsOperation(options), + _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, + cancellationToken: cancellationToken); public Task> ListCollectionsAsync(IClientSessionHandle session, ListCollectionsOptions options, CancellationToken cancellationToken) - { - Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); - var operation = CreateListCollectionsOperation(options, renderArgs); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, ReadPreference.Primary); - return ExecuteReadOperationAsync(session, operation, effectiveReadPreference, cancellationToken); - } + => OperationExecutor.ExecuteReadOperationAsync( + CreateListCollectionsOperation(options), + _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public void RenameCollection(string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) - { - UsingImplicitSession(session => RenameCollection(session, oldName, newName, options, cancellationToken), cancellationToken); - } + => OperationExecutor.ExecuteWriteOperation( + CreateRenameCollectionOperation(oldName, newName, options), + _writeOperationOptions, + cancellationToken: cancellationToken); public void RenameCollection(IClientSessionHandle session, string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNullOrEmpty(oldName, nameof(oldName)); - Ensure.IsNotNullOrEmpty(newName, nameof(newName)); - options = options ?? new RenameCollectionOptions(); - - var operation = CreateRenameCollectionOperation(oldName, newName, options); - ExecuteWriteOperation(session, operation, cancellationToken); - } + => OperationExecutor.ExecuteWriteOperation( + CreateRenameCollectionOperation(oldName, newName, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public Task RenameCollectionAsync(string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) - { - return UsingImplicitSessionAsync(session => RenameCollectionAsync(session, oldName, newName, options, cancellationToken), cancellationToken); - } + => OperationExecutor.ExecuteWriteOperationAsync( + CreateRenameCollectionOperation(oldName, newName, options), + _writeOperationOptions, + cancellationToken: cancellationToken); public Task RenameCollectionAsync(IClientSessionHandle session, string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNullOrEmpty(oldName, nameof(oldName)); - Ensure.IsNotNullOrEmpty(newName, nameof(newName)); - options = options ?? new RenameCollectionOptions(); - - var operation = CreateRenameCollectionOperation(oldName, newName, options); - return ExecuteWriteOperationAsync(session, operation, cancellationToken); - } - - public TResult RunCommand(Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => RunCommand(session, command, readPreference, cancellationToken), cancellationToken); - } - - public TResult RunCommand(IClientSessionHandle session, Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(command, nameof(command)); - - var operation = CreateRunCommandOperation(command); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, readPreference, ReadPreference.Primary); - return ExecuteReadOperation(session, operation, effectiveReadPreference, cancellationToken); - } - - public Task RunCommandAsync(Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => RunCommandAsync(session, command, readPreference, cancellationToken), cancellationToken); - } - - public Task RunCommandAsync(IClientSessionHandle session, Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(command, nameof(command)); - - var operation = CreateRunCommandOperation(command); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, readPreference, ReadPreference.Primary); - return ExecuteReadOperationAsync(session, operation, effectiveReadPreference, cancellationToken); - } + => OperationExecutor.ExecuteWriteOperationAsync( + CreateRenameCollectionOperation(oldName, newName, options), + _writeOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public TResult RunCommand(Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateRunCommandOperation(command), + _readOperationOptions with { ExplicitReadPreference = readPreference, DefaultReadPreference = ReadPreference.Primary}, + cancellationToken: cancellationToken); + + public TResult RunCommand(IClientSessionHandle session, Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateRunCommandOperation(command), + _readOperationOptions with { ExplicitReadPreference = readPreference, DefaultReadPreference = ReadPreference.Primary}, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); + + public Task RunCommandAsync(Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateRunCommandOperation(command), + _readOperationOptions with { ExplicitReadPreference = readPreference, DefaultReadPreference = ReadPreference.Primary}, + cancellationToken: cancellationToken); + + public Task RunCommandAsync(IClientSessionHandle session, Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateRunCommandOperation(command), + _readOperationOptions with { ExplicitReadPreference = readPreference, DefaultReadPreference = ReadPreference.Primary}, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public IChangeStreamCursor Watch( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSession(session => Watch(session, pipeline, options, cancellationToken), cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + cancellationToken: cancellationToken); public IChangeStreamCursor Watch( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(pipeline, nameof(pipeline)); - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); - return ExecuteReadOperation(session, operation, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperation( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public Task> WatchAsync( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - return UsingImplicitSessionAsync(session => WatchAsync(session, pipeline, options, cancellationToken), cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + cancellationToken: cancellationToken); public Task> WatchAsync( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull(pipeline, nameof(pipeline)); - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - } + CancellationToken cancellationToken = default) + => OperationExecutor.ExecuteReadOperationAsync( + CreateChangeStreamOperation(pipeline, options), + _readOperationOptions, + Ensure.IsNotNull(session, nameof(session)), + cancellationToken); public IMongoDatabase WithReadConcern(ReadConcern readConcern) { @@ -532,6 +503,7 @@ public IMongoDatabase WithWriteConcern(WriteConcern writeConcern) } // private methods + private AggregateOperation CreateAggregateOperation(RenderedPipelineDefinition renderedPipeline, AggregateOptions options) { var messageEncoderSettings = GetMessageEncoderSettings(); @@ -557,59 +529,16 @@ private AggregateOperation CreateAggregateOperation(RenderedPi }; } - private FindOperation CreateAggregateToCollectionFindOperation(BsonDocument outStage, IBsonSerializer resultSerializer, AggregateOptions options) + private IAsyncCursor CreateAggregateToCollectionResultCursor(IClientSessionHandle session, RenderedPipelineDefinition pipeline, AggregateOptions options) { - CollectionNamespace outputCollectionNamespace; - var stageName = outStage.GetElement(0).Name; - switch (stageName) - { - case "$out": - { - var outValue = outStage[0]; - DatabaseNamespace outputDatabaseNamespace; - string outputCollectionName; - if (outValue.IsString) - { - outputDatabaseNamespace = _databaseNamespace; - outputCollectionName = outValue.AsString; - } - else - { - outputDatabaseNamespace = new DatabaseNamespace(outValue["db"].AsString); - outputCollectionName = outValue["coll"].AsString; - } - outputCollectionNamespace = new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); - } - break; - case "$merge": - { - var mergeArguments = outStage[0].AsBsonDocument; - DatabaseNamespace outputDatabaseNamespace; - string outputCollectionName; - var into = mergeArguments["into"]; - if (into.IsString) - { - outputDatabaseNamespace = _databaseNamespace; - outputCollectionName = into.AsString; - } - else - { - outputDatabaseNamespace = new DatabaseNamespace(into["db"].AsString); - outputCollectionName = into["coll"].AsString; - } - outputCollectionNamespace = new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); - } - break; - default: - throw new ArgumentException($"Unexpected stage name: {stageName}."); - } + var outputCollectionNamespace = AggregateHelper.GetOutCollection(pipeline.Documents.Last(), _databaseNamespace); // because auto encryption is not supported for non-collection commands. // So, an error will be thrown in the previous CreateAggregateToCollectionOperation step. // However, since we've added encryption configuration for CreateAggregateToCollectionOperation operation, // it's not superfluous to also add it here var messageEncoderSettings = GetMessageEncoderSettings(); - return new FindOperation(outputCollectionNamespace, resultSerializer, messageEncoderSettings) + var findOperation = new FindOperation(outputCollectionNamespace, pipeline.OutputSerializer, messageEncoderSettings) { BatchSize = options.BatchSize, Collation = options.Collation, @@ -617,6 +546,16 @@ private FindOperation CreateAggregateToCollectionFindOperation ReadConcern = _settings.ReadConcern, RetryRequested = _client.Settings.RetryReads }; + + // we want to delay execution of the find because the user may + // not want to iterate the results at all... + var forkedSession = session.Fork(); + var readOperationOptions = _readOperationOptions with { ExplicitReadPreference = ReadPreference.Primary }; + var deferredCursor = new DeferredAsyncCursor( + () => forkedSession.Dispose(), + ct => OperationExecutor.ExecuteReadOperation(findOperation, readOperationOptions, forkedSession, ct), + ct => OperationExecutor.ExecuteReadOperationAsync(findOperation, readOperationOptions, forkedSession, ct)); + return deferredCursor; } private AggregateToCollectionOperation CreateAggregateToCollectionOperation(RenderedPipelineDefinition renderedPipeline, AggregateOptions options) @@ -641,25 +580,19 @@ private AggregateToCollectionOperation CreateAggregateToCollectionOperation(IClientSessionHandle session, string name, CreateCollectionOptions options, CancellationToken cancellationToken) - { - options = options ?? new CreateCollectionOptions(); - - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateCreateCollectionOperation(name, options, translationOptions); - ExecuteWriteOperation(session, operation, cancellationToken); - } + => OperationExecutor.ExecuteWriteOperation(CreateCreateCollectionOperation(name, options), + _writeOperationOptions, + session, cancellationToken); private Task CreateCollectionHelperAsync(IClientSessionHandle session, string name, CreateCollectionOptions options, CancellationToken cancellationToken) - { - options = options ?? new CreateCollectionOptions(); - - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateCreateCollectionOperation(name, options, translationOptions); - return ExecuteWriteOperationAsync(session, operation, cancellationToken); - } + => OperationExecutor.ExecuteWriteOperationAsync(CreateCreateCollectionOperation(name, options), + _writeOperationOptions, + session, cancellationToken); - private IWriteOperation CreateCreateCollectionOperation(string name, CreateCollectionOptions options, ExpressionTranslationOptions translationOptions) + private IWriteOperation CreateCreateCollectionOperation(string name, CreateCollectionOptions options) { + options ??= new CreateCollectionOptions(); + var translationOptions = _client.Settings.TranslationOptions; var serializerRegistry = options.SerializerRegistry ?? BsonSerializer.SerializerRegistry; var documentSerializer = options.DocumentSerializer ?? serializerRegistry.GetSerializer(); @@ -700,9 +633,14 @@ private CreateViewOperation CreateCreateViewOperation( string viewName, string viewOn, PipelineDefinition pipeline, - CreateViewOptions options, - ExpressionTranslationOptions translationOptions) + CreateViewOptions options) { + Ensure.IsNotNull(viewName, nameof(viewName)); + Ensure.IsNotNull(viewOn, nameof(viewOn)); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new CreateViewOptions(); + + var translationOptions = _client.Settings.TranslationOptions; var serializerRegistry = options.SerializerRegistry ?? BsonSerializer.SerializerRegistry; var documentSerializer = options.DocumentSerializer ?? serializerRegistry.GetSerializer(); var pipelineDocuments = pipeline.Render(new (documentSerializer, serializerRegistry, translationOptions: translationOptions)).Documents; @@ -715,9 +653,9 @@ private CreateViewOperation CreateCreateViewOperation( private IWriteOperation CreateDropCollectionOperation(string name, DropCollectionOptions options, IClientSessionHandle session, CancellationToken cancellationToken) { + Ensure.IsNotNullOrEmpty(name, nameof(name)); var collectionNamespace = new CollectionNamespace(_databaseNamespace, name); - - options = options ?? new DropCollectionOptions(); + options ??= new DropCollectionOptions(); var encryptedFieldsMap = _client.Settings?.AutoEncryptionOptions?.EncryptedFieldsMap; if (!EncryptedCollectionHelper.TryGetEffectiveEncryptedFields(collectionNamespace, options.EncryptedFields, encryptedFieldsMap, out var effectiveEncryptedFields)) @@ -747,6 +685,7 @@ private IWriteOperation CreateDropCollectionOperation(string name, private async Task> CreateDropCollectionOperationAsync(string name, DropCollectionOptions options, IClientSessionHandle session, CancellationToken cancellationToken) { + Ensure.IsNotNullOrEmpty(name, nameof(name)); var collectionNamespace = new CollectionNamespace(_databaseNamespace, name); options = options ?? new DropCollectionOptions(); @@ -778,9 +717,10 @@ private async Task> CreateDropCollectionOperationA }); } - private ListCollectionsOperation CreateListCollectionNamesOperation(ListCollectionNamesOptions options, RenderArgs renderArgs) + private ListCollectionsOperation CreateListCollectionNamesOperation(ListCollectionNamesOptions options) { var messageEncoderSettings = GetMessageEncoderSettings(); + var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); return new ListCollectionsOperation(_databaseNamespace, messageEncoderSettings) { AuthorizedCollections = options?.AuthorizedCollections, @@ -791,8 +731,9 @@ private ListCollectionsOperation CreateListCollectionNamesOperation(ListCollecti }; } - private ListCollectionsOperation CreateListCollectionsOperation(ListCollectionsOptions options, RenderArgs renderArgs) + private ListCollectionsOperation CreateListCollectionsOperation(ListCollectionsOptions options) { + var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); var messageEncoderSettings = GetMessageEncoderSettings(); return new ListCollectionsOperation(_databaseNamespace, messageEncoderSettings) { @@ -803,23 +744,12 @@ private ListCollectionsOperation CreateListCollectionsOperation(ListCollectionsO }; } - private IReadBinding CreateReadBinding(IClientSessionHandle session, ReadPreference readPreference) - { - if (session.IsInTransaction && readPreference.ReadPreferenceMode != ReadPreferenceMode.Primary) - { - throw new InvalidOperationException("Read preference in a transaction must be primary."); - } - - return ChannelPinningHelper.CreateReadBinding(_cluster, session.WrappedCoreSession.Fork(), readPreference); - } - - private IWriteBindingHandle CreateReadWriteBinding(IClientSessionHandle session) - { - return ChannelPinningHelper.CreateReadWriteBinding(_cluster, session.WrappedCoreSession.Fork()); - } - private RenameCollectionOperation CreateRenameCollectionOperation(string oldName, string newName, RenameCollectionOptions options) { + Ensure.IsNotNullOrEmpty(oldName, nameof(oldName)); + Ensure.IsNotNullOrEmpty(newName, nameof(newName)); + options ??= new RenameCollectionOptions(); + var messageEncoderSettings = GetMessageEncoderSettings(); return new RenameCollectionOperation( new CollectionNamespace(_databaseNamespace, oldName), @@ -833,6 +763,7 @@ private RenameCollectionOperation CreateRenameCollectionOperation(string oldName private ReadCommandOperation CreateRunCommandOperation(Command command) { + Ensure.IsNotNull(command, nameof(command)); var renderedCommand = command.Render(_settings.SerializerRegistry); var messageEncoderSettings = GetMessageEncoderSettings(); return new ReadCommandOperation(_databaseNamespace, renderedCommand.Document, renderedCommand.ResultSerializer, messageEncoderSettings) @@ -843,9 +774,11 @@ private ReadCommandOperation CreateRunCommandOperation(Command private ChangeStreamOperation CreateChangeStreamOperation( PipelineDefinition, TResult> pipeline, - ChangeStreamOptions options, - ExpressionTranslationOptions translationOptions) + ChangeStreamOptions options) { + Ensure.IsNotNull(pipeline, nameof(pipeline)); + var translationOptions = _client.Settings.TranslationOptions; + return ChangeStreamHelper.CreateChangeStreamOperation( this, pipeline, @@ -861,50 +794,6 @@ private IEnumerable ExtractCollectionNames(IEnumerable col return collections.Select(collection => collection["name"].AsString); } - private T ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) - { - var readPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, _settings.ReadPreference); - return ExecuteReadOperation(session, operation, readPreference, cancellationToken); - } - - private T ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, ReadPreference readPreference, CancellationToken cancellationToken) - { - using (var binding = CreateReadBinding(session, readPreference)) - { - return _operationExecutor.ExecuteReadOperation(binding, operation, cancellationToken); - } - } - - private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) - { - var readPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, _settings.ReadPreference); - return ExecuteReadOperationAsync(session, operation, readPreference, cancellationToken); - } - - private async Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, ReadPreference readPreference, CancellationToken cancellationToken) - { - using (var binding = CreateReadBinding(session, readPreference)) - { - return await _operationExecutor.ExecuteReadOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } - - private T ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) - { - using (var binding = CreateReadWriteBinding(session)) - { - return _operationExecutor.ExecuteWriteOperation(binding, operation, cancellationToken); - } - } - - private async Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) - { - using (var binding = CreateReadWriteBinding(session)) - { - return await _operationExecutor.ExecuteWriteOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } - private MessageEncoderSettings GetMessageEncoderSettings() { var messageEncoderSettings = new MessageEncoderSettings @@ -932,37 +821,5 @@ private RenderArgs GetRenderArgs(IBsonSerializer(documentSerializer, _settings.SerializerRegistry, translationOptions: translationOptions); } - - private void UsingImplicitSession(Action func, CancellationToken cancellationToken) - { - using (var session = _operationExecutor.StartImplicitSession(cancellationToken)) - { - func(session); - } - } - - private TResult UsingImplicitSession(Func func, CancellationToken cancellationToken) - { - using (var session = _operationExecutor.StartImplicitSession(cancellationToken)) - { - return func(session); - } - } - - private async Task UsingImplicitSessionAsync(Func funcAsync, CancellationToken cancellationToken) - { - using (var session = await _operationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - await funcAsync(session).ConfigureAwait(false); - } - } - - private async Task UsingImplicitSessionAsync(Func> funcAsync, CancellationToken cancellationToken) - { - using (var session = await _operationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - return await funcAsync(session).ConfigureAwait(false); - } - } } } diff --git a/src/MongoDB.Driver/OperationExecutor.cs b/src/MongoDB.Driver/OperationExecutor.cs index cc521cdf762..898963fc73f 100644 --- a/src/MongoDB.Driver/OperationExecutor.cs +++ b/src/MongoDB.Driver/OperationExecutor.cs @@ -13,8 +13,10 @@ * limitations under the License. */ +using System; using System.Threading; using System.Threading.Tasks; +using MongoDB.Driver.Core; using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Operations; @@ -22,41 +24,135 @@ namespace MongoDB.Driver { internal sealed class OperationExecutor : IOperationExecutor { - private readonly MongoClient _client; + private readonly IMongoClient _client; - public OperationExecutor(MongoClient client) + public OperationExecutor(IMongoClient client) { _client = client; } - public TResult ExecuteReadOperation(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken) + public TResult ExecuteReadOperation( + IReadOperation operation, + ReadOperationOptions options, + IClientSessionHandle session, + CancellationToken cancellationToken) { - return operation.Execute(binding, cancellationToken); + bool isOwnSession = session == null; + session ??= StartImplicitSession(cancellationToken); + + try + { + var readPreference = options.GetEffectiveReadPreference(session); + using var binding = CreateReadBinding(session, readPreference); + return operation.Execute(binding, cancellationToken); + } + finally + { + if (isOwnSession) + { + session.Dispose(); + } + } } - public async Task ExecuteReadOperationAsync(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken) + public async Task ExecuteReadOperationAsync( + IReadOperation operation, + ReadOperationOptions options, + IClientSessionHandle session, + CancellationToken cancellationToken) { - return await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false); + bool isOwnSession = session == null; + session ??= await StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false); + + try + { + var readPreference = options.GetEffectiveReadPreference(session); + using var binding = CreateReadBinding(session, readPreference); + return await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false); + } + finally + { + if (isOwnSession) + { + session.Dispose(); + } + } } - public TResult ExecuteWriteOperation(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken) + public TResult ExecuteWriteOperation( + IWriteOperation operation, + WriteOperationOptions options, + IClientSessionHandle session, + CancellationToken cancellationToken) { - return operation.Execute(binding, cancellationToken); + bool isOwnSession = session == null; + session ??= StartImplicitSession(cancellationToken); + + try + { + using var binding = CreateReadWriteBinding(session); + return operation.Execute(binding, cancellationToken); + } + finally + { + if (isOwnSession) + { + session.Dispose(); + } + } } - public async Task ExecuteWriteOperationAsync(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken) + public async Task ExecuteWriteOperationAsync( + IWriteOperation operation, + WriteOperationOptions options, + IClientSessionHandle session, + CancellationToken cancellationToken) { - return await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false); + bool isOwnSession = session == null; + session ??= await StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false); + + try + { + using var binding = CreateReadWriteBinding(session); + return await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false); + } + finally + { + if (isOwnSession) + { + session.Dispose(); + } + } } public IClientSessionHandle StartImplicitSession(CancellationToken cancellationToken) + => StartImplicitSession(); + + public Task StartImplicitSessionAsync(CancellationToken cancellationToken) + => Task.FromResult(StartImplicitSession()); + + private IReadBindingHandle CreateReadBinding(IClientSessionHandle session, ReadPreference readPreference) { - return _client.StartImplicitSession(cancellationToken); + if (session.IsInTransaction && readPreference.ReadPreferenceMode != ReadPreferenceMode.Primary) + { + throw new InvalidOperationException("Read preference in a transaction must be primary."); + } + + // TODO: CreateReadBinding from MongoClient did not used ChannelPinningHelper, double-check if it's OK to start using it + return ChannelPinningHelper.CreateReadBinding(_client.GetClusterInternal(), session.WrappedCoreSession.Fork(), readPreference); } - public Task StartImplicitSessionAsync(CancellationToken cancellationToken) + private IReadWriteBindingHandle CreateReadWriteBinding(IClientSessionHandle session) + { + // TODO: CreateReadWriteBinding from MongoClient did not used ChannelPinningHelper, double-check if it's OK to start using it + return ChannelPinningHelper.CreateReadWriteBinding(_client.GetClusterInternal(), session.WrappedCoreSession.Fork()); + } + + private IClientSessionHandle StartImplicitSession() { - return _client.StartImplicitSessionAsync(cancellationToken); + var options = new ClientSessionOptions { CausalConsistency = false, Snapshot = false }; + ICoreSessionHandle coreSession = _client.GetClusterInternal().StartSession(options.ToCore(isImplicit: true)); + return new ClientSessionHandle(_client, options, coreSession); } } } diff --git a/src/MongoDB.Driver/ReadOperationOptions.cs b/src/MongoDB.Driver/ReadOperationOptions.cs new file mode 100644 index 00000000000..ebb7fb9fe37 --- /dev/null +++ b/src/MongoDB.Driver/ReadOperationOptions.cs @@ -0,0 +1,42 @@ +/* Copyright 2010-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. + */ + +namespace MongoDB.Driver +{ + internal record class ReadOperationOptions(ReadPreference ExplicitReadPreference = null, ReadPreference DefaultReadPreference = null); + + internal static class ReadOperationOptionsExtensions + { + public static ReadPreference GetEffectiveReadPreference(this ReadOperationOptions options, IClientSessionHandle session) + { + if (options.ExplicitReadPreference != null) + { + return options.ExplicitReadPreference; + } + + if (session.IsInTransaction) + { + var transactionReadPreference = session.WrappedCoreSession.CurrentTransaction.TransactionOptions.ReadPreference; + if (transactionReadPreference != null) + { + return transactionReadPreference; + } + } + + return options.DefaultReadPreference ?? ReadPreference.Primary; + } + } +} + diff --git a/src/MongoDB.Driver/ReadPreferenceResolver.cs b/src/MongoDB.Driver/ReadPreferenceResolver.cs deleted file mode 100644 index 6fbc9ed27f8..00000000000 --- a/src/MongoDB.Driver/ReadPreferenceResolver.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2018-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. -*/ - -namespace MongoDB.Driver -{ - internal static class ReadPreferenceResolver - { - public static ReadPreference GetEffectiveReadPreference( - IClientSessionHandle session, - ReadPreference explicitReadPreference, - ReadPreference defaultReadPreference) - { - if (explicitReadPreference != null) - { - return explicitReadPreference; - } - - if (session.IsInTransaction) - { - var transactionReadPreference = session.WrappedCoreSession.CurrentTransaction.TransactionOptions.ReadPreference; - if (transactionReadPreference != null) - { - return transactionReadPreference; - } - } - - return defaultReadPreference ?? ReadPreference.Primary; - } - } -} diff --git a/src/MongoDB.Driver/WriteOperationOptions.cs b/src/MongoDB.Driver/WriteOperationOptions.cs new file mode 100644 index 00000000000..29c8d1118df --- /dev/null +++ b/src/MongoDB.Driver/WriteOperationOptions.cs @@ -0,0 +1,20 @@ +/* Copyright 2010-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. + */ + +namespace MongoDB.Driver +{ + internal record class WriteOperationOptions(); +} + diff --git a/tests/MongoDB.Driver.Tests/MockOperationExecutor.cs b/tests/MongoDB.Driver.Tests/MockOperationExecutor.cs index 6c9c18b6410..ad4df2bc3e8 100644 --- a/tests/MongoDB.Driver.Tests/MockOperationExecutor.cs +++ b/tests/MongoDB.Driver.Tests/MockOperationExecutor.cs @@ -58,15 +58,19 @@ public void EnqueueException(Exception exception) _results.Enqueue(exception); } - public TResult ExecuteReadOperation(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken) + public TResult ExecuteReadOperation( + IReadOperation operation, + ReadOperationOptions readOperationOptions, + IClientSessionHandle session = null, + CancellationToken cancellationToken = default) { _calls.Enqueue(new ReadCall { - Binding = binding, Operation = operation, CancellationToken = cancellationToken, - SessionId = binding.Session.Id, - UsedImplicitSession = binding.Session.IsImplicit + Options = readOperationOptions, + SessionId = session?.WrappedCoreSession.Id, + UsedImplicitSession = session == null || session.IsImplicit }); if (_results.Count > 0) @@ -85,11 +89,15 @@ public TResult ExecuteReadOperation(IReadBinding binding, IReadOperatio return default(TResult); } - public Task ExecuteReadOperationAsync(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken) + public Task ExecuteReadOperationAsync( + IReadOperation operation, + ReadOperationOptions readOperationOptions, + IClientSessionHandle session = null, + CancellationToken cancellationToken = default) { try { - var result = ExecuteReadOperation(binding, operation, cancellationToken); + var result = ExecuteReadOperation(operation, readOperationOptions, session, cancellationToken); return Task.FromResult(result); } catch (Exception ex) @@ -100,15 +108,19 @@ public Task ExecuteReadOperationAsync(IReadBinding binding, IR } } - public TResult ExecuteWriteOperation(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken) + public TResult ExecuteWriteOperation( + IWriteOperation operation, + WriteOperationOptions writeOperationOptions, + IClientSessionHandle session = null, + CancellationToken cancellationToken = default) { _calls.Enqueue(new WriteCall { - Binding = binding, Operation = operation, CancellationToken = cancellationToken, - SessionId = binding.Session.Id, - UsedImplicitSession = binding.Session.IsImplicit + Options = writeOperationOptions, + SessionId = session?.WrappedCoreSession.Id, + UsedImplicitSession = session == null || session.IsImplicit }); if (_results.Count > 0) @@ -127,11 +139,15 @@ public TResult ExecuteWriteOperation(IWriteBinding binding, IWriteOpera return default(TResult); } - public Task ExecuteWriteOperationAsync(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken) + public Task ExecuteWriteOperationAsync( + IWriteOperation operation, + WriteOperationOptions writeOperationOptions, + IClientSessionHandle session = null, + CancellationToken cancellationToken = default) { try { - var result = ExecuteWriteOperation(binding, operation, cancellationToken); + var result = ExecuteWriteOperation(operation, writeOperationOptions, session, cancellationToken); return Task.FromResult(result); } catch (Exception ex) @@ -193,18 +209,18 @@ public Task StartImplicitSessionAsync(CancellationToken ca public class ReadCall { - public IReadBinding Binding { get; set; } public IReadOperation Operation { get; set; } public CancellationToken CancellationToken { get; set; } + public ReadOperationOptions Options { get; set; } public BsonDocument SessionId { get; set; } public bool UsedImplicitSession { get; set; } } public class WriteCall { - public IWriteBinding Binding { get; set; } public IWriteOperation Operation { get; set; } public CancellationToken CancellationToken { get; set; } + public WriteOperationOptions Options { get; set; } public BsonDocument SessionId { get; set; } public bool UsedImplicitSession { get; set; } } diff --git a/tests/MongoDB.Driver.Tests/MongoClientTests.cs b/tests/MongoDB.Driver.Tests/MongoClientTests.cs index d1f06516a2f..6fbb42a8127 100644 --- a/tests/MongoDB.Driver.Tests/MongoClientTests.cs +++ b/tests/MongoDB.Driver.Tests/MongoClientTests.cs @@ -118,9 +118,6 @@ public void Disposed_client_should_throw_on_member_access() var exception = Record.Exception(() => client.Cluster); exception.Should().BeOfType(); - - exception = Record.Exception(() => client.StartImplicitSession(default)); - exception.Should().BeOfType(); } [Theory] diff --git a/tests/MongoDB.Driver.Tests/MongoDatabasTests.cs b/tests/MongoDB.Driver.Tests/MongoDatabaseTests.cs similarity index 98% rename from tests/MongoDB.Driver.Tests/MongoDatabasTests.cs rename to tests/MongoDB.Driver.Tests/MongoDatabaseTests.cs index 9b35886d6b9..1a10811ef6b 100644 --- a/tests/MongoDB.Driver.Tests/MongoDatabasTests.cs +++ b/tests/MongoDB.Driver.Tests/MongoDatabaseTests.cs @@ -1095,8 +1095,8 @@ public void RunCommand_should_default_to_ReadPreference_primary( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(ReadPreference.Primary); + call.Options.ExplicitReadPreference.Should().BeNull(); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); @@ -1142,8 +1142,8 @@ public void RunCommand_should_use_the_provided_ReadPreference( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(readPreference); + call.Options.ExplicitReadPreference.Should().Be(readPreference); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); @@ -1188,8 +1188,8 @@ public void RunCommand_should_run_a_non_read_command( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(ReadPreference.Primary); + call.Options.ExplicitReadPreference.Should().BeNull(); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); @@ -1234,8 +1234,8 @@ public void RunCommand_should_run_a_json_command( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(ReadPreference.Primary); + call.Options.ExplicitReadPreference.Should().BeNull(); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); @@ -1280,8 +1280,8 @@ public void RunCommand_should_run_a_serialized_command( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(ReadPreference.Primary); + call.Options.ExplicitReadPreference.Should().BeNull(); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); diff --git a/tests/MongoDB.Driver.Tests/OperationExecutorTests.cs b/tests/MongoDB.Driver.Tests/OperationExecutorTests.cs new file mode 100644 index 00000000000..60fadc85f1e --- /dev/null +++ b/tests/MongoDB.Driver.Tests/OperationExecutorTests.cs @@ -0,0 +1,117 @@ +/* Copyright 2010-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. + */ + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver.Core.Bindings; +using MongoDB.Driver.Core.Clusters; +using MongoDB.Driver.Core.Operations; +using MongoDB.TestHelpers.XunitExtensions; +using Moq; +using Xunit; + +namespace MongoDB.Driver.Tests +{ + public class OperationExecutorTests + { + [Theory] + [ParameterAttributeData] + public async Task StartImplicitSessionTests([Values(true, false)]bool isAsync) + { + var subject = CreateSubject(out var clusterMock, out _); + if (isAsync) + { + await subject.StartImplicitSessionAsync(CancellationToken.None); + } + else + { + subject.StartImplicitSession(CancellationToken.None); + } + + clusterMock.Verify(c => c.StartSession(It.Is(v => v.IsImplicit && v.IsCausallyConsistent == false && v.IsSnapshot == false))); + } + + [Theory] + [MemberData(nameof(ImplicitSessionTestCases))] + public async Task ExecuteReadOperationShouldStartAndDisposeImplicitSessionIfNeeded(bool shouldCreateSession, bool isAsync, IClientSessionHandle session) + { + var subject = CreateSubject(out var clusterMock, out var implicitSessionMock); + var readOperation = Mock.Of>(); + var readOperationOptions = new ReadOperationOptions(); + + _ = isAsync ? + await subject.ExecuteReadOperationAsync(readOperation, readOperationOptions, session, CancellationToken.None) : + subject.ExecuteReadOperation(readOperation, readOperationOptions, session, CancellationToken.None); + + var times = shouldCreateSession ? Times.Once() : Times.Never(); + clusterMock.Verify(c => c.StartSession(It.Is(v => v.IsImplicit && v.IsCausallyConsistent == false && v.IsSnapshot == false)), times); + implicitSessionMock.Verify(s => s.Dispose(), times); + } + + [Theory] + [MemberData(nameof(ImplicitSessionTestCases))] + public async Task ExecuteWriteOperationShouldStartAndDisposeImplicitSessionIfNeeded(bool shouldCreateSession, bool isAsync, IClientSessionHandle session) + { + var subject = CreateSubject(out var clusterMock, out var implicitSessionMock); + var writeOperation = Mock.Of>(); + var writeOperationOptions = new WriteOperationOptions(); + + _ = isAsync ? + await subject.ExecuteWriteOperationAsync(writeOperation, writeOperationOptions, session, CancellationToken.None) : + subject.ExecuteWriteOperation(writeOperation, writeOperationOptions, session, CancellationToken.None); + + var times = shouldCreateSession ? Times.Once() : Times.Never(); + clusterMock.Verify(c => c.StartSession(It.Is(v => v.IsImplicit && v.IsCausallyConsistent == false && v.IsSnapshot == false)), times); + implicitSessionMock.Verify(s => s.Dispose(), times); + } + + private static IEnumerable ImplicitSessionTestCases() + { + yield return [ true, false, null ]; + yield return [ true, true, null ]; + + var implicitSession = new Mock(); + implicitSession.SetupGet(s => s.IsImplicit).Returns(true); + implicitSession.SetupGet(s => s.WrappedCoreSession).Returns(CreateCoreSessionMock(true).Object); + yield return [ false, false, implicitSession.Object ]; + yield return [ false, true, implicitSession.Object ]; + + var regularSession = new Mock(); + regularSession.SetupGet(s => s.WrappedCoreSession).Returns(CreateCoreSessionMock(false).Object); + yield return [ false, false, regularSession.Object ]; + yield return [ false, true, regularSession.Object ]; + } + + private OperationExecutor CreateSubject(out Mock clusterMock, out Mock implicitSessionMock) + { + implicitSessionMock = CreateCoreSessionMock(true); + clusterMock = new Mock(); + clusterMock.Setup(c => c.StartSession(It.IsAny())).Returns(implicitSessionMock.Object); + var clientMock = new Mock(); + clientMock.SetupGet(c => c.Cluster).Returns(clusterMock.Object); + return new OperationExecutor(clientMock.Object); + } + + private static Mock CreateCoreSessionMock(bool isImplicit) + { + var sessionMock = new Mock(); + sessionMock.SetupGet(s => s.IsImplicit).Returns(isImplicit); + sessionMock.Setup(s => s.Fork()).Returns(() => CreateCoreSessionMock(isImplicit).Object); + return sessionMock; + } + } +} + diff --git a/tests/MongoDB.Driver.Tests/ReadOperationOptionsTests.cs b/tests/MongoDB.Driver.Tests/ReadOperationOptionsTests.cs new file mode 100644 index 00000000000..585b2e442ff --- /dev/null +++ b/tests/MongoDB.Driver.Tests/ReadOperationOptionsTests.cs @@ -0,0 +1,70 @@ +/* Copyright 2010-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. + */ + +using System.Collections.Generic; +using FluentAssertions; +using MongoDB.Driver.Core.Bindings; +using Moq; +using Xunit; + +namespace MongoDB.Driver.Tests +{ + public class ReadOperationOptionsTests + { + [Theory] + [MemberData(nameof(GetEffectiveReadPreferenceTestCases))] + public void GetEffectiveReadPreferenceTests( + ReadPreference expectedReadPreference, + ReadPreference explicitReadPreference, + ReadPreference defaultReadPreference, + IClientSessionHandle session) + { + var readOperationOptions = new ReadOperationOptions(explicitReadPreference, defaultReadPreference); + var result = readOperationOptions.GetEffectiveReadPreference(session); + + result.Should().Be(expectedReadPreference); + } + + public static IEnumerable GetEffectiveReadPreferenceTestCases() + { + var noTransactionSession = CreateSessionMock(null); + var inTransactionSession = CreateSessionMock(new TransactionOptions(readPreference: ReadPreference.Nearest)); + var inTransactionNoPreferenceSession = CreateSessionMock(new TransactionOptions()); + + yield return [ReadPreference.Primary, null, null, noTransactionSession]; + yield return [ReadPreference.Secondary, ReadPreference.Secondary, ReadPreference.SecondaryPreferred, noTransactionSession]; + yield return [ReadPreference.Secondary, ReadPreference.Secondary, ReadPreference.SecondaryPreferred, inTransactionSession]; + yield return [ReadPreference.SecondaryPreferred, null, ReadPreference.SecondaryPreferred, noTransactionSession]; + yield return [ReadPreference.Nearest, null, ReadPreference.SecondaryPreferred, inTransactionSession]; + yield return [ReadPreference.Primary, null, null, inTransactionNoPreferenceSession]; + yield return [ReadPreference.SecondaryPreferred, null, ReadPreference.SecondaryPreferred, inTransactionNoPreferenceSession]; + } + + private static IClientSessionHandle CreateSessionMock(TransactionOptions transactionOptions) + { + var sessionMock = new Mock(); + if (transactionOptions != null) + { + sessionMock.SetupGet(s => s.IsInTransaction).Returns(true); + var coreSessionMock = new Mock(); + coreSessionMock.SetupGet(s => s.CurrentTransaction).Returns(new CoreTransaction(0, transactionOptions)); + sessionMock.SetupGet(s => s.WrappedCoreSession).Returns(coreSessionMock.Object); + } + + return sessionMock.Object; + } + } +} +