diff --git a/src/MongoDB.Driver.Core/Core/Misc/Feature.cs b/src/MongoDB.Driver.Core/Core/Misc/Feature.cs index bb01704775d..0e33f526338 100644 --- a/src/MongoDB.Driver.Core/Core/Misc/Feature.cs +++ b/src/MongoDB.Driver.Core/Core/Misc/Feature.cs @@ -41,6 +41,7 @@ public class Feature private static readonly Feature __aggregateMerge = new Feature("AggregateMerge", WireVersion.Server42); private static readonly Feature __aggregateOut = new Feature("AggregateOut", WireVersion.Server26); private static readonly Feature __aggregateOutOnSecondary = new Feature("AggregateOutOnSecondary", WireVersion.Server50); + private static readonly Feature __aggregateOutTimeSeries = new Feature("AggregateOutTimeSeries", WireVersion.Server70); private static readonly Feature __aggregateOutToDifferentDatabase = new Feature("AggregateOutToDifferentDatabase", WireVersion.Server44); private static readonly Feature __aggregateToString = new Feature("AggregateToString", WireVersion.Server40); private static readonly Feature __aggregateUnionWith = new Feature("AggregateUnionWith", WireVersion.Server44); @@ -237,10 +238,15 @@ public class Feature public static Feature AggregateOut => __aggregateOut; /// - /// Gets the aggregate out on secondary feature, + /// Gets the aggregate out on secondary feature. /// public static Feature AggregateOutOnSecondary => __aggregateOutOnSecondary; + /// + /// Gets the aggregate out to time series feature. + /// + public static Feature AggregateOutTimeSeries => __aggregateOutTimeSeries; + /// /// Gets the aggregate out to a different database feature. /// diff --git a/src/MongoDB.Driver.Core/Core/Operations/AggregateToCollectionOperation.cs b/src/MongoDB.Driver.Core/Core/Operations/AggregateToCollectionOperation.cs index ca63d77f0ce..43871ae08b8 100644 --- a/src/MongoDB.Driver.Core/Core/Operations/AggregateToCollectionOperation.cs +++ b/src/MongoDB.Driver.Core/Core/Operations/AggregateToCollectionOperation.cs @@ -330,7 +330,7 @@ private IReadOnlyList SimplifyOutStageIfOutputDatabaseIsSameAsInpu { var lastStage = pipeline.Last(); var lastStageName = lastStage.GetElement(0).Name; - if (lastStageName == "$out" && lastStage["$out"] is BsonDocument outDocument) + if (lastStageName == "$out" && lastStage["$out"] is BsonDocument outDocument && !outDocument.Contains("timeseries")) { if (outDocument.TryGetValue("db", out var db) && db.IsString && outDocument.TryGetValue("coll", out var coll) && coll.IsString) diff --git a/src/MongoDB.Driver/AggregateFluent.cs b/src/MongoDB.Driver/AggregateFluent.cs index 75b53d2f1a2..b48ece1f8d4 100644 --- a/src/MongoDB.Driver/AggregateFluent.cs +++ b/src/MongoDB.Driver/AggregateFluent.cs @@ -210,6 +210,20 @@ public override IAsyncCursor Out(string collectionName, CancellationTok return Out(outputCollection, cancellationToken); } + public override IAsyncCursor Out(IMongoCollection outputCollection, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken) + { + Ensure.IsNotNull(outputCollection, nameof(outputCollection)); + var aggregate = WithPipeline(_pipeline.Out(outputCollection, timeSeriesOptions)); + return aggregate.ToCursor(cancellationToken); + } + + public override IAsyncCursor Out(string collectionName, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken) + { + Ensure.IsNotNull(collectionName, nameof(collectionName)); + var outputCollection = Database.GetCollection(collectionName); + return Out(outputCollection, timeSeriesOptions, cancellationToken); + } + public override Task> OutAsync(IMongoCollection outputCollection, CancellationToken cancellationToken) { Ensure.IsNotNull(outputCollection, nameof(outputCollection)); @@ -224,6 +238,20 @@ public override Task> OutAsync(string collectionName, Canc return OutAsync(outputCollection, cancellationToken); } + public override Task> OutAsync(IMongoCollection outputCollection, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken) + { + Ensure.IsNotNull(outputCollection, nameof(outputCollection)); + var aggregate = WithPipeline(_pipeline.Out(outputCollection, timeSeriesOptions)); + return aggregate.ToCursorAsync(cancellationToken); + } + + public override Task> OutAsync(string collectionName, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken) + { + Ensure.IsNotNull(collectionName, nameof(collectionName)); + var outputCollection = Database.GetCollection(collectionName); + return OutAsync(outputCollection, timeSeriesOptions, cancellationToken); + } + public override IAggregateFluent Project(ProjectionDefinition projection) { return WithPipeline(_pipeline.Project(projection)); diff --git a/src/MongoDB.Driver/AggregateFluentBase.cs b/src/MongoDB.Driver/AggregateFluentBase.cs index b0619e4e633..4f26aecee87 100644 --- a/src/MongoDB.Driver/AggregateFluentBase.cs +++ b/src/MongoDB.Driver/AggregateFluentBase.cs @@ -192,6 +192,18 @@ public virtual IAsyncCursor Out(string collectionName, CancellationToke throw new NotImplementedException(); } + /// + public virtual IAsyncCursor Out(IMongoCollection outputCollection, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public virtual IAsyncCursor Out(string collectionName, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + /// public virtual Task> OutAsync(IMongoCollection outputCollection, CancellationToken cancellationToken) { @@ -201,6 +213,15 @@ public virtual Task> OutAsync(IMongoCollection ou /// public abstract Task> OutAsync(string collectionName, CancellationToken cancellationToken); + /// + public virtual Task> OutAsync(IMongoCollection outputCollection, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public abstract Task> OutAsync(string collectionName, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken); + /// public abstract IAggregateFluent Project(ProjectionDefinition projection); diff --git a/src/MongoDB.Driver/IAggregateFluent.cs b/src/MongoDB.Driver/IAggregateFluent.cs index 8ad1ba38a0f..c2547b34506 100644 --- a/src/MongoDB.Driver/IAggregateFluent.cs +++ b/src/MongoDB.Driver/IAggregateFluent.cs @@ -303,6 +303,24 @@ IAggregateFluent LookupA cursor. IAsyncCursor Out(string collectionName, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Appends an out stage to the pipeline and executes it, and then returns a cursor to read the contents of the output collection. + /// + /// The output collection. + /// The time series options. + /// The cancellation token. + /// A cursor. + IAsyncCursor Out(IMongoCollection outputCollection, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Appends an out stage to the pipeline and executes it, and then returns a cursor to read the contents of the output collection. + /// + /// Name of the collection. + /// The time series options. + /// The cancellation token. + /// A cursor. + IAsyncCursor Out(string collectionName, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken = default(CancellationToken)); + /// /// Appends an out stage to the pipeline and executes it, and then returns a cursor to read the contents of the output collection. /// @@ -319,6 +337,24 @@ IAggregateFluent LookupA Task whose result is a cursor. Task> OutAsync(string collectionName, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Appends an out stage to the pipeline and executes it, and then returns a cursor to read the contents of the output collection. + /// + /// The output collection. + /// The time series options. + /// The cancellation token. + /// A Task whose result is a cursor. + Task> OutAsync(IMongoCollection outputCollection, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Appends an out stage to the pipeline and executes it, and then returns a cursor to read the contents of the output collection. + /// + /// Name of the collection. + /// The time series options. + /// The cancellation token. + /// A Task whose result is a cursor. + Task> OutAsync(string collectionName, TimeSeriesOptions timeSeriesOptions, CancellationToken cancellationToken = default(CancellationToken)); + /// /// Appends a project stage to the pipeline. /// diff --git a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs index dd358e07558..4d89ad32c54 100644 --- a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs +++ b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs @@ -960,14 +960,16 @@ public static PipelineDefinition MergeThe type of the output documents. /// The pipeline. /// The output collection. + /// The time series options. /// A new pipeline with an additional stage. /// public static PipelineDefinition Out( this PipelineDefinition pipeline, - IMongoCollection outputCollection) + IMongoCollection outputCollection, + TimeSeriesOptions timeSeriesOptions = null) { Ensure.IsNotNull(pipeline, nameof(pipeline)); - return pipeline.AppendStage(PipelineStageDefinitionBuilder.Out(outputCollection)); + return pipeline.AppendStage(PipelineStageDefinitionBuilder.Out(outputCollection, timeSeriesOptions)); } /// @@ -1111,7 +1113,7 @@ public static PipelineDefinition ReplaceWith /// /// Flag that specifies whether to return a detailed breakdown - /// of the score for each document in the result. + /// of the score for each document in the result. /// /// A new pipeline with an additional stage. public static PipelineDefinition Search( diff --git a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs index 771c3efdc13..e88172bd0b9 100644 --- a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs +++ b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs @@ -1244,14 +1244,21 @@ public static PipelineStageDefinition OfType( /// /// The type of the input documents. /// The output collection. + /// The time series options. /// The stage. public static PipelineStageDefinition Out( - IMongoCollection outputCollection) + IMongoCollection outputCollection, + TimeSeriesOptions timeSeriesOptions = null) { Ensure.IsNotNull(outputCollection, nameof(outputCollection)); var outputDatabaseName = outputCollection.Database.DatabaseNamespace.DatabaseName; var outputCollectionName = outputCollection.CollectionNamespace.CollectionName; - var outDocument = new BsonDocument { { "db", outputDatabaseName }, { "coll", outputCollectionName } }; + var outDocument = new BsonDocument + { + { "db", outputDatabaseName }, + { "coll", outputCollectionName }, + { "timeseries", () => timeSeriesOptions.ToBsonDocument(), timeSeriesOptions != null} + }; return new BsonDocumentPipelineStageDefinition(new BsonDocument("$out", outDocument)); } @@ -1331,7 +1338,7 @@ public static PipelineStageDefinition Project( /// /// /// Flag that specifies whether to return a detailed breakdown - /// of the score for each document in the result. + /// of the score for each document in the result. /// /// The stage. public static PipelineStageDefinition Search( diff --git a/tests/MongoDB.Driver.Core.Tests/Core/Operations/AggregateToCollectionOperationTests.cs b/tests/MongoDB.Driver.Core.Tests/Core/Operations/AggregateToCollectionOperationTests.cs index 65b4a9ab110..b45edc47149 100644 --- a/tests/MongoDB.Driver.Core.Tests/Core/Operations/AggregateToCollectionOperationTests.cs +++ b/tests/MongoDB.Driver.Core.Tests/Core/Operations/AggregateToCollectionOperationTests.cs @@ -607,6 +607,43 @@ public void Execute_should_return_expected_result( result.Should().HaveCount(1); } + [Theory] + [ParameterAttributeData] + public void Execute_to_time_series_collection_should_work( + [Values(false, true)] bool usingDifferentOutputDatabase, + [Values(false, true)] bool async) + { + RequireServer.Check().Supports(Feature.AggregateOutTimeSeries); + + var pipeline = new List { BsonDocument.Parse("{ $match : { _id : 1 } }") }; + var inputDatabaseName = _databaseNamespace.DatabaseName; + var inputCollectionName = _collectionNamespace.CollectionName; + var outputDatabaseName = usingDifferentOutputDatabase ? $"{inputDatabaseName}-outputdatabase-timeseries" : inputDatabaseName; + var outputCollectionName = $"{inputCollectionName}-outputcollection-timeseries"; + + pipeline.Add(new BsonDocument { {"$set", new BsonDocument { {"time", DateTime.Now } } } } ); + pipeline.Add(BsonDocument.Parse($"{{ $out : {{ db : '{outputDatabaseName}', coll : '{outputCollectionName}', timeseries: {{ timeField: 'time' }} }} }}")); + + EnsureTestData(); + if (usingDifferentOutputDatabase) + { + EnsureDatabaseExists(outputDatabaseName); + } + var subject = new AggregateToCollectionOperation(_collectionNamespace, pipeline, _messageEncoderSettings); + + ExecuteOperation(subject, async); + + var databaseNamespace = new DatabaseNamespace(outputDatabaseName); + var result = ReadAllFromCollection(new CollectionNamespace(databaseNamespace, outputCollectionName), async); + + result.Should().NotBeNull(); + result.Should().HaveCount(1); + + var output = ListCollections(databaseNamespace); + output["cursor"]["firstBatch"][0][0].ToString().Should().Be($"{outputCollectionName}"); // checking name of collection + output["cursor"]["firstBatch"][0][1].ToString().Should().Be("timeseries"); // checking type of collection + } + [Theory] [ParameterAttributeData] public void Execute_should_return_expected_result_when_AllowDiskUse_is_set( diff --git a/tests/MongoDB.Driver.Core.Tests/Core/Operations/OperationTestBase.cs b/tests/MongoDB.Driver.Core.Tests/Core/Operations/OperationTestBase.cs index 1d2ee9bdac7..6e2335dd166 100644 --- a/tests/MongoDB.Driver.Core.Tests/Core/Operations/OperationTestBase.cs +++ b/tests/MongoDB.Driver.Core.Tests/Core/Operations/OperationTestBase.cs @@ -347,6 +347,19 @@ protected void KillOpenTransactions() } } + protected BsonDocument ListCollections(DatabaseNamespace databaseNamespace) + { + var listCollectionsCommand = new BsonDocument + { + { "listCollections", 1 }, { "filter", new BsonDocument { { "type", "timeseries" } } } + }; + + var runCommandOperation = new ReadCommandOperation(databaseNamespace, listCollectionsCommand, + BsonDocumentSerializer.Instance, _messageEncoderSettings); + + return ExecuteOperation(runCommandOperation); + } + protected Profiler Profile(DatabaseNamespace databaseNamespace) { var op = new WriteCommandOperation( diff --git a/tests/MongoDB.Driver.Tests/AggregateFluentTests.cs b/tests/MongoDB.Driver.Tests/AggregateFluentTests.cs index 5f538d76e00..934ac347533 100644 --- a/tests/MongoDB.Driver.Tests/AggregateFluentTests.cs +++ b/tests/MongoDB.Driver.Tests/AggregateFluentTests.cs @@ -710,7 +710,8 @@ public void OfType_should_add_the_expected_stage( [Theory] [ParameterAttributeData] public void Out_with_collection_should_add_the_expected_stage_and_call_Aggregate( - [Values(false, true)] bool async) + [Values(false, true)] bool async, + [Values(true, false)] bool usingTimeSeriesCollection) { var inputDatabase = CreateMockDatabase("inputDatabaseName").Object; var mockInputCollection = CreateMockCollection(inputDatabase, "inputCollectionName"); @@ -720,20 +721,27 @@ public void Out_with_collection_should_add_the_expected_stage_and_call_Aggregate var outputDatabase = CreateMockDatabase("outputDatabaseName").Object; var outputCollection = outputDatabase.GetCollection("outputCollectionName"); + var timeSeriesOption = usingTimeSeriesCollection ? new TimeSeriesOptions("time") : null; + var expectedRenderedOutStage = usingTimeSeriesCollection + ? "{ $out : { db : 'outputDatabaseName', coll : 'outputCollectionName', timeseries: { timeField: 'time' } } }" + : "{ $out : { db : 'outputDatabaseName', coll : 'outputCollectionName' } }"; + Predicate> isExpectedPipeline = pipeline => { var renderedPipeline = RenderPipeline(pipeline); return renderedPipeline.Documents.Count == 2 && renderedPipeline.Documents[0] == BsonDocument.Parse("{ $match : { X : 1 } }") && - renderedPipeline.Documents[1] == BsonDocument.Parse("{ $out : { db : 'outputDatabaseName', coll : 'outputCollectionName' } }") && + renderedPipeline.Documents[1] == BsonDocument.Parse(expectedRenderedOutStage) && renderedPipeline.OutputSerializer.ValueType == typeof(C); }; IAsyncCursor cursor; if (async) { - cursor = subject.OutAsync(outputCollection, CancellationToken.None).GetAwaiter().GetResult(); + cursor = usingTimeSeriesCollection + ? subject.OutAsync(outputCollection, timeSeriesOption, CancellationToken.None).GetAwaiter().GetResult() + : subject.OutAsync(outputCollection, CancellationToken.None).GetAwaiter().GetResult(); mockInputCollection.Verify( c => c.AggregateAsync( @@ -744,7 +752,9 @@ public void Out_with_collection_should_add_the_expected_stage_and_call_Aggregate } else { - cursor = subject.Out(outputCollection, CancellationToken.None); + cursor = usingTimeSeriesCollection + ? subject.Out(outputCollection, timeSeriesOption, CancellationToken.None) + : subject.Out(outputCollection, CancellationToken.None); mockInputCollection.Verify( c => c.Aggregate( @@ -758,7 +768,8 @@ public void Out_with_collection_should_add_the_expected_stage_and_call_Aggregate [Theory] [ParameterAttributeData] public void Out_with_string_should_add_the_expected_stage_and_call_Aggregate( - [Values(false, true)] bool async) + [Values(false, true)] bool async, + [Values(true, false)] bool usingTimeSeriesCollection) { var inputDatabase = CreateMockDatabase("inputDatabaseName").Object; var mockInputCollection = CreateMockCollection(inputDatabase, "inputCollectionName"); @@ -767,20 +778,27 @@ public void Out_with_string_should_add_the_expected_stage_and_call_Aggregate( .Match(Builders.Filter.Eq(c => c.X, 1)); var outputCollectionName = "outputCollectionName"; + var timeSeriesOption = usingTimeSeriesCollection ? new TimeSeriesOptions("time") : null; + var expectedRenderedOutStage = usingTimeSeriesCollection + ? "{ $out : { db : 'inputDatabaseName', coll : 'outputCollectionName', timeseries: { timeField: 'time' } } }" + : "{ $out : { db : 'inputDatabaseName', coll : 'outputCollectionName' } }"; + Predicate> isExpectedPipeline = pipeline => { var renderedPipeline = RenderPipeline(pipeline); return renderedPipeline.Documents.Count == 2 && renderedPipeline.Documents[0] == BsonDocument.Parse("{ $match : { X : 1 } }") && - renderedPipeline.Documents[1] == BsonDocument.Parse("{ $out : { db : 'inputDatabaseName', coll : 'outputCollectionName' } }") && + renderedPipeline.Documents[1] == BsonDocument.Parse(expectedRenderedOutStage) && renderedPipeline.OutputSerializer.ValueType == typeof(C); }; IAsyncCursor cursor; if (async) { - cursor = subject.OutAsync(outputCollectionName, CancellationToken.None).GetAwaiter().GetResult(); + cursor = usingTimeSeriesCollection + ? subject.OutAsync(outputCollectionName, timeSeriesOption, CancellationToken.None).GetAwaiter().GetResult() + : subject.OutAsync(outputCollectionName, CancellationToken.None).GetAwaiter().GetResult(); mockInputCollection.Verify( c => c.AggregateAsync( @@ -791,7 +809,9 @@ public void Out_with_string_should_add_the_expected_stage_and_call_Aggregate( } else { - cursor = subject.Out(outputCollectionName, CancellationToken.None); + cursor = usingTimeSeriesCollection + ? subject.Out(outputCollectionName, timeSeriesOption, CancellationToken.None) + : subject.Out(outputCollectionName, CancellationToken.None); mockInputCollection.Verify( c => c.Aggregate( diff --git a/tests/MongoDB.Driver.Tests/Jira/CSharp3397Tests.cs b/tests/MongoDB.Driver.Tests/Jira/CSharp3397Tests.cs index 97d3ed80f5b..9de4ddb7eef 100644 --- a/tests/MongoDB.Driver.Tests/Jira/CSharp3397Tests.cs +++ b/tests/MongoDB.Driver.Tests/Jira/CSharp3397Tests.cs @@ -13,10 +13,13 @@ * limitations under the License. */ +using System; using System.Linq; using FluentAssertions; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver.Core.Misc; +using MongoDB.Driver.Core.TestHelpers.XunitExtensions; using Xunit; namespace MongoDB.Driver.Tests.Jira @@ -52,6 +55,47 @@ public void Aggregate_out_to_collection_should_work() results.Single().Count.Should().Be(1); } + + [Fact] + public void Aggregate_out_to_time_series_collection_on_secondary_should_work() + { + RequireServer.Check().Supports(Feature.AggregateOutTimeSeries); + + var client = DriverTestConfiguration.Client; + var database = client.GetDatabase("test"); + var collection = database.GetCollection("testCol"); + var outCollection = database.GetCollection("timeCol"); + + var writeConcern = WriteConcern.WMajority; + if (DriverTestConfiguration.IsReplicaSet(client)) + { + var n = DriverTestConfiguration.GetReplicaSetNumberOfDataBearingMembers(client); + writeConcern = new WriteConcern(n); + } + + database.DropCollection("testCol"); + database.DropCollection("timeCol"); + collection + .WithWriteConcern(writeConcern) + .InsertOne(new BsonDocument("_id", 1)); + + var fields = Builders.SetFields.Set("time", DateTime.Now); + var pipeline = new EmptyPipelineDefinition() + .Match(FilterDefinition.Empty) + .Set(fields) + .Out(outCollection, new TimeSeriesOptions("time")); + + var results = collection.WithReadPreference(ReadPreference.SecondaryPreferred).Aggregate(pipeline).ToList(); + results.Count.Should().Be(1); + + var listCollectionsCommand = new BsonDocument + { + { "listCollections", 1 }, { "filter", new BsonDocument { { "type", "timeseries" } } } + }; + var output = database.RunCommand(listCollectionsCommand); + output["cursor"]["firstBatch"][0][0].ToString().Should().Be("timeCol"); // checking name of collection + output["cursor"]["firstBatch"][0][1].ToString().Should().Be("timeseries"); // checking type of collection + } } public class AggregateCountResultWithId diff --git a/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs index 60865833867..1e9250f8a61 100644 --- a/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs @@ -141,6 +141,23 @@ public void Merge_should_add_expected_stage() stages[0].Should().Be("{ $merge : { into : { db : 'database', coll : 'collection' } } }"); } + [Fact] + public void Out_with_time_series_options_should_add_expected_stage() + { + var database = Mock.Of(db => db.DatabaseNamespace == new DatabaseNamespace("database")); + var outputCollection = Mock.Of>(col => + col.Database == database && + col.CollectionNamespace == new CollectionNamespace(database.DatabaseNamespace, "collection")); + + var timeSeriesOptions = new TimeSeriesOptions("time", "symbol"); + + var result = new EmptyPipelineDefinition().Out(outputCollection, timeSeriesOptions); + + var stages = RenderStages(result, BsonDocumentSerializer.Instance); + stages.Count.Should().Be(1); + stages[0].Should().Be("{ $out: { db: 'database', coll: 'collection', timeseries: { timeField: 'time', metaField: 'symbol' } } }"); + } + [Fact] public void Search_should_add_expected_stage() { diff --git a/tests/MongoDB.Driver.Tests/PipelineStageDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/PipelineStageDefinitionBuilderTests.cs index 397636e8950..0c23a903e71 100644 --- a/tests/MongoDB.Driver.Tests/PipelineStageDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/PipelineStageDefinitionBuilderTests.cs @@ -20,7 +20,9 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver.Core.TestHelpers; using MongoDB.Driver.Core.TestHelpers.XunitExtensions; +using Moq; using Xunit; namespace MongoDB.Driver.Tests @@ -183,7 +185,7 @@ public void GraphLookup_with_one_to_many_parameters_should_return_expected_resul } }"); } - + [Fact] public void GraphLookup_with_one_to_one_parameters_should_return_expected_result() { @@ -254,9 +256,9 @@ public void Lookup_with_let_should_return_the_expected_result() 'pipeline' : [ { '$match' : - { + { '$expr' : - { + { '$and' : [ { '$eq' : ['$stock_item', '$$order_item'] }, { '$gte' : ['$instock', '$$order_qty'] }] @@ -391,6 +393,21 @@ public void Merge_with_WhenNotMatched_should_return_the_expected_result(MergeSta stage.Document.Should().Be(expectedStage); } + [Fact] + public void Out_with_time_series_options_should_return_expected_result() + { + var database = Mock.Of(d => d.DatabaseNamespace == new DatabaseNamespace("database")); + var outputCollection = Mock.Of>(col => + col.Database == database && + col.CollectionNamespace == new CollectionNamespace(database.DatabaseNamespace, "collection")); + + var timeSeriesOptions = new TimeSeriesOptions("time", "symbol"); + + var result = PipelineStageDefinitionBuilder.Out(outputCollection, timeSeriesOptions); + + RenderStage(result).Document.Should().Be("{ $out: { db: 'database', coll: 'collection', timeseries: { timeField: 'time', metaField: 'symbol' } } }"); + } + public class Order { [BsonElement("stockdata")] @@ -456,7 +473,7 @@ public void Lookup_with_entity_generic_params_should_return_the_expected_result( '$match' : { '$expr' : - { + { '$and' : [ { '$eq' : ['$stock_item', '$$order_item'] }, { '$gte' : ['$instock', '$$order_qty'] }]