-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
CSHARP-5201: Allow update to supply sort option #1453
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall! Now we need to wait for spec finalization.
@@ -342,7 +342,7 @@ private class TestCaseFactory : JsonDrivenTestCaseFactory | |||
new[] | |||
{ | |||
"MongoDB.Driver.Tests.Specifications.crud.tests.v1", | |||
"MongoDB.Driver.Tests.Specifications.crud.tests.v2" | |||
"MongoDB.Driver.Tests.Specifications.crud.tests.v2" //TODO I don't think we have v2 anymore? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, we don't, can be removed.
@@ -124,8 +124,8 @@ protected override IEnumerable<JsonDrivenTestCase> CreateTestCases(BsonDocument | |||
} | |||
|
|||
var description = testCase.Test["description"].AsString; | |||
var lineNumer = lines.FirstOrDefault(p => p.Line.Contains(description)).Index; | |||
testCase.Test.Add("_lineNumber", lineNumer); | |||
var lineNumber = lines.FirstOrDefault(p => p.Line.Contains(description)).Index; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
@@ -112,6 +112,7 @@ public UnifiedUpdateOneOperation Build(string targetCollectionId, BsonDocument a | |||
{ | |||
switch (argument.Name) | |||
{ | |||
//TODO Why don't we create UpdateOptions beforehand? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably to exercise options == null
path.
}; | ||
//TODO Why don't UpdateOne and UpdateOneAsync have a common path for creating the UpdateOneModel and BulkWriteOptions (if not more)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case that can be done elegantly, we should do that in a separate ticket for other methods as well. Usually we can address such improvements on green-build Fridays.
/// <summary> | ||
/// Gets or sets the sort document. | ||
/// </summary> | ||
public BsonDocument Sort |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alphabetical order.
/// <summary> | ||
/// The sort document to use. | ||
/// </summary> | ||
public BsonDocument Sort { get; init; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use SortDefinition<TDocument>
?
@@ -31,6 +31,7 @@ public sealed class ReplaceOneModel<TDocument> : WriteModel<TDocument> | |||
private BsonValue _hint; | |||
private bool _isUpsert; | |||
private readonly TDocument _replacement; | |||
private BsonDocument _sort; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use SortDefinition<TDocument>
?
src/MongoDB.Driver/ReplaceOptions.cs
Outdated
/// <summary> | ||
/// Gets or sets the sort document. | ||
/// </summary> | ||
public BsonDocument Sort |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use SortDefinition<TDocument>
.
src/MongoDB.Driver/UpdateOneModel.cs
Outdated
/// <summary> | ||
/// Gets or sets the sort document. | ||
/// </summary> | ||
public BsonDocument Sort |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use SortDefinition<TDocument>
.
src/MongoDB.Driver/UpdateOptions.cs
Outdated
/// <summary> | ||
/// Gets or sets the sort document. | ||
/// </summary> | ||
public BsonDocument Sort |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use SortDefinition<TDocument>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately UpdateOptions
and ReplaceOptions
are not generic classes, so we can't parametrize the properties. We can add the parameter, but I think it would be a breaking change.
We could also add the generic UpdateOptions<TDocument>
alongside the non generic version and make new methods that use this, while deprecating the methods that are using the non generico version. This would be the long road
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have done this only for ReplaceOptions
for now. If all I've done makes sense, I'll do the same for UpdateOptions
.
This required to create a new class ReplaceOptions<T>
and an implicit conversion operator from ReplaceOptions
to ReplaceOptions<T>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should decide if we are ready for the breaking change by replacing ReplaceOption with ReplaceOption before reviewing further changes.
@@ -200,6 +201,7 @@ public void RenderUpdateOne<TDocument>(RenderArgs<BsonDocument> renderArgs, Bson | |||
WriteArrayFilters(serializationContext, model.ArrayFilters); | |||
WriteHint(serializationContext, model.Hint); | |||
WriteCollation(serializationContext, model.Collation); | |||
WriteSort(serializationContext, model.Sort?.Render(renderArgs.WithNewDocumentType(documentSerializer))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to make WriteSort and pass model.Sort there? And move Render there as well.
@@ -972,8 +972,7 @@ public interface IMongoCollection<TDocument> | |||
/// <returns> | |||
/// The result of the replacement. | |||
/// </returns> | |||
ReplaceOneResult ReplaceOne(FilterDefinition<TDocument> filter, TDocument replacement, ReplaceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)); | |||
|
|||
ReplaceOneResult ReplaceOne(FilterDefinition<TDocument> filter, TDocument replacement, ReplaceOptions<TDocument> options = null, CancellationToken cancellationToken = default(CancellationToken)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be a code breaking change (client's code has to be rewritten) as far as I understood. Should we create a new overload instead? And support both ReplaceOptions
and ReplaceOptions<TDocument>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BorisDog WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup we avoid breaking changes. We'll probably need an extension method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a binary breaking change but not a source code breaking change.
There is also an implicit conversion operator from ReplaceOptions
to ReplaceOptions<TDocument>
that provides backward compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I discussed this with Robert and this was an attempt to avoid creating more methods/overloads and keep source compatibility.
I was reading a little bit around online about semver in regards to binary compatibility. It seems that there is not a complete agreement on it, but overall it's recommended to increase major version with a binary-incompatible change.
Personally (irregardless of this case), I think this is a matter of deciding where do we stand on this and then take it from there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer a solution that doesn't involve doubling (or worse) the number of overloads. Too many overloads is a bad thing.
The current approach uses an implicit conversion from ReplaceOptions
to ReplaceOptions<TDocument>
to preserve backward compatibility.
Another approach could be to use subclassing: ReplaceOptions<TDocument> : ReplaceOptions
. Then we could leave the current overloads unchanged at the expense of some type safety. The code that uses the replaceOptions
would have to test whether the passed in options are the subclass or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given what we discussed at Triage today I'll park this ticket until next when we can have a more general discussion about breaking changes in general
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to apologize, I did not notice the implicit conversion. So this is not code breaking change.
On other hand I suppose it's better to follow Robert's suggestion with using of subclasses as clearer way to do the same thing. At least because this way we could avoid changes to public API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went for Robert suggestion and made the generic options derive from the base class we have now
src/MongoDB.Driver/ReplaceOptions.cs
Outdated
/// <summary> | ||
/// Options for replacing a single document. | ||
/// </summary> | ||
public sealed class ReplaceOptions : ReplaceOptionsBase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to ensure that this change is not breaking.
@@ -972,8 +972,7 @@ public interface IMongoCollection<TDocument> | |||
/// <returns> | |||
/// The result of the replacement. | |||
/// </returns> | |||
ReplaceOneResult ReplaceOne(FilterDefinition<TDocument> filter, TDocument replacement, ReplaceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)); | |||
|
|||
ReplaceOneResult ReplaceOne(FilterDefinition<TDocument> filter, TDocument replacement, ReplaceOptions<TDocument> options = null, CancellationToken cancellationToken = default(CancellationToken)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup we avoid breaking changes. We'll probably need an extension method.
: this(CollectionNamespace.FromFullName(collectionNamespace), filter, replacement, collation, hint, isUpsert) | ||
bool isUpsert = false, | ||
SortDefinition<TDocument> sort = null) | ||
: this(CollectionNamespace.FromFullName(collectionNamespace), filter, replacement, collation, hint, isUpsert, sort) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not binary compatible.
The alternative would be to add other two constructors (given that we already have two) but:
- Then we have 4 very similar constructors
- Developers using those constructors will have errors due to constructor ambiguity if they do not specify all parameters. This is theoretically still source compatible, but definitely annoying
Any suggestions about this? Personally I'd just keep the sort definition out of the constructor if we cannot find another alternative
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we add new constructor with sort
as the very last non-optional parameter? In such way it would not be a breaking change and there would not be an ambiguity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea, I didn't think about it :)
I've added two new constructor to mirror the ones we have already, and added a new private one that is being called by all the others
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! And I suppose the plan should be to eliminate this copied ctors in next major release, by making the sort
parameter as optional. So the question is: how we going to track this and not forget to do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that would be the plan! I've added the constructors and the non generic options to the list of breaking changes for next version.
@@ -1132,7 +1132,7 @@ public void ReplaceOne_should_call_collection_with_expected_arguments( | |||
|
|||
assertReplaceOne(); | |||
|
|||
var replaceOptions = new ReplaceOptions(); | |||
var replaceOptions = new ReplaceOptions<Person>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we revert this change as we should be backward compatible now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely
: this(CollectionNamespace.FromFullName(collectionNamespace), filter, replacement, collation, hint, isUpsert) | ||
bool isUpsert = false, | ||
SortDefinition<TDocument> sort = null) | ||
: this(CollectionNamespace.FromFullName(collectionNamespace), filter, replacement, collation, hint, isUpsert, sort) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we add new constructor with sort
as the very last non-optional parameter? In such way it would not be a breaking change and there would not be an ambiguity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ready to LGTM the changes, but could you please rebase before that? Because in the current main we already have that apicompat script, just want to be sure we did everything right in terms of backward compatibility.
@sanych-sun just rebased on main, curious to see what will the script say 😁 |
@sanych-sun it seems the validate-apicompat task gave its ok 😁 |
No description provided.