Skip to content

Commit 07bb37e

Browse files
BorisDogDmitryLukyanov
authored andcommitted
CSHARP-4575: Fix uint and add DateTimeOffset support in Search.Range (#1044)
1 parent 3ed97d9 commit 07bb37e

File tree

3 files changed

+175
-18
lines changed

3 files changed

+175
-18
lines changed

src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ internal sealed class RangeSearchDefinition<TDocument, TField> : OperatorSearchD
290290
where TField : struct, IComparable<TField>
291291
{
292292
private readonly SearchRange<TField> _range;
293+
private readonly BsonValue _min;
294+
private readonly BsonValue _max;
293295

294296
public RangeSearchDefinition(
295297
SearchPathDefinition<TDocument> path,
@@ -298,28 +300,32 @@ public RangeSearchDefinition(
298300
: base(OperatorType.Range, path, score)
299301
{
300302
_range = range;
303+
_min = ToBsonValue(_range.Min);
304+
_max = ToBsonValue(_range.Max);
301305
}
302306

303307
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
304308
new()
305309
{
306-
{ _range.IsMinInclusive ? "gte" : "gt", () => ToBsonValue(_range.Min.Value), _range.Min != null },
307-
{ _range.IsMaxInclusive ? "lte" : "lt", () => ToBsonValue(_range.Max.Value), _range.Max != null },
310+
{ _range.IsMinInclusive ? "gte" : "gt", _min, _min != null },
311+
{ _range.IsMaxInclusive ? "lte" : "lt", _max, _max != null },
308312
};
309313

310-
private static BsonValue ToBsonValue(TField value) =>
314+
private static BsonValue ToBsonValue(TField? value) =>
311315
value switch
312316
{
313317
sbyte v => (BsonInt32)v,
314318
byte v => (BsonInt32)v,
315319
short v => (BsonInt32)v,
316320
ushort v => (BsonInt32)v,
317321
int v => (BsonInt32)v,
318-
uint v => (BsonInt32)v,
322+
uint v => (BsonInt64)v,
319323
long v => (BsonInt64)v,
320324
float v => (BsonDouble)v,
321325
double v => (BsonDouble)v,
322326
DateTime v => (BsonDateTime)v,
327+
DateTimeOffset v => (BsonDateTime)v.UtcDateTime,
328+
null => null,
323329
_ => throw new InvalidCastException()
324330
};
325331
}

tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -648,23 +648,21 @@ public void RangeDouble()
648648
"{ range: { path: 'age', gt: 1.5, lt: 2.5 } }");
649649
}
650650

651-
[Fact]
652-
public void RangeInt32()
651+
[Theory]
652+
[InlineData(1, null, false, false, "gt: 1")]
653+
[InlineData(1, null, true, false, "gte: 1")]
654+
[InlineData(null, 1, false, false, "lt: 1")]
655+
[InlineData(null, 1, false, true, "lte: 1")]
656+
[InlineData(1, 10, false, false, "gt: 1, lt: 10")]
657+
[InlineData(1, 10, true, false, "gte: 1, lt: 10")]
658+
[InlineData(1, 10, false, true, "gt: 1, lte: 10")]
659+
[InlineData(1, 10, true, true, "gte: 1, lte: 10")]
660+
public void Range_should_render_correct_operator(int? min, int? max, bool minInclusive, bool maxInclusive, string rangeRendered)
653661
{
654662
var subject = CreateSubject<BsonDocument>();
655-
656-
AssertRendered(
657-
subject.Range("x", SearchRangeBuilder.Gt(1).Lt(10)),
658-
"{ range: { path: 'x', gt: 1, lt: 10 } }");
659-
AssertRendered(
660-
subject.Range("x", SearchRangeBuilder.Lt(10).Gt(1)),
661-
"{ range: { path: 'x', gt: 1, lt: 10 } }");
662663
AssertRendered(
663-
subject.Range("x", SearchRangeBuilder.Gte(1).Lte(10)),
664-
"{ range: { path: 'x', gte: 1, lte: 10 } }");
665-
AssertRendered(
666-
subject.Range("x", SearchRangeBuilder.Lte(10).Gte(1)),
667-
"{ range: { path: 'x', gte: 1, lte: 10 } }");
664+
subject.Range("x", new SearchRange<int>(min, max, minInclusive, maxInclusive)),
665+
$"{{ range: {{ path: 'x', {rangeRendered} }} }}");
668666
}
669667

