Skip to content

Commit 7387ef8

Browse files
CSHARP-2840: Allow passing hint to findAndModify update and replace operations
1 parent 63b9ef8 commit 7387ef8

26 files changed

+1282
-10
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public class Feature
5858
private static readonly Feature __findCommand = new Feature("FindCommand", new SemanticVersion(3, 2, 0));
5959
private static readonly Feature __geoNearCommand = new Feature("GeoNearCommand", new SemanticVersion(1, 0, 0), new SemanticVersion(4, 1, 0, ""));
6060
private static readonly Feature __groupCommand = new Feature("GroupCommand", new SemanticVersion(1, 0, 0), new SemanticVersion(4, 1, 1, ""));
61+
private static readonly HintForFindAndModifyFeature __hintForFindAndModifyFeature = new HintForFindAndModifyFeature("HintForFindAndModify", new SemanticVersion(4, 3, 0, ""));
6162
private static readonly HintForUpdateAndReplaceOperationsFeature __hintForUpdateAndReplaceOperations = new HintForUpdateAndReplaceOperationsFeature("HintForUpdateAndReplaceOperations", new SemanticVersion(4, 2, 0));
6263
private static readonly Feature __keepConnectionPoolWhenNotMasterConnectionException = new Feature("KeepConnectionPoolWhenNotMasterConnectionException", new SemanticVersion(4, 1, 10));
6364
private static readonly Feature __keepConnectionPoolWhenReplSetStepDown = new Feature("KeepConnectionPoolWhenReplSetStepDown", new SemanticVersion(4, 1, 10));
@@ -259,6 +260,11 @@ public class Feature
259260
/// </summary>
260261
public static Feature GroupCommand => __groupCommand;
261262

263+
/// <summary>
264+
/// Gets the hint for find and modify operations feature.
265+
/// </summary>
266+
public static HintForFindAndModifyFeature HintForFindAndModifyFeature => __hintForFindAndModifyFeature;
267+
262268
/// <summary>
263269
/// Gets the hint for write operations feature.
264270
/// </summary>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* Copyright 2020-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
namespace MongoDB.Driver.Core.Misc
17+
{
18+
/// <summary>
19+
/// Represents the hint for find and modify feature.
20+
/// </summary>
21+
public class HintForFindAndModifyFeature : Feature
22+
{
23+
private readonly SemanticVersion _firstServerVersionWhereWeRelyOnServerToReturnError = new SemanticVersion(4, 2, 0);
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="HintForFindAndModifyFeature"/> class.
27+
/// </summary>
28+
/// <param name="name">The name of the feature.</param>
29+
/// <param name="firstSupportedVersion">The first server version that supports the feature.</param>
30+
public HintForFindAndModifyFeature(string name, SemanticVersion firstSupportedVersion)
31+
: base(name, firstSupportedVersion)
32+
{
33+
}
34+
35+
/// <summary>
36+
/// Determines whether the driver must throw an exception if the feature is not supported by the server.
37+
/// </summary>
38+
/// <param name="serverVersion">The server version.</param>
39+
/// <returns>Whether the driver must throw if feature is not supported.</returns>
40+
public bool DriverMustThrowIfNotSupported(SemanticVersion serverVersion)
41+
{
42+
return serverVersion < _firstServerVersionWhereWeRelyOnServerToReturnError;
43+
}
44+
}
45+
}

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@
1414
*/
1515

1616
using System;
17-
using System.Collections.Generic;
18-
using System.Linq;
19-
using System.Text;
20-
using System.Threading.Tasks;
2117
using MongoDB.Bson;
2218
using MongoDB.Bson.IO;
2319
using MongoDB.Bson.Serialization;
@@ -39,6 +35,7 @@ public class FindOneAndReplaceOperation<TResult> : FindAndModifyOperationBase<TR
3935
// fields
4036
private bool? _bypassDocumentValidation;
4137
private readonly BsonDocument _filter;
38+
private BsonValue _hint;
4239
private bool _isUpsert;
4340
private TimeSpan? _maxTime;
4441
private BsonDocument _projection;
@@ -87,6 +84,18 @@ public BsonDocument Filter
8784
get { return _filter; }
8885
}
8986

