Skip to content

CSHARP-5377: Eliminate unnecessary killCursors command when batchSize == limit #1729

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions specifications/crud/tests/unified/find.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,154 @@
]
}
]
},
{
"description": "Find with filter",
"operations": [
{
"object": "collection0",
"name": "find",
"arguments": {
"filter": {
"_id": 1
}
},
"expectResult": [
{
"_id": 1,
"x": 11
}
]
}
]
},
{
"description": "Find with filter, sort, skip, and limit",
"operations": [
{
"object": "collection0",
"name": "find",
"arguments": {
"filter": {
"_id": {
"$gt": 2
}
},
"sort": {
"_id": 1
},
"skip": 2,
"limit": 2
},
"expectResult": [
{
"_id": 5,
"x": 55
},
{
"_id": 6,
"x": 66
}
]
}
]
},
{
"description": "Find with limit, sort, and batchsize",
"operations": [
{
"object": "collection0",
"name": "find",
"arguments": {
"filter": {},
"sort": {
"_id": 1
},
"limit": 4,
"batchSize": 2
},
"expectResult": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
}
]
}
]
},
{
"description": "Find with batchSize equal to limit",
"operations": [
{
"object": "collection0",
"name": "find",
"arguments": {
"filter": {
"_id": {
"$gt": 1
}
},
"sort": {
"_id": 1
},
"limit": 4,
"batchSize": 4
},
"expectResult": [
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
},
{
"_id": 5,
"x": 55
}
]
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"find": "coll0",
"filter": {
"_id": {
"$gt": 1
}
},
"limit": 4,
"batchSize": 5
},
"commandName": "find",
"databaseName": "find-tests"
}
}
]
}
]
}
]
}
69 changes: 68 additions & 1 deletion specifications/crud/tests/unified/find.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,71 @@ tests:
batchSize: 2
commandName: getMore
databaseName: *database0Name

-
description: 'Find with filter'
operations:
-
object: *collection0
name: find
arguments:
filter: { _id: 1 }
expectResult:
- { _id: 1, x: 11 }
-
description: 'Find with filter, sort, skip, and limit'
operations:
-
object: *collection0
name: find
arguments:
filter: { _id: { $gt: 2 } }
sort: { _id: 1 }
skip: 2
limit: 2
expectResult:
- { _id: 5, x: 55 }
- { _id: 6, x: 66 }
-
description: 'Find with limit, sort, and batchsize'
operations:
-
object: *collection0
name: find
arguments:
filter: { }
sort: { _id: 1 }
limit: 4
batchSize: 2
expectResult:
- { _id: 1, x: 11 }
- { _id: 2, x: 22 }
- { _id: 3, x: 33 }
- { _id: 4, x: 44 }
-
description: 'Find with batchSize equal to limit'
operations:
-
object: *collection0
name: find
arguments:
filter: { _id: { $gt: 1 } }
sort: { _id: 1 }
limit: 4
batchSize: 4
expectResult:
- { _id: 2, x: 22 }
- { _id: 3, x: 33 }
- { _id: 4, x: 44 }
- { _id: 5, x: 55 }
expectEvents:
- client: *client0
events:
- commandStartedEvent:
command:
find: *collection0Name
filter: { _id: { $gt: 1 } }
limit: 4
# Drivers use limit + 1 for batchSize to ensure the server closes the cursor
batchSize: 5
commandName: find
databaseName: *database0Name
17 changes: 8 additions & 9 deletions src/MongoDB.Driver/Core/Operations/FindOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ internal sealed class FindOperation<TDocument> : IReadOperation<IAsyncCursor<TDo
private BsonValue _comment;
private CursorType _cursorType;
private BsonDocument _filter;
private int? _firstBatchSize;
private BsonValue _hint;
private BsonDocument _let;
private int? _limit;
Expand Down Expand Up @@ -125,12 +124,6 @@ public BsonDocument Filter
set { _filter = value; }
}

public int? FirstBatchSize
{
get { return _firstBatchSize; }
set { _firstBatchSize = Ensure.IsNullOrGreaterThanOrEqualToZero(value, nameof(value)); }
}

