Skip to content

Commit 05c177a

Browse files
authored
CSHARP-5672: Support sorting by value in PushEach operation (#1748)
1 parent 3669ba7 commit 05c177a

File tree

6 files changed

+125
-33
lines changed

6 files changed

+125
-33
lines changed

src/MongoDB.Driver/IndexKeysDefinitionBuilder.cs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -487,20 +487,7 @@ public override BsonDocument Render(RenderArgs<TDocument> args)
487487
{
488488
var renderedField = _field.Render(args);
489489

490-
BsonValue value;
491-
switch (_direction)
492-
{
493-
case SortDirection.Ascending:
494-
value = 1;
495-
break;
496-
case SortDirection.Descending:
497-
value = -1;
498-
break;
499-
default:
500-
throw new InvalidOperationException("Unknown value for " + typeof(SortDirection) + ".");
501-
}
502-
503-
return new BsonDocument(renderedField.FieldName, value);
490+
return new BsonDocument(renderedField.FieldName, _direction.Render());
504491
}
505492
}
506493

src/MongoDB.Driver/SortDefinition.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public abstract class SortDefinition<TDocument>
4747
/// <returns>A <see cref="BsonDocument"/>.</returns>
4848
public abstract BsonDocument Render(RenderArgs<TDocument> args);
4949

50+
// TODO: remove this and refactor Render to return a BsonValue in 4.0
51+
internal virtual BsonValue RenderAsBsonValue(RenderArgs<TDocument> args) => Render(args);
52+
5053
/// <summary>
5154
/// Performs an implicit conversion from <see cref="BsonDocument"/> to <see cref="SortDefinition{TDocument}"/>.
5255
/// </summary>

src/MongoDB.Driver/SortDefinitionBuilder.cs

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,18 @@ public static SortDefinition<TDocument> MetaTextScore<TDocument>(this SortDefini
131131
public sealed class SortDefinitionBuilder<TDocument>
132132
{
133133
/// <summary>
134-
/// Creates an ascending sort.
134+
/// Creates an ascending sort on a value rather than on a field of a document. For example, "$sort : 1".
135+
/// This is used when sorting primitive values like strings or numbers, but can also be used to sort whole documents.
136+
/// </summary>
137+
/// <returns>A value ascending sort.</returns>
138+
public SortDefinition<TDocument> Ascending()
139+
{
140+
return new ValueDirectionalSortDefinition<TDocument>(SortDirection.Ascending);
141+
}
142+
143+
/// <summary>
144+
/// Creates an ascending sort based on a specific field within the document. For example, "$sort : { field : 1 }".
145+
/// This is used when values are documents, and you want to sort by a particular field's value.
135146
/// </summary>
136147
/// <param name="field">The field.</param>
137148
/// <returns>An ascending sort.</returns>
@@ -141,7 +152,8 @@ public SortDefinition<TDocument> Ascending(FieldDefinition<TDocument> field)
141152
}
142153

143154
/// <summary>
144-
/// Creates an ascending sort.
155+
/// Creates an ascending sort based on a specific field within the document. For example, "$sort : { field : 1 }".
156+
/// This is used when values are documents, and you want to sort by a particular field's value.
145157
/// </summary>
146158
/// <param name="field">The field.</param>
147159
/// <returns>An ascending sort.</returns>
@@ -171,7 +183,18 @@ public SortDefinition<TDocument> Combine(IEnumerable<SortDefinition<TDocument>>
171183
}
172184

173185
/// <summary>
174-
/// Creates a descending sort.
186+
/// Creates a descending sort on a value rather than on a field of a document. For example, "$sort : -1".
187+
/// This is used when sorting primitive values like strings or numbers, but can also be used to sort whole documents.
188+
/// </summary>
189+
/// <returns>A value descending sort.</returns>
190+
public SortDefinition<TDocument> Descending()
191+
{
192+
return new ValueDirectionalSortDefinition<TDocument>(SortDirection.Descending);
193+
}
194+
195+
/// <summary>
196+
/// Creates a descending sort based on a specific field within the document. For example, "$sort: { field: -1 }".
197+
/// This is used when values are documents, and you want to sort by a particular field's value.
175198
/// </summary>
176199
/// <param name="field">The field.</param>
177200
/// <returns>A descending sort.</returns>
@@ -181,7 +204,8 @@ public SortDefinition<TDocument> Descending(FieldDefinition<TDocument> field)
181204
}
182205

183206
/// <summary>
184-
/// Creates a descending sort.
207+
/// Creates a descending sort based on a specific field within the document. For example, "$sort: { field: -1 }".
208+
/// This is used when values are documents, and you want to sort by a particular field's value.
185209
/// </summary>
186210
/// <param name="field">The field.</param>
187211
/// <returns>A descending sort.</returns>
@@ -232,6 +256,11 @@ internal sealed class CombinedSortDefinition<TDocument> : SortDefinition<TDocume
232256
public CombinedSortDefinition(IEnumerable<SortDefinition<TDocument>> sorts)
233257
{
234258
_sorts = Ensure.IsNotNull(sorts, nameof(sorts)).ToList();
259+
260+
if (_sorts.Any(sort => sort is ValueDirectionalSortDefinition<TDocument>))
261+
{
262+
throw new InvalidOperationException("Value-based sort cannot be combined with other sorts. When sorting by the entire element value, no other sorting criteria can be applied.");
263+
}
235264
}
236265