87+
/// <summary>
88+
/// Gets or sets the hint.
89+
/// </summary>
90+
/// <value>
91+
/// The hint.
92+
/// </value>
93+
public BsonValue Hint
94+
{
95+
get { return _hint; }
96+
set { _hint = value; }
97+
}
98+
9099
/// <summary>
91100
/// Gets a value indicating whether a document should be inserted if no matching document is found.
92101
/// </summary>
@@ -163,6 +172,13 @@ internal override BsonDocument CreateCommand(ICoreSessionHandle session, Connect
163172
{
164173
var serverVersion = connectionDescription.ServerVersion;
165174
Feature.Collation.ThrowIfNotSupported(serverVersion, Collation);
175+
if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(serverVersion))
176+
{
177+
if (_hint != null)
178+
{
179+
throw new NotSupportedException($"Server version {serverVersion} does not support hints.");
180+
}
181+
}
166182

167183
var writeConcern = WriteConcernHelper.GetWriteConcernForCommand(session, WriteConcern, serverVersion, Feature.FindAndModifyWriteConcern);
168184
return new BsonDocument
@@ -178,6 +194,7 @@ internal override BsonDocument CreateCommand(ICoreSessionHandle session, Connect
178194
{ "writeConcern", writeConcern, writeConcern != null },
179195
{ "bypassDocumentValidation", () => _bypassDocumentValidation.Value, _bypassDocumentValidation.HasValue && Feature.BypassDocumentValidation.IsSupported(serverVersion) },
180196
{ "collation", () => Collation.ToBsonDocument(), Collation != null },
197+
{ "hint", () => _hint, _hint != null },
181198
{ "txnNumber", () => transactionNumber, transactionNumber.HasValue }
182199
};
183200
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class FindOneAndUpdateOperation<TResult> : FindAndModifyOperationBase<TRe
3737
private IEnumerable<BsonDocument> _arrayFilters;
3838
private bool? _bypassDocumentValidation;
3939
private readonly BsonDocument _filter;
40+
private BsonValue _hint;
4041
private bool _isUpsert;
4142
private TimeSpan? _maxTime;
4243
private BsonDocument _projection;
@@ -97,6 +98,18 @@ public BsonDocument Filter
9798
get { return _filter; }
9899
}
99100

101+
/// <summary>
102+
/// Gets or sets the hint.
103+
/// </summary>
104+
/// <value>
105+
/// The hint.
106+
/// </value>
107+
public BsonValue Hint
108+
{
109+
get { return _hint; }
110+
set { _hint = value; }
111+
}
112+
100113
/// <summary>
101114
/// Gets a value indicating whether a document should be inserted if no matching document is found.
102115
/// </summary>
@@ -173,6 +186,13 @@ internal override BsonDocument CreateCommand(ICoreSessionHandle session, Connect
173186
{
174187
var serverVersion = connectionDescription.ServerVersion;
175188
Feature.Collation.ThrowIfNotSupported(serverVersion, Collation);
189+
if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(serverVersion))
190+
{
191+
if (_hint != null)
192+
{
193+
throw new NotSupportedException($"Server version {serverVersion} does not support hints.");
194+
}
195+
}
176196

177197
var writeConcern = WriteConcernHelper.GetWriteConcernForCommand(session, WriteConcern, serverVersion, Feature.FindAndModifyWriteConcern);
178198
return new BsonDocument
@@ -188,6 +208,7 @@ internal override BsonDocument CreateCommand(ICoreSessionHandle session, Connect
188208
{ "writeConcern", writeConcern, writeConcern != null },
189209
{ "bypassDocumentValidation", () => _bypassDocumentValidation.Value, _bypassDocumentValidation.HasValue && Feature.BypassDocumentValidation.IsSupported(serverVersion) },
190210
{ "collation", () => Collation.ToBsonDocument(), Collation != null },
211+
{ "hint", () => _hint, _hint != null },
191212
{ "arrayFilters", () => new BsonArray(_arrayFilters), _arrayFilters != null },
192213
{ "txnNumber", () => transactionNumber, transactionNumber.HasValue }
193214
};

src/MongoDB.Driver/FindOneAndReplaceOptions.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using MongoDB.Bson;
1718
using MongoDB.Driver.Core.Misc;
1819

1920
namespace MongoDB.Driver
@@ -28,6 +29,7 @@ public class FindOneAndReplaceOptions<TDocument, TProjection>
2829
// fields
2930
private bool? _bypassDocumentValidation;
3031
private Collation _collation;
32+
private BsonValue _hint;
3133
private bool _isUpsert;
3234
private TimeSpan? _maxTime;
3335
private ProjectionDefinition<TDocument, TProjection> _projection;
@@ -44,6 +46,15 @@ public FindOneAndReplaceOptions()
4446
}
4547