public BsonValue Hint
{
get { return _hint; }
Expand Down Expand Up @@ -249,7 +242,13 @@ public BsonDocument CreateCommand(ConnectionDescription connectionDescription, I
var wireVersion = connectionDescription.MaxWireVersion;
FindProjectionChecker.ThrowIfAggregationExpressionIsUsedWhenNotSupported(_projection, wireVersion);

var firstBatchSize = _firstBatchSize ?? (_batchSize > 0 ? _batchSize : null);
var batchSize = _batchSize;
// https://github.com/mongodb/specifications/blob/668992950d975d3163e538849dd20383a214fc37/source/crud/crud.md?plain=1#L803
if (batchSize.HasValue && batchSize == _limit)
{
batchSize = _limit + 1;
}

var isShardRouter = connectionDescription.HelloResult.ServerType == ServerType.ShardRouter;

var effectiveComment = _comment;
Expand All @@ -271,7 +270,7 @@ public BsonDocument CreateCommand(ConnectionDescription connectionDescription, I
{ "hint", effectiveHint, effectiveHint != null },
{ "skip", () => _skip.Value, _skip.HasValue },
{ "limit", () => Math.Abs(_limit.Value), _limit.HasValue && _limit != 0 },
{ "batchSize", () => firstBatchSize.Value, firstBatchSize.HasValue },
{ "batchSize", () => batchSize.Value, batchSize.HasValue && batchSize > 0 },
{ "singleBatch", () => _limit < 0 || _singleBatch.Value, _limit < 0 || _singleBatch.HasValue },
{ "comment", effectiveComment, effectiveComment != null },
{ "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(effectiveMaxTime.Value), effectiveMaxTime.HasValue },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ public void constructor_should_initialize_instance()
subject.Comment.Should().BeNull();
subject.CursorType.Should().Be(CursorType.NonTailable);
subject.Filter.Should().BeNull();
subject.FirstBatchSize.Should().NotHaveValue();
subject.Hint.Should().BeNull();
subject.Limit.Should().NotHaveValue();
subject.Max.Should().BeNull();
Expand Down Expand Up @@ -352,30 +351,6 @@ public void CreateCommand_should_return_expected_result_when_Filter_is_set(
result.Should().Be(expectedResult);
}

[Theory]
[ParameterAttributeData]
public void CreateCommand_should_return_expected_result_when_FirstBatchSize_is_set(
[Values(null, 0, 1)]
int? firstBatchSize)
{
var subject = new FindOperation<BsonDocument>(_collectionNamespace, BsonDocumentSerializer.Instance, _messageEncoderSettings)
{
FirstBatchSize = firstBatchSize
};

var connectionDescription = OperationTestHelper.CreateConnectionDescription();
var session = OperationTestHelper.CreateSession();

var result = subject.CreateCommand(connectionDescription, session);

var expectedResult = new BsonDocument
{
{ "find", _collectionNamespace.CollectionName },
{ "batchSize", () => firstBatchSize.Value, firstBatchSize.HasValue }
};
result.Should().Be(expectedResult);
}

[Theory]
[ParameterAttributeData]
public void CreateCommand_should_return_expected_result_when_Hint_is_set(
Expand Down Expand Up @@ -937,34 +912,6 @@ public void Filter_get_and_set_should_work(
result.Should().BeSameAs(value);
}

[Theory]
[ParameterAttributeData]
public void FirstBatchSize_get_and_set_should_work(
[Values(null, 0, 1, 2)]
int? value)
{
var subject = new FindOperation<BsonDocument>(_collectionNamespace, BsonDocumentSerializer.Instance, _messageEncoderSettings);

subject.FirstBatchSize = value;
var result = subject.FirstBatchSize;

result.Should().Be(value);
}

[Theory]
[ParameterAttributeData]
public void FirstBatchSize_set_should_throw_when_value_is_invalid(
[Values(-2, -1)]
int value)
{
var subject = new FindOperation<BsonDocument>(_collectionNamespace, BsonDocumentSerializer.Instance, _messageEncoderSettings);

var exception = Record.Exception(() => { subject.FirstBatchSize = value; });

var argumentOutOfRangeException = exception.Should().BeOfType<ArgumentOutOfRangeException>().Subject;
argumentOutOfRangeException.ParamName.Should().Be("value");
}

[Theory]
[ParameterAttributeData]
public void Hint_get_and_set_should_work(
Expand Down