diff --git a/build.cake b/build.cake
index d18483f8d70..eb277fe4fe0 100644
--- a/build.cake
+++ b/build.cake
@@ -240,6 +240,13 @@ Task("TestAtlasDataLake")
action: (BuildConfig buildConfig, Path testProject) =>
RunTests(buildConfig, testProject, filter: "Category=\"AtlasDataLake\""));
+Task("TestAtlasSearch")
+ .IsDependentOn("Build")
+ .DoesForEach(
+ items: GetFiles("./**/MongoDB.Driver.Tests.csproj"),
+ action: (BuildConfig buildConfig, Path testProject) =>
+ RunTests(buildConfig, testProject, filter: "Category=\"AtlasSearch\""));
+
Task("TestOcsp")
.IsDependentOn("Build")
.DoesForEach(
diff --git a/evergreen/evergreen.yml b/evergreen/evergreen.yml
index 2a48d6909e5..85223c09a38 100644
--- a/evergreen/evergreen.yml
+++ b/evergreen/evergreen.yml
@@ -684,6 +684,15 @@ functions:
${PREPARE_SHELL}
evergreen/run-atlas-data-lake-test.sh
+ run-atlas-search-test:
+ - command: shell.exec
+ type: test
+ params:
+ working_dir: mongo-csharp-driver
+ script: |
+ ${PREPARE_SHELL}
+ ATLAS_SEARCH="${ATLAS_SEARCH}" evergreen/run-atlas-search-test.sh
+
run-ocsp-test:
- command: shell.exec
type: test
@@ -1202,6 +1211,10 @@ tasks:
- func: bootstrap-mongohoused
- func: run-atlas-data-lake-test
+ - name: atlas-search-test
+ commands:
+ - func: run-atlas-search-test
+
- name: test-serverless-net472
exec_timeout_secs: 2700 # 45 minutes: 15 for setup + 30 for tests
commands:
@@ -2068,6 +2081,13 @@ buildvariants:
tasks:
- name: atlas-data-lake-test
+- name: atlas-search-test
+ display_name: "Atlas Search Tests"
+ run_on:
+ - windows-64-vs2017-test
+ tasks:
+ - name: atlas-search-test
+
- name: gssapi-auth-tests-windows
run_on:
- windows-64-vs2017-test
diff --git a/evergreen/run-atlas-search-test.sh b/evergreen/run-atlas-search-test.sh
new file mode 100644
index 00000000000..88b9b151a85
--- /dev/null
+++ b/evergreen/run-atlas-search-test.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+set -o xtrace
+set -o errexit # Exit the script with error if any of the commands fail
+
+# Environment variables produced as output
+# ATLAS_SEARCH_TESTS_ENABLED Enable atlas search tests.
+
+############################################
+# Main Program #
+############################################
+
+echo "Running Atlas Search driver tests"
+
+export ATLAS_SEARCH_TESTS_ENABLED=true
+
+powershell.exe .\\build.ps1 --target=TestAtlasSearch
\ No newline at end of file
diff --git a/src/MongoDB.Driver.Core/Core/Misc/Ensure.cs b/src/MongoDB.Driver.Core/Core/Misc/Ensure.cs
index 9d9def7ebe4..10560a1cbd7 100644
--- a/src/MongoDB.Driver.Core/Core/Misc/Ensure.cs
+++ b/src/MongoDB.Driver.Core/Core/Misc/Ensure.cs
@@ -27,6 +27,22 @@ namespace MongoDB.Driver.Core.Misc
[DebuggerStepThrough]
public static class Ensure
{
+ ///
+ /// Ensures that the value of a parameter is not null.
+ ///
+ /// Type type of the value.
+ /// The value of the parameter.
+ /// The name of the parameter.
+ /// The value of the parameter.
+ public static Nullable HasValue(Nullable value, string paramName) where T : struct
+ {
+ if (!value.HasValue)
+ {
+ throw new ArgumentException("The Nullable parameter must have a value.", paramName);
+ }
+ return value;
+ }
+
///
/// Ensures that the value of a parameter is between a minimum and a maximum value.
///
@@ -65,34 +81,36 @@ public static T IsEqualTo(T value, T comparand, string paramName)
}
///
- /// Ensures that the value of a parameter is greater than or equal to a comparand.
+ /// Ensures that the value of a parameter is greater than a comparand.
///
/// Type type of the value.
/// The value of the parameter.
/// The comparand.
/// The name of the parameter.
/// The value of the parameter.
- public static T IsGreaterThanOrEqualTo(T value, T comparand, string paramName) where T : IComparable
+ public static T IsGreaterThan(T value, T comparand, string paramName) where T : IComparable
{
- if (value.CompareTo(comparand) < 0)
+ if (value.CompareTo(comparand) <= 0)
{
- var message = string.Format("Value is not greater than or equal to {1}: {0}.", value, comparand);
+ var message = $"Value is not greater than {comparand}: {value}.";
throw new ArgumentOutOfRangeException(paramName, message);
}
return value;
}
///
- /// Ensures that the value of a parameter is greater than or equal to zero.
+ /// Ensures that the value of a parameter is greater than or equal to a comparand.
///
+ /// Type type of the value.
/// The value of the parameter.
+ /// The comparand.
/// The name of the parameter.
/// The value of the parameter.
- public static int IsGreaterThanOrEqualToZero(int value, string paramName)
+ public static T IsGreaterThanOrEqualTo(T value, T comparand, string paramName) where T : IComparable
{
- if (value < 0)
+ if (value.CompareTo(comparand) < 0)
{
- var message = string.Format("Value is not greater than or equal to 0: {0}.", value);
+ var message = string.Format("Value is not greater than or equal to {1}: {0}.", value, comparand);
throw new ArgumentOutOfRangeException(paramName, message);
}
return value;
@@ -104,15 +122,8 @@ public static int IsGreaterThanOrEqualToZero(int value, string paramName)
/// The value of the parameter.
/// The name of the parameter.
/// The value of the parameter.
- public static long IsGreaterThanOrEqualToZero(long value, string paramName)
- {
- if (value < 0)
- {
- var message = string.Format("Value is not greater than or equal to 0: {0}.", value);
- throw new ArgumentOutOfRangeException(paramName, message);
- }
- return value;
- }
+ public static int IsGreaterThanOrEqualToZero(int value, string paramName) =>
+ IsGreaterThanOrEqualTo(value, 0, paramName);
///
/// Ensures that the value of a parameter is greater than or equal to zero.
@@ -120,15 +131,17 @@ public static long IsGreaterThanOrEqualToZero(long value, string paramName)
/// The value of the parameter.
/// The name of the parameter.
/// The value of the parameter.
- public static TimeSpan IsGreaterThanOrEqualToZero(TimeSpan value, string paramName)
- {
- if (value < TimeSpan.Zero)
- {
- var message = string.Format("Value is not greater than or equal to zero: {0}.", TimeSpanParser.ToString(value));
- throw new ArgumentOutOfRangeException(paramName, message);
- }
- return value;
- }
+ public static long IsGreaterThanOrEqualToZero(long value, string paramName) =>
+ IsGreaterThanOrEqualTo(value, 0, paramName);
+
+ ///
+ /// Ensures that the value of a parameter is greater than or equal to zero.
+ ///
+ /// The value of the parameter.
+ /// The name of the parameter.
+ /// The value of the parameter.
+ public static TimeSpan IsGreaterThanOrEqualToZero(TimeSpan value, string paramName) =>
+ IsGreaterThanOrEqualTo(value, TimeSpan.Zero, paramName);
///
/// Ensures that the value of a parameter is greater than zero.
@@ -136,15 +149,8 @@ public static TimeSpan IsGreaterThanOrEqualToZero(TimeSpan value, string paramNa
/// The value of the parameter.
/// The name of the parameter.
/// The value of the parameter.
- public static int IsGreaterThanZero(int value, string paramName)
- {
- if (value <= 0)
- {
- var message = string.Format("Value is not greater than zero: {0}.", value);
- throw new ArgumentOutOfRangeException(paramName, message);
- }
- return value;
- }
+ public static int IsGreaterThanZero(int value, string paramName) =>
+ IsGreaterThan(value, 0, paramName);
///
/// Ensures that the value of a parameter is greater than zero.
@@ -152,15 +158,8 @@ public static int IsGreaterThanZero(int value, string paramName)
/// The value of the parameter.
/// The name of the parameter.
/// The value of the parameter.
- public static long IsGreaterThanZero(long value, string paramName)
- {
- if (value <= 0)
- {
- var message = string.Format("Value is not greater than zero: {0}.", value);
- throw new ArgumentOutOfRangeException(paramName, message);
- }
- return value;
- }
+ public static long IsGreaterThanZero(long value, string paramName) =>
+ IsGreaterThan(value, 0, paramName);
///
/// Ensures that the value of a parameter is greater than zero.
@@ -168,15 +167,17 @@ public static long IsGreaterThanZero(long value, string paramName)
/// The value of the parameter.
/// The name of the parameter.
/// The value of the parameter.
- public static TimeSpan IsGreaterThanZero(TimeSpan value, string paramName)
- {
- if (value <= TimeSpan.Zero)
- {
- var message = string.Format("Value is not greater than zero: {0}.", value);
- throw new ArgumentOutOfRangeException(paramName, message);
- }
- return value;
- }
+ public static double IsGreaterThanZero(double value, string paramName) =>
+ IsGreaterThan(value, 0, paramName);
+
+ ///
+ /// Ensures that the value of a parameter is greater than zero.
+ ///
+ /// The value of the parameter.
+ /// The name of the parameter.
+ /// The value of the parameter.
+ public static TimeSpan IsGreaterThanZero(TimeSpan value, string paramName) =>
+ IsGreaterThan(value, TimeSpan.Zero, paramName);
///
/// Ensures that the value of a parameter is infinite or greater than or equal to zero.
@@ -247,22 +248,6 @@ public static IEnumerable IsNotNullAndDoesNotContainAnyNulls(IEnumerable
- /// Ensures that the value of a parameter is not null.
- ///
- /// Type type of the value.
- /// The value of the parameter.
- /// The name of the parameter.
- /// The value of the parameter.
- public static Nullable HasValue(Nullable value, string paramName) where T : struct
- {
- if (!value.HasValue)
- {
- throw new ArgumentException("The Nullable parameter must have a value.", paramName);
- }
- return value;
- }
-
///
/// Ensures that the value of a parameter is not null or empty.
///
@@ -298,6 +283,24 @@ public static T IsNull(T value, string paramName) where T : class
return value;
}
+ ///
+ /// Ensures that the value of a parameter is null or is between a minimum and a maximum value.
+ ///
+ /// Type type of the value.
+ /// The value of the parameter.
+ /// The minimum value.
+ /// The maximum value.
+ /// The name of the parameter.
+ /// The value of the parameter.
+ public static T? IsNullOrBetween(T? value, T min, T max, string paramName) where T : struct, IComparable
+ {
+ if (value != null)
+ {
+ IsBetween(value.Value, min, max, paramName);
+ }
+ return value;
+ }
+
///
/// Ensures that the value of a parameter is null or greater than or equal to zero.
///
diff --git a/src/MongoDB.Driver/AggregateFluent.cs b/src/MongoDB.Driver/AggregateFluent.cs
index 38fd1dca4f8..f4732d3fc69 100644
--- a/src/MongoDB.Driver/AggregateFluent.cs
+++ b/src/MongoDB.Driver/AggregateFluent.cs
@@ -13,13 +13,14 @@
* limitations under the License.
*/
-using MongoDB.Bson;
-using MongoDB.Bson.Serialization;
-using MongoDB.Driver.Core.Misc;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+using MongoDB.Driver.Core.Misc;
+using MongoDB.Driver.Search;
namespace MongoDB.Driver
{
@@ -238,6 +239,24 @@ public override IAggregateFluent ReplaceWith(AggregateEx
return WithPipeline(_pipeline.ReplaceWith(newRoot));
}
+ public override IAggregateFluent Search(
+ SearchDefinition searchDefinition,
+ SearchHighlightOptions highlight = null,
+ string indexName = null,
+ SearchCountOptions count = null,
+ bool returnStoredSource = false)
+ {
+ return WithPipeline(_pipeline.Search(searchDefinition, highlight, indexName, count, returnStoredSource));
+ }
+
+ public override IAggregateFluent SearchMeta(
+ SearchDefinition searchDefinition,
+ string indexName = null,
+ SearchCountOptions count = null)
+ {
+ return WithPipeline(_pipeline.SearchMeta(searchDefinition, indexName, count));
+ }
+
public override IAggregateFluent SetWindowFields(
AggregateExpressionDefinition, TWindowFields> output)
{
diff --git a/src/MongoDB.Driver/AggregateFluentBase.cs b/src/MongoDB.Driver/AggregateFluentBase.cs
index ec7fdc18023..8239750962a 100644
--- a/src/MongoDB.Driver/AggregateFluentBase.cs
+++ b/src/MongoDB.Driver/AggregateFluentBase.cs
@@ -19,6 +19,7 @@
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
+using MongoDB.Driver.Search;
namespace MongoDB.Driver
{
@@ -215,6 +216,26 @@ public virtual IAggregateFluent ReplaceWith(AggregateExp
throw new NotImplementedException();
}
+ ///
+ public virtual IAggregateFluent Search(
+ SearchDefinition searchDefinition,
+ SearchHighlightOptions highlight = null,
+ string indexName = null,
+ SearchCountOptions count = null,
+ bool returnStoredSource = false)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public virtual IAggregateFluent SearchMeta(
+ SearchDefinition searchDefinition,
+ string indexName = null,
+ SearchCountOptions count = null)
+ {
+ throw new NotImplementedException();
+ }
+
///
public virtual IAggregateFluent SetWindowFields(
AggregateExpressionDefinition, TWindowFields> output)
diff --git a/src/MongoDB.Driver/Builders.cs b/src/MongoDB.Driver/Builders.cs
index 2748d915ce5..cace9a352f0 100644
--- a/src/MongoDB.Driver/Builders.cs
+++ b/src/MongoDB.Driver/Builders.cs
@@ -13,6 +13,8 @@
* limitations under the License.
*/
+using MongoDB.Driver.Search;
+
namespace MongoDB.Driver
{
///
@@ -21,50 +23,38 @@ namespace MongoDB.Driver
/// The type of the document.
public static class Builders
{
- private static FilterDefinitionBuilder __filter = new FilterDefinitionBuilder();
- private static IndexKeysDefinitionBuilder __index = new IndexKeysDefinitionBuilder();
- private static ProjectionDefinitionBuilder __projection = new ProjectionDefinitionBuilder();
- private static SortDefinitionBuilder __sort = new SortDefinitionBuilder();
- private static UpdateDefinitionBuilder __update = new UpdateDefinitionBuilder();
+ /// Gets a .
+ public static FilterDefinitionBuilder Filter { get; } = new FilterDefinitionBuilder();
+
+ /// Gets an .
+ public static IndexKeysDefinitionBuilder IndexKeys { get; } = new IndexKeysDefinitionBuilder();
+
+ /// Gets a .
+ public static ProjectionDefinitionBuilder Projection { get; } = new ProjectionDefinitionBuilder();
+
+ /// Gets a .
+ public static SortDefinitionBuilder Sort { get; } = new SortDefinitionBuilder();
+
+ /// Gets an .
+ public static UpdateDefinitionBuilder Update { get; } = new UpdateDefinitionBuilder();
+
+ // Search builders
+ /// Gets a .
+ public static SearchFacetBuilder SearchFacet { get; } = new SearchFacetBuilder();
- ///
- /// Gets a .
- ///
- public static FilterDefinitionBuilder Filter
- {
- get { return __filter; }
- }
+ /// Gets a .
+ public static SearchPathDefinitionBuilder SearchPath { get; } = new SearchPathDefinitionBuilder();
- ///
- /// Gets an .
- ///
- public static IndexKeysDefinitionBuilder IndexKeys
- {
- get { return __index; }
- }
+ /// Gets a .
+ public static SearchScoreDefinitionBuilder SearchScore { get; } = new SearchScoreDefinitionBuilder();
- ///
- /// Gets a .
- ///
- public static ProjectionDefinitionBuilder Projection
- {
- get { return __projection; }
- }
+ /// Gets a .
+ public static SearchScoreFunctionBuilder SearchScoreFunction { get; } = new SearchScoreFunctionBuilder();
- ///
- /// Gets a .
- ///
- public static SortDefinitionBuilder Sort
- {
- get { return __sort; }
- }
+ /// Gets a .
+ public static SearchDefinitionBuilder Search { get; } = new SearchDefinitionBuilder();
- ///
- /// Gets an .
- ///
- public static UpdateDefinitionBuilder Update
- {
- get { return __update; }
- }
+ /// Gets a .
+ public static SearchSpanDefinitionBuilder SearchSpan { get; } = new SearchSpanDefinitionBuilder();
}
}
diff --git a/src/MongoDB.Driver/IAggregateFluent.cs b/src/MongoDB.Driver/IAggregateFluent.cs
index ff7a85714d0..cca33c9e1c9 100644
--- a/src/MongoDB.Driver/IAggregateFluent.cs
+++ b/src/MongoDB.Driver/IAggregateFluent.cs
@@ -19,6 +19,7 @@
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
+using MongoDB.Driver.Search;
namespace MongoDB.Driver
{
@@ -353,6 +354,37 @@ IAggregateFluent Lookup SetWindowFields(
AggregateExpressionDefinition, TWindowFields> output);
+ ///
+ /// Appends a $search stage to the pipeline.
+ ///
+ /// The search definition.
+ /// The highlight options.
+ /// The index name.
+ /// The count options.
+ ///
+ /// Flag that specifies whether to perform a full document lookup on the backend database
+ /// or return only stored source fields directly from Atlas Search.
+ ///
+ /// The fluent aggregate interface.
+ IAggregateFluent Search(
+ SearchDefinition searchDefinition,
+ SearchHighlightOptions highlight = null,
+ string indexName = null,
+ SearchCountOptions count = null,
+ bool returnStoredSource = false);
+
+ ///
+ /// Appends a $searchMeta stage to the pipeline.
+ ///
+ /// The search definition.
+ /// The index name.
+ /// The count options.
+ /// The fluent aggregate interface.
+ IAggregateFluent SearchMeta(
+ SearchDefinition searchDefinition,
+ string indexName = null,
+ SearchCountOptions count = null);
+
///
/// Appends a $setWindowFields to the pipeline.
///
diff --git a/src/MongoDB.Driver/Linq/MongoQueryable.cs b/src/MongoDB.Driver/Linq/MongoQueryable.cs
index 7f342daad74..f2dcdab9751 100644
--- a/src/MongoDB.Driver/Linq/MongoQueryable.cs
+++ b/src/MongoDB.Driver/Linq/MongoQueryable.cs
@@ -21,6 +21,7 @@
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson.Serialization;
+using MongoDB.Driver.Search;
using MongoDB.Driver.Core.Misc;
namespace MongoDB.Driver.Linq
@@ -1110,6 +1111,53 @@ public static IMongoQueryable Sample(this IMongoQueryable
+ /// Appends a $search stage to the LINQ pipeline.
+ ///
+ /// The type of the elements of .
+ /// A sequence of values.
+ /// The search definition.
+ /// The highlight options.
+ /// The index name.
+ /// The count options.
+ ///
+ /// Flag that specifies whether to perform a full document lookup on the backend database
+ /// or return only stored source fields directly from Atlas Search.
+ ///
+ /// The queryable with a new stage appended.
+ public static IMongoQueryable Search(
+ this IMongoQueryable source,
+ SearchDefinition searchDefinition,
+ SearchHighlightOptions highlight = null,
+ string indexName = null,
+ SearchCountOptions count = null,
+ bool returnStoredSource = false)
+ {
+ return AppendStage(
+ source,
+ PipelineStageDefinitionBuilder.Search(searchDefinition, highlight, indexName, count, returnStoredSource));
+ }
+
+ ///
+ /// Appends a $searchMeta stage to the LINQ pipeline.
+ ///
+ /// The type of the elements of .
+ /// A sequence of values.
+ /// The search definition.
+ /// The index name.
+ /// The count options.
+ /// The queryable with a new stage appended.
+ public static IMongoQueryable SearchMeta(
+ this IMongoQueryable source,
+ SearchDefinition searchDefinition,
+ string indexName = null,
+ SearchCountOptions count = null)
+ {
+ return AppendStage(
+ source,
+ PipelineStageDefinitionBuilder.SearchMeta(searchDefinition, indexName, count));
+ }
+
///
/// Projects each element of a sequence into a new form by incorporating the
/// element's index.
diff --git a/src/MongoDB.Driver/MongoUtils.cs b/src/MongoDB.Driver/MongoUtils.cs
index 60cff0bcb9a..43717b4dfc9 100644
--- a/src/MongoDB.Driver/MongoUtils.cs
+++ b/src/MongoDB.Driver/MongoUtils.cs
@@ -14,8 +14,6 @@
*/
using System;
-using System.Runtime.InteropServices;
-using System.Security;
using System.Security.Cryptography;
using System.Text;
using MongoDB.Bson;
@@ -61,5 +59,8 @@ public static string ToCamelCase(string value)
{
return value.Length == 0 ? "" : value.Substring(0, 1).ToLower() + value.Substring(1);
}
+
+ internal static string ToCamelCase(this TEnum @enum) where TEnum : Enum =>
+ ToCamelCase(@enum.ToString());
}
}
diff --git a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
index 360b5cad918..cd31f0014d2 100644
--- a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs
@@ -21,6 +21,7 @@
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Linq;
+using MongoDB.Driver.Search;
namespace MongoDB.Driver
{
@@ -1162,6 +1163,57 @@ public static PipelineDefinition ReplaceWith
+ /// Appends a $search stage to the pipeline.
+ ///
+ /// The type of the input documents.
+ /// The type of the output documents.
+ /// The pipeline.
+ /// The search definition.
+ /// The highlight options.
+ /// The index name.
+ /// The count options.
+ ///
+ /// Flag that specifies whether to perform a full document lookup on the backend database
+ /// or return only stored source fields directly from Atlas Search.
+ ///
+ ///
+ /// A new pipeline with an additional stage.
+ ///
+ public static PipelineDefinition Search(
+ this PipelineDefinition pipeline,
+ SearchDefinition searchDefinition,
+ SearchHighlightOptions highlight = null,
+ string indexName = null,
+ SearchCountOptions count = null,
+ bool returnStoredSource = false)
+ {
+ Ensure.IsNotNull(pipeline, nameof(pipeline));
+ return pipeline.AppendStage(PipelineStageDefinitionBuilder.Search(searchDefinition, highlight, indexName, count, returnStoredSource));
+ }
+
+ ///
+ /// Appends a $searchMeta stage to the pipeline.
+ ///
+ /// The type of the input documents.
+ /// The type of the output documents.
+ /// The pipeline.
+ /// The search definition.
+ /// The index name.
+ /// The count options.
+ ///
+ /// A new pipeline with an additional stage.
+ ///
+ public static PipelineDefinition SearchMeta(
+ this PipelineDefinition pipeline,
+ SearchDefinition query,
+ string indexName = null,
+ SearchCountOptions count = null)
+ {
+ Ensure.IsNotNull(pipeline, nameof(pipeline));
+ return pipeline.AppendStage(PipelineStageDefinitionBuilder.SearchMeta(query, indexName, count));
+ }
+
///
/// Create a $setWindowFields stage.
///
diff --git a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
index 940818d693a..70d4c24bd13 100644
--- a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
@@ -22,9 +22,9 @@
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Linq;
-using MongoDB.Driver.Linq.Linq3Implementation.Misc;
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
using MongoDB.Driver.Linq.Linq3Implementation.Translators;
+using MongoDB.Driver.Search;
namespace MongoDB.Driver
{
@@ -1309,6 +1309,80 @@ public static PipelineStageDefinition Project(
return Project(new ProjectExpressionProjection(projection, translationOptions));
}
+ ///
+ /// Creates a $search stage.
+ ///
+ /// The type of the input documents.
+ /// The search definition.
+ /// The highlight options.
+ /// The index name.
+ /// The count options.
+ ///
+ /// Flag that specifies whether to perform a full document lookup on the backend database
+ /// or return only stored source fields directly from Atlas Search.
+ ///
+ /// The stage.
+ public static PipelineStageDefinition Search(
+ SearchDefinition searchDefinition,
+ SearchHighlightOptions highlight = null,
+ string indexName = null,
+ SearchCountOptions count = null,
+ bool returnStoredSource = false)
+ {
+ Ensure.IsNotNull(searchDefinition, nameof(searchDefinition));
+
+ const string operatorName = "$search";
+ var stage = new DelegatedPipelineStageDefinition(
+ operatorName,
+ (s, sr, linqProvider) =>
+ {
+ var renderedSearchDefinition = searchDefinition.Render(s, sr);
+ renderedSearchDefinition.Add("highlight", () => highlight.Render(s, sr), highlight != null);
+ renderedSearchDefinition.Add("count", () => count.Render(), count != null);
+ renderedSearchDefinition.Add("index", indexName, indexName != null);
+ renderedSearchDefinition.Add("returnStoredSource", returnStoredSource, returnStoredSource);
+
+ var document = new BsonDocument(operatorName, renderedSearchDefinition);
+ return new RenderedPipelineStageDefinition(operatorName, document, s);
+ });
+
+ return stage;
+ }
+
+ ///
+ /// Creates a $searchMeta stage.
+ ///
+ /// The type of the input documents.
+ /// The search definition.
+ /// The index name.
+ /// The count options.
+ /// The stage.
+ public static PipelineStageDefinition SearchMeta(
+ SearchDefinition searchDefinition,
+ string indexName = null,
+ SearchCountOptions count = null)
+ {
+ Ensure.IsNotNull(searchDefinition, nameof(searchDefinition));
+
+ const string operatorName = "$searchMeta";
+ var stage = new DelegatedPipelineStageDefinition(
+ operatorName,
+ (s, sr, linqProvider) =>
+ {
+ var renderedSearchDefinition = searchDefinition.Render(s, sr);
+ renderedSearchDefinition.Add("count", () => count.Render(), count != null);
+ renderedSearchDefinition.Add("index", indexName, indexName != null);
+
+ var document = new BsonDocument(operatorName, renderedSearchDefinition);
+ return new RenderedPipelineStageDefinition(
+ operatorName,
+ document,
+ sr.GetSerializer());
+ });
+
+ return stage;
+ }
+
///
/// Creates a $replaceRoot stage.
///
diff --git a/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs b/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs
index 42fe10e0c9a..528116cb272 100644
--- a/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs
@@ -156,6 +156,40 @@ public static ProjectionDefinition Meta(this ProjectionDef
return builder.Combine(projection, builder.Meta(field, metaFieldName));
}
+ ///
+ /// Combines an existing projection with a search highlights projection.
+ ///
+ /// The type of the document.
+ /// The projection.
+ /// The field.
+ ///
+ /// A combined projection.
+ ///
+ public static ProjectionDefinition MetaSearchHighlights(
+ this ProjectionDefinition projection,
+ string field)
+ {
+ var builder = Builders.Projection;
+ return builder.Combine(projection, builder.MetaSearchHighlights(field));
+ }
+
+ ///
+ /// Combines an existing projection with a search score projection.
+ ///
+ /// The type of the document.
+ /// The projection.
+ /// The field.
+ ///
+ /// A combined projection.
+ ///
+ public static ProjectionDefinition MetaSearchScore(
+ this ProjectionDefinition projection,
+ string field)
+ {
+ var builder = Builders.Projection;
+ return builder.Combine(projection, builder.MetaSearchScore(field));
+ }
+
///
/// Combines an existing projection with a text score projection.
///
@@ -171,6 +205,40 @@ public static ProjectionDefinition MetaTextScore(this Proj
return builder.Combine(projection, builder.MetaTextScore(field));
}
+ ///
+ /// Combines an existing projection with a search metadata projection.
+ ///
+ /// The type of the document.
+ /// The projection.
+ /// The field.
+ ///
+ /// A combined projection.
+ ///
+ public static ProjectionDefinition SearchMeta(
+ this ProjectionDefinition projection,
+ FieldDefinition field)
+ {
+ var builder = Builders.Projection;
+ return builder.Combine(projection, builder.SearchMeta(field));
+ }
+
+ ///
+ /// Combines an existing projection with a search metadata projection.
+ ///
+ /// The type of the document.
+ /// The projection.
+ /// The field.
+ ///
+ /// A combined projection.
+ ///
+ public static ProjectionDefinition SearchMeta(
+ this ProjectionDefinition projection,
+ Expression> field)
+ {
+ var builder = Builders.Projection;
+ return builder.Combine(projection, builder.SearchMeta(field));
+ }
+
///
/// Combines an existing projection with an array slice projection.
///
@@ -363,6 +431,30 @@ public ProjectionDefinition Meta(string field, string metaFieldName)
return new SingleFieldProjectionDefinition(field, new BsonDocument("$meta", metaFieldName));
}
+ ///
+ /// Creates a search highlights projection.
+ ///
+ /// The field.
+ ///
+ /// A search highlights projection.
+ ///
+ public ProjectionDefinition MetaSearchHighlights(string field)
+ {
+ return Meta(field, "searchHighlights");
+ }
+
+ ///
+ /// Creates a search score projection.
+ ///
+ /// The field.
+ ///
+ /// A search score projection.
+ ///
+ public ProjectionDefinition MetaSearchScore(string field)
+ {
+ return Meta(field, "searchScore");
+ }
+
///
/// Creates a text score projection.
///
@@ -375,6 +467,30 @@ public ProjectionDefinition MetaTextScore(string field)
return Meta(field, "textScore");
}
+ ///
+ /// Creates a search metadata projection.
+ ///
+ /// The field.
+ ///
+ /// A search metadata projection.
+ ///
+ public ProjectionDefinition SearchMeta(FieldDefinition field)
+ {
+ return new SingleFieldProjectionDefinition(field, new BsonString("$$SEARCH_META"));
+ }
+
+ ///
+ /// Creates a search metadata projection.
+ ///
+ /// The field.
+ ///
+ /// A search metadata projection.
+ ///
+ public ProjectionDefinition SearchMeta(Expression> field)
+ {
+ return SearchMeta(new ExpressionFieldDefinition(field));
+ }
+
///
/// Creates an array slice projection.
///
diff --git a/src/MongoDB.Driver/Search/CompoundSearchDefinitionBuilder.cs b/src/MongoDB.Driver/Search/CompoundSearchDefinitionBuilder.cs
new file mode 100644
index 00000000000..d37516ae84d
--- /dev/null
+++ b/src/MongoDB.Driver/Search/CompoundSearchDefinitionBuilder.cs
@@ -0,0 +1,139 @@
+/* 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 System.Collections.Generic;
+using MongoDB.Driver.Core.Misc;
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// A builder for compound search definitions.
+ ///
+ /// The type of the document.
+ public sealed class CompoundSearchDefinitionBuilder
+ {
+ private List> _must;
+ private List> _mustNot;
+ private List> _should;
+ private List> _filter;
+ private int _minimumShouldMatch = 0;
+
+ ///
+ /// Adds clauses which must match to produce results.
+ ///
+ /// The clauses.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder Must(IEnumerable> clauses) =>
+ AddClauses(ref _must, clauses);
+
+ ///
+ /// Adds clauses which must match to produce results.
+ ///
+ /// The clauses.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder Must(params SearchDefinition[] clauses) =>
+ Must((IEnumerable>)clauses);
+
+ ///
+ /// Adds clauses which must not match for a document to be included in the
+ /// results.
+ ///
+ /// The clauses.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder MustNot(IEnumerable> clauses) =>
+ AddClauses(ref _mustNot, clauses);
+
+ ///
+ /// Adds clauses which must not match for a document to be included in the
+ /// results.
+ ///
+ /// The clauses.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder MustNot(params SearchDefinition[] clauses) =>
+ MustNot((IEnumerable>)clauses);
+
+ ///
+ /// Adds clauses which cause documents in the result set to be scored higher if
+ /// they match.
+ ///
+ /// The clauses.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder Should(IEnumerable> clauses) =>
+ AddClauses(ref _should, clauses);
+
+ ///
+ /// Adds clauses which cause documents in the result set to be scored higher if
+ /// they match.
+ ///
+ /// The clauses.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder Should(params SearchDefinition[] clauses) =>
+ Should((IEnumerable>)clauses);
+
+ ///
+ /// Adds clauses which must all match for a document to be included in the
+ /// results.
+ ///
+ /// The clauses.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder Filter(IEnumerable> clauses) =>
+ AddClauses(ref _filter, clauses);
+
+ ///
+ /// Adds clauses which must all match for a document to be included in the
+ /// results.
+ ///
+ /// The clauses.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder Filter(params SearchDefinition[] clauses) =>
+ Filter((IEnumerable>)clauses);
+
+ ///
+ /// Sets a value specifying the minimum number of should clauses the must match
+ /// to include a document in the results.
+ ///
+ /// The value to set.
+ /// The compound search definition builder.
+ public CompoundSearchDefinitionBuilder MinimumShouldMatch(int minimumShouldMatch)
+ {
+ _minimumShouldMatch = minimumShouldMatch;
+ return this;
+ }
+
+ ///
+ /// Constructs a search definition from the builder.
+ ///
+ /// A compound search definition.
+ public SearchDefinition ToSearchDefinition() =>
+ new CompoundSearchDefinition(_must, _mustNot, _should, _filter, _minimumShouldMatch);
+
+ ///
+ /// Performs an implicit conversion from a
+ /// to a .
+ ///
+ /// The compound search definition builder.
+ /// The result of the conversion.
+ public static implicit operator SearchDefinition(CompoundSearchDefinitionBuilder builder) =>
+ builder.ToSearchDefinition();
+
+ private CompoundSearchDefinitionBuilder AddClauses(ref List> clauses, IEnumerable> newClauses)
+ {
+ Ensure.IsNotNull(newClauses, nameof(newClauses));
+ (clauses ??= new()).AddRange(newClauses);
+
+ return this;
+ }
+ }
+}
diff --git a/src/MongoDB.Driver/Search/GeoShapeRelation.cs b/src/MongoDB.Driver/Search/GeoShapeRelation.cs
new file mode 100644
index 00000000000..98bab0fec0b
--- /dev/null
+++ b/src/MongoDB.Driver/Search/GeoShapeRelation.cs
@@ -0,0 +1,44 @@
+/* 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.
+*/
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// The relation of the query shape geometry to the indexed field geometry in a
+ /// geo shape search definition.
+ ///
+ public enum GeoShapeRelation
+ {
+ ///
+ /// Indicates that the indexed geometry contains the query geometry.
+ ///
+ Contains,
+
+ ///
+ /// Indicates that both the query and indexed geometries have nothing in common.
+ ///
+ Disjoint,
+
+ ///
+ /// Indicates that both the query and indexed geometries intersect.
+ ///
+ Intersects,
+
+ ///
+ /// Indicates that the indexed geometry is within the query geometry.
+ ///
+ Within
+ }
+}
diff --git a/src/MongoDB.Driver/Search/GeoWithinArea.cs b/src/MongoDB.Driver/Search/GeoWithinArea.cs
new file mode 100644
index 00000000000..4622409d474
--- /dev/null
+++ b/src/MongoDB.Driver/Search/GeoWithinArea.cs
@@ -0,0 +1,113 @@
+/* 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;
+using MongoDB.Driver.Core.Misc;
+using MongoDB.Driver.GeoJsonObjectModel;
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// Base class for area argument for GeoWithin queries.
+ ///
+ /// The type of the coordinates.
+ public abstract class GeoWithinArea where TCoordinates : GeoJsonCoordinates
+ {
+ internal abstract BsonElement Render();
+ }
+
+ ///
+ /// Object that specifies the bottom left and top right GeoJSON points of a box to
+ /// search within.
+ ///
+ /// The type of the coordinates.
+ public sealed class GeoWithinBox : GeoWithinArea where TCoordinates : GeoJsonCoordinates
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The bottom left GeoJSON point.
+ /// The top right GeoJSON point.
+ public GeoWithinBox(GeoJsonPoint bottomLeft, GeoJsonPoint topRight)
+ {
+ BottomLeft = Ensure.IsNotNull(bottomLeft, nameof(bottomLeft));
+ TopRight = Ensure.IsNotNull(topRight, nameof(topRight));
+ }
+
+ /// Gets the bottom left GeoJSON point.
+ public GeoJsonPoint BottomLeft { get; }
+
+ /// Gets the top right GeoJSON point.
+ public GeoJsonPoint TopRight { get; }
+
+ internal override BsonElement Render() =>
+ new("box", new BsonDocument
+ {
+ { "bottomLeft", BottomLeft.ToBsonDocument() },
+ { "topRight", TopRight.ToBsonDocument() }
+ });
+ }
+
+ ///
+ /// Object that specifies the center point and the radius in meters to search within.
+ ///
+ public sealed class GeoWithinCircle : GeoWithinArea where TCoordinates : GeoJsonCoordinates
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Center of the circle specified as a GeoJSON point.
+ /// Radius specified in meters.
+ public GeoWithinCircle(GeoJsonPoint center, double radius)
+ {
+ Center = Ensure.IsNotNull(center, nameof(center));
+ Radius = Ensure.IsGreaterThanZero(radius, nameof(radius));
+ }
+
+ /// Gets the center of the circle specified as a GeoJSON point.
+ public GeoJsonPoint Center { get; }
+
+ /// Gets the radius specified in meters.
+ public double Radius { get; }
+
+ internal override BsonElement Render() =>
+ new("circle", new BsonDocument
+ {
+ { "center", Center.ToBsonDocument() },
+ { "radius", Radius }
+ });
+ }
+
+ ///
+ /// Object that specifies the GeoJson geometry to search within.
+ ///
+ /// The type of the coordinates.
+ public sealed class GeoWithinGeometry : GeoWithinArea where TCoordinates : GeoJsonCoordinates
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// GeoJSON object specifying the MultiPolygon or Polygon.
+ public GeoWithinGeometry(GeoJsonGeometry geometry)
+ {
+ Geometry = Ensure.IsNotNull(geometry, nameof(geometry));
+ }
+
+ /// Gets the GeoJson geometry.
+ public GeoJsonGeometry Geometry { get; }
+
+ internal override BsonElement Render() => new("geometry", Geometry.ToBsonDocument());
+ }
+}
diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs
new file mode 100644
index 00000000000..de3ac5a9b14
--- /dev/null
+++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs
@@ -0,0 +1,393 @@
+/* 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 System;
+using System.Collections.Generic;
+using System.Linq;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+using MongoDB.Driver.Core.Misc;
+using MongoDB.Driver.GeoJsonObjectModel;
+
+namespace MongoDB.Driver.Search
+{
+ internal sealed class AutocompleteSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly SearchFuzzyOptions _fuzzy;
+ private readonly SearchQueryDefinition _query;
+ private readonly SearchAutocompleteTokenOrder _tokenOrder;
+
+ public AutocompleteSearchDefinition(
+ SearchPathDefinition path,
+ SearchQueryDefinition query,
+ SearchAutocompleteTokenOrder tokenOrder,
+ SearchFuzzyOptions fuzzy,
+ SearchScoreDefinition score)
+ : base(OperatorType.Autocomplete, path, score)
+ {
+ _query = Ensure.IsNotNull(query, nameof(query));
+ _tokenOrder = tokenOrder;
+ _fuzzy = fuzzy;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "query", _query.Render() },
+ { "tokenOrder", _tokenOrder.ToCamelCase(), _tokenOrder != SearchAutocompleteTokenOrder.Any },
+ { "fuzzy", () => _fuzzy.Render(), _fuzzy != null },
+ };
+ }
+
+ internal sealed class CompoundSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly List> _filter;
+ private readonly int _minimumShouldMatch;
+ private readonly List> _must;
+ private readonly List> _mustNot;
+ private readonly List> _should;
+
+ public CompoundSearchDefinition(
+ List> must,
+ List> mustNot,
+ List> should,
+ List> filter,
+ int minimumShouldMatch)
+ : base(OperatorType.Compound)
+ {
+ // This constructor should always be called from the compound search definition builder that ensures the arguments are valid.
+ _must = must;
+ _mustNot = mustNot;
+ _should = should;
+ _filter = filter;
+ _minimumShouldMatch = minimumShouldMatch;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry)
+ {
+ return new()
+ {
+ { "must", Render(_must), _must != null },
+ { "mustNot", Render(_mustNot), _mustNot != null },
+ { "should", Render(_should), _should != null },
+ { "filter", Render(_filter), _filter != null },
+ { "minimumShouldMatch", _minimumShouldMatch, _minimumShouldMatch > 0 },
+ };
+
+ Func Render(List> searchDefinitions) =>
+ () => new BsonArray(searchDefinitions.Select(clause => clause.Render(documentSerializer, serializerRegistry)));
+ }
+ }
+
+ internal sealed class EqualsSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly BsonValue _value;
+
+ public EqualsSearchDefinition(FieldDefinition path, BsonValue value, SearchScoreDefinition score)
+ : base(OperatorType.Equals, path, score)
+ {
+ _value = value;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new("value", _value);
+ }
+
+ internal sealed class ExistsSearchDefinition : OperatorSearchDefinition
+ {
+ public ExistsSearchDefinition(FieldDefinition path)
+ : base(OperatorType.Exists, path, null)
+ {
+ }
+ }
+
+ internal sealed class FacetSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly SearchFacet[] _facets;
+ private readonly SearchDefinition _operator;
+
+ public FacetSearchDefinition(SearchDefinition @operator, IEnumerable> facets)
+ : base(OperatorType.Facet)
+ {
+ _operator = Ensure.IsNotNull(@operator, nameof(@operator));
+ _facets = Ensure.IsNotNull(facets, nameof(facets)).ToArray();
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "operator", _operator.Render(documentSerializer, serializerRegistry) },
+ { "facets", new BsonDocument(_facets.Select(f => new BsonElement(f.Name, f.Render(documentSerializer, serializerRegistry)))) }
+ };
+ }
+
+ internal sealed class GeoShapeSearchDefinition : OperatorSearchDefinition
+ where TCoordinates : GeoJsonCoordinates
+ {
+ private readonly GeoJsonGeometry _geometry;
+ private readonly GeoShapeRelation _relation;
+
+ public GeoShapeSearchDefinition(
+ SearchPathDefinition path,
+ GeoShapeRelation relation,
+ GeoJsonGeometry geometry,
+ SearchScoreDefinition score)
+ : base(OperatorType.GeoShape, path, score)
+ {
+ _geometry = Ensure.IsNotNull(geometry, nameof(geometry));
+ _relation = relation;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "geometry", _geometry.ToBsonDocument() },
+ { "relation", _relation.ToCamelCase() }
+ };
+ }
+
+ internal sealed class GeoWithinSearchDefinition : OperatorSearchDefinition
+ where TCoordinates : GeoJsonCoordinates
+ {
+ private readonly GeoWithinArea _area;
+
+ public GeoWithinSearchDefinition(
+ SearchPathDefinition path,
+ GeoWithinArea area,
+ SearchScoreDefinition score)
+ : base(OperatorType.GeoWithin, path, score)
+ {
+ _area = Ensure.IsNotNull(area, nameof(area));
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new(_area.Render());
+ }
+
+ internal sealed class MoreLikeThisSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly TLike[] _like;
+
+ public MoreLikeThisSearchDefinition(IEnumerable like)
+ : base(OperatorType.MoreLikeThis)
+ {
+ _like = Ensure.IsNotNull(like, nameof(like)).ToArray();
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry)
+ {
+ var likeSerializer = typeof(TLike) switch
+ {
+ var t when t == typeof(BsonDocument) => null,
+ var t when t == typeof(TDocument) => (IBsonSerializer)documentSerializer,
+ _ => serializerRegistry.GetSerializer()
+ };
+
+ return new("like", new BsonArray(_like.Select(document => document.ToBsonDocument(likeSerializer))));
+ }
+ }
+
+ internal sealed class NearSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly BsonValue _origin;
+ private readonly BsonValue _pivot;
+
+ public NearSearchDefinition(
+ SearchPathDefinition path,
+ BsonValue origin,
+ BsonValue pivot,
+ SearchScoreDefinition score = null)
+ : base(OperatorType.Near, path, score)
+ {
+ _origin = origin;
+ _pivot = pivot;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "origin", _origin },
+ { "pivot", _pivot }
+ };
+ }
+
+ internal sealed class PhraseSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly SearchQueryDefinition _query;
+ private readonly int? _slop;
+
+ public PhraseSearchDefinition(
+ SearchPathDefinition path,
+ SearchQueryDefinition query,
+ int? slop,
+ SearchScoreDefinition score)
+ : base(OperatorType.Phrase, path, score)
+ {
+ _query = Ensure.IsNotNull(query, nameof(query));
+ _slop = slop;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "query", _query.Render() },
+ { "slop", _slop, _slop != null }
+ };
+ }
+
+ internal sealed class QueryStringSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly FieldDefinition _defaultPath;
+ private readonly string _query;
+
+ public QueryStringSearchDefinition(FieldDefinition defaultPath, string query, SearchScoreDefinition score)
+ : base(OperatorType.QueryString, score)
+ {
+ _defaultPath = Ensure.IsNotNull(defaultPath, nameof(defaultPath));
+ _query = Ensure.IsNotNull(query, nameof(query));
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "defaultPath", _defaultPath.Render(documentSerializer, serializerRegistry).FieldName },
+ { "query", _query }
+ };
+ }
+
+ internal sealed class RangeSearchDefinition : OperatorSearchDefinition
+ where TField : struct, IComparable
+ {
+ private readonly SearchRange _range;
+
+ public RangeSearchDefinition(
+ SearchPathDefinition path,
+ SearchRange range,
+ SearchScoreDefinition score)
+ : base(OperatorType.Range, path, score)
+ {
+ _range = range;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { _range.IsMinInclusive ? "gte" : "gt", () => ToBsonValue(_range.Min.Value), _range.Min != null },
+ { _range.IsMaxInclusive ? "lte" : "lt", () => ToBsonValue(_range.Max.Value), _range.Max != null },
+ };
+
+ private static BsonValue ToBsonValue(TField value) =>
+ value switch
+ {
+ sbyte v => (BsonInt32)v,
+ byte v => (BsonInt32)v,
+ short v => (BsonInt32)v,
+ ushort v => (BsonInt32)v,
+ int v => (BsonInt32)v,
+ uint v => (BsonInt32)v,
+ long v => (BsonInt64)v,
+ float v => (BsonDouble)v,
+ double v => (BsonDouble)v,
+ DateTime v => (BsonDateTime)v,
+ _ => throw new InvalidCastException()
+ };
+ }
+
+ internal sealed class RegexSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly bool _allowAnalyzedField;
+ private readonly SearchQueryDefinition _query;
+
+ public RegexSearchDefinition(
+ SearchPathDefinition path,
+ SearchQueryDefinition query,
+ bool allowAnalyzedField,
+ SearchScoreDefinition score)
+ : base(OperatorType.Regex, path, score)
+ {
+ _query = Ensure.IsNotNull(query, nameof(query));
+ _allowAnalyzedField = allowAnalyzedField;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "query", _query.Render() },
+ { "allowAnalyzedField", _allowAnalyzedField, _allowAnalyzedField },
+ };
+ }
+
+ internal sealed class SpanSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly SearchSpanDefinition _clause;
+
+ public SpanSearchDefinition(SearchSpanDefinition clause)
+ : base(OperatorType.Span)
+ {
+ _clause = Ensure.IsNotNull(clause, nameof(clause));
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ _clause.Render(documentSerializer, serializerRegistry);
+ }
+
+ internal sealed class TextSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly SearchFuzzyOptions _fuzzy;
+ private readonly SearchQueryDefinition _query;
+
+ public TextSearchDefinition(
+ SearchPathDefinition path,
+ SearchQueryDefinition query,
+ SearchFuzzyOptions fuzzy,
+ SearchScoreDefinition score)
+ : base(OperatorType.Text, path, score)
+ {
+ _query = Ensure.IsNotNull(query, nameof(query));
+ _fuzzy = fuzzy;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "query", _query.Render() },
+ { "fuzzy", () => _fuzzy.Render(), _fuzzy != null },
+ };
+ }
+
+ internal sealed class WildcardSearchDefinition : OperatorSearchDefinition
+ {
+ private readonly bool _allowAnalyzedField;
+ private readonly SearchQueryDefinition _query;
+
+ public WildcardSearchDefinition(
+ SearchPathDefinition path,
+ SearchQueryDefinition query,
+ bool allowAnalyzedField,
+ SearchScoreDefinition score)
+ : base(OperatorType.Wildcard, path, score)
+ {
+ _query = Ensure.IsNotNull(query, nameof(query));
+ _allowAnalyzedField = allowAnalyzedField;
+ }
+
+ private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
+ new()
+ {
+ { "query", _query.Render() },
+ { "allowAnalyzedField", _allowAnalyzedField, _allowAnalyzedField },
+ };
+ }
+}
diff --git a/src/MongoDB.Driver/Search/SearchAutocompleteTokenOrder.cs b/src/MongoDB.Driver/Search/SearchAutocompleteTokenOrder.cs
new file mode 100644
index 00000000000..780698e2d93
--- /dev/null
+++ b/src/MongoDB.Driver/Search/SearchAutocompleteTokenOrder.cs
@@ -0,0 +1,34 @@
+/* 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.
+*/
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// The order in which to search for tokens in an autocomplete search definition.
+ ///
+ public enum SearchAutocompleteTokenOrder
+ {
+ ///
+ /// Indicates that tokens in the query can appear in any order in the documents.
+ ///
+ Any,
+
+ ///
+ /// Indicates that tokens in the query must appear adjacent to each other or in the order
+ /// specified in the query in the documents.
+ ///
+ Sequential
+ }
+}
diff --git a/src/MongoDB.Driver/Search/SearchCountOptions.cs b/src/MongoDB.Driver/Search/SearchCountOptions.cs
new file mode 100644
index 00000000000..c3624c9175b
--- /dev/null
+++ b/src/MongoDB.Driver/Search/SearchCountOptions.cs
@@ -0,0 +1,55 @@
+/* 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;
+using MongoDB.Driver.Core.Misc;
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// Options for counting the search results.
+ ///
+ public sealed class SearchCountOptions
+ {
+ private int? _threshold;
+ private SearchCountType _type = SearchCountType.LowerBound;
+
+ ///
+ /// Gets or sets the number of documents to include in the exact count if
+ /// is .
+ ///
+ public int? Threshold
+ {
+ get => _threshold;
+ set => _threshold = Ensure.IsNullOrGreaterThanZero(value, nameof(value));
+ }
+
+ ///
+ /// Gets or sets the type of count of the documents in the result set.
+ ///
+ public SearchCountType Type
+ {
+ get => _type;
+ set => _type = value;
+ }
+
+ internal BsonDocument Render() =>
+ new()
+ {
+ { "type", _type.ToCamelCase(), _type != SearchCountType.LowerBound },
+ { "threshold", _threshold, _threshold != null }
+ };
+ }
+}
diff --git a/src/MongoDB.Driver/Search/SearchCountType.cs b/src/MongoDB.Driver/Search/SearchCountType.cs
new file mode 100644
index 00000000000..f4a87047eae
--- /dev/null
+++ b/src/MongoDB.Driver/Search/SearchCountType.cs
@@ -0,0 +1,33 @@
+/* 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.
+*/
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// The type of count of the documents in a search result set.
+ ///
+ public enum SearchCountType
+ {
+ ///
+ /// A lower bound count of the number of documents that match the query.
+ ///
+ LowerBound,
+
+ ///
+ /// An exact count of the number of documents that match the query.
+ ///
+ Total
+ }
+}
diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs
new file mode 100644
index 00000000000..a8bc26686e4
--- /dev/null
+++ b/src/MongoDB.Driver/Search/SearchDefinition.cs
@@ -0,0 +1,166 @@
+/* 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;
+using MongoDB.Bson.Serialization;
+using MongoDB.Driver.Core.Misc;
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// Base class for search definitions.
+ ///
+ /// The type of the document.
+ public abstract class SearchDefinition
+ {
+ ///
+ /// Renders the search definition to a .
+ ///
+ /// The document serializer.
+ /// The serializer registry.
+ /// A .
+ public abstract BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry);
+
+ ///
+ /// Performs an implicit conversion from a BSON document to a