4648
// properties
49+
/// <summary>
50+
/// Gets or sets a value indicating whether to bypass document validation.
51+
/// </summary>
52+
public bool? BypassDocumentValidation
53+
{
54+
get { return _bypassDocumentValidation; }
55+
set { _bypassDocumentValidation = value; }
56+
}
57+
4758
/// <summary>
4859
/// Gets or sets the collation.
4960
/// </summary>
@@ -54,12 +65,12 @@ public Collation Collation
5465
}
5566

5667
/// <summary>
57-
/// Gets or sets a value indicating whether to bypass document validation.
68+
/// Gets or sets the hint.
5869
/// </summary>
59-
public bool? BypassDocumentValidation
70+
public BsonValue Hint
6071
{
61-
get { return _bypassDocumentValidation; }
62-
set { _bypassDocumentValidation = value; }
72+
get { return _hint; }
73+
set { _hint = value; }
6374
}
6475

6576
/// <summary>

src/MongoDB.Driver/FindOneAndUpdateOptions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18-
using System.Linq;
18+
using MongoDB.Bson;
1919
using MongoDB.Driver.Core.Misc;
2020

2121
namespace MongoDB.Driver
@@ -31,6 +31,7 @@ public class FindOneAndUpdateOptions<TDocument, TProjection>
3131
private IEnumerable<ArrayFilterDefinition> _arrayFilters;
3232
private bool? _bypassDocumentValidation;
3333
private Collation _collation;
34+
private BsonValue _hint;
3435
private bool _isUpsert;
3536
private TimeSpan? _maxTime;
3637
private ProjectionDefinition<TDocument, TProjection> _projection;
@@ -77,6 +78,15 @@ public Collation Collation
7778
set { _collation = value; }
7879
}
7980

81+
/// <summary>
82+
/// Gets or sets the hint.
83+
/// </summary>
84+
public BsonValue Hint
85+
{
86+
get { return _hint; }
87+
set { _hint = value; }
88+
}
89+
8090
/// <summary>
8191
/// Gets or sets a value indicating whether to insert the document if it doesn't already exist.
8292
/// </summary>

