Skip to content

Commit b00a225

Browse files
authored
CSHARP-4095: Use count command for estimatedDocumentCount. (#789)
1 parent 4c1419e commit b00a225

File tree

188 files changed

+9567
-5037
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

188 files changed

+9567
-5037
lines changed

Docs/reference/content/reference/driver/stable_api.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,12 @@ var database = client.GetDatabase("db");
6767
var result = database.RunCommand<BsonDocument>(new BsonDocument("commandDeprecatedInV1", 1)); // Example fail:
6868
// MongoDB.Driver.MongoCommandException : Command commandDeprecatedInV1 failed: Provided deprecationErrors:true, but the command commandDeprecatedInV1 is deprecated in API Version 1.
6969
```
70+
71+
### EstimatedDocumentCount and Stable API
72+
73+
`EstimatedDocumentCount` is implemented using the `count` server command. Due to an oversight in versions
74+
5.0.0-5.0.8 of MongoDB, the `count` command, which `EstimatedDocumentCount` uses in its implementation,
75+
was not included in v1 of the Stable API. If you are using the Stable API with `EstimatedDocumentCount`,
76+
you must upgrade to server version 5.0.9+ or set `strict: false` when configuring `ServerApi` to avoid
77+
encountering errors.
78+

src/MongoDB.Driver.Core/Core/Misc/Feature.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2016-present MongoDB Inc.
1+
/* Copyright 2016-present MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -59,7 +59,6 @@ public class Feature
5959
private static readonly Feature __currentOpCommand = new Feature("CurrentOpCommand", WireVersion.Server32);
6060
private static readonly Feature __documentValidation = new Feature("DocumentValidation", WireVersion.Server32);
6161
private static readonly Feature __directConnectionSetting = new Feature("DirectConnectionSetting", WireVersion.Server44);
62-
private static readonly Feature __estimatedDocumentCountByCollStats = new Feature("EstimatedDocumentCountByCollStats", WireVersion.Server49);
6362
private static readonly Feature __eval = new Feature("Eval", WireVersion.Zero, WireVersion.Server42);
6463
private static readonly Feature __explainCommand = new Feature("ExplainCommand", WireVersion.Server30);
6564
private static readonly Feature __failPoints = new Feature("FailPoints", WireVersion.Zero);
@@ -318,11 +317,6 @@ public class Feature
318317
/// </summary>
319318
public static Feature DirectConnectionSetting => __directConnectionSetting;
320319

321-
/// <summary>
322-
/// Gets the estimatedDocumentCountByCollStats feature.
323-
/// </summary>
324-
public static Feature EstimatedDocumentCountByCollStats => __estimatedDocumentCountByCollStats;
325-
326320
/// <summary>
327321
/// Gets the eval feature.
328322
/// </summary>
@@ -692,7 +686,7 @@ internal void ThrowIfNotSupported(int wireVersion)
692686
{
693687
if (!IsSupported(wireVersion))
694688
{
695-
string errorMessage;
689+
string errorMessage;
696690
if (_notSupportedMessage != null)
697691
{
698692
errorMessage = _notSupportedMessage; ;

src/MongoDB.Driver.Core/Core/Operations/EstimatedDocumentCountOperation.cs

Lines changed: 6 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@
1414
*/
1515

1616
using System;
17-
using System.Collections.Generic;
1817
using System.Threading;
1918
using System.Threading.Tasks;
2019
using MongoDB.Bson;
21-
using MongoDB.Bson.Serialization.Serializers;
2220
using MongoDB.Driver.Core.Bindings;
2321
using MongoDB.Driver.Core.Misc;
2422
using MongoDB.Driver.Core.WireProtocol.Messages.Encoders;
@@ -104,29 +102,9 @@ public long Execute(IReadBinding binding, CancellationToken cancellationToken)
104102

105103
using (var context = RetryableReadContext.Create(binding, _retryRequested, cancellationToken))
106104
{
107-
if (Feature.EstimatedDocumentCountByCollStats.IsSupported(context.Channel.ConnectionDescription.MaxWireVersion))
108-
{
109-
var operation = CreateAggregationOperation();
110-
IAsyncCursor<BsonDocument> cursor;
111-
try
112-
{
113-
cursor = operation.Execute(context, cancellationToken);
114-
}
115-
catch (MongoCommandException ex) when (ex.Code == (int)ServerErrorCode.NamespaceNotFound)
116-
{
117-
// In the event this aggregation is run against a non-existent namespace, a NamespaceNotFound(26) error will be returned during execution.
118-
return 0;
119-
}
120-
var results = cursor.ToList(cancellationToken);
121-
122-
return ExtractCountFromAggregationResults(results);
123-
}
124-
else
125-
{
126-
var operation = CreateCountOperation();
127-
128-
return operation.Execute(context, cancellationToken);
129-
}
105+
var operation = CreateCountOperation();
106+
107+
return operation.Execute(context, cancellationToken);
130108
}
131109
}
132110

@@ -137,58 +115,13 @@ public async Task<long> ExecuteAsync(IReadBinding binding, CancellationToken can
137115

138116
using (var context = RetryableReadContext.Create(binding, _retryRequested, cancellationToken))
139117
{
140-
if (Feature.EstimatedDocumentCountByCollStats.IsSupported(context.Channel.ConnectionDescription.MaxWireVersion))
141-
{
142-
var operation = CreateAggregationOperation();
143-
IAsyncCursor<BsonDocument> cursor;
144-
try
145-
{
146-
cursor = await operation.ExecuteAsync(context, cancellationToken).ConfigureAwait(false);
147-
}
148-
catch (MongoCommandException ex) when (ex.Code == (int)ServerErrorCode.NamespaceNotFound)
149-
{
150-
// In the event this aggregation is run against a non-existent namespace, a NamespaceNotFound(26) error will be returned during execution.
151-
return 0;
152-
}
153-
var results = await cursor.ToListAsync(cancellationToken).ConfigureAwait(false);
154-
155-
return ExtractCountFromAggregationResults(results);
156-
}
157-
else
158-
{
159-
var operation = CreateCountOperation();
160-
161-
return await operation.ExecuteAsync(context, cancellationToken).ConfigureAwait(false);
162-
}
118+
var operation = CreateCountOperation();
119+
120+
return await operation.ExecuteAsync(context, cancellationToken).ConfigureAwait(false);
163121
}
164122
}
165123

166124
// private methods
167-
private IExecutableInRetryableReadContext<IAsyncCursor<BsonDocument>> CreateAggregationOperation()
168-
{
169-
var pipeline = CreateAggregationPipeline();
170-
var aggregateOperation = new AggregateOperation<BsonDocument>(_collectionNamespace, pipeline, BsonDocumentSerializer.Instance, _messageEncoderSettings)
171-
{
172-
MaxTime = _maxTime,
173-
ReadConcern = _readConcern,
174-
RetryRequested = _retryRequested
175-
};
176-
return aggregateOperation;
177-
178-
IEnumerable<BsonDocument> CreateAggregationPipeline() =>
179-
new BsonDocument[]
180-
{
181-
new BsonDocument("$collStats", new BsonDocument("count", new BsonDocument())),
182-
new BsonDocument(
183-
"$group",
184-
new BsonDocument
185-
{
186-
{ "_id", 1 },
187-
{ "n", new BsonDocument("$sum", "$count") }
188-
})
189-
};
190-
}
191-
192125
private IExecutableInRetryableReadContext<long> CreateCountOperation()
193126
{
194127
var countOperation = new CountOperation(_collectionNamespace, _messageEncoderSettings)
@@ -200,13 +133,5 @@ private IExecutableInRetryableReadContext<long> CreateCountOperation()
200133
};
201134
return countOperation;
202135
}
203-
204-
private long ExtractCountFromAggregationResults(List<BsonDocument> results) =>
205-
results.Count switch
206-
{
207-
0 => 0,
208-
1 => results[0]["n"].ToInt64(),
209-
_ => throw new MongoClientException($"Expected aggregate command for {nameof(EstimatedDocumentCountOperation)} to return 1 document, but got {results.Count}."),
210-
};
211136
}
212137
}

src/MongoDB.Driver/IMongoCollection.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public interface IMongoCollection<TDocument>
242242
/// </summary>
243243
/// <remarks>
244244
/// Note: when migrating from Count to CountDocuments the following query operations must be replaced:
245-
///
245+
///
246246
/// <code>
247247
/// +-------------+--------------------------------+
248248
/// | Operator | Replacement |
@@ -269,7 +269,7 @@ public interface IMongoCollection<TDocument>
269269
/// </summary>
270270
/// <remarks>
271271
/// Note: when migrating from Count to CountDocuments the following query operations must be replaced:
272-
///
272+
///
273273
/// <code>
274274
/// +-------------+--------------------------------+
275275
/// | Operator | Replacement |
@@ -297,7 +297,7 @@ public interface IMongoCollection<TDocument>
297297
/// </summary>
298298
/// <remarks>
299299
/// Note: when migrating from CountAsync to CountDocumentsAsync the following query operations must be replaced:
300-
///
300+
///
301301
/// <code>
302302
/// +-------------+--------------------------------+
303303
/// | Operator | Replacement |
@@ -324,7 +324,7 @@ public interface IMongoCollection<TDocument>
324324
/// </summary>
325325
/// <remarks>
326326
/// Note: when migrating from CountAsync to CountDocumentsAsync the following query operations must be replaced:
327-
///
327+
///
328328
/// <code>
329329
/// +-------------+--------------------------------+
330330
/// | Operator | Replacement |
@@ -536,6 +536,12 @@ public interface IMongoCollection<TDocument>
536536
/// <returns>
537537
/// An estimate of the number of documents in the collection.
538538
/// </returns>
539+
/// <remarks>
540+
/// Due to an oversight in versions 5.0.0-5.0.8 of MongoDB, the count command, which estimatedDocumentCount uses
541+
/// in its implementation, was not included in v1 of the Stable API. If you are using the Stable API with
542+
/// estimatedDocumentCount, you must upgrade to server version 5.0.9+ or set strict: false when configuring
543+
/// ServerApi to avoid encountering errors.
544+
/// </remarks>
539545
long EstimatedDocumentCount(EstimatedDocumentCountOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
540546

541547
/// <summary>

tests/MongoDB.Driver.Core.Tests/Core/Operations/EstimatedDocumentCountOperationTests.cs

Lines changed: 12 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright 2021-present MongoDB Inc.
1+
/* Copyright 2021-present MongoDB Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
2121
using MongoDB.Driver.Core.Bindings;
2222
using MongoDB.Driver.Core.Clusters;
2323
using MongoDB.Driver.Core.Connections;
24-
using MongoDB.Driver.Core.Misc;
2524
using MongoDB.Driver.Core.TestHelpers;
2625
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
2726
using Xunit;
@@ -296,68 +295,29 @@ public void Execute_should_send_session_id_when_supported([Values(false, true)]
296295
EnsureTestData();
297296
var subject = new EstimatedDocumentCountOperation(_collectionNamespace, _messageEncoderSettings);
298297

299-
if (Feature.EstimatedDocumentCountByCollStats.IsSupported(CoreTestConfiguration.MaxWireVersion))
300-
{
301-
VerifySessionIdWasSentWhenSupported(subject, "aggregate", async);
302-
}
303-
else
304-
{
305-
VerifySessionIdWasSentWhenSupported(subject, "count", async);
306-
}
298+
VerifySessionIdWasSentWhenSupported(subject, "count", async);
307299
}
308300

309301
// private methods
310302
private void AssertCommandDocument(BsonDocument actualResult, int? expectedMaxTimeMS = null, BsonDocument readConcern = null)
311303
{
312-
if (Feature.EstimatedDocumentCountByCollStats.IsSupported(CoreTestConfiguration.MaxWireVersion))
304+
var expectedResult = new BsonDocument
313305
{
314-
var expectedResult = new BsonDocument
315-
{
316-
{ "aggregate", _collectionNamespace.CollectionName },
317-
{
318-
"pipeline",
319-
BsonArray.Create(
320-
new []
321-
{
322-
BsonDocument.Parse("{ $collStats : { count : { } } }"),
323-
BsonDocument.Parse("{ $group : { _id : 1, n : { $sum : '$count' } } } ")
324-
})
325-
},
326-
{ "maxTimeMS", () => expectedMaxTimeMS.Value, expectedMaxTimeMS.HasValue },
327-
{ "readConcern", () => readConcern, readConcern != null },
328-
{ "cursor", new BsonDocument() }
329-
};
330-
actualResult.Should().Be(expectedResult);
331-
}
332-
else
306+
{ "count", _collectionNamespace.CollectionName },
307+
{ "maxTimeMS", () => expectedMaxTimeMS.Value, expectedMaxTimeMS.HasValue },
308+
{ "readConcern", () => readConcern, readConcern != null }
309+
};
310+
actualResult.Should().Be(expectedResult);
311+
if (actualResult.TryGetValue("maxTimeMS", out var maxTimeMS))
333312
{
334-
var expectedResult = new BsonDocument
335-
{
336-
{ "count", _collectionNamespace.CollectionName },
337-
{ "maxTimeMS", () => expectedMaxTimeMS.Value, expectedMaxTimeMS.HasValue },
338-
{ "readConcern", () => readConcern, readConcern != null }
339-
};
340-
actualResult.Should().Be(expectedResult);
341-
if (actualResult.TryGetValue("maxTimeMS", out var maxTimeMS))
342-
{
343-
maxTimeMS.BsonType.Should().Be(BsonType.Int32);
344-
}
313+
maxTimeMS.BsonType.Should().Be(BsonType.Int32);
345314
}
346315
}
347316

348317
private BsonDocument CreateCommand(EstimatedDocumentCountOperation subject, ConnectionDescription connectionDescription, ICoreSession session)
349318
{
350-
var currentServerVersion = CoreTestConfiguration.ServerVersion;
351-
if (Feature.EstimatedDocumentCountByCollStats.IsSupported(CoreTestConfiguration.MaxWireVersion))
352-
{
353-
var aggregationOperation = (AggregateOperation<BsonDocument>)subject.CreateAggregationOperation();
354-
return aggregationOperation.CreateCommand(connectionDescription, session);
355-
}
356-
else
357-
{
358-
var countOperation = (CountOperation)subject.CreateCountOperation();
359-
return countOperation.CreateCommand(connectionDescription, session);
360-
}
319+
var countOperation = (CountOperation)subject.CreateCountOperation();
320+
return countOperation.CreateCommand(connectionDescription, session);
361321
}
362322

363323
private void EnsureTestData()
@@ -373,11 +333,6 @@ private void EnsureTestData()
373333

374334
internal static class EstimatedDocumentCountOperationReflector
375335
{
376-
public static IExecutableInRetryableReadContext<IAsyncCursor<BsonDocument>> CreateAggregationOperation(this EstimatedDocumentCountOperation operation)
377-
{
378-
return (IExecutableInRetryableReadContext<IAsyncCursor<BsonDocument>>)Reflector.Invoke(operation, nameof(CreateAggregationOperation));
379-
}
380-
381336
public static IExecutableInRetryableReadContext<long> CreateCountOperation(this EstimatedDocumentCountOperation operation)
382337
{
383338
return (IExecutableInRetryableReadContext<long>)Reflector.Invoke(operation, nameof(CreateCountOperation));

tests/MongoDB.Driver.Tests/Specifications/crud/tests/README.rst

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ Subdirectories for Test Formats
2121

2222
This document describes a legacy format for CRUD tests: legacy-v1, which dates back
2323
to the first version of the CRUD specification. New CRUD tests should be written
24-
in the `unified test format <../../../../unified-test-format/unified-test-format.rst>`_
24+
in the `unified test format <../../unified-test-format/unified-test-format.rst>`_
2525
and placed under ``unified/``. Until such time that all original tests have been ported
2626
to the unified test format, tests in each format will be grouped in their own subdirectory:
2727

2828
- ``v1/``: Legacy-v1 format tests
29-
- ``unified/``: Tests using the `unified test format <../../../../unified-test-format/unified-test-format.rst>`_
29+
- ``unified/``: Tests using the `unified test format <../../unified-test-format/unified-test-format.rst>`_
3030

3131
Since some drivers may not have a unified test runner capable of executing tests
3232
in all two formats, segregating tests in this manner will make it easier for
@@ -52,11 +52,13 @@ single operation. Notable differences from the legacy-v2 format are as follows:
5252
fields.
5353

5454
- Instead of a top-level ``runOn`` field, server requirements are denoted by
55-
separate top-level ``minServerVersion`` and ``maxServerVersion`` fields. The
56-
minimum server version is an inclusive lower bound for running the test. The
57-
maximum server version is an exclusive upper bound for running the test. If a
58-
field is not present, it should be assumed that there is no corresponding bound
59-
on the required server version.
55+
separate top-level ``minServerVersion``, ``maxServerVersion``, and
56+
``serverless`` fields. The minimum server version is an inclusive lower bound
57+
for running the test. The maximum server version is an exclusive upper bound
58+
for running the test. If a field is not present, it should be assumed that
59+
there is no corresponding bound on the required server version. The
60+
``serverless`` requirement behaves the same as the ``serverless`` field of the
61+
`unified test format's runOnRequirement <../../unified-test-format/unified-test-format.rst#runonrequirement>`_.
6062

6163
The legacy-v1 format should not conflict with the newer, multi-operation format
6264
used by other specs (e.g. Transactions). It is possible to create a unified test
@@ -255,7 +257,7 @@ Test that ``writeErrors[].errInfo`` in a command response is propagated as
255257
``WriteError.details`` (or equivalent) in the driver.
256258

257259
Using a 5.0+ server, create a collection with
258-
`document validation <https://docs.mongodb.com/manual/core/schema-validation/>`_
260+
`document validation <https://www.mongodb.com/docs/manual/core/schema-validation/>`_
259261
like so:
260262

261263
.. code:: javascript

tests/MongoDB.Driver.Tests/Specifications/crud/tests/unified/aggregate-merge.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"description": "aggregate-merge",
3-
"schemaVersion": "1.1",
3+
"schemaVersion": "1.0",
44
"runOnRequirements": [
55
{
66
"minServerVersion": "4.1.11"

tests/MongoDB.Driver.Tests/Specifications/crud/tests/unified/aggregate-merge.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Please review the generated file, then remove this notice.
33

44
description: aggregate-merge
5-
schemaVersion: '1.1'
5+
schemaVersion: '1.0'
66
runOnRequirements:
77
-
88
minServerVersion: 4.1.11

tests/MongoDB.Driver.Tests/Specifications/crud/tests/unified/bulkWrite-arrayFilters-clientError.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"description": "bulkWrite-arrayFilters-clientError",
3-
"schemaVersion": "1.1",
3+
"schemaVersion": "1.0",
44
"runOnRequirements": [
55
{
66
"maxServerVersion": "3.5.5"

0 commit comments

Comments
 (0)