diff --git a/src/MongoDB.Driver/Core/Misc/Feature.cs b/src/MongoDB.Driver/Core/Misc/Feature.cs
index b6b73d33e02..506e28de10c 100644
--- a/src/MongoDB.Driver/Core/Misc/Feature.cs
+++ b/src/MongoDB.Driver/Core/Misc/Feature.cs
@@ -311,6 +311,8 @@ public class Feature
///
/// Gets the geoNear command feature.
///
+ ///
+ [Obsolete("This property will be removed in a later release.")]
public static Feature GeoNearCommand => __geoNearCommand;
///
diff --git a/src/MongoDB.Driver/Core/Operations/GeoNearOperation.cs b/src/MongoDB.Driver/Core/Operations/GeoNearOperation.cs
deleted file mode 100644
index 1b9a14f1dd9..00000000000
--- a/src/MongoDB.Driver/Core/Operations/GeoNearOperation.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-/* Copyright 2015-present MongoDB Inc.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using MongoDB.Bson;
-using MongoDB.Bson.Serialization;
-using MongoDB.Driver.Core.Bindings;
-using MongoDB.Driver.Core.Connections;
-using MongoDB.Driver.Core.Misc;
-using MongoDB.Driver.Core.WireProtocol.Messages.Encoders;
-
-namespace MongoDB.Driver.Core.Operations
-{
- internal sealed class GeoNearOperation : IReadOperation
- {
- private Collation _collation;
- private readonly CollectionNamespace _collectionNamespace;
- private double? _distanceMultiplier;
- private BsonDocument _filter;
- private bool? _includeLocs;
- private int? _limit;
- private double? _maxDistance;
- private TimeSpan? _maxTime;
- private readonly MessageEncoderSettings _messageEncoderSettings;
- private readonly BsonValue _near;
- private ReadConcern _readConcern = ReadConcern.Default;
- private readonly IBsonSerializer _resultSerializer;
- private bool? _spherical;
- private bool? _uniqueDocs;
-
- public GeoNearOperation(CollectionNamespace collectionNamespace, BsonValue near, IBsonSerializer resultSerializer, MessageEncoderSettings messageEncoderSettings)
- {
- _collectionNamespace = Ensure.IsNotNull(collectionNamespace, nameof(collectionNamespace));
- _near = Ensure.IsNotNull(near, nameof(near));
- _resultSerializer = Ensure.IsNotNull(resultSerializer, nameof(resultSerializer));
- _messageEncoderSettings = Ensure.IsNotNull(messageEncoderSettings, nameof(messageEncoderSettings));
- }
-
- public Collation Collation
- {
- get { return _collation; }
- set { _collation = value; }
- }
-
- public CollectionNamespace CollectionNamespace
- {
- get { return _collectionNamespace; }
- }
-
- public double? DistanceMultiplier
- {
- get { return _distanceMultiplier; }
- set { _distanceMultiplier = value; }
- }
-
- public BsonDocument Filter
- {
- get { return _filter; }
- set { _filter = value; }
- }
-
- public bool? IncludeLocs
- {
- get { return _includeLocs; }
- set { _includeLocs = value; }
- }
-
- public int? Limit
- {
- get { return _limit; }
- set { _limit = value; }
- }
-
- public double? MaxDistance
- {
- get { return _maxDistance; }
- set { _maxDistance = value; }
- }
-
- public TimeSpan? MaxTime
- {
- get { return _maxTime; }
- set { _maxTime = Ensure.IsNullOrInfiniteOrGreaterThanOrEqualToZero(value, nameof(value)); }
- }
-
- public MessageEncoderSettings MessageEncoderSettings
- {
- get { return _messageEncoderSettings; }
- }
-
- public BsonValue Near
- {
- get { return _near; }
- }
-
- public ReadConcern ReadConcern
- {
- get { return _readConcern; }
- set { _readConcern = Ensure.IsNotNull(value, nameof(value)); }
- }
-
- public IBsonSerializer ResultSerializer
- {
- get { return _resultSerializer; }
- }
-
- public bool? Spherical
- {
- get { return _spherical; }
- set { _spherical = value; }
- }
-
- public bool? UniqueDocs
- {
- get { return _uniqueDocs; }
- set { _uniqueDocs = value; }
- }
-
- public BsonDocument CreateCommand(ConnectionDescription connectionDescription, ICoreSession session)
- {
- var readConcern = ReadConcernHelper.GetReadConcernForCommand(session, connectionDescription, _readConcern);
- return new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", _near },
- { "limit", () => _limit.Value, _limit.HasValue },
- { "maxDistance", () => _maxDistance.Value, _maxDistance.HasValue },
- { "query", _filter, _filter != null },
- { "spherical", () => _spherical.Value, _spherical.HasValue },
- { "distanceMultiplier", () => _distanceMultiplier.Value, _distanceMultiplier.HasValue },
- { "includeLocs", () => _includeLocs.Value, _includeLocs.HasValue },
- { "uniqueDocs", () => _uniqueDocs.Value, _uniqueDocs.HasValue },
- { "maxTimeMS", () => MaxTimeHelper.ToMaxTimeMS(_maxTime.Value), _maxTime.HasValue },
- { "collation", () => _collation.ToBsonDocument(), _collation != null },
- { "readConcern", readConcern, readConcern != null }
- };
- }
-
- public TResult Execute(IReadBinding binding, CancellationToken cancellationToken)
- {
- Ensure.IsNotNull(binding, nameof(binding));
- using (var channelSource = binding.GetReadChannelSource(cancellationToken))
- using (var channel = channelSource.GetChannel(cancellationToken))
- using (var channelBinding = new ChannelReadBinding(channelSource.Server, channel, binding.ReadPreference, binding.Session.Fork()))
- {
- var operation = CreateOperation(channel, channelBinding);
- return operation.Execute(channelBinding, cancellationToken);
- }
- }
-
- public async Task ExecuteAsync(IReadBinding binding, CancellationToken cancellationToken)
- {
- Ensure.IsNotNull(binding, nameof(binding));
- using (var channelSource = await binding.GetReadChannelSourceAsync(cancellationToken).ConfigureAwait(false))
- using (var channel = await channelSource.GetChannelAsync(cancellationToken).ConfigureAwait(false))
- using (var channelBinding = new ChannelReadBinding(channelSource.Server, channel, binding.ReadPreference, binding.Session.Fork()))
- {
- var operation = CreateOperation(channel, channelBinding);
- return await operation.ExecuteAsync(channelBinding, cancellationToken).ConfigureAwait(false);
- }
- }
-
- private ReadCommandOperation CreateOperation(IChannel channel, IBinding binding)
- {
- var command = CreateCommand(channel.ConnectionDescription, binding.Session);
- return new ReadCommandOperation(
- _collectionNamespace.DatabaseNamespace,
- command,
- _resultSerializer,
- _messageEncoderSettings)
- {
- RetryRequested = false
- };
- }
- }
-}
diff --git a/src/MongoDB.Driver/GeoNearOptions.cs b/src/MongoDB.Driver/GeoNearOptions.cs
new file mode 100644
index 00000000000..5211fa35719
--- /dev/null
+++ b/src/MongoDB.Driver/GeoNearOptions.cs
@@ -0,0 +1,71 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using MongoDB.Bson.Serialization;
+
+namespace MongoDB.Driver
+{
+ ///
+ /// Represents options for the $geoNear stage.
+ ///
+ public class GeoNearOptions
+ {
+ ///
+ /// Gets or sets the output field that contains the calculated distance. Required if querying a time-series collection.
+ /// Optional for non-time series collections in MongoDB 8.1+
+ ///
+ public FieldDefinition DistanceField { get; set; }
+
+ ///
+ /// Gets or sets the factor to multiply all distances returned by the query.
+ ///
+ public double? DistanceMultiplier { get; set; }
+
+ ///
+ /// Gets or sets the output field that identifies the location used to calculate the distance.
+ ///
+ public FieldDefinition IncludeLocs { get; set; }
+
+ ///
+ /// Gets or sets the geospatial indexed field used when calculating the distance.
+ ///
+ public string Key { get; set; }
+
+ ///
+ /// Gets or sets the max distance from the center point that the documents can be.
+ ///
+ public double? MaxDistance { get; set; }
+
+ ///
+ /// Gets or sets the min distance from the center point that the documents can be.
+ ///
+ public double? MinDistance { get; set; }
+
+ ///
+ /// Gets or sets the output serializer.
+ ///
+ public IBsonSerializer OutputSerializer { get; set; }
+
+ ///
+ /// Gets or sets the query that limits the results to the documents that match the query.
+ ///
+ public FilterDefinition Query { get; set; }
+
+ ///
+ /// Gets or sets the spherical option which determines how to calculate the distance between two points.
+ ///
+ public bool? Spherical { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/MongoDB.Driver/IAggregateFluentExtensions.cs b/src/MongoDB.Driver/IAggregateFluentExtensions.cs
index fba43b6a38a..4e5e5fe44bf 100644
--- a/src/MongoDB.Driver/IAggregateFluentExtensions.cs
+++ b/src/MongoDB.Driver/IAggregateFluentExtensions.cs
@@ -22,6 +22,7 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Core.Misc;
+using MongoDB.Driver.GeoJsonObjectModel;
namespace MongoDB.Driver
{
@@ -252,6 +253,44 @@ public static IAggregateFluent Facet(
return aggregate.AppendStage(PipelineStageDefinitionBuilder.Facet(facets));
}
+ ///
+ /// Appends a $geoNear stage to the pipeline.
+ ///
+ /// The type of the result.
+ /// The type of the new result.
+ /// The type of the coordinates for the point.
+ /// The aggregate.
+ /// The point for which to find the closest documents.
+ /// The options.
+ /// The fluent aggregate interface.
+ public static IAggregateFluent GeoNear(
+ this IAggregateFluent aggregate,
+ GeoJsonPoint near,
+ GeoNearOptions options = null)
+ where TCoordinates : GeoJsonCoordinates
+ {
+ Ensure.IsNotNull(aggregate, nameof(aggregate));
+ return aggregate.AppendStage(PipelineStageDefinitionBuilder.GeoNear, TNewResult>(near, options));
+ }
+
+ ///
+ /// Appends a $geoNear stage to the pipeline.
+ ///
+ /// The type of the result.
+ /// The type of the new result.
+ /// The aggregate.
+ /// The point for which to find the closest documents.
+ /// The options.
+ /// The fluent aggregate interface.
+ public static IAggregateFluent GeoNear(
+ this IAggregateFluent aggregate,
+ double[] near,
+ GeoNearOptions options = null)
+ {
+ Ensure.IsNotNull(aggregate, nameof(aggregate));
+ return aggregate.AppendStage(PipelineStageDefinitionBuilder.GeoNear(near, options));
+ }
+
///
/// Appends a $graphLookup stage to the pipeline.
///
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstGeoNearStage.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstGeoNearStage.cs
index 2d4dae4ba61..9a5945a4a6d 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstGeoNearStage.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstGeoNearStage.cs
@@ -45,7 +45,7 @@ public AstGeoNearStage(
string key)
{
_near = Ensure.IsNotNull(near, nameof(near));
- _distanceField = Ensure.IsNotNull(distanceField, nameof(distanceField));
+ _distanceField = distanceField;
_spherical = spherical;
_maxDistance = maxDistance;
_query = query;
@@ -80,7 +80,7 @@ public override BsonValue Render()
{ "$geoNear", new BsonDocument
{
{ "near", _near },
- { "distanceField", _distanceField },
+ { "distanceField", _distanceField, _distanceField != null },
{ "spherical", () => _spherical.Value, _spherical.HasValue },
{ "maxDistance", () => _maxDistance.Value, _maxDistance.HasValue },
{ "query", _query, _query != null },
diff --git a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
index cd315417425..3ad6eca971a 100644
--- a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
@@ -20,6 +20,7 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Core.Misc;
+using MongoDB.Driver.GeoJsonObjectModel;
using MongoDB.Driver.Search;
namespace MongoDB.Driver
@@ -484,6 +485,46 @@ public static PipelineDefinition For(IBsonSerializer(inputSerializer);
}
+ ///
+ /// Appends a $geoNear stage to the pipeline.
+ ///
+ /// The type of the input documents.
+ /// The type of the intermediate documents.
+ /// The type of the output documents.
+ /// The type of the coordinates for the point.
+ /// The pipeline.
+ /// The point for which to find the closest documents.
+ /// The options.
+ /// A new pipeline with an additional stage.
+ public static PipelineDefinition GeoNear(
+ this PipelineDefinition pipeline,
+ GeoJsonPoint near,
+ GeoNearOptions options = null)
+ where TCoordinates : GeoJsonCoordinates
+ {
+ Ensure.IsNotNull(pipeline, nameof(pipeline));
+ return pipeline.AppendStage(PipelineStageDefinitionBuilder.GeoNear(near, options));
+ }
+
+ ///
+ /// Appends a $geoNear stage to the pipeline.
+ ///
+ /// The type of the input documents.
+ /// The type of the intermediate documents.
+ /// The type of the output documents.
+ /// The pipeline.
+ /// The point for which to find the closest documents.
+ /// The options.
+ /// A new pipeline with an additional stage.
+ public static PipelineDefinition GeoNear(
+ this PipelineDefinition pipeline,
+ double[] near,
+ GeoNearOptions options = null)
+ {
+ Ensure.IsNotNull(pipeline, nameof(pipeline));
+ return pipeline.AppendStage(PipelineStageDefinitionBuilder.GeoNear(near, options));
+ }
+
///
/// Appends a $graphLookup stage to the pipeline.
///
diff --git a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
index 57fe14be168..b8c7ee834fb 100644
--- a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
@@ -21,8 +21,8 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
-using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Core.Misc;
+using MongoDB.Driver.GeoJsonObjectModel;
using MongoDB.Driver.Linq;
using MongoDB.Driver.Linq.Linq3Implementation;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
@@ -605,6 +605,74 @@ public static PipelineStageDefinition Facet(
return Facet((IEnumerable>)facets);
}
+ internal static PipelineStageDefinition GeoNear(
+ TPoint near,
+ GeoNearOptions options = null)
+ // where TPoint is either a GeoJsonPoint or a legacy coordinate array
+ {
+ const string operatorName = "$geoNear";
+ var stage = new DelegatedPipelineStageDefinition(
+ operatorName,
+ args =>
+ {
+ ClientSideProjectionHelper.ThrowIfClientSideProjection(args.DocumentSerializer, operatorName);
+ var pointSerializer = args.SerializerRegistry.GetSerializer();
+ var outputSerializer = options?.OutputSerializer ?? args.GetSerializer();
+ var outputRenderArgs = args.WithNewDocumentType(outputSerializer);
+ var geoNearOptions = new BsonDocument
+ {
+ { "near", pointSerializer.ToBsonValue(near)},
+ { "distanceField", options?.DistanceField?.Render(outputRenderArgs).FieldName, options?.DistanceField != null },
+ { "maxDistance", options?.MaxDistance, options?.MaxDistance != null },
+ { "minDistance", options?.MinDistance, options?.MinDistance != null },
+ { "distanceMultiplier", options?.DistanceMultiplier, options?.DistanceMultiplier != null },
+ { "key", options?.Key, options?.Key != null },
+ { "query", options?.Query?.Render(args), options?.Query != null },
+ { "includeLocs", options?.IncludeLocs?.Render(outputRenderArgs).FieldName, options?.IncludeLocs != null },
+ { "spherical", options?.Spherical, options?.Spherical != null }
+ };
+
+ return new RenderedPipelineStageDefinition(operatorName, new BsonDocument(operatorName, geoNearOptions), outputSerializer);
+ });
+
+ return stage;
+ }
+
+ ///
+ /// Creates a $geoNear stage.
+ ///
+ /// The type of the input documents.
+ /// The type of the output documents.
+ /// The type of the coordinates for the point.
+ /// The point for which to find the closest documents.
+ /// The options.
+ /// The stage.
+ public static PipelineStageDefinition GeoNear(
+ GeoJsonPoint near,
+ GeoNearOptions options = null)
+ where TCoordinates : GeoJsonCoordinates
+ {
+ Ensure.IsNotNull(near, nameof(near));
+ return GeoNear, TOutput>(near, options);
+ }
+
+ ///
+ /// Creates a $geoNear stage.
+ ///
+ /// The type of the input documents.
+ /// The type of the output documents.
+ /// The point for which to find the closest documents.
+ /// The options.
+ /// The stage.
+ public static PipelineStageDefinition GeoNear(
+ double[] near,
+ GeoNearOptions options = null)
+ {
+ Ensure.IsNotNull(near, nameof(near));
+ Ensure.That(near.Length, len => len is 2 or 3, nameof(near), "Legacy coordinates array should have 2 or 3 coordinates.");
+ return GeoNear(near, options);
+ }
+
///
/// Creates a $graphLookup stage.
///
diff --git a/tests/MongoDB.Driver.Tests/AggregateGeoNearTests.cs b/tests/MongoDB.Driver.Tests/AggregateGeoNearTests.cs
new file mode 100644
index 00000000000..0b80fb3af78
--- /dev/null
+++ b/tests/MongoDB.Driver.Tests/AggregateGeoNearTests.cs
@@ -0,0 +1,180 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using FluentAssertions;
+using MongoDB.Bson.Serialization.Attributes;
+using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
+using MongoDB.Driver.GeoJsonObjectModel;
+using Xunit;
+
+namespace MongoDB.Driver.Tests
+{
+ public class AggregateGeoNearTests : IntegrationTest
+ {
+ public AggregateGeoNearTests(ClassFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ [Fact]
+ public void GeoNear_omitting_distanceField_should_return_expected_result()
+ {
+ RequireServer.Check().VersionGreaterThanOrEqualTo("8.1.0");
+
+ var collection = Fixture.GeoCollection;
+ var result = collection
+ .Aggregate()
+ .GeoNear(
+ GeoJson.Point(GeoJson.Geographic(-73.99279, 40.719296)),
+ new GeoNearOptions
+ {
+ MaxDistance = 2,
+ Key = "GeoJsonPointLocation",
+ Query = Builders.Filter.Eq(p => p.Category,
+ "Parks"),
+ Spherical = true
+ })
+ .ToList();
+
+ result.Count.Should().Be(1);
+ result[0].Name.Should().Be("Sara D. Roosevelt Park");
+ }
+
+ [Fact]
+ public void GeoNear_using_pipeline_should_return_expected_result()
+ {
+ var collection = Fixture.GeoCollection;
+ var pipeline = new EmptyPipelineDefinition()
+ .GeoNear(
+ [-73.99279, 40.719296],
+ new GeoNearOptions
+ {
+ DistanceField = "Distance",
+ MaxDistance = 0.000313917534,
+ Key = "LegacyCoordinateLocation",
+ Query = Builders.Filter.Eq(p => p.Category,
+ "Parks"),
+ Spherical = true
+ });
+
+ var result = collection.Aggregate(pipeline).ToList();
+
+ result.Count.Should().Be(1);
+ result[0].Name.Should().Be("Sara D. Roosevelt Park");
+ }
+
+ [Fact]
+ public void GeoNear_with_array_legacy_coordinates_should_return_expected_result()
+ {
+ var collection = Fixture.GeoCollection;
+
+ var result = collection
+ .Aggregate()
+ .GeoNear(
+ [-73.99279, 40.719296],
+ new GeoNearOptions
+ {
+ DistanceField = "Distance",
+ MaxDistance = 0.000313917534,
+ Key = "LegacyCoordinateLocation",
+ Query = Builders.Filter.Eq(p => p.Category,
+ "Parks"),
+ Spherical = true
+ })
+ .ToList();
+
+ result.Count.Should().Be(1);
+ result[0].Name.Should().Be("Sara D. Roosevelt Park");
+ }
+
+ [Fact]
+ public void GeoNear_with_GeoJsonPoint_should_return_expected_result()
+ {
+ var collection = Fixture.GeoCollection;
+
+ var result = collection
+ .Aggregate()
+ .GeoNear(
+ GeoJson.Point(GeoJson.Geographic(-73.99279, 40.719296)),
+ new GeoNearOptions
+ {
+ DistanceField = "Distance",
+ MaxDistance = 2,
+ Key = "GeoJsonPointLocation",
+ Query = Builders.Filter.Eq(p => p.Category,
+ "Parks"),
+ Spherical = true
+ })
+ .ToList();
+
+ result.Count.Should().Be(1);
+ result[0].Name.Should().Be("Sara D. Roosevelt Park");
+ }
+
+ [BsonIgnoreExtraElements]
+ public class Place
+ {
+ public string Name { get; set; }
+ public GeoJsonPoint GeoJsonPointLocation { get; set; }
+ public double[] LegacyCoordinateLocation { get; set; }
+ public string Category { get; set; }
+ }
+
+ [BsonIgnoreExtraElements]
+ public class PlaceResult : Place
+ {
+ [BsonElement("dist")]
+ public double Distance { get; set; }
+ }
+
+ public sealed class ClassFixture : MongoDatabaseFixture
+ {
+ public IMongoCollection GeoCollection { get; private set; }
+
+ protected override void InitializeFixture()
+ {
+ GeoCollection = CreateCollection("geoCollection");
+ GeoCollection.InsertMany([
+ new()
+ {
+ Name = "Central Park",
+ GeoJsonPointLocation = GeoJson.Point(GeoJson.Geographic(-73.97, 40.77)),
+ LegacyCoordinateLocation = [-73.97, 40.77],
+ Category = "Parks"
+ },
+ new()
+ {
+ Name = "Sara D. Roosevelt Park",
+ GeoJsonPointLocation = GeoJson.Point(GeoJson.Geographic(-73.9928, 40.7193)),
+ LegacyCoordinateLocation = [-73.9928, 40.7193],
+ Category = "Parks"
+ },
+ new()
+ {
+ Name = "Polo Grounds",
+ GeoJsonPointLocation = GeoJson.Point(GeoJson.Geographic(-73.9375, 40.8303)),
+ LegacyCoordinateLocation = [-73.9375, 40.8303],
+ Category = "Stadiums"
+ }
+ ]);
+
+ GeoCollection.Indexes.CreateOne(
+ new CreateIndexModel(Builders.IndexKeys.Geo2DSphere(p => p.GeoJsonPointLocation)));
+ GeoCollection.Indexes.CreateOne(
+ new CreateIndexModel(Builders.IndexKeys.Geo2D(p => p.LegacyCoordinateLocation)));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/MongoDB.Driver.Tests/Core/Operations/GeoNearOperationTests.cs b/tests/MongoDB.Driver.Tests/Core/Operations/GeoNearOperationTests.cs
deleted file mode 100644
index 93de6ccd054..00000000000
--- a/tests/MongoDB.Driver.Tests/Core/Operations/GeoNearOperationTests.cs
+++ /dev/null
@@ -1,651 +0,0 @@
-/* Copyright 2013-present MongoDB Inc.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-using System;
-using System.Linq;
-using FluentAssertions;
-using MongoDB.Bson;
-using MongoDB.Bson.Serialization;
-using MongoDB.Bson.Serialization.Serializers;
-using MongoDB.Driver.Core.Clusters;
-using MongoDB.Driver.Core.Misc;
-using MongoDB.Driver.Core.TestHelpers;
-using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
-using MongoDB.TestHelpers.XunitExtensions;
-using Xunit;
-
-namespace MongoDB.Driver.Core.Operations
-{
- public class GeoNearOperationTests : OperationTestBase
- {
- private readonly BsonValue _near = new BsonArray { 1, 2 };
- private readonly IBsonSerializer _resultSerializer = BsonDocumentSerializer.Instance;
-
- [Fact]
- public void constructor_should_initialize_instance()
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- subject.CollectionNamespace.Should().BeSameAs(_collectionNamespace);
- subject.Near.Should().BeSameAs(_near);
- subject.ResultSerializer.Should().BeSameAs(_resultSerializer);
- subject.MessageEncoderSettings.Should().BeSameAs(_messageEncoderSettings);
-
- subject.Collation.Should().BeNull();
- subject.DistanceMultiplier.Should().NotHaveValue();
- subject.Filter.Should().BeNull();
- subject.IncludeLocs.Should().NotHaveValue();
- subject.Limit.Should().NotHaveValue();
- subject.MaxDistance.Should().NotHaveValue();
- subject.MaxTime.Should().NotHaveValue();
- subject.ReadConcern.Should().BeSameAs(ReadConcern.Default);
- subject.Spherical.Should().NotHaveValue();
- subject.UniqueDocs.Should().NotHaveValue();
- }
-
- [Fact]
- public void Constructor_should_throw_when_collectionNamespace_is_null()
- {
- var exception = Record.Exception(() => new GeoNearOperation(null, _near, _resultSerializer, _messageEncoderSettings));
-
- var argumentNullException = exception.Should().BeOfType().Subject;
- argumentNullException.ParamName.Should().Be("collectionNamespace");
- }
-
- [Fact]
- public void Constructor_should_throw_when_near_is_null()
- {
- var exception = Record.Exception(() => new GeoNearOperation(_collectionNamespace, null, _resultSerializer, _messageEncoderSettings));
-
- var argumentNullException = exception.Should().BeOfType().Subject;
- argumentNullException.ParamName.Should().Be("near");
- }
-
- [Fact]
- public void Constructor_should_throw_when_resultSerializer_is_null()
- {
- var exception = Record.Exception(() => new GeoNearOperation(_collectionNamespace, _near, null, _messageEncoderSettings));
-
- var argumentNullException = exception.Should().BeOfType().Subject;
- argumentNullException.ParamName.Should().Be("resultSerializer");
- }
-
- [Fact]
- public void Constructor_should_throw_when_messageEncoderSettings_is_null()
- {
- var exception = Record.Exception(() => new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, null));
-
- var argumentNullException = exception.Should().BeOfType().Subject;
- argumentNullException.ParamName.Should().Be("messageEncoderSettings");
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Collation_get_and_set_should_work(
- [Values(null, "en_US", "fr_CA")]
- string locale)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
- var value = locale == null ? null : new Collation(locale);
-
- subject.Collation = value;
- var result = subject.Collation;
-
- result.Should().BeSameAs(value);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void DistanceMultiplier_get_and_set_should_work(
- [Values(null, 1.0, 2.0)]
- double? value)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- subject.DistanceMultiplier = value;
- var result = subject.DistanceMultiplier;
-
- result.Should().Be(value);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Filter_get_and_set_should_work(
- [Values(null, "{ x : 1 }", "{ x : 2 }")]
- string valueString)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
- var value = valueString == null ? null : BsonDocument.Parse(valueString);
-
- subject.Filter = value;
- var result = subject.Filter;
-
- result.Should().BeSameAs(value);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void IncludeLocs_get_and_set_should_work(
- [Values(null, false, true)]
- bool? value)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- subject.IncludeLocs = value;
- var result = subject.IncludeLocs;
-
- result.Should().Be(value);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Limit_get_and_set_should_work(
- [Values(null, 1, 2)]
- int? value)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- subject.Limit = value;
- var result = subject.Limit;
-
- result.Should().Be(value);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void MaxDistance_get_and_set_should_work(
- [Values(null, 1.0, 2.0)]
- double? value)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- subject.MaxDistance = value;
- var result = subject.MaxDistance;
-
- result.Should().Be(value);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void MaxTime_get_and_set_should_work(
- [Values(-10000, 0, 1, 10000, 99999)] long maxTimeTicks)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
- var value = TimeSpan.FromTicks(maxTimeTicks);
-
- subject.MaxTime = value;
- var result = subject.MaxTime;
-
- result.Should().Be(value);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void MaxTime_set_should_throw_when_value_is_invalid(
- [Values(-10001, -9999, -1)] long maxTimeTicks)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
- var value = TimeSpan.FromTicks(maxTimeTicks);
-
- var exception = Record.Exception(() => subject.MaxTime = value);
-
- var e = exception.Should().BeOfType().Subject;
- e.ParamName.Should().Be("value");
- }
-
- [Theory]
- [ParameterAttributeData]
- public void ReadConcern_get_and_set_should_work(
- [Values(ReadConcernLevel.Linearizable, ReadConcernLevel.Local)]
- ReadConcernLevel level)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
- var value = new ReadConcern(level);
-
- subject.ReadConcern = value;
- var result = subject.ReadConcern;
-
- result.Should().Be(value);
- }
-
- [Fact]
- public void ReadConcern_set_should_throw_when_value_is_null()
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- var exception = Record.Exception(() => subject.ReadConcern = null);
-
- var argumentNulException = exception.Should().BeOfType().Subject;
- argumentNulException.ParamName.Should().Be("value");
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Spherical_get_and_set_should_work(
- [Values(null, false, true)]
- bool? value)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- subject.Spherical = value;
- var result = subject.Spherical;
-
- result.Should().Be(value);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void UniqueDocs_get_and_set_should_work(
- [Values(null, false, true)]
- bool? value)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- subject.UniqueDocs = value;
- var result = subject.UniqueDocs;
-
- result.Should().Be(value);
- }
-
- [Fact]
- public void CreateCommand_should_return_expected_result()
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_Collation_is_set(
- [Values(null, "en_US", "fr_CA")]
- string locale)
- {
- var collation = locale == null ? null : new Collation(locale);
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- Collation = collation
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "collation", () => collation.ToBsonDocument(), collation != null }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_DistanceMultiplier_is_set(
- [Values(null, 1.0, 2.0)]
- double? distanceMultiplier)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- DistanceMultiplier = distanceMultiplier
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "distanceMultiplier", () => distanceMultiplier.Value, distanceMultiplier.HasValue }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_Filter_is_set(
- [Values(null, "{ x : 1 }", "{ x : 2 }")]
- string filterString)
- {
- var filter = filterString == null ? null : BsonDocument.Parse(filterString);
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- Filter = filter
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "query", () => filter, filter != null }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_IncludeLocs_is_set(
- [Values(null, false, true)]
- bool? includeLocs)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- IncludeLocs = includeLocs
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "includeLocs", () => includeLocs.Value, includeLocs.HasValue }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_Limit_is_set(
- [Values(null, 1, 2)]
- int? limit)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- Limit = limit
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "limit", () => limit.Value, limit.HasValue }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_MaxDistance_is_set(
- [Values(null, 1.0, 2.0)]
- double? maxDistance)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- MaxDistance = maxDistance
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "maxDistance", () => maxDistance.Value, maxDistance.HasValue }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [InlineData(-10000, 0)]
- [InlineData(0, 0)]
- [InlineData(1, 1)]
- [InlineData(9999, 1)]
- [InlineData(10000, 1)]
- [InlineData(10001, 2)]
- public void CreateCommand_should_return_expected_result_when_MaxTime_is_set(long maxTimeTicks, int expectedMaxTimeMS)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- MaxTime = TimeSpan.FromTicks(maxTimeTicks)
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "maxTimeMS", expectedMaxTimeMS }
- };
- result.Should().Be(expectedResult);
- result["maxTimeMS"].BsonType.Should().Be(BsonType.Int32);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_ReadConcern_is_set(
- [Values(null, ReadConcernLevel.Linearizable, ReadConcernLevel.Local)]
- ReadConcernLevel? level)
- {
- var readConcern = new ReadConcern(level);
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- ReadConcern = readConcern
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "readConcern", () => readConcern.ToBsonDocument(), !readConcern.IsServerDefault }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_Spherical_is_set(
- [Values(null, false, true)]
- bool? spherical)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- Spherical = spherical
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "spherical", () => spherical.Value, spherical.HasValue }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_expected_result_when_UniqueDocs_is_set(
- [Values(null, false, true)]
- bool? uniqueDocs)
- {
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- UniqueDocs = uniqueDocs
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription();
- var session = OperationTestHelper.CreateSession();
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "uniqueDocs", () => uniqueDocs.Value, uniqueDocs.HasValue }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void CreateCommand_should_return_the_expected_result_when_using_causal_consistency(
- [Values(null, ReadConcernLevel.Linearizable, ReadConcernLevel.Local)]
- ReadConcernLevel? level)
- {
- var readConcern = new ReadConcern(level);
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- ReadConcern = readConcern
- };
-
- var connectionDescription = OperationTestHelper.CreateConnectionDescription(supportsSessions: true);
- var session = OperationTestHelper.CreateSession(true, new BsonTimestamp(100));
-
- var result = subject.CreateCommand(connectionDescription, session);
-
- var expectedReadConcernDocument = readConcern.ToBsonDocument();
- expectedReadConcernDocument["afterClusterTime"] = new BsonTimestamp(100);
-
- var expectedResult = new BsonDocument
- {
- { "geoNear", _collectionNamespace.CollectionName },
- { "near", new BsonArray { 1, 2 } },
- { "readConcern", expectedReadConcernDocument }
- };
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Execute_should_return_expected_result(
- [Values(false, true)]
- bool async)
- {
- RequireServer.Check().Supports(Feature.GeoNearCommand);
- EnsureTestData();
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- var result = ExecuteOperation(subject, async);
-
- result["results"].AsBsonArray.Count.Should().Be(5);
- result["results"].AsBsonArray.Select(i => i["dis"].ToDouble()).Should().BeInAscendingOrder();
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Execute_should_return_expected_result_when_Collation_is_set(
- [Values(false, true)]
- bool caseSensitive,
- [Values(false, true)]
- bool async)
- {
- RequireServer.Check().Supports(Feature.GeoNearCommand);
- EnsureTestData();
- var collation = new Collation("en_US", caseLevel: caseSensitive, strength: CollationStrength.Primary);
- var filter = BsonDocument.Parse("{ x : 'x' }");
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings)
- {
- Collation = collation,
- Filter = filter
- };
-
- var result = ExecuteOperation(subject, async);
-
- result["results"].AsBsonArray.Count.Should().Be(caseSensitive ? 2 : 5);
- result["results"].AsBsonArray.Select(i => i["dis"].ToDouble()).Should().BeInAscendingOrder();
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Execute_should_send_session_id_when_supported(
- [Values(false, true)] bool async)
- {
- RequireServer.Check().Supports(Feature.GeoNearCommand);
- EnsureTestData();
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
-
- VerifySessionIdWasSentWhenSupported(subject, "geoNear", async);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Execute_should_throw_when_maxTime_is_exceeded(
- [Values(false, true)] bool async)
- {
- RequireServer.Check().Supports(Feature.GeoNearCommand).ClusterTypes(ClusterType.Standalone, ClusterType.ReplicaSet);
- var subject = new GeoNearOperation(_collectionNamespace, _near, _resultSerializer, _messageEncoderSettings);
- subject.MaxTime = TimeSpan.FromSeconds(9001);
-
- using (var failPoint = FailPoint.ConfigureAlwaysOn(_cluster, _session, FailPointName.MaxTimeAlwaysTimeout))
- {
- var exception = Record.Exception(() => ExecuteOperation(subject, failPoint.Binding, async));
-
- exception.Should().BeOfType();
- }
- }
-
- // helper methods
- private void EnsureTestData()
- {
- RunOncePerFixture(() =>
- {
- DropCollection();
- Insert(Enumerable.Range(1, 5).Select(id => new BsonDocument
- {
- { "_id", id },
- { "Location", new BsonArray { id, id + 1 } },
- { "x", (id % 2) == 0 ? "x" : "X" } // some lower case and some upper case
- }));
- CreateIndexes(new CreateIndexRequest(new BsonDocument("Location", "2d")));
- });
- }
- }
-}
diff --git a/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs
index 7e32b5afac4..3c556553c7d 100644
--- a/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs
+++ b/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs
@@ -19,7 +19,7 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
-using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
+using MongoDB.Driver.GeoJsonObjectModel;
using MongoDB.Driver.Search;
using Moq;
using Xunit;
@@ -107,10 +107,65 @@ public void ChangeStreamSplitLargeEvent_should_throw_when_pipeline_is_null()
}
[Fact]
- public void Lookup_should_throw_when_pipeline_is_null()
+ public void GeoNear_with_geojson_point_should_add_the_expected_stage()
+ {
+ var pipeline = new EmptyPipelineDefinition();
+
+ var result = pipeline.GeoNear(
+ GeoJson.Point(GeoJson.Geographic(34, 67)),
+ new GeoNearOptions
+ {
+ DistanceField = "calculatedDistance"
+ });
+
+ var stages = RenderStages(result, BsonDocumentSerializer.Instance);
+ stages.Count.Should().Be(1);
+ stages[0].Should().Be("""{ "$geoNear" : { "near" : { "type" : "Point", "coordinates" : [34.0, 67.0] }, "distanceField" : "calculatedDistance" } }""");
+ }
+
+ [Fact]
+ public void GeoNear_with_array_should_add_the_expected_stage()
+ {
+ var pipeline = new EmptyPipelineDefinition();
+
+ var result = pipeline.GeoNear(
+ [34.0, 67.0],
+ new GeoNearOptions
+ {
+ DistanceField = "calculatedDistance"
+ });
+
+ var stages = RenderStages(result, BsonDocumentSerializer.Instance);
+ stages.Count.Should().Be(1);
+ stages[0].Should().Be("""{ "$geoNear" : { "near" : [34.0, 67.0], "distanceField" : "calculatedDistance" } }""");
+ }
+
+ [Fact]
+ public void GeoNear_should_throw_when_pipeline_is_null()
{
- RequireServer.Check();
+ PipelineDefinition pipeline = null;
+
+ var exception = Record.Exception(() =>
+ pipeline.GeoNear([1.0, 2.0]));
+
+ exception.Should().BeOfType()
+ .Which.ParamName.Should().Be("pipeline");
+ }
+ [Fact]
+ public void GeoNear_should_throw_when_near_point_is_null()
+ {
+ var pipeline = new EmptyPipelineDefinition();
+
+ var exception = Record.Exception(() => pipeline.GeoNear(null));
+
+ exception.Should().BeOfType()
+ .Which.ParamName.Should().Be("near");
+ }
+
+ [Fact]
+ public void Lookup_should_throw_when_pipeline_is_null()
+ {
PipelineDefinition> pipeline = null;
IMongoCollection collection = null;
diff --git a/tests/MongoDB.Driver.Tests/PipelineStageDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/PipelineStageDefinitionBuilderTests.cs
index 6815cd56e9c..d78985a8932 100644
--- a/tests/MongoDB.Driver.Tests/PipelineStageDefinitionBuilderTests.cs
+++ b/tests/MongoDB.Driver.Tests/PipelineStageDefinitionBuilderTests.cs
@@ -21,6 +21,7 @@
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
+using MongoDB.Driver.GeoJsonObjectModel;
using Moq;
using Xunit;
@@ -133,6 +134,91 @@ public void ChangeStreamSplitLargeEvent_should_return_the_expected_result()
stage.Document.Should().Be("{ $changeStreamSplitLargeEvent : { } }");
}
+ [Fact]
+ public void GeoNear_with_array_should_return_the_expected_result()
+ {
+ var result = PipelineStageDefinitionBuilder.GeoNear(
+ [34.0, 67.0],
+ new GeoNearOptions
+ {
+ DistanceField = "calculatedDistance",
+ MaxDistance = 3,
+ IncludeLocs = "usedLocation",
+ Spherical = true,
+ Query = new BsonDocument("testfield", "testvalue")
+ });
+
+ var stage = RenderStage(result);
+ stage.Document.Should().Be("""
+ {
+ "$geoNear" : {
+ "near" : [34.0, 67.0],
+ "distanceField" : "calculatedDistance",
+ "maxDistance" : 3.0,
+ "query" : { "testfield" : "testvalue" },
+ "includeLocs" : "usedLocation",
+ "spherical" : true
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public void GeoNear_with_geojson_point_should_return_the_expected_result()
+ {
+ var result = PipelineStageDefinitionBuilder.GeoNear(
+ GeoJson.Point(GeoJson.Geographic(34, 67)),
+ new GeoNearOptions
+ {
+ DistanceField = "calculatedDistance",
+ MaxDistance = 3,
+ IncludeLocs = "usedLocation",
+ Spherical = true,
+ Query = new BsonDocument("testfield", "testvalue")
+ });
+
+ var stage = RenderStage(result);
+ stage.Document.Should().Be("""
+ {
+ "$geoNear" : {
+ "near" : { "type" : "Point", "coordinates" : [34.0, 67.0] },
+ "distanceField" : "calculatedDistance",
+ "maxDistance" : 3.0,
+ "query" : { "testfield" : "testvalue" },
+ "includeLocs" : "usedLocation",
+ "spherical" : true
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public void GeoNear_with_no_options_should_return_the_expected_result()
+ {
+ var result =
+ PipelineStageDefinitionBuilder.GeoNear(
+ GeoJson.Point(GeoJson.Geographic(34, 67)));
+
+ var stage = RenderStage(result);
+ stage.Document.Should().Be("""{ "$geoNear" : { "near" : { "type" : "Point", "coordinates" : [34.0, 67.0] } } }""");
+ }
+
+ [Fact]
+ public void GeoNear_with_wrong_legacy_coordinates_should_throw_exception()
+ {
+ Assert.Throws(() =>
+ {
+ var result =
+ PipelineStageDefinitionBuilder.GeoNear([34.0, 67.0, 23.0, 34.5]);
+ });
+
+ Assert.Throws(() =>
+ {
+ var result =
+ PipelineStageDefinitionBuilder.GeoNear([34.0]);
+ });
+ }
+
[Fact]
public void GraphLookup_with_many_to_one_parameters_should_return_expected_result()
{