src/MongoDB.Driver/MongoCollectionImpl.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,7 @@ private FindOneAndReplaceOperation<TProjection> CreateFindOneAndReplaceOperation
921921
{
922922
BypassDocumentValidation = options.BypassDocumentValidation,
923923
Collation = options.Collation,
924+
Hint = options.Hint,
924925
IsUpsert = options.IsUpsert,
925926
MaxTime = options.MaxTime,
926927
Projection = renderedProjection.Document,
@@ -946,6 +947,7 @@ private FindOneAndUpdateOperation<TProjection> CreateFindOneAndUpdateOperation<T
946947
ArrayFilters = RenderArrayFilters(options.ArrayFilters),
947948
BypassDocumentValidation = options.BypassDocumentValidation,
948949
Collation = options.Collation,
950+
Hint = options.Hint,
949951
IsUpsert = options.IsUpsert,
950952
MaxTime = options.MaxTime,
951953
Projection = renderedProjection.Document,

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public void Constructor_should_initialize_object()
9898

9999
subject.BypassDocumentValidation.Should().NotHaveValue();
100100
subject.Collation.Should().BeNull();
101+
subject.Hint.Should().BeNull();
101102
subject.IsUpsert.Should().BeFalse();
102103
subject.MaxTime.Should().NotHaveValue();
103104
subject.Projection.Should().BeNull();
@@ -135,6 +136,20 @@ public void Collation_get_and_set_should_work(
135136
result.Should().BeSameAs(value);
136137
}
137138

139+
[Theory]
140+
[ParameterAttributeData]
141+
public void Hint_get_and_set_should_work(
142+
[Values(null, "_id_")] string hintString)
143+
{
144+
var subject = new FindOneAndReplaceOperation<BsonDocument>(_collectionNamespace, _filter, _replacement, BsonDocumentSerializer.Instance, _messageEncoderSettings);
145+
var value = (BsonValue)hintString;
146+
147+
subject.Hint = value;
148+
var result = subject.Hint;
149+
150+
result.Should().Be(value);
151+
}
152+
138153
[Theory]
139154
[ParameterAttributeData]
140155
public void IsUpsert_get_and_set_should_work(
@@ -311,6 +326,31 @@ public void CreateCommand_should_return_expected_result_when_Collation_is_set(
311326
result.Should().Be(expectedResult);
312327
}
313328

329+
[Theory]
330+
[ParameterAttributeData]
331+
public void CreateCommand_should_return_expected_result_when_Hint_is_set(
332+
[Values(null, "_id_")] string hintString)
333+
{
334+
var hint = (BsonValue)hintString;
335+
var subject = new FindOneAndReplaceOperation<BsonDocument>(_collectionNamespace, _filter, _replacement, BsonDocumentSerializer.Instance, _messageEncoderSettings)
336+
{
337+
Hint = hint
338+
};
339+
var session = OperationTestHelper.CreateSession();
340+
var connectionDescription = OperationTestHelper.CreateConnectionDescription(serverVersion: Feature.HintForFindAndModifyFeature.FirstSupportedVersion);
341+
342+
var result = subject.CreateCommand(session, connectionDescription, null);
343+
344+
var expectedResult = new BsonDocument
345+
{
346+
{ "findAndModify", _collectionNamespace.CollectionName },
347+
{ "query", _filter },
348+
{ "update", _replacement },
349+
{ "hint", () => hint, hint != null }
350+
};
351+
result.Should().Be(expectedResult);
352+
}
353+
314354
[Theory]
315355
[ParameterAttributeData]
316356
public void CreateCommand_should_return_expected_result_when_IsUpsert_is_set(
@@ -671,6 +711,33 @@ public void Execute_should_throw_when_there_is_a_write_concern_error(
671711
);
672712
}
673713

714+
[SkippableTheory]
715+
[ParameterAttributeData]
716+
public void Execute_with_hint_should_throw_when_hint_is_not_supported(
717+
[Values(false, true)] bool async)
718+
{
719+
var serverVersion = CoreTestConfiguration.ServerVersion;
720+
var subject = new FindOneAndReplaceOperation<BsonDocument>(_collectionNamespace, _filter, _replacement, _findAndModifyValueDeserializer, _messageEncoderSettings)
721+
{
722+
Hint = new BsonDocument("_id", 1)
723+
};
724+
725+
var exception = Record.Exception(() => ExecuteOperation(subject, async));
726+
727+
if (Feature.HintForFindAndModifyFeature.IsSupported(serverVersion))
728+
{
729+
exception.Should().BeNull();
730+
}
731+
else if (Feature.HintForFindAndModifyFeature.DriverMustThrowIfNotSupported(serverVersion))
732+
{
733+
exception.Should().BeOfType<NotSupportedException>();
734+
}
735+
else
736+
{
737+
exception.Should().BeOfType<MongoCommandException>();
738+
}
739+
}
740+
674741
private void EnsureTestData()
675742
{
676743
DropCollection();

0 commit comments

Comments
 (0)