diff --git a/packages/autocomplete/src/index.spec.ts b/packages/autocomplete/src/index.spec.ts index c1819f464d..a562d18784 100644 --- a/packages/autocomplete/src/index.spec.ts +++ b/packages/autocomplete/src/index.spec.ts @@ -280,7 +280,9 @@ describe('completer.completer', () => { const adjusted = collComplete .filter(c => ![ - 'count', 'update', 'remove', 'insert', 'save', 'findAndModify', 'reIndex', 'mapReduce' + 'count', 'update', 'remove', 'insert', 'save', 'findAndModify', 'reIndex', 'mapReduce', + // search index helpers are 6.0+ + 'getSearchIndexes', 'createSearchIndex', 'createSearchIndexes', 'dropSearchIndex', 'updateSearchIndex' ].includes(c) ) .map(c => `${i}${c}`); diff --git a/packages/i18n/src/locales/en_US.ts b/packages/i18n/src/locales/en_US.ts index 221e6f925e..cf73367583 100644 --- a/packages/i18n/src/locales/en_US.ts +++ b/packages/i18n/src/locales/en_US.ts @@ -698,6 +698,31 @@ const translations: Catalog = { link: 'https://docs.mongodb.com/manual/reference/method/db.collection.configureQueryAnalyzer', description: 'Starts or stops collecting metrics about reads and writes against an unsharded or sharded collection.', example: 'db.coll.configureQueryAnalyzer(options)' + }, + getSearchIndexes: { + link: 'https://docs.mongodb.com/manual/reference/method/db.collection.getSearchIndexes', + description: 'Returns an array that holds a list of documents that identify and describe the existing search indexes on the collection.', + example: 'db.coll.getSearchIndexes()' + }, + createSearchIndexes: { + link: 'https://docs.mongodb.com/manual/reference/method/db.collection.createSearchIndexes', + description: 'Creates one or more search indexes on a collection', + example: 'db.coll.createSearchIndexes(descriptions)' + }, + createSearchIndex: { + link: 'https://docs.mongodb.com/manual/reference/method/db.collection.createSearchIndex', + description: 'Creates one search indexes on a collection', + example: 'db.coll.createSearchIndex(description)' + }, + dropSearchIndex: { + link: 'https://docs.mongodb.com/manual/reference/method/db.collection.dropSearchIndex', + description: 'Drops or removes the specified search index from a collection.', + example: 'db.coll.dropSearchIndex(name)' + }, + updateSearchIndex: { + link: 'https://docs.mongodb.com/manual/reference/method/db.collection.updateSearchIndex', + description: 'Updates the sepecified search index.', + example: 'db.coll.updateSearchIndex(name, definition)' } } } diff --git a/packages/service-provider-core/src/readable.ts b/packages/service-provider-core/src/readable.ts index c0c3d4d8f5..11a3643fcd 100644 --- a/packages/service-provider-core/src/readable.ts +++ b/packages/service-provider-core/src/readable.ts @@ -12,7 +12,7 @@ import type { FindCursor, DbOptions, ReadPreferenceFromOptions, - ReadPreferenceLike + ReadPreferenceLike, } from './all-transport-types'; import { ChangeStream, ChangeStreamOptions } from './all-transport-types'; @@ -206,5 +206,23 @@ export default interface Readable { db?: string, coll?: string ): ChangeStream; + + /** + * Returns an array of documents that identify and describe the existing + * search indexes on the collection. + * + * @param {String} database - The db name. + * @param {String} collection - The collection name. + * @param {Document} options - The command options. + * @param {DbOptions} dbOptions - The database options + * + * @return {Promise} + */ + getSearchIndexes( + database: string, + collection: string, + // TODO(MONGOSH-1471): use ListSearchIndexesOptions once available + options: any, + dbOptions?: DbOptions): Promise; } diff --git a/packages/service-provider-core/src/writable.ts b/packages/service-provider-core/src/writable.ts index d5a47a1495..283cfb7837 100644 --- a/packages/service-provider-core/src/writable.ts +++ b/packages/service-provider-core/src/writable.ts @@ -345,5 +345,54 @@ export default interface Writable { options?: BulkWriteOptions, dbOptions?: DbOptions ): Promise; + + /** + * Adds new search indexes to a collection. + * + * @param {String} database - The db name. + * @param {String} collection - The collection name. + * @param {Object[]} descriptions - The specs of the indexes to be created. + * @param {DbOptions} dbOptions - The database options + */ + createSearchIndexes( + database: string, + collection: string, + // TODO(MONGOSH-1471): use SearchIndexDescription[] once available + descriptions: any[], + dbOptions?: DbOptions + ): Promise + + /** + * Drops a search index. + * + * @param {String} database - The db name. + * @param {String} collection - The collection name. + * @param {String} indexName - The index name + * @param {DbOptions} dbOptions - The database options + */ + dropSearchIndex( + database: string, + collection: string, + index: string, + dbOptions?: DbOptions + ): Promise + + /** + * Update a search index. + * + * @param {String} database - The db name. + * @param {String} collection - The collection name. + * @param {String} indexName - The index name. + * @param {Object} description - The update. + * @param {DbOptions} dbOptions - The database options + */ + updateSearchIndex( + database: string, + collection: string, + index: string, + // TODO(MONGOSH-1471): use SearchIndexDescription once available + description: any, + dbOptions?: DbOptions + ): Promise } diff --git a/packages/service-provider-server/src/cli-service-provider.integration.spec.ts b/packages/service-provider-server/src/cli-service-provider.integration.spec.ts index eee5aaae17..a1841a38c7 100644 --- a/packages/service-provider-server/src/cli-service-provider.integration.spec.ts +++ b/packages/service-provider-server/src/cli-service-provider.integration.spec.ts @@ -577,6 +577,8 @@ describe('CliServiceProvider [integration]', function() { }); }); + // TODO(MONGOSH-1465): integration tests for search indexes + describe('#listCollections', () => { it('returns the list of collections', async() => { await db.createCollection('coll1'); diff --git a/packages/service-provider-server/src/cli-service-provider.spec.ts b/packages/service-provider-server/src/cli-service-provider.spec.ts index ef30780d96..0960c4ef37 100644 --- a/packages/service-provider-server/src/cli-service-provider.spec.ts +++ b/packages/service-provider-server/src/cli-service-provider.spec.ts @@ -861,4 +861,122 @@ describe('CliServiceProvider', () => { ).parentState).to.equal(undefined); }); }); + + describe('#getSearchIndexes', () => { + let descriptions; + let nativeMethodResult; + let getSearchIndexesOptions; + + beforeEach(() => { + descriptions = [ + { name: 'foo', description: {} }, + { name: 'bar', description: {} } + ]; + + nativeMethodResult = { + toArray: () => { + return Promise.resolve(descriptions); + } + }; + + getSearchIndexesOptions = { allowDiskUse: true }; + + collectionStub = stubInterface(); + // @ts-expect-error still @internal + collectionStub.listSearchIndexes.returns(nativeMethodResult); + serviceProvider = new CliServiceProvider(createClientStub(collectionStub), bus, dummyOptions); + }); + + it('calls listSearchIndexes and toArray on the resulting cursor', async() => { + const result = await serviceProvider.getSearchIndexes( + 'db1', + 'coll1', + getSearchIndexesOptions); + expect(result).to.deep.equal(descriptions); + // @ts-expect-error still @internal + expect(collectionStub.listSearchIndexes).to.have.been.calledWith(getSearchIndexesOptions); + }); + }); + + describe('#createSearchIndexes', () => { + let descriptions; + let nativeMethodResult; + + beforeEach(() => { + descriptions = [ + { name: 'foo', description: {} }, + { name: 'bar', description: {} } + ]; + + nativeMethodResult = [ + 'index_1', + ]; + + collectionStub = stubInterface(); + // @ts-expect-error still @internal + collectionStub.createSearchIndexes.resolves(nativeMethodResult); + serviceProvider = new CliServiceProvider(createClientStub(collectionStub), bus, dummyOptions); + }); + + it('executes the command against the database', async() => { + const result = await serviceProvider.createSearchIndexes( + 'db1', + 'coll1', + descriptions); + expect(result).to.deep.equal(nativeMethodResult); + // @ts-expect-error still @internal + expect(collectionStub.createSearchIndexes).to.have.been.calledWith(descriptions); + }); + }); + + describe('#dropSearchIndex', () => { + let indexName; + + beforeEach(() => { + indexName = 'foo'; + + collectionStub = stubInterface(); + // @ts-expect-error still @internal + collectionStub.dropSearchIndex.resolves(); + serviceProvider = new CliServiceProvider(createClientStub(collectionStub), bus, dummyOptions); + }); + + it('executes the command against the database', async() => { + const result = await serviceProvider.dropSearchIndex( + 'db1', + 'coll1', + indexName); + expect(result).to.deep.equal(undefined); + // @ts-expect-error still @internal + expect(collectionStub.dropSearchIndex).to.have.been.calledWith(indexName); + }); + }); + + + describe('#updateSearchIndex', () => { + let indexName; + let description; + + beforeEach(() => { + indexName = 'foo'; + description = { x: 1, y: 2 }; + + collectionStub = stubInterface(); + + // @ts-expect-error still @internal + collectionStub.updateSearchIndex.resolves(); + serviceProvider = new CliServiceProvider(createClientStub(collectionStub), bus, dummyOptions); + }); + + it('executes the command against the database', async() => { + const result = await serviceProvider.updateSearchIndex( + 'db1', + 'coll1', + indexName, + description); + expect(result).to.deep.equal(undefined); + // @ts-expect-error still @internal + expect(collectionStub.updateSearchIndex).to.have.been.calledWith(indexName, description); + }); + }); }); diff --git a/packages/service-provider-server/src/cli-service-provider.ts b/packages/service-provider-server/src/cli-service-provider.ts index b16fcaf429..ace5608af3 100644 --- a/packages/service-provider-server/src/cli-service-provider.ts +++ b/packages/service-provider-server/src/cli-service-provider.ts @@ -1206,6 +1206,54 @@ class CliServiceProvider extends ServiceProviderCore implements ServiceProvider ...opts }); } + + getSearchIndexes( + database: string, + collection: string, + // TODO(MONGOSH-1471): use ListSearchIndexesOptions once available + options: any, + dbOptions?: DbOptions): Promise { + return this.db(database, dbOptions) + .collection(collection) + // @ts-expect-error still @internal + .listSearchIndexes(options).toArray(); + } + + createSearchIndexes( + database: string, + collection: string, + // TODO(MONGOSH-1471): use SearchIndexDescription[] once available + descriptions: any[], + dbOptions?: DbOptions): Promise { + return this.db(database, dbOptions) + .collection(collection) + // @ts-expect-error still @internal + .createSearchIndexes(descriptions); + } + + dropSearchIndex( + database: string, + collection: string, + indexName: string, + dbOptions?: DbOptions): Promise { + return this.db(database, dbOptions) + .collection(collection) + // @ts-expect-error still @internal + .dropSearchIndex(indexName); + } + + updateSearchIndex( + database: string, + collection: string, + indexName: string, + // TODO(MONGOSH-1471): use SearchIndexDescription once available + description: any, + dbOptions?: DbOptions): Promise { + return this.db(database, dbOptions) + .collection(collection) + // @ts-expect-error still @internal + .updateSearchIndex(indexName, description); + } } export default CliServiceProvider; diff --git a/packages/shell-api/src/collection.spec.ts b/packages/shell-api/src/collection.spec.ts index a482c7a293..c6c740eb00 100644 --- a/packages/shell-api/src/collection.spec.ts +++ b/packages/shell-api/src/collection.spec.ts @@ -2095,7 +2095,110 @@ describe('Collection', () => { expect.fail('Failed to throw'); }); }); + + describe('getSearchIndexes', () => { + let descriptions; + + beforeEach(() => { + descriptions = [{ name: 'foo', description: {} }, { name: 'bar', description: {} }]; + serviceProvider.getSearchIndexes.resolves(descriptions); + }); + + context('without options', () => { + it('calls serviceProvider.listSearchIndexes(), then toArray() on the returned cursor', async() => { + const result = await collection.getSearchIndexes(); + + expect(result).to.equal(descriptions); + + expect(serviceProvider.getSearchIndexes).to.have.been.calledWith( + 'db1', + 'coll1' + ); + }); + }); + + context('with options', () => { + it('calls serviceProvider.listSearchIndexes(options), then toArray() on the returned cursor', async() => { + const options = { allowDiskUse: true }; + const result = await collection.getSearchIndexes(options); + + expect(result).to.equal(descriptions); + + expect(serviceProvider.getSearchIndexes).to.have.been.calledWith( + 'db1', + 'coll1', + options + ); + }); + }); + }); + + describe('createSearchIndex', () => { + beforeEach(() => { + serviceProvider.createSearchIndexes.resolves(['index_1']); + }); + + it('calls serviceProvider.createIndexes', async() => { + await collection.createSearchIndex({ name: 'foo', description: {} }); + + expect(serviceProvider.createSearchIndexes).to.have.been.calledWith( + 'db1', + 'coll1', + [ { name: 'foo', description: {} }] + ); + }); + }); + + describe('createSearchIndexes', () => { + beforeEach(() => { + serviceProvider.createSearchIndexes.resolves(['index_1', 'index_2']); + }); + + it('calls serviceProvider.createIndexes', async() => { + await collection.createSearchIndexes([{ name: 'foo', description: {} }, { name: 'bar', description: {} }]); + + expect(serviceProvider.createSearchIndexes).to.have.been.calledWith( + 'db1', + 'coll1', + [{ name: 'foo', description: {} }, { name: 'bar', description: {} }] + ); + }); + }); + + describe('dropSearchIndex', () => { + beforeEach(() => { + serviceProvider.dropSearchIndex.resolves(); + }); + + it('calls serviceProvider.dropSearchIndex', async() => { + await collection.dropSearchIndex('foo'); + + expect(serviceProvider.dropSearchIndex).to.have.been.calledWith( + 'db1', + 'coll1', + 'foo' + ); + }); + }); + + describe('updateSearchIndex', () => { + beforeEach(() => { + serviceProvider.updateSearchIndex.resolves(); + }); + + it('calls serviceProvider.updateSearchIndex', async() => { + await collection.updateSearchIndex('foo', { description: { x: 1, y: 2 } }); + + expect(serviceProvider.updateSearchIndex).to.have.been.calledWith( + 'db1', + 'coll1', + 'foo', + { description: { x: 1, y: 2 } } + ); + }); + }); }); + describe('fle2', () => { let mongo1: Mongo; let mongo2: Mongo; @@ -2229,10 +2332,19 @@ describe('Collection', () => { hideIndex: { m: 'runCommandWithCheck', i: 2 }, unhideIndex: { m: 'runCommandWithCheck', i: 2 }, remove: { m: 'deleteMany' }, - watch: { i: 1 } + watch: { i: 1 }, + getSearchIndexes: { i: 2 }, }; const ignore: (keyof (typeof Collection)['prototype'])[] = [ - 'getShardDistribution', 'stats', 'isCapped', 'compactStructuredEncryptionData' + 'getShardDistribution', + 'stats', + 'isCapped', + 'compactStructuredEncryptionData', + // none of these search index helpers take an options param, so they don't take session + 'createSearchIndex', + 'createSearchIndexes', + 'dropSearchIndex', + 'updateSearchIndex', ]; const args = [ { query: {} }, {}, { out: 'coll' } ]; beforeEach(() => { @@ -2249,6 +2361,10 @@ describe('Collection', () => { serviceProvider.listCollections.resolves([]); serviceProvider.watch.returns({ closed: false, tryNext: async() => {} } as any); serviceProvider.countDocuments.resolves(1); + serviceProvider.getSearchIndexes.resolves([]); + serviceProvider.createSearchIndexes.resolves(['index_1']); + serviceProvider.dropSearchIndex.resolves(); + serviceProvider.updateSearchIndex.resolves(); serviceProvider.runCommandWithCheck.resolves({ ok: 1, version: 1, bits: 1, commands: 1, users: [], roles: [], logComponentVerbosity: 1 }); [ 'bulkWrite', 'deleteMany', 'deleteOne', 'insert', 'insertMany', diff --git a/packages/shell-api/src/collection.ts b/packages/shell-api/src/collection.ts index ec43774078..3e91761fa7 100644 --- a/packages/shell-api/src/collection.ts +++ b/packages/shell-api/src/collection.ts @@ -2056,4 +2056,70 @@ export default class Collection extends ShellApiWithMongoClass { ...options }); } + + @serverVersions(['6.0.0', ServerVersions.latest]) + @returnsPromise + @apiVersions([]) + // TODO(MONGOSH-1471): use ListSearchIndexesOptions once available + async getSearchIndexes(options?: any): Promise { + this._emitCollectionApiCall('getSearchIndexes', { options }); + return await this._mongo._serviceProvider.getSearchIndexes( + this._database._name, + this._name, + { ...await this._database._baseOptions(), ...options } + ); + } + + @serverVersions(['6.0.0', ServerVersions.latest]) + @returnsPromise + @apiVersions([]) + // TODO(MONGOSH-1471): use SearchIndexDescription once available + async createSearchIndex(description: any): Promise { + this._emitCollectionApiCall('createSearchIndex', { description }); + const results = await this._mongo._serviceProvider.createSearchIndexes( + this._database._name, + this._name, + [description] + ); + return results[0]; + } + + @serverVersions(['6.0.0', ServerVersions.latest]) + @returnsPromise + @apiVersions([]) + // TODO(MONGOSH-1471): use SearchIndexDescription once available + async createSearchIndexes(descriptions: any[]): Promise { + this._emitCollectionApiCall('createSearchIndexes', { descriptions }); + return await this._mongo._serviceProvider.createSearchIndexes( + this._database._name, + this._name, + descriptions + ); + } + + @serverVersions(['6.0.0', ServerVersions.latest]) + @returnsPromise + @apiVersions([]) + async dropSearchIndex(indexName: string): Promise { + this._emitCollectionApiCall('dropSearchIndex', { indexName }); + return await this._mongo._serviceProvider.dropSearchIndex( + this._database._name, + this._name, + indexName + ); + } + + @serverVersions(['6.0.0', ServerVersions.latest]) + @returnsPromise + @apiVersions([]) + // TODO(MONGOSH-1471): use SearchIndexDescription once available + async updateSearchIndex(indexName: string, description: any): Promise { + this._emitCollectionApiCall('updateSearchIndex', { indexName, description }); + return await this._mongo._serviceProvider.updateSearchIndex( + this._database._name, + this._name, + indexName, + description + ); + } } diff --git a/packages/shell-api/src/integration.spec.ts b/packages/shell-api/src/integration.spec.ts index e76f478ccf..ada0685377 100644 --- a/packages/shell-api/src/integration.spec.ts +++ b/packages/shell-api/src/integration.spec.ts @@ -626,6 +626,8 @@ describe('Shell API (integration)', function() { }); }); + // TODO(MONGOSH-1465): integration tests for search indexes + describe('dataSize', () => { skipIfApiStrict();