diff --git a/src/collection.ts b/src/collection.ts index 6b4a97031c9..c6778cc5412 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -53,7 +53,6 @@ import { CreateIndexesOperation, type CreateIndexesOptions, CreateIndexOperation, - DropIndexesOperation, type DropIndexesOptions, DropIndexOperation, type IndexDescription, @@ -646,11 +645,16 @@ export class Collection { * * @param options - Optional settings for the command */ - async dropIndexes(options?: DropIndexesOptions): Promise { - return executeOperation( - this.client, - new DropIndexesOperation(this as TODO_NODE_3286, resolveOptions(this, options)) - ); + async dropIndexes(options?: DropIndexesOptions): Promise { + try { + await executeOperation( + this.client, + new DropIndexOperation(this as TODO_NODE_3286, '*', resolveOptions(this, options)) + ); + return true; + } catch { + return false; + } } /** diff --git a/src/operations/common_functions.ts b/src/operations/common_functions.ts index 4d342b76f61..6f79db71d31 100644 --- a/src/operations/common_functions.ts +++ b/src/operations/common_functions.ts @@ -1,10 +1,8 @@ import type { Document } from '../bson'; import type { Collection } from '../collection'; import type { Db } from '../db'; -import { MongoTopologyClosedError } from '../error'; import type { ReadPreference } from '../read_preference'; import type { ClientSession } from '../sessions'; -import { type Callback, getTopology } from '../utils'; /** @public */ export interface IndexInformationOptions { @@ -18,66 +16,31 @@ export interface IndexInformationOptions { * @param db - The Db instance on which to retrieve the index info. * @param name - The name of the collection. */ -export function indexInformation(db: Db, name: string, callback: Callback): void; -export function indexInformation( +export async function indexInformation(db: Db, name: string): Promise; +export async function indexInformation( db: Db, name: string, - options: IndexInformationOptions, - callback?: Callback -): void; -export function indexInformation( + options?: IndexInformationOptions +): Promise; +export async function indexInformation( db: Db, name: string, - _optionsOrCallback: IndexInformationOptions | Callback, - _callback?: Callback -): void { - let options = _optionsOrCallback as IndexInformationOptions; - let callback = _callback as Callback; - if ('function' === typeof _optionsOrCallback) { - callback = _optionsOrCallback; + options?: IndexInformationOptions +): Promise { + if (options == null) { options = {}; } // If we specified full information const full = options.full == null ? false : options.full; + // Get the list of indexes of the specified collection + const indexes = await db.collection(name).listIndexes(options).toArray(); + if (full) return indexes; - let topology; - try { - topology = getTopology(db); - } catch (error) { - return callback(error); - } - - // Did the user destroy the topology - if (topology.isDestroyed()) return callback(new MongoTopologyClosedError()); - // Process all the results from the index command and collection - function processResults(indexes: any) { - // Contains all the information - const info: any = {}; - // Process all the indexes - for (let i = 0; i < indexes.length; i++) { - const index = indexes[i]; - // Let's unpack the object - info[index.name] = []; - for (const name in index.key) { - info[index.name].push([name, index.key[name]]); - } - } - - return info; + const info: Record> = {}; + for (const index of indexes) { + info[index.name] = Object.entries(index.key); } - - // Get the list of indexes of the specified collection - db.collection(name) - .listIndexes(options) - .toArray() - .then( - indexes => { - if (!Array.isArray(indexes)) return callback(undefined, []); - if (full) return callback(undefined, indexes); - callback(undefined, processResults(indexes)); - }, - error => callback(error) - ); + return info; } export function prepareDocs( diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts index 481cdda7f48..69f998fd130 100644 --- a/src/operations/indexes.ts +++ b/src/operations/indexes.ts @@ -2,20 +2,19 @@ import type { Document } from '../bson'; import type { Collection } from '../collection'; import type { Db } from '../db'; import { MongoCompatibilityError, MONGODB_ERROR_CODES, MongoError } from '../error'; -import type { OneOrMore } from '../mongo_types'; +import { type OneOrMore } from '../mongo_types'; import { ReadPreference } from '../read_preference'; import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { type Callback, isObject, maxWireVersion, type MongoDBNamespace } from '../utils'; import { type CollationOptions, - CommandCallbackOperation, CommandOperation, type CommandOperationOptions, type OperationParent } from './command'; import { indexInformation, type IndexInformationOptions } from './common_functions'; -import { AbstractCallbackOperation, Aspect, defineAspects } from './operation'; +import { AbstractOperation, Aspect, defineAspects } from './operation'; const VALID_INDEX_OPTIONS = new Set([ 'background', @@ -177,7 +176,7 @@ function makeIndexSpec( } /** @internal */ -export class IndexesOperation extends AbstractCallbackOperation { +export class IndexesOperation extends AbstractOperation { override options: IndexInformationOptions; collection: Collection; @@ -187,27 +186,23 @@ export class IndexesOperation extends AbstractCallbackOperation { this.collection = collection; } - override executeCallback( - server: Server, - session: ClientSession | undefined, - callback: Callback - ): void { + override execute(_server: Server, session: ClientSession | undefined): Promise { const coll = this.collection; const options = this.options; - indexInformation( - coll.s.db, - coll.collectionName, - { full: true, ...options, readPreference: this.readPreference, session }, - callback - ); + return indexInformation(coll.s.db, coll.collectionName, { + full: true, + ...options, + readPreference: this.readPreference, + session + }); } } /** @internal */ export class CreateIndexesOperation< T extends string | string[] = string[] -> extends CommandCallbackOperation { +> extends CommandOperation { override options: CreateIndexesOptions; collectionName: string; indexes: ReadonlyArray & { key: Map }>; @@ -240,11 +235,7 @@ export class CreateIndexesOperation< }); } - override executeCallback( - server: Server, - session: ClientSession | undefined, - callback: Callback - ): void { + override async execute(server: Server, session: ClientSession | undefined): Promise { const options = this.options; const indexes = this.indexes; @@ -254,12 +245,9 @@ export class CreateIndexesOperation< if (options.commitQuorum != null) { if (serverWireVersion < 9) { - callback( - new MongoCompatibilityError( - 'Option `commitQuorum` for `createIndexes` not supported on servers < 4.4' - ) + throw new MongoCompatibilityError( + 'Option `commitQuorum` for `createIndexes` not supported on servers < 4.4' ); - return; } cmd.commitQuorum = options.commitQuorum; } @@ -267,15 +255,18 @@ export class CreateIndexesOperation< // collation is set on each index, it should not be defined at the root this.options.collation = undefined; - super.executeCommandCallback(server, session, cmd, err => { - if (err) { - callback(err); - return; - } + await super.executeCommand(server, session, cmd); - const indexNames = indexes.map(index => index.name || ''); - callback(undefined, indexNames as T); - }); + const indexNames = indexes.map(index => index.name || ''); + return indexNames as T; + } + + protected executeCallback( + _server: Server, + _session: ClientSession | undefined, + _callback: Callback + ): void { + throw new Error('Method not implemented.'); } } @@ -289,15 +280,9 @@ export class CreateIndexOperation extends CreateIndexesOperation { ) { super(parent, collectionName, [makeIndexSpec(indexSpec, options)], options); } - override executeCallback( - server: Server, - session: ClientSession | undefined, - callback: Callback - ): void { - super.executeCallback(server, session, (err, indexNames) => { - if (err || !indexNames) return callback(err); - return callback(undefined, indexNames[0]); - }); + override async execute(server: Server, session: ClientSession | undefined): Promise { + const indexNames = await super.execute(server, session); + return indexNames[0]; } } @@ -318,30 +303,19 @@ export class EnsureIndexOperation extends CreateIndexOperation { this.collectionName = collectionName; } - override executeCallback( - server: Server, - session: ClientSession | undefined, - callback: Callback - ): void { + override async execute(server: Server, session: ClientSession | undefined): Promise { const indexName = this.indexes[0].name; - const cursor = this.db.collection(this.collectionName).listIndexes({ session }); - cursor.toArray().then( - indexes => { - indexes = Array.isArray(indexes) ? indexes : [indexes]; - if (indexes.some(index => index.name === indexName)) { - callback(undefined, indexName); - return; - } - super.executeCallback(server, session, callback); - }, - error => { - if (error instanceof MongoError && error.code === MONGODB_ERROR_CODES.NamespaceNotFound) { - // ignore "NamespaceNotFound" errors - return super.executeCallback(server, session, callback); - } - return callback(error); - } - ); + const indexes = await this.db + .collection(this.collectionName) + .listIndexes({ session }) + .toArray() + .catch(error => { + if (error instanceof MongoError && error.code === MONGODB_ERROR_CODES.NamespaceNotFound) + return []; + throw error; + }); + if (indexName && indexes.some(index => index.name === indexName)) return indexName; + return super.execute(server, session); } } @@ -349,7 +323,7 @@ export class EnsureIndexOperation extends CreateIndexOperation { export type DropIndexesOptions = CommandOperationOptions; /** @internal */ -export class DropIndexOperation extends CommandCallbackOperation { +export class DropIndexOperation extends CommandOperation { override options: DropIndexesOptions; collection: Collection; indexName: string; @@ -362,31 +336,17 @@ export class DropIndexOperation extends CommandCallbackOperation { this.indexName = indexName; } - override executeCallback( - server: Server, - session: ClientSession | undefined, - callback: Callback - ): void { + override async execute(server: Server, session: ClientSession | undefined): Promise { const cmd = { dropIndexes: this.collection.collectionName, index: this.indexName }; - super.executeCommandCallback(server, session, cmd, callback); - } -} - -/** @internal */ -export class DropIndexesOperation extends DropIndexOperation { - constructor(collection: Collection, options: DropIndexesOptions) { - super(collection, '*', options); + return super.executeCommand(server, session, cmd); } - override executeCallback( - server: Server, - session: ClientSession | undefined, - callback: Callback + protected executeCallback( + _server: Server, + _session: ClientSession | undefined, + _callback: Callback ): void { - super.executeCallback(server, session, err => { - if (err) return callback(err, false); - callback(undefined, true); - }); + throw new Error('Method not implemented.'); } } @@ -442,7 +402,7 @@ export class ListIndexesOperation extends CommandOperation { } /** @internal */ -export class IndexExistsOperation extends AbstractCallbackOperation { +export class IndexExistsOperation extends AbstractOperation { override options: IndexInformationOptions; collection: Collection; indexes: string | string[]; @@ -458,39 +418,24 @@ export class IndexExistsOperation extends AbstractCallbackOperation { this.indexes = indexes; } - override executeCallback( - server: Server, - session: ClientSession | undefined, - callback: Callback - ): void { + override async execute(server: Server, session: ClientSession | undefined): Promise { const coll = this.collection; const indexes = this.indexes; - indexInformation( - coll.s.db, - coll.collectionName, - { ...this.options, readPreference: this.readPreference, session }, - (err, indexInformation) => { - // If we have an error return - if (err != null) return callback(err); - // Let's check for the index names - if (!Array.isArray(indexes)) return callback(undefined, indexInformation[indexes] != null); - // Check in list of indexes - for (let i = 0; i < indexes.length; i++) { - if (indexInformation[indexes[i]] == null) { - return callback(undefined, false); - } - } - - // All keys found return true - return callback(undefined, true); - } - ); + const info = await indexInformation(coll.s.db, coll.collectionName, { + ...this.options, + readPreference: this.readPreference, + session + }); + // Let's check for the index names + if (!Array.isArray(indexes)) return info[indexes] != null; + // All keys found return true + return indexes.every(indexName => info[indexName] != null); } } /** @internal */ -export class IndexInformationOperation extends AbstractCallbackOperation { +export class IndexInformationOperation extends AbstractOperation { override options: IndexInformationOptions; db: Db; name: string; @@ -502,20 +447,15 @@ export class IndexInformationOperation extends AbstractCallbackOperation - ): void { + override execute(server: Server, session: ClientSession | undefined): Promise { const db = this.db; const name = this.name; - indexInformation( - db, - name, - { ...this.options, readPreference: this.readPreference, session }, - callback - ); + return indexInformation(db, name, { + ...this.options, + readPreference: this.readPreference, + session + }); } } @@ -528,4 +468,3 @@ defineAspects(CreateIndexesOperation, [Aspect.WRITE_OPERATION]); defineAspects(CreateIndexOperation, [Aspect.WRITE_OPERATION]); defineAspects(EnsureIndexOperation, [Aspect.WRITE_OPERATION]); defineAspects(DropIndexOperation, [Aspect.WRITE_OPERATION]); -defineAspects(DropIndexesOperation, [Aspect.WRITE_OPERATION]); diff --git a/test/integration/index_management.test.js b/test/integration/index_management.test.js index e2465cae8c1..188452f11e0 100644 --- a/test/integration/index_management.test.js +++ b/test/integration/index_management.test.js @@ -1,13 +1,26 @@ 'use strict'; const { expect } = require('chai'); + const { assert: test, setupDatabase } = require('./shared'); const shared = require('../tools/contexts'); describe('Indexes', function () { + let client; + let db; + before(function () { return setupDatabase(this.configuration); }); + beforeEach(function () { + client = this.configuration.newClient(); + db = client.db(); + }); + + afterEach(async function () { + await client.close(); + }); + it('Should correctly execute createIndex', { metadata: { requires: { @@ -258,39 +271,106 @@ describe('Indexes', function () { } }); - it('shouldCorrectlyDropIndexes', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, + context('when dropIndexes succeeds', function () { + let collection; - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection('test_drop_indexes', function (err, collection) { - collection.insert({ a: 1 }, configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - // Create an index on the collection - db.createIndex( - collection.collectionName, - 'a', - configuration.writeConcernMax(), - function (err, indexName) { - test.equal('a_1', indexName); - // Drop all the indexes - collection.dropIndexes(function (err, result) { - test.equal(true, result); + beforeEach(async function () { + collection = await db.createCollection('test_drop_indexes'); + await collection.insert({ a: 1 }); + // Create an index on the collection + await db.createIndex(collection.collectionName, 'a'); + }); - collection.indexInformation(function (err, result) { - test.ok(result['a_1'] == null); - client.close(done); - }); - }); - } - ); + afterEach(async function () { + await db.dropCollection('test_drop_indexes'); + }); + + it('should return true and should no longer exist in the collection', async function () { + // Drop all the indexes + const result = await collection.dropIndexes(); + expect(result).to.equal(true); + + const res = await collection.indexInformation(); + expect(res['a_1']).to.equal(undefined); + }); + }); + + context('when dropIndexes fails', function () { + let collection; + + beforeEach(async function () { + collection = await db.createCollection('test_drop_indexes'); + await collection.insert({ a: 1 }); + // Create an index on the collection + await db.createIndex(collection.collectionName, 'a'); + /**@type {import('../tools/utils').FailPoint} */ + await client + .db() + .admin() + .command({ + configureFailPoint: 'failCommand', + mode: { + times: 1 + }, + data: { + failCommands: ['dropIndexes'], + errorCode: 91 + } }); - }); - } + }); + + afterEach(async function () { + await db.dropCollection('test_drop_indexes'); + }); + + it('should return false', { + metadata: { + requires: { + mongodb: '>4.0' + } + }, + + test: async function () { + const result = await collection.dropIndexes(); + expect(result).to.equal(false); + } + }); + }); + + context('indexExists', function () { + let collection; + + beforeEach(async function () { + collection = await db.createCollection('test_index_exists'); + await collection.insert({ a: 1 }); + + await db.createIndex(collection.collectionName, 'a'); + await db.createIndex(collection.collectionName, ['c', 'd', 'e']); + }); + + afterEach(async function () { + await db.dropCollection('test_index_exists'); + }); + + it('should return true when index of type string exists', async function () { + const result = await collection.indexExists('a_1'); + expect(result).to.equal(true); + }); + + it('should return false when index of type string does not exist', async function () { + const result = await collection.indexExists('b_2'); + expect(result).to.equal(false); + }); + + it('should return true when an array of indexes exists', async function () { + const result = await collection.indexExists(['c_1_d_1_e_1', 'a_1']); + expect(result).to.equal(true); + }); + + it('should return false when an array of indexes does not exist', async function () { + const result = await collection.indexExists(['d_1_e_1', 'c_1']); + expect(result).to.equal(false); + }); }); it('shouldCorrectlyHandleDistinctIndexes', {