From 79a8b6aabb849142b1cd182665d383999576b95f Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 17 Oct 2025 12:29:08 +0100 Subject: [PATCH 1/4] CSHARP-5717: Typed builders for vector indexes Replaces #1769 --- evergreen/evergreen.yml | 2 +- src/MongoDB.Driver/CreateSearchIndexModel.cs | 42 ++-- .../CreateSearchIndexModelBase.cs | 40 ++++ src/MongoDB.Driver/CreateVectorIndexModel.cs | 168 ++++++++++++++ src/MongoDB.Driver/MongoCollectionImpl.cs | 32 ++- .../Search/IMongoSearchIndexManager.cs | 8 +- src/MongoDB.Driver/VectorQuantization.cs | 42 ++++ src/MongoDB.Driver/VectorSimilarity.cs | 39 ++++ .../Search/AtlasSearchIndexManagmentTests.cs | 211 ++++++++++++++++-- 9 files changed, 543 insertions(+), 41 deletions(-) create mode 100644 src/MongoDB.Driver/CreateSearchIndexModelBase.cs create mode 100644 src/MongoDB.Driver/CreateVectorIndexModel.cs create mode 100644 src/MongoDB.Driver/VectorQuantization.cs create mode 100644 src/MongoDB.Driver/VectorSimilarity.cs diff --git a/evergreen/evergreen.yml b/evergreen/evergreen.yml index 617e84b5b30..15a17b5fea2 100644 --- a/evergreen/evergreen.yml +++ b/evergreen/evergreen.yml @@ -2099,7 +2099,7 @@ task_groups: - "AWS_SESSION_TOKEN" env: CLUSTER_PREFIX: dbx-csharp-search-index - MONGODB_VERSION: "7.0" + MONGODB_VERSION: "8.0" args: - ${DRIVERS_TOOLS}/.evergreen/atlas/setup-atlas-cluster.sh - command: expansions.update diff --git a/src/MongoDB.Driver/CreateSearchIndexModel.cs b/src/MongoDB.Driver/CreateSearchIndexModel.cs index bb5a2498a4f..62b5cd6438a 100644 --- a/src/MongoDB.Driver/CreateSearchIndexModel.cs +++ b/src/MongoDB.Driver/CreateSearchIndexModel.cs @@ -18,38 +18,48 @@ namespace MongoDB.Driver { /// - /// Model for creating a search index. + /// Defines a vector search index model using a definition. Consider using + /// to build Atlas indexes without specifying the BSON directly. /// - public sealed class CreateSearchIndexModel + public sealed class CreateSearchIndexModel : CreateSearchIndexModelBase { - /// Gets the index name. - /// The index name. - public string Name { get; } - /// Gets the index type. /// The index type. - public SearchIndexType? Type { get; } + public override SearchIndexType? Type { get; } /// Gets the index definition. /// The definition. public BsonDocument Definition { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class, passing the index + /// model as a . /// - /// The name. - /// The definition. - public CreateSearchIndexModel(string name, BsonDocument definition) : this(name, null, definition) { } + /// + /// Consider using to build Atlas indexes without specifying the + /// BSON directly. + /// + /// The index name. + /// The index definition. + public CreateSearchIndexModel(string name, BsonDocument definition) + : this(name, null, definition) + { + } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class, passing the index + /// model as a . /// - /// The name. - /// The type. - /// The definition. + /// + /// Consider using to build indexes without specifying the + /// BSON directly. + /// + /// The index name. + /// The index type. + /// The index definition. public CreateSearchIndexModel(string name, SearchIndexType? type, BsonDocument definition) + : base(name) { - Name = name; Type = type; Definition = definition; } diff --git a/src/MongoDB.Driver/CreateSearchIndexModelBase.cs b/src/MongoDB.Driver/CreateSearchIndexModelBase.cs new file mode 100644 index 00000000000..443f61cff07 --- /dev/null +++ b/src/MongoDB.Driver/CreateSearchIndexModelBase.cs @@ -0,0 +1,40 @@ +/* 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; + +/// +/// Abstract base class for search and vector index definitions. Concrete implementations are +/// and +/// +public abstract class CreateSearchIndexModelBase +{ + /// Gets the index name. + /// The index name. + public virtual string Name { get; } + + /// Gets the index type. + /// The index type. + public abstract SearchIndexType? Type { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The index name. + protected CreateSearchIndexModelBase(string name) + { + Name = name; + } +} diff --git a/src/MongoDB.Driver/CreateVectorIndexModel.cs b/src/MongoDB.Driver/CreateVectorIndexModel.cs new file mode 100644 index 00000000000..f7786bb9e25 --- /dev/null +++ b/src/MongoDB.Driver/CreateVectorIndexModel.cs @@ -0,0 +1,168 @@ +/* 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 System.Linq.Expressions; +using MongoDB.Bson; + +namespace MongoDB.Driver; + +/// +/// Defines a vector index model using strongly-typed C# APIs. +/// +public sealed class CreateVectorIndexModel : CreateSearchIndexModelBase +{ + /// + /// Initializes a new instance of the class, passing the + /// required options for and the number of vector dimensions to the constructor. + /// + /// The index name. + /// The field containing the vectors to index. + /// The to use to search for top K-nearest neighbors. + /// Number of vector dimensions that vector search enforces at index-time and query-time. + /// Fields that may be used as filters in the vector query. + public CreateVectorIndexModel( + FieldDefinition field, + string name, + VectorSimilarity similarity, + int dimensions, + params FieldDefinition[] filterFields) + : base(name) + { + Field = field; + Similarity = similarity; + Dimensions = dimensions; + FilterFields = filterFields?.ToList() ?? []; + } + + /// + /// Initializes a new instance of the class, passing the + /// required options for and the number of vector dimensions to the constructor. + /// + /// The index name. + /// An expression pointing to the field containing the vectors to index. + /// The to use to search for top K-nearest neighbors. + /// Number of vector dimensions that vector search enforces at index-time and query-time. + /// Expressions pointing to fields that may be used as filters in the vector query. + public CreateVectorIndexModel( + Expression> field, + string name, + VectorSimilarity similarity, + int dimensions, + params Expression>[] filterFields) + : this( + new ExpressionFieldDefinition(field), + name, + similarity, + dimensions, + filterFields? + .Select(f => (FieldDefinition)new ExpressionFieldDefinition(f)) + .ToArray()) + { + } + + /// + /// The field containing the vectors to index. + /// + public FieldDefinition Field { get; } + + /// + /// The to use to search for top K-nearest neighbors. + /// + public VectorSimilarity Similarity { get; } + + /// + /// Number of vector dimensions that vector search enforces at index-time and query-time. + /// + public int Dimensions { get; } + + /// + /// Fields that may be used as filters in the vector query. + /// + public IReadOnlyList> FilterFields { get; } + + /// + /// Type of automatic vector quantization for your vectors. + /// + public VectorQuantization? Quantization { get; init; } + + /// + /// Maximum number of edges (or connections) that a node can have in the Hierarchical Navigable Small Worlds graph. + /// + public int? HnswMaxEdges { get; init; } + + /// + /// Analogous to numCandidates at query-time, this parameter controls the maximum number of nodes to evaluate to find the closest neighbors to connect to a new node. + /// + public int? HnswNumEdgeCandidates { get; init; } + + /// + public override SearchIndexType? Type => SearchIndexType.VectorSearch; + + /// + /// Renders the index model to a . + /// + /// The render arguments. + /// A . + public BsonDocument Render(RenderArgs renderArgs) + { + var similarityValue = Similarity == VectorSimilarity.DotProduct + ? "dotProduct" // Because neither "DotProduct" or "dotproduct" are allowed. + : Similarity.ToString().ToLowerInvariant(); + + var vectorField = new BsonDocument + { + { "type", BsonString.Create("vector") }, + { "path", Field.Render(renderArgs).FieldName }, + { "numDimensions", BsonInt32.Create(Dimensions) }, + { "similarity", BsonString.Create(similarityValue) }, + }; + + if (Quantization.HasValue) + { + vectorField.Add("quantization", BsonString.Create(Quantization.ToString()?.ToLower())); + } + + if (HnswMaxEdges != null || HnswNumEdgeCandidates != null) + { + var hnswDocument = new BsonDocument + { + { "maxEdges", BsonInt32.Create(HnswMaxEdges ?? 16) }, + { "numEdgeCandidates", BsonInt32.Create(HnswNumEdgeCandidates ?? 100) } + }; + vectorField.Add("hnswOptions", hnswDocument); + } + + var fieldDocuments = new List { vectorField }; + + if (FilterFields != null) + { + foreach (var filterPath in FilterFields) + { + var fieldDocument = new BsonDocument + { + { "type", BsonString.Create("filter") }, + { "path", BsonString.Create(filterPath.Render(renderArgs).FieldName) } + }; + + fieldDocuments.Add(fieldDocument); + } + } + + return new BsonDocument { { "fields", BsonArray.Create(fieldDocuments) } }; + } +} diff --git a/src/MongoDB.Driver/MongoCollectionImpl.cs b/src/MongoDB.Driver/MongoCollectionImpl.cs index 20b11c8c942..0ab5217944a 100644 --- a/src/MongoDB.Driver/MongoCollectionImpl.cs +++ b/src/MongoDB.Driver/MongoCollectionImpl.cs @@ -1655,7 +1655,7 @@ public MongoSearchIndexManager(MongoCollectionImpl collection) _collection = Ensure.IsNotNull(collection, nameof(collection)); } - public IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default) + public IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default) { using var session = _collection._operationExecutor.StartImplicitSession(); var operation = CreateCreateIndexesOperation(models); @@ -1664,7 +1664,7 @@ public IEnumerable CreateMany(IEnumerable models return GetIndexNames(result); } - public async Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default) + public async Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default) { using var session = _collection._operationExecutor.StartImplicitSession(); var operation = CreateCreateIndexesOperation(models); @@ -1676,7 +1676,7 @@ public async Task> CreateManyAsync(IEnumerable CreateOne(new CreateSearchIndexModel(name, definition), cancellationToken); - public string CreateOne(CreateSearchIndexModel model, CancellationToken cancellationToken = default) + public string CreateOne(CreateSearchIndexModelBase model, CancellationToken cancellationToken = default) { var result = CreateMany(new[] { model }, cancellationToken); return result.Single(); @@ -1685,7 +1685,7 @@ public string CreateOne(CreateSearchIndexModel model, CancellationToken cancella public Task CreateOneAsync(BsonDocument definition, string name = null, CancellationToken cancellationToken = default) => CreateOneAsync(new CreateSearchIndexModel(name, definition), cancellationToken); - public async Task CreateOneAsync(CreateSearchIndexModel model, CancellationToken cancellationToken = default) + public async Task CreateOneAsync(CreateSearchIndexModelBase model, CancellationToken cancellationToken = default) { var result = await CreateManyAsync(new[] { model }, cancellationToken).ConfigureAwait(false); return result.Single(); @@ -1741,10 +1741,28 @@ private PipelineDefinition CreateListIndexesStage(strin return new BsonDocumentStagePipelineDefinition(new[] { stage }); } - private CreateSearchIndexesOperation CreateCreateIndexesOperation(IEnumerable models) => - new(_collection._collectionNamespace, - models.Select(m => new CreateSearchIndexRequest(m.Name, m.Type, m.Definition)), + private CreateSearchIndexesOperation CreateCreateIndexesOperation( + IEnumerable models) + { + var renderArgs = _collection.GetRenderArgs(); + + return new CreateSearchIndexesOperation( + _collection._collectionNamespace, + models.Select(model + => new CreateSearchIndexRequest( + model.Name, + model.Type, + model switch + { + CreateSearchIndexModel createSearchIndexModel + => createSearchIndexModel.Definition, + CreateVectorIndexModel createAtlasVectorIndexModel + => createAtlasVectorIndexModel.Render(renderArgs), + _ => throw new NotSupportedException( + $"'{model.GetType().Name}' is not a supported index model type.") + })), _collection._messageEncoderSettings); + } private string[] GetIndexNames(BsonDocument createSearchIndexesResponse) => createSearchIndexesResponse["indexesCreated"] diff --git a/src/MongoDB.Driver/Search/IMongoSearchIndexManager.cs b/src/MongoDB.Driver/Search/IMongoSearchIndexManager.cs index b0741339fa7..2ace2bc2b26 100644 --- a/src/MongoDB.Driver/Search/IMongoSearchIndexManager.cs +++ b/src/MongoDB.Driver/Search/IMongoSearchIndexManager.cs @@ -33,7 +33,7 @@ public interface IMongoSearchIndexManager /// /// An of the names of the indexes that were created. /// - IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default); + IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default); /// /// Creates multiple indexes. @@ -43,7 +43,7 @@ public interface IMongoSearchIndexManager /// /// A Task whose result is an of the names of the indexes that were created. /// - Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default); + Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default); /// /// Creates a search index. @@ -64,7 +64,7 @@ public interface IMongoSearchIndexManager /// /// The name of the index that was created. /// - string CreateOne(CreateSearchIndexModel model, CancellationToken cancellationToken = default); + string CreateOne(CreateSearchIndexModelBase model, CancellationToken cancellationToken = default); /// /// Creates a search index. @@ -85,7 +85,7 @@ public interface IMongoSearchIndexManager /// /// A Task whose result is the name of the index that was created. /// - Task CreateOneAsync(CreateSearchIndexModel model, CancellationToken cancellationToken = default); + Task CreateOneAsync(CreateSearchIndexModelBase model, CancellationToken cancellationToken = default); /// /// Drops an index by its name. diff --git a/src/MongoDB.Driver/VectorQuantization.cs b/src/MongoDB.Driver/VectorQuantization.cs new file mode 100644 index 00000000000..0b0fa37297c --- /dev/null +++ b/src/MongoDB.Driver/VectorQuantization.cs @@ -0,0 +1,42 @@ +/* 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; + +/// +/// Type of automatic vector quantization for your vectors. Use this setting only if your embeddings are float +/// or double vectors. See +/// Vector Quantization for more information. +/// +public enum VectorQuantization +{ + /// + /// Indicates no automatic quantization for the vector embeddings. Use this setting if you have pre-quantized + /// vectors for ingestion. If omitted, this is the default value. + /// + None, + + /// + /// Indicates scalar quantization, which transforms values to 1 byte integers. + /// + Scalar, + + /// + /// Indicates binary quantization, which transforms values to a single bit. + /// To use this value, numDimensions must be a multiple of 8. + /// If precision is critical, select or instead of . + /// + Binary, +} diff --git a/src/MongoDB.Driver/VectorSimilarity.cs b/src/MongoDB.Driver/VectorSimilarity.cs new file mode 100644 index 00000000000..3c38a1f1de7 --- /dev/null +++ b/src/MongoDB.Driver/VectorSimilarity.cs @@ -0,0 +1,39 @@ +/* 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; + +/// +/// Vector similarity function to use to search for top K-nearest neighbors. +/// See How to Index Fields for +/// Vector Search for more information. +/// +public enum VectorSimilarity +{ + /// + /// Measures the distance between ends of vectors. + /// + Euclidean, + + /// + /// Measures similarity based on the angle between vectors. + /// + Cosine, + + /// + /// mMasures similarity like cosine, but takes into account the magnitude of the vector. + /// + DotProduct, +} diff --git a/tests/MongoDB.Driver.Tests/Search/AtlasSearchIndexManagmentTests.cs b/tests/MongoDB.Driver.Tests/Search/AtlasSearchIndexManagmentTests.cs index 798648fceec..5551c94df36 100644 --- a/tests/MongoDB.Driver.Tests/Search/AtlasSearchIndexManagmentTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/AtlasSearchIndexManagmentTests.cs @@ -37,6 +37,7 @@ public class AtlasSearchIndexManagementTests : LoggableTestClass private readonly IMongoDatabase _database; private readonly IMongoClient _mongoClient; private readonly BsonDocument _indexDefinition = BsonDocument.Parse("{ mappings: { dynamic: false } }"); + private readonly BsonDocument _indexDefinitionWithFields = BsonDocument.Parse("{ mappings: { dynamic: false, fields: { } } }"); private readonly BsonDocument _vectorIndexDefinition = BsonDocument.Parse("{ fields: [ { type: 'vector', path: 'plot_embedding', numDimensions: 1536, similarity: 'euclidean' } ] }"); public AtlasSearchIndexManagementTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) @@ -68,10 +69,16 @@ public Task Case1_driver_should_successfully_create_and_list_search_indexes( [Theory(Timeout = Timeout)] [ParameterAttributeData] public async Task Case2_driver_should_successfully_create_multiple_indexes_in_batch( - [Values(false, true)] bool async) + [Values(false, true)] bool async, + [Values(false, true)] bool includeFields) { - var indexDefinition1 = new CreateSearchIndexModel(async ? "test-search-index-1-async" : "test-search-index-1", _indexDefinition); - var indexDefinition2 = new CreateSearchIndexModel(async ? "test-search-index-2-async" : "test-search-index-2", _indexDefinition); + var indexDefinition1 = new CreateSearchIndexModel( + async ? "test-search-index-1-async" : "test-search-index-1", + includeFields ? _indexDefinitionWithFields : _indexDefinition); + + var indexDefinition2 = new CreateSearchIndexModel( + async ? "test-search-index-2-async" : "test-search-index-2", + includeFields ? _indexDefinitionWithFields : _indexDefinition); var indexNamesActual = async ? await _collection.SearchIndexes.CreateManyAsync(new[] { indexDefinition1, indexDefinition2 }) @@ -81,8 +88,8 @@ public async Task Case2_driver_should_successfully_create_multiple_indexes_in_ba var indexes = await GetIndexes(async, indexDefinition1.Name, indexDefinition2.Name); - indexes[0]["latestDefinition"].AsBsonDocument.Should().Be(_indexDefinition); - indexes[1]["latestDefinition"].AsBsonDocument.Should().Be(_indexDefinition); + indexes[0]["latestDefinition"].AsBsonDocument.Should().Be(_indexDefinitionWithFields); + indexes[1]["latestDefinition"].AsBsonDocument.Should().Be(_indexDefinitionWithFields); } [Theory(Timeout = Timeout)] @@ -130,7 +137,7 @@ public async Task Case4_driver_can_update_a_search_index( [Values(false, true)] bool async) { var indexName = async ? "test-search-index-async" : "test-search-index"; - var indexNewDefinition = BsonDocument.Parse("{ mappings: { dynamic: true }}"); + var indexNewDefinition = BsonDocument.Parse("{ mappings: { dynamic: true, fields: { } }}"); await CreateIndexAndValidate(indexName, _indexDefinition, async); if (async) @@ -166,7 +173,8 @@ public async Task Case5_dropSearchIndex_suppresses_namespace_not_found_errors( [Theory(Timeout = Timeout)] [ParameterAttributeData] public async Task Case6_driver_can_create_and_list_search_indexes_with_non_default_read_write_concern( - [Values(false, true)] bool async) + [Values(false, true)] bool async, + [Values(false, true)] bool includeFields) { var indexName = async ? "test-search-index-case6-async" : "test-search-index-case6"; @@ -175,13 +183,18 @@ public async Task Case6_driver_can_create_and_list_search_indexes_with_non_defau .WithWriteConcern(WriteConcern.WMajority); var indexNameCreated = async - ? await collection.SearchIndexes.CreateOneAsync(_indexDefinition, indexName) - : collection.SearchIndexes.CreateOne(_indexDefinition, indexName); + ? await collection.SearchIndexes.CreateOneAsync(includeFields + ? _indexDefinitionWithFields + : _indexDefinition, indexName) + : collection.SearchIndexes.CreateOne( + includeFields + ? _indexDefinitionWithFields + : _indexDefinition, indexName); indexNameCreated.Should().Be(indexName); var indexes = await GetIndexes(async, indexName); - indexes[0]["latestDefinition"].AsBsonDocument.Should().Be(_indexDefinition); + indexes[0]["latestDefinition"].AsBsonDocument.Should().Be(_indexDefinitionWithFields); } [Theory(Timeout = Timeout)] @@ -231,10 +244,182 @@ public async Task Case8_driver_requires_explicit_type_to_create_vector_search_in var indexName = async ? "test-search-index-case8-error-async" : "test-search-index-case8-error"; var exception = async - ? await Record.ExceptionAsync(() => _collection.SearchIndexes.CreateOneAsync(_vectorIndexDefinition, indexName)) - : Record.Exception(() => _collection.SearchIndexes.CreateOne(_vectorIndexDefinition, indexName)); + ? await Record.ExceptionAsync(() => _collection.SearchIndexes.CreateOneAsync( + new CreateSearchIndexModel(indexName, _vectorIndexDefinition).Definition, indexName)) + : Record.Exception(() => _collection.SearchIndexes.CreateOne( + new CreateSearchIndexModel(indexName, _vectorIndexDefinition).Definition, indexName)); + + exception.Message.Should().Contain("Command createSearchIndexes failed: \"userCommand.indexes[0].mappings\" is required."); + } + + [Theory(Timeout = Timeout)] + [ParameterAttributeData] + public async Task Can_create_Atlas_vector_index_for_all_options_using_typed_API( + [Values(false, true)] bool async) + { + var indexName = async ? "test-index-vector-optional-async" : "test-index-vector-optional"; + + var indexModel = new CreateVectorIndexModel( + e => e.Floats, indexName, VectorSimilarity.Cosine, dimensions: 2) + { + HnswMaxEdges = 18, HnswNumEdgeCandidates = 102, Quantization = VectorQuantization.Scalar + }; + + var collection = _database.GetCollection(_collection.CollectionNamespace.CollectionName); + var createdName = async + ? await collection.SearchIndexes.CreateOneAsync(indexModel) + : collection.SearchIndexes.CreateOne(indexModel); + + createdName.Should().Be(indexName); + + var index = (await GetIndexes(async, indexName))[0]; + index["type"].AsString.Should().Be("vectorSearch"); + + var fields = index["latestDefinition"].AsBsonDocument["fields"].AsBsonArray; + fields.Count.Should().Be(1); + + var indexField = fields[0].AsBsonDocument; + indexField["type"].AsString.Should().Be("vector"); + indexField["path"].AsString.Should().Be("Floats"); + indexField["numDimensions"].AsInt32.Should().Be(2); + indexField["similarity"].AsString.Should().Be("cosine"); + indexField["quantization"].AsString.Should().Be("scalar"); + indexField["hnswOptions"].AsBsonDocument["maxEdges"].AsInt32.Should().Be(18); + indexField["hnswOptions"].AsBsonDocument["numEdgeCandidates"].AsInt32.Should().Be(102); + } + + [Theory(Timeout = Timeout)] + [ParameterAttributeData] + public async Task Can_create_Atlas_vector_index_for_required_only_options_using_typed_API( + [Values(false, true)] bool async) + { + var indexName = async ? "test-index-vector-required-async" : "test-index-vector-required"; + + var indexModel = new CreateVectorIndexModel("vectors", indexName, VectorSimilarity.Euclidean, dimensions: 4); + + var collection = _database.GetCollection(_collection.CollectionNamespace.CollectionName); + var createdName = async + ? await collection.SearchIndexes.CreateOneAsync(indexModel) + : collection.SearchIndexes.CreateOne(indexModel); + + createdName.Should().Be(indexName); + + var index = (await GetIndexes(async, indexName))[0]; + index["type"].AsString.Should().Be("vectorSearch"); + + var fields = index["latestDefinition"].AsBsonDocument["fields"].AsBsonArray; + fields.Count.Should().Be(1); + + var indexField = fields[0].AsBsonDocument; + indexField["type"].AsString.Should().Be("vector"); + indexField["path"].AsString.Should().Be("vectors"); + indexField["numDimensions"].AsInt32.Should().Be(4); + indexField["similarity"].AsString.Should().Be("euclidean"); + + indexField.Contains("quantization").Should().Be(false); + indexField.Contains("hnswOptions").Should().Be(false); + } - exception.Message.Should().Contain("Attribute mappings missing"); + [Theory(Timeout = Timeout)] + [ParameterAttributeData] + public async Task Can_create_Atlas_vector_index_for_all_options_using_typed_API_with_filters( + [Values(false, true)] bool async) + { + var indexName = async ? "test-index-vector-typed-filters-async" : "test-index-typed-filters"; + + var indexModel = new CreateVectorIndexModel( + e => e.Floats, + indexName, + VectorSimilarity.Cosine, + dimensions: 2, + e => e.Filter1, e => e.Filter2, e => e.Filter3) + { + HnswMaxEdges = 18, + HnswNumEdgeCandidates = 102, + Quantization = VectorQuantization.Scalar, + }; + + var collection = _database.GetCollection(_collection.CollectionNamespace.CollectionName); + var createdName = async + ? await collection.SearchIndexes.CreateOneAsync(indexModel) + : collection.SearchIndexes.CreateOne(indexModel); + + createdName.Should().Be(indexName); + + var index = (await GetIndexes(async, indexName))[0]; + index["type"].AsString.Should().Be("vectorSearch"); + + var fields = index["latestDefinition"].AsBsonDocument["fields"].AsBsonArray; + fields.Count.Should().Be(4); + + var indexField = fields[0].AsBsonDocument; + indexField["type"].AsString.Should().Be("vector"); + indexField["path"].AsString.Should().Be("Floats"); + indexField["numDimensions"].AsInt32.Should().Be(2); + indexField["similarity"].AsString.Should().Be("cosine"); + indexField["quantization"].AsString.Should().Be("scalar"); + indexField["hnswOptions"].AsBsonDocument["maxEdges"].AsInt32.Should().Be(18); + indexField["hnswOptions"].AsBsonDocument["numEdgeCandidates"].AsInt32.Should().Be(102); + + for (var i = 1; i <= 3; i++) + { + var filterField = fields[i].AsBsonDocument; + filterField["type"].AsString.Should().Be("filter"); + filterField["path"].AsString.Should().Be($"Filter{i}"); + } + } + + [Theory(Timeout = Timeout)] + [ParameterAttributeData] + public async Task Can_create_Atlas_vector_index_for_required_only_options_using_typed_API_with_filters( + [Values(false, true)] bool async) + { + var indexName = async ? "test-index-untyped-filters-async" : "test-index-untyped-filters"; + + var indexModel = new CreateVectorIndexModel( + "vectors", + indexName, + VectorSimilarity.Euclidean, + dimensions: 4, + "f1", "f2", "f3"); + + var collection = _database.GetCollection(_collection.CollectionNamespace.CollectionName); + var createdName = async + ? await collection.SearchIndexes.CreateOneAsync(indexModel) + : collection.SearchIndexes.CreateOne(indexModel); + + createdName.Should().Be(indexName); + + var index = (await GetIndexes(async, indexName))[0]; + index["type"].AsString.Should().Be("vectorSearch"); + + var fields = index["latestDefinition"].AsBsonDocument["fields"].AsBsonArray; + fields.Count.Should().Be(4); + + var indexField = fields[0].AsBsonDocument; + indexField["type"].AsString.Should().Be("vector"); + indexField["path"].AsString.Should().Be("vectors"); + indexField["numDimensions"].AsInt32.Should().Be(4); + indexField["similarity"].AsString.Should().Be("euclidean"); + + indexField.Contains("quantization").Should().Be(false); + indexField.Contains("hnswOptions").Should().Be(false); + + for (var i = 1; i <= 3; i++) + { + var filterField = fields[i].AsBsonDocument; + filterField["type"].AsString.Should().Be("filter"); + filterField["path"].AsString.Should().Be($"f{i}"); + } + } + + private class EntityWithVector + { + public ObjectId Id { get; set; } + public float[] Floats { get; set; } + public bool Filter1 { get; set; } + public string Filter2 { get; set; } + public int Filter3 { get; set; } } private async Task CreateIndexAndValidate(string indexName, BsonDocument indexDefinition, bool async) From 5e486cb69ef3408d8009344c8989ee74c9b32f26 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 17 Oct 2025 16:15:27 +0100 Subject: [PATCH 2/4] Removed binary break. --- src/MongoDB.Driver/CreateSearchIndexModel.cs | 24 +++++- .../CreateSearchIndexModelBase.cs | 40 --------- src/MongoDB.Driver/CreateVectorIndexModel.cs | 85 ++++++++++--------- src/MongoDB.Driver/MongoCollectionImpl.cs | 22 ++--- .../Search/IMongoSearchIndexManager.cs | 8 +- src/MongoDB.Driver/VectorSimilarity.cs | 2 +- 6 files changed, 78 insertions(+), 103 deletions(-) delete mode 100644 src/MongoDB.Driver/CreateSearchIndexModelBase.cs diff --git a/src/MongoDB.Driver/CreateSearchIndexModel.cs b/src/MongoDB.Driver/CreateSearchIndexModel.cs index 62b5cd6438a..cb2760870ed 100644 --- a/src/MongoDB.Driver/CreateSearchIndexModel.cs +++ b/src/MongoDB.Driver/CreateSearchIndexModel.cs @@ -21,15 +21,19 @@ namespace MongoDB.Driver /// Defines a vector search index model using a definition. Consider using /// to build Atlas indexes without specifying the BSON directly. /// - public sealed class CreateSearchIndexModel : CreateSearchIndexModelBase + public class CreateSearchIndexModel { + /// Gets the index name. + /// The index name. + public string Name { get; } + /// Gets the index type. /// The index type. - public override SearchIndexType? Type { get; } + public SearchIndexType? Type { get; } /// Gets the index definition. /// The definition. - public BsonDocument Definition { get; } + public virtual BsonDocument Definition { get; } /// /// Initializes a new instance of the class, passing the index @@ -58,10 +62,22 @@ public CreateSearchIndexModel(string name, BsonDocument definition) /// The index type. /// The index definition. public CreateSearchIndexModel(string name, SearchIndexType? type, BsonDocument definition) - : base(name) { + Name = name; Type = type; Definition = definition; + + } + + /// + /// Initializes a new instance of the class. + /// + /// The index name. + /// The index type. + protected CreateSearchIndexModel(string name, SearchIndexType? type) + { + Name = name; + Type = type; } } } diff --git a/src/MongoDB.Driver/CreateSearchIndexModelBase.cs b/src/MongoDB.Driver/CreateSearchIndexModelBase.cs deleted file mode 100644 index 443f61cff07..00000000000 --- a/src/MongoDB.Driver/CreateSearchIndexModelBase.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* 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; - -/// -/// Abstract base class for search and vector index definitions. Concrete implementations are -/// and -/// -public abstract class CreateSearchIndexModelBase -{ - /// Gets the index name. - /// The index name. - public virtual string Name { get; } - - /// Gets the index type. - /// The index type. - public abstract SearchIndexType? Type { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The index name. - protected CreateSearchIndexModelBase(string name) - { - Name = name; - } -} diff --git a/src/MongoDB.Driver/CreateVectorIndexModel.cs b/src/MongoDB.Driver/CreateVectorIndexModel.cs index f7786bb9e25..47d293c9455 100644 --- a/src/MongoDB.Driver/CreateVectorIndexModel.cs +++ b/src/MongoDB.Driver/CreateVectorIndexModel.cs @@ -24,8 +24,51 @@ namespace MongoDB.Driver; /// /// Defines a vector index model using strongly-typed C# APIs. /// -public sealed class CreateVectorIndexModel : CreateSearchIndexModelBase +public sealed class CreateVectorIndexModel : CreateSearchIndexModel { + /// + /// The field containing the vectors to index. + /// + public FieldDefinition Field { get; } + + /// + /// The to use to search for top K-nearest neighbors. + /// + public VectorSimilarity Similarity { get; } + + /// + /// Number of vector dimensions that vector search enforces at index-time and query-time. + /// + public int Dimensions { get; } + + /// + /// Fields that may be used as filters in the vector query. + /// + public IReadOnlyList> FilterFields { get; } + + /// + /// Type of automatic vector quantization for your vectors. + /// + public VectorQuantization? Quantization { get; init; } + + /// + /// Maximum number of edges (or connections) that a node can have in the Hierarchical Navigable Small Worlds graph. + /// + public int? HnswMaxEdges { get; init; } + + /// + /// Analogous to numCandidates at query-time, this parameter controls the maximum number of nodes to evaluate to find the closest neighbors to connect to a new node. + /// + public int? HnswNumEdgeCandidates { get; init; } + + /// + /// This method should not be called on this subtype. Instead, call to create a BSON + /// document for the index model. + /// + public override BsonDocument Definition + => throw new NotSupportedException( + "This method should not be called on this subtype. Instead, call 'Render' to create a BSON document for the index model."); + /// /// Initializes a new instance of the class, passing the /// required options for and the number of vector dimensions to the constructor. @@ -41,7 +84,7 @@ public CreateVectorIndexModel( VectorSimilarity similarity, int dimensions, params FieldDefinition[] filterFields) - : base(name) + : base(name, SearchIndexType.VectorSearch) { Field = field; Similarity = similarity; @@ -75,44 +118,6 @@ public CreateVectorIndexModel( { } - /// - /// The field containing the vectors to index. - /// - public FieldDefinition Field { get; } - - /// - /// The to use to search for top K-nearest neighbors. - /// - public VectorSimilarity Similarity { get; } - - /// - /// Number of vector dimensions that vector search enforces at index-time and query-time. - /// - public int Dimensions { get; } - - /// - /// Fields that may be used as filters in the vector query. - /// - public IReadOnlyList> FilterFields { get; } - - /// - /// Type of automatic vector quantization for your vectors. - /// - public VectorQuantization? Quantization { get; init; } - - /// - /// Maximum number of edges (or connections) that a node can have in the Hierarchical Navigable Small Worlds graph. - /// - public int? HnswMaxEdges { get; init; } - - /// - /// Analogous to numCandidates at query-time, this parameter controls the maximum number of nodes to evaluate to find the closest neighbors to connect to a new node. - /// - public int? HnswNumEdgeCandidates { get; init; } - - /// - public override SearchIndexType? Type => SearchIndexType.VectorSearch; - /// /// Renders the index model to a . /// diff --git a/src/MongoDB.Driver/MongoCollectionImpl.cs b/src/MongoDB.Driver/MongoCollectionImpl.cs index 0ab5217944a..7d755c9048f 100644 --- a/src/MongoDB.Driver/MongoCollectionImpl.cs +++ b/src/MongoDB.Driver/MongoCollectionImpl.cs @@ -1655,7 +1655,7 @@ public MongoSearchIndexManager(MongoCollectionImpl collection) _collection = Ensure.IsNotNull(collection, nameof(collection)); } - public IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default) + public IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default) { using var session = _collection._operationExecutor.StartImplicitSession(); var operation = CreateCreateIndexesOperation(models); @@ -1664,7 +1664,7 @@ public IEnumerable CreateMany(IEnumerable mo return GetIndexNames(result); } - public async Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default) + public async Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default) { using var session = _collection._operationExecutor.StartImplicitSession(); var operation = CreateCreateIndexesOperation(models); @@ -1676,7 +1676,7 @@ public async Task> CreateManyAsync(IEnumerable CreateOne(new CreateSearchIndexModel(name, definition), cancellationToken); - public string CreateOne(CreateSearchIndexModelBase model, CancellationToken cancellationToken = default) + public string CreateOne(CreateSearchIndexModel model, CancellationToken cancellationToken = default) { var result = CreateMany(new[] { model }, cancellationToken); return result.Single(); @@ -1685,7 +1685,7 @@ public string CreateOne(CreateSearchIndexModelBase model, CancellationToken canc public Task CreateOneAsync(BsonDocument definition, string name = null, CancellationToken cancellationToken = default) => CreateOneAsync(new CreateSearchIndexModel(name, definition), cancellationToken); - public async Task CreateOneAsync(CreateSearchIndexModelBase model, CancellationToken cancellationToken = default) + public async Task CreateOneAsync(CreateSearchIndexModel model, CancellationToken cancellationToken = default) { var result = await CreateManyAsync(new[] { model }, cancellationToken).ConfigureAwait(false); return result.Single(); @@ -1742,7 +1742,7 @@ private PipelineDefinition CreateListIndexesStage(strin } private CreateSearchIndexesOperation CreateCreateIndexesOperation( - IEnumerable models) + IEnumerable models) { var renderArgs = _collection.GetRenderArgs(); @@ -1752,15 +1752,9 @@ private CreateSearchIndexesOperation CreateCreateIndexesOperation( => new CreateSearchIndexRequest( model.Name, model.Type, - model switch - { - CreateSearchIndexModel createSearchIndexModel - => createSearchIndexModel.Definition, - CreateVectorIndexModel createAtlasVectorIndexModel - => createAtlasVectorIndexModel.Render(renderArgs), - _ => throw new NotSupportedException( - $"'{model.GetType().Name}' is not a supported index model type.") - })), + model is CreateVectorIndexModel createVectorIndexModel + ? createVectorIndexModel.Render(renderArgs) + : model.Definition)), _collection._messageEncoderSettings); } diff --git a/src/MongoDB.Driver/Search/IMongoSearchIndexManager.cs b/src/MongoDB.Driver/Search/IMongoSearchIndexManager.cs index 2ace2bc2b26..b0741339fa7 100644 --- a/src/MongoDB.Driver/Search/IMongoSearchIndexManager.cs +++ b/src/MongoDB.Driver/Search/IMongoSearchIndexManager.cs @@ -33,7 +33,7 @@ public interface IMongoSearchIndexManager /// /// An of the names of the indexes that were created. /// - IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default); + IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default); /// /// Creates multiple indexes. @@ -43,7 +43,7 @@ public interface IMongoSearchIndexManager /// /// A Task whose result is an of the names of the indexes that were created. /// - Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default); + Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default); /// /// Creates a search index. @@ -64,7 +64,7 @@ public interface IMongoSearchIndexManager /// /// The name of the index that was created. /// - string CreateOne(CreateSearchIndexModelBase model, CancellationToken cancellationToken = default); + string CreateOne(CreateSearchIndexModel model, CancellationToken cancellationToken = default); /// /// Creates a search index. @@ -85,7 +85,7 @@ public interface IMongoSearchIndexManager /// /// A Task whose result is the name of the index that was created. /// - Task CreateOneAsync(CreateSearchIndexModelBase model, CancellationToken cancellationToken = default); + Task CreateOneAsync(CreateSearchIndexModel model, CancellationToken cancellationToken = default); /// /// Drops an index by its name. diff --git a/src/MongoDB.Driver/VectorSimilarity.cs b/src/MongoDB.Driver/VectorSimilarity.cs index 3c38a1f1de7..fe000e400ec 100644 --- a/src/MongoDB.Driver/VectorSimilarity.cs +++ b/src/MongoDB.Driver/VectorSimilarity.cs @@ -33,7 +33,7 @@ public enum VectorSimilarity Cosine, /// - /// mMasures similarity like cosine, but takes into account the magnitude of the vector. + /// Measures similarity like cosine, but takes into account the magnitude of the vector. /// DotProduct, } From 0ed29a8135de7fc5f7a13bc348ff7d879b48682c Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Mon, 20 Oct 2025 13:12:04 +0100 Subject: [PATCH 3/4] Tweaks --- src/MongoDB.Driver/CreateSearchIndexModel.cs | 40 ++++++++++++-------- src/MongoDB.Driver/CreateVectorIndexModel.cs | 8 ---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/MongoDB.Driver/CreateSearchIndexModel.cs b/src/MongoDB.Driver/CreateSearchIndexModel.cs index cb2760870ed..bfc34439236 100644 --- a/src/MongoDB.Driver/CreateSearchIndexModel.cs +++ b/src/MongoDB.Driver/CreateSearchIndexModel.cs @@ -13,35 +13,44 @@ * limitations under the License. */ +using System; using MongoDB.Bson; namespace MongoDB.Driver { /// - /// Defines a vector search index model using a definition. Consider using - /// to build Atlas indexes without specifying the BSON directly. + /// Defines a search index model using a definition. Consider using + /// to build vector indexes without specifying the BSON directly. /// public class CreateSearchIndexModel { + private readonly BsonDocument _definition; + private readonly SearchIndexType? _type; + private readonly string _name; + /// Gets the index name. /// The index name. - public string Name { get; } + public string Name => _name; /// Gets the index type. /// The index type. - public SearchIndexType? Type { get; } + public SearchIndexType? Type => _type; - /// Gets the index definition. + /// + /// Gets the index definition, if one was passed to a constructor of this class, otherwise throws. + /// /// The definition. - public virtual BsonDocument Definition { get; } + public BsonDocument Definition + => _definition ?? throw new NotSupportedException( + "This method should not be called on this subtype. Instead, call 'Render' to create a BSON document for the index model."); /// /// Initializes a new instance of the class, passing the index /// model as a . /// /// - /// Consider using to build Atlas indexes without specifying the - /// BSON directly. + /// Consider using to build vector indexes without specifying + /// the BSON directly. /// /// The index name. /// The index definition. @@ -55,18 +64,17 @@ public CreateSearchIndexModel(string name, BsonDocument definition) /// model as a . /// /// - /// Consider using to build indexes without specifying the - /// BSON directly. + /// Consider using to build vector indexes without specifying + /// the BSON directly. /// /// The index name. /// The index type. /// The index definition. public CreateSearchIndexModel(string name, SearchIndexType? type, BsonDocument definition) { - Name = name; - Type = type; - Definition = definition; - + _name = name; + _type = type; + _definition = definition; } /// @@ -76,8 +84,8 @@ public CreateSearchIndexModel(string name, SearchIndexType? type, BsonDocument d /// The index type. protected CreateSearchIndexModel(string name, SearchIndexType? type) { - Name = name; - Type = type; + _name = name; + _type = type; } } } diff --git a/src/MongoDB.Driver/CreateVectorIndexModel.cs b/src/MongoDB.Driver/CreateVectorIndexModel.cs index 47d293c9455..a3bc9237d2a 100644 --- a/src/MongoDB.Driver/CreateVectorIndexModel.cs +++ b/src/MongoDB.Driver/CreateVectorIndexModel.cs @@ -61,14 +61,6 @@ public sealed class CreateVectorIndexModel : CreateSearchIndexModel /// public int? HnswNumEdgeCandidates { get; init; } - /// - /// This method should not be called on this subtype. Instead, call to create a BSON - /// document for the index model. - /// - public override BsonDocument Definition - => throw new NotSupportedException( - "This method should not be called on this subtype. Instead, call 'Render' to create a BSON document for the index model."); - /// /// Initializes a new instance of the class, passing the /// required options for and the number of vector dimensions to the constructor. From 3d5152c50ab7158a360c57856a7bddab1791ce81 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Mon, 27 Oct 2025 13:16:06 +0000 Subject: [PATCH 4/4] Feedback. --- src/MongoDB.Driver/CreateSearchIndexModel.cs | 6 +++--- ...IndexModel.cs => CreateVectorSearchIndexModel.cs} | 12 ++++++------ src/MongoDB.Driver/MongoCollectionImpl.cs | 4 ++-- .../Search/AtlasSearchIndexManagmentTests.cs | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) rename src/MongoDB.Driver/{CreateVectorIndexModel.cs => CreateVectorSearchIndexModel.cs} (94%) diff --git a/src/MongoDB.Driver/CreateSearchIndexModel.cs b/src/MongoDB.Driver/CreateSearchIndexModel.cs index bfc34439236..b0839e68bb5 100644 --- a/src/MongoDB.Driver/CreateSearchIndexModel.cs +++ b/src/MongoDB.Driver/CreateSearchIndexModel.cs @@ -20,7 +20,7 @@ namespace MongoDB.Driver { /// /// Defines a search index model using a definition. Consider using - /// to build vector indexes without specifying the BSON directly. + /// to build vector indexes without specifying the BSON directly. /// public class CreateSearchIndexModel { @@ -49,7 +49,7 @@ public BsonDocument Definition /// model as a . /// /// - /// Consider using to build vector indexes without specifying + /// Consider using to build vector indexes without specifying /// the BSON directly. /// /// The index name. @@ -64,7 +64,7 @@ public CreateSearchIndexModel(string name, BsonDocument definition) /// model as a . /// /// - /// Consider using to build vector indexes without specifying + /// Consider using to build vector indexes without specifying /// the BSON directly. /// /// The index name. diff --git a/src/MongoDB.Driver/CreateVectorIndexModel.cs b/src/MongoDB.Driver/CreateVectorSearchIndexModel.cs similarity index 94% rename from src/MongoDB.Driver/CreateVectorIndexModel.cs rename to src/MongoDB.Driver/CreateVectorSearchIndexModel.cs index a3bc9237d2a..0d82df576a4 100644 --- a/src/MongoDB.Driver/CreateVectorIndexModel.cs +++ b/src/MongoDB.Driver/CreateVectorSearchIndexModel.cs @@ -24,7 +24,7 @@ namespace MongoDB.Driver; /// /// Defines a vector index model using strongly-typed C# APIs. /// -public sealed class CreateVectorIndexModel : CreateSearchIndexModel +public sealed class CreateVectorSearchIndexModel : CreateSearchIndexModel { /// /// The field containing the vectors to index. @@ -62,7 +62,7 @@ public sealed class CreateVectorIndexModel : CreateSearchIndexModel public int? HnswNumEdgeCandidates { get; init; } /// - /// Initializes a new instance of the class, passing the + /// Initializes a new instance of the class, passing the /// required options for and the number of vector dimensions to the constructor. /// /// The index name. @@ -70,7 +70,7 @@ public sealed class CreateVectorIndexModel : CreateSearchIndexModel /// The to use to search for top K-nearest neighbors. /// Number of vector dimensions that vector search enforces at index-time and query-time. /// Fields that may be used as filters in the vector query. - public CreateVectorIndexModel( + public CreateVectorSearchIndexModel( FieldDefinition field, string name, VectorSimilarity similarity, @@ -85,7 +85,7 @@ public CreateVectorIndexModel( } /// - /// Initializes a new instance of the class, passing the + /// Initializes a new instance of the class, passing the /// required options for and the number of vector dimensions to the constructor. /// /// The index name. @@ -93,7 +93,7 @@ public CreateVectorIndexModel( /// The to use to search for top K-nearest neighbors. /// Number of vector dimensions that vector search enforces at index-time and query-time. /// Expressions pointing to fields that may be used as filters in the vector query. - public CreateVectorIndexModel( + public CreateVectorSearchIndexModel( Expression> field, string name, VectorSimilarity similarity, @@ -160,6 +160,6 @@ public BsonDocument Render(RenderArgs renderArgs) } } - return new BsonDocument { { "fields", BsonArray.Create(fieldDocuments) } }; + return new BsonDocument { { "fields", new BsonArray(fieldDocuments) } }; } } diff --git a/src/MongoDB.Driver/MongoCollectionImpl.cs b/src/MongoDB.Driver/MongoCollectionImpl.cs index 7d755c9048f..a083a40c453 100644 --- a/src/MongoDB.Driver/MongoCollectionImpl.cs +++ b/src/MongoDB.Driver/MongoCollectionImpl.cs @@ -1752,8 +1752,8 @@ private CreateSearchIndexesOperation CreateCreateIndexesOperation( => new CreateSearchIndexRequest( model.Name, model.Type, - model is CreateVectorIndexModel createVectorIndexModel - ? createVectorIndexModel.Render(renderArgs) + model is CreateVectorSearchIndexModel createVectorSearchIndexModel + ? createVectorSearchIndexModel.Render(renderArgs) : model.Definition)), _collection._messageEncoderSettings); } diff --git a/tests/MongoDB.Driver.Tests/Search/AtlasSearchIndexManagmentTests.cs b/tests/MongoDB.Driver.Tests/Search/AtlasSearchIndexManagmentTests.cs index 5551c94df36..c11bc735c55 100644 --- a/tests/MongoDB.Driver.Tests/Search/AtlasSearchIndexManagmentTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/AtlasSearchIndexManagmentTests.cs @@ -259,7 +259,7 @@ public async Task Can_create_Atlas_vector_index_for_all_options_using_typed_API( { var indexName = async ? "test-index-vector-optional-async" : "test-index-vector-optional"; - var indexModel = new CreateVectorIndexModel( + var indexModel = new CreateVectorSearchIndexModel( e => e.Floats, indexName, VectorSimilarity.Cosine, dimensions: 2) { HnswMaxEdges = 18, HnswNumEdgeCandidates = 102, Quantization = VectorQuantization.Scalar @@ -295,7 +295,7 @@ public async Task Can_create_Atlas_vector_index_for_required_only_options_using_ { var indexName = async ? "test-index-vector-required-async" : "test-index-vector-required"; - var indexModel = new CreateVectorIndexModel("vectors", indexName, VectorSimilarity.Euclidean, dimensions: 4); + var indexModel = new CreateVectorSearchIndexModel("vectors", indexName, VectorSimilarity.Euclidean, dimensions: 4); var collection = _database.GetCollection(_collection.CollectionNamespace.CollectionName); var createdName = async @@ -327,7 +327,7 @@ public async Task Can_create_Atlas_vector_index_for_all_options_using_typed_API_ { var indexName = async ? "test-index-vector-typed-filters-async" : "test-index-typed-filters"; - var indexModel = new CreateVectorIndexModel( + var indexModel = new CreateVectorSearchIndexModel( e => e.Floats, indexName, VectorSimilarity.Cosine, @@ -376,7 +376,7 @@ public async Task Can_create_Atlas_vector_index_for_required_only_options_using_ { var indexName = async ? "test-index-untyped-filters-async" : "test-index-untyped-filters"; - var indexModel = new CreateVectorIndexModel( + var indexModel = new CreateVectorSearchIndexModel( "vectors", indexName, VectorSimilarity.Euclidean,