From 3598c23bff588f4f05f0f456261eb558d6f48cbf Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Tue, 23 Apr 2024 08:38:27 -0600 Subject: [PATCH] feat(NODE-5919): support new `type` option in create search index helpers (#4060) --- src/operations/search_indexes/create.ts | 5 +- .../search-index-management.prose.test.ts | 162 ++++++++++++++++++ .../index-management/createSearchIndex.json | 72 +++++++- .../index-management/createSearchIndex.yml | 30 +++- .../index-management/createSearchIndexes.json | 74 +++++++- .../index-management/createSearchIndexes.yml | 30 +++- 6 files changed, 356 insertions(+), 17 deletions(-) diff --git a/src/operations/search_indexes/create.ts b/src/operations/search_indexes/create.ts index 054ba02629..7e5e55d18d 100644 --- a/src/operations/search_indexes/create.ts +++ b/src/operations/search_indexes/create.ts @@ -8,12 +8,15 @@ import { AbstractOperation } from '../operation'; /** * @public */ -export interface SearchIndexDescription { +export interface SearchIndexDescription extends Document { /** The name of the index. */ name?: string; /** The index definition. */ definition: Document; + + /** The type of the index. Currently `search` or `vectorSearch` are supported. */ + type?: string; } /** @internal */ diff --git a/test/manual/search-index-management.prose.test.ts b/test/manual/search-index-management.prose.test.ts index 6bd0c82132..282158f54b 100644 --- a/test/manual/search-index-management.prose.test.ts +++ b/test/manual/search-index-management.prose.test.ts @@ -340,5 +340,167 @@ describe('Index Management Prose Tests', function () { .to.deep.equal({ dynamic: false }); } ); + + it( + 'Case 7: Driver can successfully handle search index types when creating indexes', + metadata, + async function () { + // 01. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + const coll0 = collection; + { + // 02. Create a new search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + // ```typescript + // { + // name: 'test-search-index-case7-implicit', + // definition: { + // mappings: { dynamic: false } + // } + // } + // ``` + const indexName = await coll0.createSearchIndex({ + name: 'test-search-index-case7-implicit', + definition: { + mappings: { dynamic: false } + } + }); + // 03. Assert that the command returns the name of the index: `"test-search-index-case7-implicit"`. + expect(indexName).to.equal('test-search-index-case7-implicit'); + // 04. Run `coll0.listSearchIndexes('test-search-index-case7-implicit')` repeatedly every 5 seconds until the following + // condition is satisfied and store the value in a variable `index1`: + + // - An index with the `name` of `test-search-index-case7-implicit` is present and the index has a field `queryable` + // with a value of `true`. + + const [index1] = await waitForIndexes({ + predicate: indexes => indexes.every(index => index.queryable), + indexNames: 'test-search-index-case7-implicit', + collection: coll0 + }); + + // 05. Assert that `index1` has a property `type` whose value is `search`. + expect(index1).to.have.property('type', 'search'); + } + { + // 06. Create a new search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + // ```typescript + // { + // name: 'test-search-index-case7-explicit', + // type: 'search', + // definition: { + // mappings: { dynamic: false } + // } + // } + // ``` + const indexName = await coll0.createSearchIndex({ + name: 'test-search-index-case7-explicit', + type: 'search', + definition: { + mappings: { dynamic: false } + } + }); + // 07. Assert that the command returns the name of the index: `"test-search-index-case7-explicit"`. + expect(indexName).to.equal('test-search-index-case7-explicit'); + // 08. Run `coll0.listSearchIndexes('test-search-index-case7-explicit')` repeatedly every 5 seconds until the following + // condition is satisfied and store the value in a variable `index2`: + + // - An index with the `name` of `test-search-index-case7-explicit` is present and the index has a field `queryable` + // with a value of `true`. + + const [index2] = await waitForIndexes({ + predicate: indexes => indexes.every(index => index.queryable), + indexNames: 'test-search-index-case7-explicit', + collection: coll0 + }); + // 09. Assert that `index2` has a property `type` whose value is `search`. + expect(index2).to.have.property('type', 'search'); + } + { + // 10. Create a new vector search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + // ```typescript + // { + // name: 'test-search-index-case7-vector', + // type: 'vectorSearch', + // definition: { + // "fields": [ + // { + // "type": "vector", + // "path": "plot_embedding", + // "numDimensions": 1536, + // "similarity": "euclidean", + // }, + // ] + // } + // } + // ``` + + const indexName = await coll0.createSearchIndex({ + name: 'test-search-index-case7-vector', + type: 'vectorSearch', + definition: { + fields: [ + { + type: 'vector', + path: 'plot_embedding', + numDimensions: 1536, + similarity: 'euclidean' + } + ] + } + }); + // 11. Assert that the command returns the name of the index: `"test-search-index-case7-vector"`. + expect(indexName).to.equal('test-search-index-case7-vector'); + // 12. Run `coll0.listSearchIndexes('test-search-index-case7-vector')` repeatedly every 5 seconds until the following + // condition is satisfied and store the value in a variable `index3`: + // - An index with the `name` of `test-search-index-case7-vector` is present and the index has a field `queryable` with + // a value of `true`. + const [index3] = await waitForIndexes({ + predicate: indexes => indexes.every(index => index.queryable), + indexNames: 'test-search-index-case7-vector', + collection: coll0 + }); + + // 13. Assert that `index3` has a property `type` whose value is `vectorSearch`. + expect(index3).to.have.property('type', 'vectorSearch'); + } + } + ); + + it('Case 8: Driver requires explicit type to create a vector search index', async function () { + // 1. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + const coll0 = collection; + + // 2. Create a new vector search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + // { + // name: 'test-search-index-case8-error', + // definition: { + // fields: [ + // { + // type: 'vector', + // path: 'plot_embedding', + // numDimensions: 1536, + // similarity: 'euclidean', + // }, + // ] + // } + // } + const definition = { + name: 'test-search-index-case8-error', + definition: { + fields: [ + { + type: 'vector', + path: 'plot_embedding', + numDimensions: 1536, + similarity: 'euclidean' + } + ] + } + }; + const error = await coll0.createSearchIndex(definition).catch(e => e); + + // 3. Assert that the command throws an exception containing the string "Attribute mappings missing" due to the `mappings` + // field missing. + expect(error).to.match(/Attribute mappings missing/i); + }); }); }); diff --git a/test/spec/index-management/createSearchIndex.json b/test/spec/index-management/createSearchIndex.json index f9c4e44d3e..327cb61259 100644 --- a/test/spec/index-management/createSearchIndex.json +++ b/test/spec/index-management/createSearchIndex.json @@ -50,7 +50,8 @@ "mappings": { "dynamic": true } - } + }, + "type": "search" } }, "expectError": { @@ -73,7 +74,8 @@ "mappings": { "dynamic": true } - } + }, + "type": "search" } ], "$db": "database0" @@ -97,7 +99,8 @@ "dynamic": true } }, - "name": "test index" + "name": "test index", + "type": "search" } }, "expectError": { @@ -121,7 +124,68 @@ "dynamic": true } }, - "name": "test index" + "name": "test index", + "type": "search" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "create a vector search index", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "fields": [ + { + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean" + } + ] + }, + "name": "test index", + "type": "vectorSearch" + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "fields": [ + { + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean" + } + ] + }, + "name": "test index", + "type": "vectorSearch" } ], "$db": "database0" diff --git a/test/spec/index-management/createSearchIndex.yml b/test/spec/index-management/createSearchIndex.yml index 2e3cf50f8d..a32546cacf 100644 --- a/test/spec/index-management/createSearchIndex.yml +++ b/test/spec/index-management/createSearchIndex.yml @@ -26,7 +26,7 @@ tests: - name: createSearchIndex object: *collection0 arguments: - model: { definition: &definition { mappings: { dynamic: true } } } + model: { definition: &definition { mappings: { dynamic: true } } , type: 'search' } expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. @@ -39,7 +39,7 @@ tests: - commandStartedEvent: command: createSearchIndexes: *collection0 - indexes: [ { definition: *definition } ] + indexes: [ { definition: *definition, type: 'search'} ] $db: *database0 - description: "name provided for an index definition" @@ -47,7 +47,7 @@ tests: - name: createSearchIndex object: *collection0 arguments: - model: { definition: &definition { mappings: { dynamic: true } } , name: 'test index' } + model: { definition: &definition { mappings: { dynamic: true } } , name: 'test index', type: 'search' } expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. @@ -60,5 +60,27 @@ tests: - commandStartedEvent: command: createSearchIndexes: *collection0 - indexes: [ { definition: *definition, name: 'test index' } ] + indexes: [ { definition: *definition, name: 'test index', type: 'search' } ] + $db: *database0 + + - description: "create a vector search index" + operations: + - name: createSearchIndex + object: *collection0 + arguments: + model: { definition: &definition { fields: [ {"type": "vector", "path": "plot_embedding", "numDimensions": 1536, "similarity": "euclidean"} ] } + , name: 'test index', type: 'vectorSearch' } + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index', type: 'vectorSearch' } ] $db: *database0 diff --git a/test/spec/index-management/createSearchIndexes.json b/test/spec/index-management/createSearchIndexes.json index 3cf56ce12e..d91d7d9cf3 100644 --- a/test/spec/index-management/createSearchIndexes.json +++ b/test/spec/index-management/createSearchIndexes.json @@ -83,7 +83,8 @@ "mappings": { "dynamic": true } - } + }, + "type": "search" } ] }, @@ -107,7 +108,8 @@ "mappings": { "dynamic": true } - } + }, + "type": "search" } ], "$db": "database0" @@ -132,7 +134,8 @@ "dynamic": true } }, - "name": "test index" + "name": "test index", + "type": "search" } ] }, @@ -157,7 +160,70 @@ "dynamic": true } }, - "name": "test index" + "name": "test index", + "type": "search" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "create a vector search index", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [ + { + "definition": { + "fields": [ + { + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean" + } + ] + }, + "name": "test index", + "type": "vectorSearch" + } + ] + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "fields": [ + { + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean" + } + ] + }, + "name": "test index", + "type": "vectorSearch" } ], "$db": "database0" diff --git a/test/spec/index-management/createSearchIndexes.yml b/test/spec/index-management/createSearchIndexes.yml index db8f02e551..cac442cb87 100644 --- a/test/spec/index-management/createSearchIndexes.yml +++ b/test/spec/index-management/createSearchIndexes.yml @@ -48,7 +48,7 @@ tests: - name: createSearchIndexes object: *collection0 arguments: - models: [ { definition: &definition { mappings: { dynamic: true } } } ] + models: [ { definition: &definition { mappings: { dynamic: true } } , type: 'search' } ] expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. @@ -61,7 +61,7 @@ tests: - commandStartedEvent: command: createSearchIndexes: *collection0 - indexes: [ { definition: *definition } ] + indexes: [ { definition: *definition, type: 'search'} ] $db: *database0 - description: "name provided for an index definition" @@ -69,7 +69,7 @@ tests: - name: createSearchIndexes object: *collection0 arguments: - models: [ { definition: &definition { mappings: { dynamic: true } } , name: 'test index' } ] + models: [ { definition: &definition { mappings: { dynamic: true } } , name: 'test index' , type: 'search' } ] expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. @@ -82,5 +82,27 @@ tests: - commandStartedEvent: command: createSearchIndexes: *collection0 - indexes: [ { definition: *definition, name: 'test index' } ] + indexes: [ { definition: *definition, name: 'test index', type: 'search' } ] + $db: *database0 + + - description: "create a vector search index" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [ { definition: &definition { fields: [ {"type": "vector", "path": "plot_embedding", "numDimensions": 1536, "similarity": "euclidean"} ] }, + name: 'test index' , type: 'vectorSearch' } ] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index', type: 'vectorSearch' } ] $db: *database0