670668
[Fact]
@@ -680,6 +678,62 @@ public void RangeInt32_typed()
680678
"{ range: { path: 'age', gte: 18, lt: 65 } }");
681679
}
682680

681+
[Theory]
682+
[MemberData(nameof(RangeSupportedTypesTestData))]
683+
public void Range_should_render_supported_types<T>(
684+
T min,
685+
T max,
686+
string minRendered,
687+
string maxRendered,
688+
Expression<Func<Person, T>> fieldExpression,
689+
string fieldRendered)
690+
where T : struct, IComparable<T>
691+
{
692+
var subject = CreateSubject<BsonDocument>();
693+
var subjectTyped = CreateSubject<Person>();
694+
695+
AssertRendered(
696+
subject.Range("age", SearchRangeBuilder.Gte(min).Lt(max)),
697+
$"{{ range: {{ path: 'age', gte: {minRendered}, lt: {maxRendered} }} }}");
698+
699+
AssertRendered(
700+
subjectTyped.Range(fieldExpression, SearchRangeBuilder.Gte(min).Lt(max)),
701+
$"{{ range: {{ path: '{fieldRendered}', gte: {minRendered}, lt: {maxRendered} }} }}");
702+
}
703+
704+
public static object[][] RangeSupportedTypesTestData => new[]
705+
{
706+
new object[] { (sbyte)1, (sbyte)2, "1", "2", Exp(p => p.Int8), nameof(Person.Int8) },
707+
new object[] { (byte)1, (byte)2, "1", "2", Exp(p => p.UInt8), nameof(Person.UInt8) },
708+
new object[] { (short)1, (short)2, "1", "2", Exp(p => p.Int16), nameof(Person.Int16) },
709+
new object[] { (ushort)1, (ushort)2, "1", "2", Exp(p => p.UInt16), nameof(Person.UInt16) },
710+
new object[] { (int)1, (int)2, "1", "2", Exp(p => p.Int32), nameof(Person.Int32) },
711+
new object[] { (uint)1, (uint)2, "1", "2", Exp(p => p.UInt32), nameof(Person.UInt32) },
712+
new object[] { long.MinValue, long.MaxValue, "NumberLong(\"-9223372036854775808\")", "NumberLong(\"9223372036854775807\")", Exp(p => p.Int64), nameof(Person.Int64) },
713+
new object[] { (float)1, (float)2, "1", "2", Exp(p => p.Float), nameof(Person.Float) },
714+
new object[] { (double)1, (double)2, "1", "2", Exp(p => p.Double), nameof(Person.Double) },
715+
new object[] { DateTime.MinValue, DateTime.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.Birthday), "dob" },
716+
new object[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) }
717+
};
718+
719+
[Theory]
720+
[MemberData(nameof(RangeUnsupportedTypesTestData))]
721+
public void Range_should_throw_on_unsupported_types<T>(T value, Expression<Func<Person, T>> fieldExpression)
722+
where T : struct, IComparable<T>
723+
{
724+
var subject = CreateSubject<BsonDocument>();
725+
Record.Exception(() => subject.Range("age", SearchRangeBuilder.Gte(value).Lt(value))).Should().BeOfType<InvalidCastException>();
726+
727+
var subjectTyped = CreateSubject<Person>();
728+
Record.Exception(() => subjectTyped.Range(fieldExpression, SearchRangeBuilder.Gte(value).Lt(value))).Should().BeOfType<InvalidCastException>();
729+
}
730+
731+
public static object[][] RangeUnsupportedTypesTestData => new[]
732+
{
733+
new object[] { (ulong)1, Exp(p => p.UInt64) },
734+
new object[] { TimeSpan.Zero, Exp(p => p.TimeSpan) },
735+
};
736+
683737
[Fact]
684738
public void Regex()
685739
{
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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 FluentAssertions;
18+
using MongoDB.Driver.Search;
19+
using Xunit;
20+
21+
namespace MongoDB.Driver.Tests.Search
22+
{
23+
public class SearchRangeBuilderTests
24+
{
25+
[Theory]
26+
[InlineData(1, 0, true, true)]
27+
public void SearchRange_ctor_should_throw_on_invalid_arguments(int min, int max, bool minInclusive, bool maxInclusive)
28+
{
29+
Record.Exception(() => new SearchRange<int>(min, max, minInclusive, maxInclusive))
30+
.Should()
31+
.BeOfType<ArgumentOutOfRangeException>();
32+
}
33+
34+
[Theory]
35+
[InlineData(0, null, true, true)]
36+
[InlineData(null, 1, true, true)]
37+
[InlineData(0, 0, true, true)]
38+
[InlineData(0, 1, true, true)]
39+
[InlineData(0, 1, false, true)]
40+
[InlineData(0, 1, true, false)]
41+
[InlineData(0, 1, true, true)]
42+
public void SearchRange_ctor_should_construct_valid_instance(int? min, int? max, bool minInclusive, bool maxInclusive)
43+
{
44+
var subject = new SearchRange<int>(min, max, minInclusive, maxInclusive);
45+
subject.Min.Should().Be(min);
46+
subject.Max.Should().Be(max);
47+
subject.IsMinInclusive.Should().Be(minInclusive);
48+
subject.IsMaxInclusive.Should().Be(maxInclusive);
49+
}
50+
51+
[Theory]
52+
[InlineData(1, 0, true, true)]
53+
[InlineData(100, -1, false, false)]
54+
public void SearchRangeBuilder_should_throw_on_invalid_arguments(int min, int max, bool minInclusive, bool maxInclusive)
55+
{
56+
SearchRangeBuilder.Lt(min).Gt(max);
57+
Record.Exception(() => new SearchRange<int>(min, max, minInclusive, maxInclusive))
58+
.Should()
59+
.BeOfType<ArgumentOutOfRangeException>();
60+
}
61+
62+
[Theory]
63+
[InlineData(0, null, true, false)]
64+
[InlineData(null, 1, false, true)]
65+
[InlineData(0, 0, true, true)]
66+
[InlineData(0, 1, true, true)]
67+
[InlineData(0, 1, false, true)]
68+
[InlineData(0, 1, true, false)]
69+
[InlineData(0, 1, true, true)]
70+
public void SearchRangeBuilder_should_return_valid_instance(int? min, int? max, bool minInclusive, bool maxInclusive)
71+
{
72+
SearchRange<int>? subject = null;
73+
74+
if (min != null)
75+
{
76+
subject = minInclusive ? SearchRangeBuilder.Gte(min.Value) : SearchRangeBuilder.Gt(min.Value);
77+
}
78+
79+
if (max != null)
80+
{
81+
if (subject != null)
82+
{
83+
subject = maxInclusive ? subject.Value.Lte(max.Value) : subject.Value.Lt(max.Value);
84+
}
85+
else
86+
{
87+
subject = maxInclusive ? SearchRangeBuilder.Lte(max.Value) : SearchRangeBuilder.Lt(max.Value);
88+
}
89+
}
90+
91+
subject.Value.Min.Should().Be(min);
92+
subject.Value.Max.Should().Be(max);
93+
subject.Value.IsMinInclusive.Should().Be(minInclusive);
94+
subject.Value.IsMaxInclusive.Should().Be(maxInclusive);
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)