237266
public override BsonDocument Render(RenderArgs<TDocument> args)
@@ -272,20 +301,25 @@ public override BsonDocument Render(RenderArgs<TDocument> args)
272301
{
273302
var renderedField = _field.Render(args);
274303

275-
BsonValue value;
276-
switch (_direction)
277-
{
278-
case SortDirection.Ascending:
279-
value = 1;
280-
break;
281-
case SortDirection.Descending:
282-
value = -1;
283-
break;
284-
default:
285-
throw new InvalidOperationException("Unknown value for " + typeof(SortDirection) + ".");
286-
}
304+
return new BsonDocument(renderedField.FieldName, _direction.Render());
305+
}
306+
}
287307

288-
return new BsonDocument(renderedField.FieldName, value);
308+
internal sealed class ValueDirectionalSortDefinition<TDocument> : SortDefinition<TDocument>
309+
{
310+
private readonly SortDirection _direction;
311+
312+
public ValueDirectionalSortDefinition(SortDirection direction)
313+
{
314+
_direction = direction;
315+
}
316+
317+
public override BsonDocument Render(RenderArgs<TDocument> args)
318+
{
319+
throw new InvalidOperationException(
320+
"Value-based sort cannot be rendered as a document. You might be trying to use a value-based sort where a field-based sort is expected.");
289321
}
322+
323+
internal override BsonValue RenderAsBsonValue(RenderArgs<TDocument> args) => _direction.Render();
290324
}
291325
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* Copyright 2010-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+
using System;
17+
using MongoDB.Bson;
18+
19+
namespace MongoDB.Driver
20+
{
21+
internal static class SortDirectionExtensions
22+
{
23+
internal static BsonValue Render(this SortDirection direction) =>
24+
direction switch
25+
{
26+
SortDirection.Ascending => 1,
27+
SortDirection.Descending => -1,
28+
_ => throw new InvalidOperationException($"Invalid sort direction: {direction}.")
29+
};
30+
}
31+
}

src/MongoDB.Driver/UpdateDefinitionBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1685,7 +1685,7 @@ public override BsonValue Render(RenderArgs<TDocument> args)
16851685

16861686
if (_sort != null)
16871687
{
1688-
document["$push"][renderedField.FieldName]["$sort"] = _sort.Render(args.WithNewDocumentType((IBsonSerializer<TItem>)itemSerializer));
1688+
document["$push"][renderedField.FieldName]["$sort"] = _sort.RenderAsBsonValue(args.WithNewDocumentType((IBsonSerializer<TItem>)itemSerializer));
16891689
}
16901690

16911691
return document;

tests/MongoDB.Driver.Tests/SortDefinitionBuilderTests.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
1617
using FluentAssertions;
1718
using MongoDB.Bson;
1819
using MongoDB.Bson.Serialization;
@@ -31,6 +32,14 @@ public void Ascending()
3132
Assert(subject.Ascending("a"), "{a: 1}");
3233
}
3334

35+
[Fact]
36+
public void Ascending_value()
37+
{
38+
var subject = CreateSubject<BsonDocument>();
39+
40+
Assert(subject.Ascending(), "1");
41+
}
42+
3443
[Fact]
3544
public void Ascending_Typed()
3645
{
@@ -40,6 +49,16 @@ public void Ascending_Typed()
4049
Assert(subject.Ascending("FirstName"), "{fn: 1}");
4150
}
4251

52+
[Fact]
53+
public void Calling_render_on_value_based_sort_should_throw()
54+
{
55+
var subject = CreateSubject<BsonDocument>();
56+
57+
var exception = Record.Exception(() => subject.Ascending().Render(new RenderArgs<BsonDocument>()));
58+
59+
exception.Should().BeOfType<InvalidOperationException>();
60+
}
61+
4362
[Fact]
4463
public void Combine()
4564
{
@@ -76,6 +95,16 @@ public void Combine_with_repeated_fields_using_extension_methods()
7695
Assert(sort, "{b: -1, a: -1}");
7796
}
7897

98+
[Fact]
99+
public void Combine_with_value_based_sort_and_additional_sort_should_throw()
100+
{
101+
var subject = CreateSubject<BsonDocument>();
102+
103+
var exception = Record.Exception(() => subject.Ascending().Descending("b"));
104+
105+
exception.Should().BeOfType<InvalidOperationException>();
106+
}
107+
79108
[Fact]
80109
public void Descending()
81110
{
@@ -84,6 +113,14 @@ public void Descending()
84113
Assert(subject.Descending("a"), "{a: -1}");
85114
}
86115

116+
[Fact]
117+
public void Descending_value()
118+
{
119+
var subject = CreateSubject<BsonDocument>();
120+
121+
Assert(subject.Descending(), "-1");
122+
}
123+
87124
[Fact]
88125
public void Descending_Typed()
89126
{
@@ -120,7 +157,7 @@ public void MetaTextScore()
120157
private void Assert<TDocument>(SortDefinition<TDocument> sort, string expectedJson)
121158
{
122159
var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer<TDocument>();
123-
var renderedSort = sort.Render(new(documentSerializer, BsonSerializer.SerializerRegistry));
160+
var renderedSort = sort.RenderAsBsonValue(new(documentSerializer, BsonSerializer.SerializerRegistry));
124161

125162
renderedSort.Should().Be(expectedJson);
126163
}

0 commit comments

Comments
 (0)