diff --git a/src/bulk/common.ts b/src/bulk/common.ts index 5de005449e..61050677c2 100644 --- a/src/bulk/common.ts +++ b/src/bulk/common.ts @@ -12,7 +12,6 @@ import { } from '../error'; import type { Filter, OneOrMore, OptionalId, UpdateFilter, WithoutId } from '../mongo_types'; import type { CollationOptions, CommandOperationOptions } from '../operations/command'; -import { maybeAddIdToDocuments } from '../operations/common_functions'; import { DeleteOperation, type DeleteStatement, makeDeleteStatement } from '../operations/delete'; import { executeOperation } from '../operations/execute_operation'; import { InsertOperation } from '../operations/insert'; @@ -21,6 +20,7 @@ import { makeUpdateStatement, UpdateOperation, type UpdateStatement } from '../o import type { Server } from '../sdam/server'; import type { Topology } from '../sdam/topology'; import type { ClientSession } from '../sessions'; +import { maybeAddIdToDocuments } from '../utils'; import { applyRetryableWrites, type Callback, diff --git a/src/collection.ts b/src/collection.ts index a735a3a082..b6581675ca 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -24,7 +24,6 @@ import type { } from './mongo_types'; import type { AggregateOptions } from './operations/aggregate'; import { BulkWriteOperation } from './operations/bulk_write'; -import type { IndexInformationOptions } from './operations/common_functions'; import { CountOperation, type CountOptions } from './operations/count'; import { CountDocumentsOperation, type CountDocumentsOptions } from './operations/count_documents'; import { @@ -49,19 +48,16 @@ import { FindOneAndUpdateOperation, type FindOneAndUpdateOptions } from './operations/find_and_modify'; -import { - CreateIndexesOperation, - type CreateIndexesOptions, - CreateIndexOperation, - type DropIndexesOptions, - DropIndexOperation, - type IndexDescription, - IndexesOperation, - IndexExistsOperation, - IndexInformationOperation, - type IndexSpecification, - type ListIndexesOptions +import type { + CreateIndexesOptions, + DropIndexesOptions, + IndexDescription, + IndexDirection, + IndexInformationOptions, + IndexSpecification, + ListIndexesOptions } from './operations/indexes'; +import { CreateIndexesOperation, DropIndexOperation } from './operations/indexes'; import { InsertManyOperation, type InsertManyResult, @@ -575,15 +571,17 @@ export class Collection { indexSpec: IndexSpecification, options?: CreateIndexesOptions ): Promise { - return executeOperation( + const indexes = await executeOperation( this.client, - new CreateIndexOperation( - this as TODO_NODE_3286, + CreateIndexesOperation.fromIndexSpecification( + this, this.collectionName, indexSpec, resolveOptions(this, options) ) ); + + return indexes[0]; } /** @@ -623,8 +621,8 @@ export class Collection { ): Promise { return executeOperation( this.client, - new CreateIndexesOperation( - this as TODO_NODE_3286, + CreateIndexesOperation.fromIndexDescriptionArray( + this, this.collectionName, indexSpecs, resolveOptions(this, { ...options, maxTimeMS: undefined }) @@ -680,14 +678,14 @@ export class Collection { * @param indexes - One or more index names to check. * @param options - Optional settings for the command */ - async indexExists( - indexes: string | string[], - options?: IndexInformationOptions - ): Promise { - return executeOperation( - this.client, - new IndexExistsOperation(this as TODO_NODE_3286, indexes, resolveOptions(this, options)) + async indexExists(indexes: string | string[], options?: ListIndexesOptions): Promise { + const indexNames: string[] = Array.isArray(indexes) ? indexes : [indexes]; + const allIndexes: Set = new Set( + await this.listIndexes(options) + .map(({ name }) => name) + .toArray() ); + return indexNames.every(name => allIndexes.has(name)); } /** @@ -696,10 +694,7 @@ export class Collection { * @param options - Optional settings for the command */ async indexInformation(options?: IndexInformationOptions): Promise { - return executeOperation( - this.client, - new IndexInformationOperation(this.s.db, this.collectionName, resolveOptions(this, options)) - ); + return this.indexes({ ...options, full: options?.full ?? false }); } /** @@ -804,10 +799,19 @@ export class Collection { * @param options - Optional settings for the command */ async indexes(options?: IndexInformationOptions): Promise { - return executeOperation( - this.client, - new IndexesOperation(this as TODO_NODE_3286, resolveOptions(this, options)) - ); + const indexes = await this.listIndexes(options).toArray(); + const full = options?.full ?? true; + if (full) { + return indexes; + } + + const object: Record< + string, + Array<[name: string, direction: IndexDirection]> + > = Object.fromEntries(indexes.map(({ name, key }) => [name, Object.entries(key)])); + + // @ts-expect-error TODO(NODE-6029): fix return type of `indexes()` and `indexInformation()` + return object; } /** diff --git a/src/db.ts b/src/db.ts index c12c22c724..a6fb89e6c7 100644 --- a/src/db.ts +++ b/src/db.ts @@ -11,7 +11,6 @@ import type { MongoClient, PkFactory } from './mongo_client'; import type { TODO_NODE_3286 } from './mongo_types'; import type { AggregateOptions } from './operations/aggregate'; import { CollectionsOperation } from './operations/collections'; -import type { IndexInformationOptions } from './operations/common_functions'; import { CreateCollectionOperation, type CreateCollectionOptions @@ -24,9 +23,9 @@ import { } from './operations/drop'; import { executeOperation } from './operations/execute_operation'; import { + CreateIndexesOperation, type CreateIndexesOptions, - CreateIndexOperation, - IndexInformationOperation, + type IndexInformationOptions, type IndexSpecification } from './operations/indexes'; import type { CollectionInfo, ListCollectionsOptions } from './operations/list_collections'; @@ -426,10 +425,11 @@ export class Db { indexSpec: IndexSpecification, options?: CreateIndexesOptions ): Promise { - return executeOperation( + const indexes = await executeOperation( this.client, - new CreateIndexOperation(this, name, indexSpec, resolveOptions(this, options)) + CreateIndexesOperation.fromIndexSpecification(this, name, indexSpec, options) ); + return indexes[0]; } /** @@ -480,10 +480,7 @@ export class Db { * @param options - Optional settings for the command */ async indexInformation(name: string, options?: IndexInformationOptions): Promise { - return executeOperation( - this.client, - new IndexInformationOperation(this, name, resolveOptions(this, options)) - ); + return this.collection(name).indexInformation(resolveOptions(this, options)); } /** diff --git a/src/index.ts b/src/index.ts index fa88e4638b..9cd58ec0ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -447,7 +447,6 @@ export type { CommandOperationOptions, OperationParent } from './operations/command'; -export type { IndexInformationOptions } from './operations/common_functions'; export type { CountOptions } from './operations/count'; export type { CountDocumentsOptions } from './operations/count_documents'; export type { @@ -466,6 +465,7 @@ export type { FindOneAndReplaceOptions, FindOneAndUpdateOptions } from './operations/find_and_modify'; +export type { IndexInformationOptions } from './operations/indexes'; export type { CreateIndexesOptions, DropIndexesOptions, diff --git a/src/operations/common_functions.ts b/src/operations/common_functions.ts deleted file mode 100644 index 785ecbaf12..0000000000 --- a/src/operations/common_functions.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Document } from '../bson'; -import type { Collection } from '../collection'; -import type { Db } from '../db'; -import type { ReadPreference } from '../read_preference'; -import type { ClientSession } from '../sessions'; - -/** @public */ -export interface IndexInformationOptions { - full?: boolean; - readPreference?: ReadPreference; - session?: ClientSession; -} -/** - * Retrieves this collections index info. - * - * @param db - The Db instance on which to retrieve the index info. - * @param name - The name of the collection. - */ -export async function indexInformation(db: Db, name: string): Promise; -export async function indexInformation( - db: Db, - name: string, - options?: IndexInformationOptions -): Promise; -export async function indexInformation( - db: Db, - name: string, - 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; - - const info: Record> = {}; - for (const index of indexes) { - info[index.name] = Object.entries(index.key); - } - return info; -} - -export function maybeAddIdToDocuments( - coll: Collection, - docs: Document[], - options: { forceServerObjectId?: boolean } -): Document[]; -export function maybeAddIdToDocuments( - coll: Collection, - docs: Document, - options: { forceServerObjectId?: boolean } -): Document; -export function maybeAddIdToDocuments( - coll: Collection, - docOrDocs: Document[] | Document, - options: { forceServerObjectId?: boolean } -): Document[] | Document { - const forceServerObjectId = - typeof options.forceServerObjectId === 'boolean' - ? options.forceServerObjectId - : coll.s.db.options?.forceServerObjectId; - - // no need to modify the docs if server sets the ObjectId - if (forceServerObjectId === true) { - return docOrDocs; - } - - const transform = (doc: Document): Document => { - if (doc._id == null) { - doc._id = coll.s.pkFactory.createPk(); - } - - return doc; - }; - return Array.isArray(docOrDocs) ? docOrDocs.map(transform) : transform(docOrDocs); -} diff --git a/src/operations/create_collection.ts b/src/operations/create_collection.ts index 9732e879b1..8edc7e9a1c 100644 --- a/src/operations/create_collection.ts +++ b/src/operations/create_collection.ts @@ -10,7 +10,7 @@ import type { PkFactory } from '../mongo_client'; import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { CommandOperation, type CommandOperationOptions } from './command'; -import { CreateIndexOperation } from './indexes'; +import { CreateIndexesOperation } from './indexes'; import { Aspect, defineAspects } from './operation'; const ILLEGAL_COMMAND_FIELDS = new Set([ @@ -167,7 +167,12 @@ export class CreateCollectionOperation extends CommandOperation { if (encryptedFields) { // Create the required index for queryable encryption support. - const createIndexOp = new CreateIndexOperation(db, name, { __safeContent__: 1 }, {}); + const createIndexOp = CreateIndexesOperation.fromIndexSpecification( + db, + name, + { __safeContent__: 1 }, + {} + ); await createIndexOp.execute(server, session); } diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts index cbeb82ff81..a1c2f12bfc 100644 --- a/src/operations/indexes.ts +++ b/src/operations/indexes.ts @@ -1,9 +1,8 @@ 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 AbstractCursorOptions } from '../cursor/abstract_cursor'; +import { MongoCompatibilityError } from '../error'; import { type OneOrMore } from '../mongo_types'; -import { ReadPreference } from '../read_preference'; import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { isObject, maxWireVersion, type MongoDBNamespace } from '../utils'; @@ -13,8 +12,7 @@ import { type CommandOperationOptions, type OperationParent } from './command'; -import { indexInformation, type IndexInformationOptions } from './common_functions'; -import { AbstractOperation, Aspect, defineAspects } from './operation'; +import { Aspect, defineAspects } from './operation'; const VALID_INDEX_OPTIONS = new Set([ 'background', @@ -73,6 +71,29 @@ export type IndexSpecification = OneOrMore< | Map >; +/** @public */ +export interface IndexInformationOptions extends ListIndexesOptions { + /** + * When `true`, an array of index descriptions is returned. + * When `false`, the driver returns an object that with keys corresponding to index names with values + * corresponding to the entries of the indexes' key. + * + * For example, the given the following indexes: + * ``` + * [ { name: 'a_1', key: { a: 1 } }, { name: 'b_1_c_1' , key: { b: 1, c: 1 } }] + * ``` + * + * When `full` is `true`, the above array is returned. When `full` is `false`, the following is returned: + * ``` + * { + * 'a_1': [['a', 1]], + * 'b_1_c_1': [['b', 1], ['c', 1]], + * } + * ``` + */ + full?: boolean; +} + /** @public */ export interface IndexDescription extends Pick< @@ -176,42 +197,12 @@ function makeIndexSpec( } /** @internal */ -export class IndexesOperation extends AbstractOperation { - override options: IndexInformationOptions; - collection: Collection; - - constructor(collection: Collection, options: IndexInformationOptions) { - super(options); - this.options = options; - this.collection = collection; - } - - override get commandName() { - return 'listIndexes' as const; - } - - override async execute(_server: Server, session: ClientSession | undefined): Promise { - const coll = this.collection; - const options = this.options; - - return indexInformation(coll.s.db, coll.collectionName, { - full: true, - ...options, - readPreference: this.readPreference, - session - }); - } -} - -/** @internal */ -export class CreateIndexesOperation< - T extends string | string[] = string[] -> extends CommandOperation { +export class CreateIndexesOperation extends CommandOperation { override options: CreateIndexesOptions; collectionName: string; indexes: ReadonlyArray & { key: Map }>; - constructor( + private constructor( parent: OperationParent, collectionName: string, indexes: IndexDescription[], @@ -239,11 +230,34 @@ export class CreateIndexesOperation< }); } + static fromIndexDescriptionArray( + parent: OperationParent, + collectionName: string, + indexes: IndexDescription[], + options?: CreateIndexesOptions + ): CreateIndexesOperation { + return new CreateIndexesOperation(parent, collectionName, indexes, options); + } + + static fromIndexSpecification( + parent: OperationParent, + collectionName: string, + indexSpec: IndexSpecification, + options?: CreateIndexesOptions + ): CreateIndexesOperation { + return new CreateIndexesOperation( + parent, + collectionName, + [makeIndexSpec(indexSpec, options)], + options + ); + } + override get commandName() { return 'createIndexes'; } - override async execute(server: Server, session: ClientSession | undefined): Promise { + override async execute(server: Server, session: ClientSession | undefined): Promise { const options = this.options; const indexes = this.indexes; @@ -266,61 +280,7 @@ export class CreateIndexesOperation< await super.executeCommand(server, session, cmd); const indexNames = indexes.map(index => index.name || ''); - return indexNames as T; - } -} - -/** @internal */ -export class CreateIndexOperation extends CreateIndexesOperation { - constructor( - parent: OperationParent, - collectionName: string, - indexSpec: IndexSpecification, - options?: CreateIndexesOptions - ) { - super(parent, collectionName, [makeIndexSpec(indexSpec, options)], options); - } - - override async execute(server: Server, session: ClientSession | undefined): Promise { - const indexNames = await super.execute(server, session); - return indexNames[0]; - } -} - -/** @internal */ -export class EnsureIndexOperation extends CreateIndexOperation { - db: Db; - - constructor( - db: Db, - collectionName: string, - indexSpec: IndexSpecification, - options?: CreateIndexesOptions - ) { - super(db, collectionName, indexSpec, options); - - this.readPreference = ReadPreference.primary; - this.db = db; - this.collectionName = collectionName; - } - - override get commandName() { - return 'listIndexes'; - } - - override async execute(server: Server, session: ClientSession | undefined): Promise { - const indexName = this.indexes[0].name; - 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); + return indexNames; } } @@ -352,10 +312,7 @@ export class DropIndexOperation extends CommandOperation { } /** @public */ -export interface ListIndexesOptions extends Omit { - /** The batchSize for the returned command cursor or if pre 2.8 the systems batch collection */ - batchSize?: number; -} +export type ListIndexesOptions = AbstractCursorOptions; /** @internal */ export class ListIndexesOperation extends CommandOperation { @@ -398,78 +355,10 @@ export class ListIndexesOperation extends CommandOperation { } } -/** @internal */ -export class IndexExistsOperation extends AbstractOperation { - override options: IndexInformationOptions; - collection: Collection; - indexes: string | string[]; - - constructor( - collection: Collection, - indexes: string | string[], - options: IndexInformationOptions - ) { - super(options); - this.options = options; - this.collection = collection; - this.indexes = indexes; - } - - override get commandName() { - return 'listIndexes' as const; - } - - override async execute(server: Server, session: ClientSession | undefined): Promise { - const coll = this.collection; - const indexes = this.indexes; - - 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 AbstractOperation { - override options: IndexInformationOptions; - db: Db; - name: string; - - constructor(db: Db, name: string, options?: IndexInformationOptions) { - super(options); - this.options = options ?? {}; - this.db = db; - this.name = name; - } - - override get commandName() { - return 'listIndexes' as const; - } - - override async execute(server: Server, session: ClientSession | undefined): Promise { - const db = this.db; - const name = this.name; - - return indexInformation(db, name, { - ...this.options, - readPreference: this.readPreference, - session - }); - } -} - defineAspects(ListIndexesOperation, [ Aspect.READ_OPERATION, Aspect.RETRYABLE, Aspect.CURSOR_CREATING ]); defineAspects(CreateIndexesOperation, [Aspect.WRITE_OPERATION]); -defineAspects(CreateIndexOperation, [Aspect.WRITE_OPERATION]); -defineAspects(EnsureIndexOperation, [Aspect.WRITE_OPERATION]); defineAspects(DropIndexOperation, [Aspect.WRITE_OPERATION]); diff --git a/src/operations/insert.ts b/src/operations/insert.ts index 9b3ba4b24b..0246590ff3 100644 --- a/src/operations/insert.ts +++ b/src/operations/insert.ts @@ -6,10 +6,10 @@ import type { InferIdType } from '../mongo_types'; import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; import type { MongoDBNamespace } from '../utils'; +import { maybeAddIdToDocuments } from '../utils'; import { WriteConcern } from '../write_concern'; import { BulkWriteOperation } from './bulk_write'; import { CommandOperation, type CommandOperationOptions } from './command'; -import { maybeAddIdToDocuments } from './common_functions'; import { AbstractOperation, Aspect, defineAspects } from './operation'; /** @internal */ diff --git a/src/utils.ts b/src/utils.ts index 8b2faf7d3b..3e158d42fd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1284,3 +1284,38 @@ export async function once(ee: EventEmitter, name: string): Promise { throw error; } } + +export function maybeAddIdToDocuments( + coll: Collection, + docs: Document[], + options: { forceServerObjectId?: boolean } +): Document[]; +export function maybeAddIdToDocuments( + coll: Collection, + docs: Document, + options: { forceServerObjectId?: boolean } +): Document; +export function maybeAddIdToDocuments( + coll: Collection, + docOrDocs: Document[] | Document, + options: { forceServerObjectId?: boolean } +): Document[] | Document { + const forceServerObjectId = + typeof options.forceServerObjectId === 'boolean' + ? options.forceServerObjectId + : coll.s.db.options?.forceServerObjectId; + + // no need to modify the docs if server sets the ObjectId + if (forceServerObjectId === true) { + return docOrDocs; + } + + const transform = (doc: Document): Document => { + if (doc._id == null) { + doc._id = coll.s.pkFactory.createPk(); + } + + return doc; + }; + return Array.isArray(docOrDocs) ? docOrDocs.map(transform) : transform(docOrDocs); +} diff --git a/test/integration/collection-management/collection.test.ts b/test/integration/collection-management/collection.test.ts index 5d645c46aa..a4fa1d0367 100644 --- a/test/integration/collection-management/collection.test.ts +++ b/test/integration/collection-management/collection.test.ts @@ -389,20 +389,10 @@ describe('Collection', function () { ); }); - it('should support createIndex with no options', function (done) { - db.createCollection('create_index_without_options', {}, (err, collection) => { - collection.createIndex({ createdAt: 1 }, err => { - expect(err).to.not.exist; - - collection.indexInformation({ full: true }, (err, indexes) => { - expect(err).to.not.exist; - const indexNames = indexes.map(i => i.name); - expect(indexNames).to.include('createdAt_1'); - - done(); - }); - }); - }); + it('should support createIndex with no options', async function () { + const collection = await db.createCollection('create_index_without_options', {}); + await collection.createIndex({ createdAt: 1 }); + expect(await collection.indexExists('createdAt_1')).to.be.true; }); }); diff --git a/test/integration/crud/abstract_operation.test.ts b/test/integration/crud/abstract_operation.test.ts index 8134a7d437..7f408ce497 100644 --- a/test/integration/crud/abstract_operation.test.ts +++ b/test/integration/crud/abstract_operation.test.ts @@ -19,10 +19,8 @@ describe('abstract operation', async function () { 'OptionsOperation', 'IsCappedOperation', 'BulkWriteOperation', - 'IndexExistsOperation', 'IndexOperation', - 'CollectionsOperation', - 'IndexInformationOperation' + 'CollectionsOperation' ]; const sameServerOnlyOperationSubclasses = ['GetMoreOperation', 'KillCursorsOperation']; @@ -141,28 +139,12 @@ describe('abstract operation', async function () { subclassType: mongodb.GetMoreOperation, correctCommandName: 'getMore' }, - { - subclassCreator: () => new mongodb.IndexesOperation(collection, {}), - subclassType: mongodb.IndexesOperation, - correctCommandName: 'listIndexes' - }, - { - subclassCreator: () => new mongodb.CreateIndexesOperation(db, 'bar', [{ key: { a: 1 } }]), - subclassType: mongodb.CreateIndexesOperation, - correctCommandName: 'createIndexes' - }, { subclassCreator: () => - new mongodb.CreateIndexOperation(db, 'collectionName', 'indexDescription'), - subclassType: mongodb.CreateIndexOperation, + mongodb.CreateIndexesOperation.fromIndexDescriptionArray(db, 'bar', [{ key: { a: 1 } }]), + subclassType: mongodb.CreateIndexesOperation, correctCommandName: 'createIndexes' }, - { - subclassCreator: () => - new mongodb.EnsureIndexOperation(db, 'collectionName', 'indexDescription'), - subclassType: mongodb.EnsureIndexOperation, - correctCommandName: 'listIndexes' - }, { subclassCreator: () => new mongodb.DropIndexOperation(collection, 'a', {}), subclassType: mongodb.DropIndexOperation, @@ -173,16 +155,6 @@ describe('abstract operation', async function () { subclassType: mongodb.ListIndexesOperation, correctCommandName: 'listIndexes' }, - { - subclassCreator: () => new mongodb.IndexExistsOperation(collection, 'a', {}), - subclassType: mongodb.IndexExistsOperation, - correctCommandName: 'listIndexes' - }, - { - subclassCreator: () => new mongodb.IndexInformationOperation(db, 'a', {}), - subclassType: mongodb.IndexInformationOperation, - correctCommandName: 'listIndexes' - }, { subclassCreator: () => new mongodb.InsertOperation(collection.fullNamespace, [{ a: 1 }], {}), diff --git a/test/integration/index_management.test.js b/test/integration/index_management.test.js deleted file mode 100644 index 188452f11e..0000000000 --- a/test/integration/index_management.test.js +++ /dev/null @@ -1,1416 +0,0 @@ -'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: { - topology: ['single'] - } - }, - - test: function (done) { - var configuration = this.configuration; - const client = configuration.newClient({ maxPoolSize: 5 }); - // Create an index - client - .db(configuration.db) - .createIndex('promiseCollectionCollections1', { a: 1 }) - .then(function (r) { - test.ok(r != null); - - client.close(done); - }); - } - }); - - it('Should correctly execute ensureIndex using Promise', { - metadata: { - requires: { - topology: ['single'] - } - }, - - test: function (done) { - var configuration = this.configuration; - const client = configuration.newClient({ maxPoolSize: 5 }); - - // Create an index - client - .db(configuration.db) - .createIndex('promiseCollectionCollections2', { a: 1 }) - .then(function (r) { - test.ok(r != null); - - client.close(done); - }); - } - }); - - it('shouldCorrectlyExtractIndexInformation', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection('test_index_information', function (err, collection) { - collection.insertMany([{ 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) { - expect(err).to.not.exist; - test.equal('a_1', indexName); - - // Let's fetch the index information - db.indexInformation(collection.collectionName, function (err, collectionInfo) { - expect(err).to.not.exist; - test.ok(collectionInfo['_id_'] != null); - test.equal('_id', collectionInfo['_id_'][0][0]); - test.ok(collectionInfo['a_1'] != null); - test.deepEqual([['a', 1]], collectionInfo['a_1']); - - db.indexInformation(collection.collectionName, function (err, collectionInfo2) { - var count1 = Object.keys(collectionInfo).length, - count2 = Object.keys(collectionInfo2).length; - - // Tests - test.ok(count2 >= count1); - test.ok(collectionInfo2['_id_'] != null); - test.equal('_id', collectionInfo2['_id_'][0][0]); - test.ok(collectionInfo2['a_1'] != null); - test.deepEqual([['a', 1]], collectionInfo2['a_1']); - test.ok(collectionInfo[indexName] != null); - test.deepEqual([['a', 1]], collectionInfo[indexName]); - - // Let's close the db - client.close(done); - }); - }); - } - ); - }); - }); - } - }); - - it('shouldCorrectlyHandleMultipleColumnIndexes', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection('test_multiple_index_cols', function (err, collection) { - collection.insert({ a: 1 }, function (err) { - expect(err).to.not.exist; - // Create an index on the collection - db.createIndex( - collection.collectionName, - [ - ['a', -1], - ['b', 1], - ['c', -1] - ], - configuration.writeConcernMax(), - function (err, indexName) { - expect(err).to.not.exist; - test.equal('a_-1_b_1_c_-1', indexName); - // Let's fetch the index information - db.indexInformation(collection.collectionName, function (err, collectionInfo) { - var count1 = Object.keys(collectionInfo).length; - - // Test - test.equal(2, count1); - test.ok(collectionInfo[indexName] != null); - test.deepEqual( - [ - ['a', -1], - ['b', 1], - ['c', -1] - ], - collectionInfo[indexName] - ); - - // Let's close the db - client.close(done); - }); - } - ); - }); - }); - } - }); - - it('shouldCorrectlyHandleUniqueIndex', { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { requires: { topology: 'single' } }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - // Create a non-unique index and test inserts - db.createCollection('test_unique_index', function (err, collection) { - db.createIndex( - collection.collectionName, - 'hello', - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - // Insert some docs - collection.insert( - [{ hello: 'world' }, { hello: 'mike' }, { hello: 'world' }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - - // Create a unique index and test that insert fails - db.createCollection('test_unique_index2', function (err, collection) { - db.createIndex( - collection.collectionName, - 'hello', - { unique: true, writeConcern: { w: 1 } }, - function (err) { - expect(err).to.not.exist; - // Insert some docs - collection.insert( - [{ hello: 'world' }, { hello: 'mike' }, { hello: 'world' }], - configuration.writeConcernMax(), - function (err) { - test.ok(err != null); - test.equal(11000, err.code); - client.close(done); - } - ); - } - ); - }); - } - ); - } - ); - }); - } - }); - - it('shouldCorrectlyCreateSubfieldIndex', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - // Create a non-unique index and test inserts - db.createCollection('test_index_on_subfield', function (err, collection) { - collection.insert( - [{ hello: { a: 4, b: 5 } }, { hello: { a: 7, b: 2 } }, { hello: { a: 4, b: 10 } }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - - // Create a unique subfield index and test that insert fails - db.createCollection('test_index_on_subfield2', function (err, collection) { - db.createIndex( - collection.collectionName, - 'hello_a', - { writeConcern: { w: 1 }, unique: true }, - function (err) { - expect(err).to.not.exist; - - collection.insert( - [ - { hello: { a: 4, b: 5 } }, - { hello: { a: 7, b: 2 } }, - { hello: { a: 4, b: 10 } } - ], - configuration.writeConcernMax(), - function (err) { - // Assert that we have erros - test.ok(err != null); - client.close(done); - } - ); - } - ); - }); - } - ); - }); - } - }); - - context('when dropIndexes succeeds', 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'); - }); - - 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', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection('test_distinct_queries', function (err, collection) { - collection.insert( - [ - { a: 0, b: { c: 'a' } }, - { a: 1, b: { c: 'b' } }, - { a: 1, b: { c: 'c' } }, - { a: 2, b: { c: 'a' } }, - { a: 3 }, - { a: 3 } - ], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - collection.distinct('a', function (err, docs) { - test.deepEqual([0, 1, 2, 3], docs.sort()); - - collection.distinct('b.c', function (err, docs) { - test.deepEqual(['a', 'b', 'c'], docs.sort()); - client.close(done); - }); - }); - } - ); - }); - } - }); - - it('shouldCorrectlyExecuteEnsureIndex', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection('test_ensure_index', function (err, collection) { - expect(err).to.not.exist; - // Create an index on the collection - db.createIndex( - collection.collectionName, - 'a', - configuration.writeConcernMax(), - function (err, indexName) { - expect(err).to.not.exist; - test.equal('a_1', indexName); - // Let's fetch the index information - db.indexInformation(collection.collectionName, function (err, collectionInfo) { - test.ok(collectionInfo['_id_'] != null); - test.equal('_id', collectionInfo['_id_'][0][0]); - test.ok(collectionInfo['a_1'] != null); - test.deepEqual([['a', 1]], collectionInfo['a_1']); - - db.createIndex( - collection.collectionName, - 'a', - configuration.writeConcernMax(), - function (err, indexName) { - test.equal('a_1', indexName); - // Let's fetch the index information - db.indexInformation(collection.collectionName, function (err, collectionInfo) { - test.ok(collectionInfo['_id_'] != null); - test.equal('_id', collectionInfo['_id_'][0][0]); - test.ok(collectionInfo['a_1'] != null); - test.deepEqual([['a', 1]], collectionInfo['a_1']); - // Let's close the db - client.close(done); - }); - } - ); - }); - } - ); - }); - } - }); - - it('shouldCorrectlyCreateAndUseSparseIndex', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection('create_and_use_sparse_index_test', function (err) { - expect(err).to.not.exist; - const collection = db.collection('create_and_use_sparse_index_test'); - collection.createIndex( - { title: 1 }, - { sparse: true, writeConcern: { w: 1 } }, - function (err) { - expect(err).to.not.exist; - collection.insert( - [{ name: 'Jim' }, { name: 'Sarah', title: 'Princess' }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - collection - .find({ title: { $ne: null } }) - .sort({ title: 1 }) - .toArray(function (err, items) { - test.equal(1, items.length); - test.equal('Sarah', items[0].name); - - // Fetch the info for the indexes - collection.indexInformation({ full: true }, function (err, indexInfo) { - expect(err).to.not.exist; - test.equal(2, indexInfo.length); - client.close(done); - }); - }); - } - ); - } - ); - }); - } - }); - - it('shouldCorrectlyHandleGeospatialIndexes', { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { - requires: { - mongodb: '>2.6.0', - topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection('geospatial_index_test', function (err) { - expect(err).to.not.exist; - const collection = db.collection('geospatial_index_test'); - collection.createIndex({ loc: '2d' }, configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - collection.insert({ loc: [-100, 100] }, configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - - collection.insert({ loc: [200, 200] }, configuration.writeConcernMax(), function (err) { - test.ok(err.errmsg.indexOf('point not in interval of') !== -1); - test.ok(err.errmsg.indexOf('-180') !== -1); - test.ok(err.errmsg.indexOf('180') !== -1); - client.close(done); - }); - }); - }); - }); - } - }); - - it('shouldCorrectlyHandleGeospatialIndexesAlteredRange', { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { - requires: { - mongodb: '>2.6.0', - topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection('geospatial_index_altered_test', function (err) { - expect(err).to.not.exist; - const collection = db.collection('geospatial_index_altered_test'); - collection.createIndex( - { loc: '2d' }, - { min: 0, max: 1024, writeConcern: { w: 1 } }, - function (err) { - expect(err).to.not.exist; - collection.insert({ loc: [100, 100] }, configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - collection.insert( - { loc: [200, 200] }, - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - collection.insert( - { loc: [-200, -200] }, - configuration.writeConcernMax(), - function (err) { - test.ok(err.errmsg.indexOf('point not in interval of') !== -1); - test.ok(err.errmsg.indexOf('0') !== -1); - test.ok(err.errmsg.indexOf('1024') !== -1); - client.close(done); - } - ); - } - ); - }); - } - ); - }); - } - }); - - it('shouldThrowDuplicateKeyErrorWhenCreatingIndex', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection( - 'shouldThrowDuplicateKeyErrorWhenCreatingIndex', - function (err, collection) { - collection.insert([{ a: 1 }, { a: 1 }], configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - - collection.createIndex( - { a: 1 }, - { unique: true, writeConcern: { w: 1 } }, - function (err) { - test.ok(err != null); - client.close(done); - } - ); - }); - } - ); - } - }); - - it('shouldThrowDuplicateKeyErrorWhenDriverInStrictMode', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.createCollection( - 'shouldThrowDuplicateKeyErrorWhenDriverInStrictMode', - function (err, collection) { - collection.insert([{ a: 1 }, { a: 1 }], configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - - collection.createIndex( - { a: 1 }, - { unique: true, writeConcern: { w: 1 } }, - function (err) { - test.ok(err != null); - client.close(done); - } - ); - }); - } - ); - } - }); - - it('shouldCorrectlyUseMinMaxForSettingRangeInEnsureIndex', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - // Establish connection to db - db.createCollection( - 'shouldCorrectlyUseMinMaxForSettingRangeInEnsureIndex', - function (err, collection) { - expect(err).to.not.exist; - - collection.createIndex( - { loc: '2d' }, - { min: 200, max: 1400, writeConcern: { w: 1 } }, - function (err) { - expect(err).to.not.exist; - - collection.insert( - { loc: [600, 600] }, - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - client.close(done); - } - ); - } - ); - } - ); - } - }); - - it('Should correctly create an index with overriden name', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - // Establish connection to db - db.createCollection( - 'shouldCorrectlyCreateAnIndexWithOverridenName', - function (err, collection) { - expect(err).to.not.exist; - - collection.createIndex('name', { name: 'myfunky_name' }, function (err) { - expect(err).to.not.exist; - - // Fetch full index information - collection.indexInformation({ full: false }, function (err, indexInformation) { - test.ok(indexInformation['myfunky_name'] != null); - client.close(done); - }); - }); - } - ); - } - }); - - it('should handle index declarations using objects from other contexts', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - - db.collection('indexcontext').createIndex( - shared.object, - { background: true }, - function (err) { - expect(err).to.not.exist; - db.collection('indexcontext').createIndex( - shared.array, - { background: true }, - function (err) { - expect(err).to.not.exist; - client.close(done); - } - ); - } - ); - } - }); - - it('should correctly return error message when applying unique index to duplicate documents', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('should_throw_error_due_to_duplicates'); - collection.insert( - [{ a: 1 }, { a: 1 }, { a: 1 }], - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - - collection.createIndex( - { a: 1 }, - { writeConcern: { w: 1 }, unique: true }, - function (err) { - test.ok(err != null); - client.close(done); - } - ); - } - ); - } - }); - - it('should correctly drop index with no callback', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('should_correctly_drop_index'); - collection.insert([{ a: 1 }], configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - - collection.createIndex({ a: 1 }, configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - collection - .dropIndex('a_1') - .then(() => { - client.close(done); - }) - .catch(err => { - client.close(); - done(err); - }); - }); - }); - } - }); - - it('should correctly apply hint to find', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('should_correctly_apply_hint'); - collection.insert([{ a: 1 }], configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - - collection.createIndex({ a: 1 }, configuration.writeConcernMax(), function (err) { - expect(err).to.not.exist; - - collection.indexInformation({ full: false }, function (err) { - expect(err).to.not.exist; - - collection.find({}, { hint: 'a_1' }).toArray(function (err, docs) { - expect(err).to.not.exist; - test.equal(1, docs[0].a); - client.close(done); - }); - }); - }); - }); - } - }); - - it('should correctly set language_override option', { - metadata: { - requires: { - mongodb: '>=2.6.0', - topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('should_correctly_set_language_override'); - collection.insert( - [{ text: 'Lorem ipsum dolor sit amet.', langua: 'italian' }], - function (err) { - expect(err).to.not.exist; - - collection.createIndex( - { text: 'text' }, - { language_override: 'langua', name: 'language_override_index' }, - function (err) { - expect(err).to.not.exist; - - collection.indexInformation({ full: true }, function (err, indexInformation) { - expect(err).to.not.exist; - for (var i = 0; i < indexInformation.length; i++) { - if (indexInformation[i].name === 'language_override_index') - test.equal(indexInformation[i].language_override, 'langua'); - } - - client.close(done); - }); - } - ); - } - ); - } - }); - - it('should correctly use listIndexes to retrieve index list', { - metadata: { - requires: { mongodb: '>=2.4.0', topology: ['single', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.collection('testListIndexes').createIndex({ a: 1 }, function (err) { - expect(err).to.not.exist; - - // Get the list of indexes - db.collection('testListIndexes') - .listIndexes() - .toArray(function (err, indexes) { - expect(err).to.not.exist; - test.equal(2, indexes.length); - - client.close(done); - }); - }); - } - }); - - it('should correctly use listIndexes to retrieve index list using hasNext', { - metadata: { - requires: { mongodb: '>=2.4.0', topology: ['single', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.collection('testListIndexes_2').createIndex({ a: 1 }, function (err) { - expect(err).to.not.exist; - - // Get the list of indexes - db.collection('testListIndexes_2') - .listIndexes() - .hasNext(function (err, result) { - expect(err).to.not.exist; - test.equal(true, result); - - client.close(done); - }); - }); - } - }); - - it('should correctly ensureIndex for nested style index name c.d', { - metadata: { - requires: { mongodb: '>=2.4.0', topology: ['single', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.collection('ensureIndexWithNestedStyleIndex').createIndex({ 'c.d': 1 }, function (err) { - expect(err).to.not.exist; - - // Get the list of indexes - db.collection('ensureIndexWithNestedStyleIndex') - .listIndexes() - .toArray(function (err, indexes) { - expect(err).to.not.exist; - test.equal(2, indexes.length); - - client.close(done); - }); - }); - } - }); - - it('should correctly execute createIndexes with multiple indexes', { - metadata: { requires: { mongodb: '>=2.6.0', topology: ['single'] } }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.collection('createIndexes').createIndexes( - [{ key: { a: 1 } }, { key: { b: 1 }, name: 'hello1' }], - function (err, r) { - expect(err).to.not.exist; - expect(r).to.deep.equal(['a_1', 'hello1']); - - db.collection('createIndexes') - .listIndexes() - .toArray(function (err, docs) { - expect(err).to.not.exist; - var keys = {}; - - for (var i = 0; i < docs.length; i++) { - keys[docs[i].name] = true; - } - - test.ok(keys['a_1']); - test.ok(keys['hello1']); - - client.close(done); - }); - } - ); - } - }); - - it('should correctly execute createIndexes with one index', { - metadata: { requires: { mongodb: '>=2.6.0', topology: ['single'] } }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.collection('createIndexes').createIndexes([{ key: { a: 1 } }], function (err, r) { - expect(err).to.not.exist; - expect(r).to.deep.equal(['a_1']); - - db.collection('createIndexes') - .listIndexes() - .toArray(function (err, docs) { - expect(err).to.not.exist; - var keys = {}; - - for (var i = 0; i < docs.length; i++) { - keys[docs[i].name] = true; - } - - test.ok(keys['a_1']); - test.ok(keys['hello1']); - - client.close(done); - }); - }); - } - }); - - it('shouldCorrectlyCreateTextIndex', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - db.collection('text_index').createIndex( - { '$**': 'text' }, - { name: 'TextIndex' }, - function (err, r) { - expect(err).to.not.exist; - test.equal('TextIndex', r); - // Let's close the db - client.close(done); - } - ); - } - }); - - it('should correctly pass partialIndexes through to createIndexCommand', { - metadata: { - requires: { - topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'], - mongodb: '>=3.1.8' - } - }, - - test: function (done) { - var configuration = this.configuration; - var started = []; - var succeeded = []; - var client = configuration.newClient(configuration.writeConcernMax(), { - maxPoolSize: 1, - monitorCommands: true - }); - - client.on('commandStarted', function (event) { - if (event.commandName === 'createIndexes') started.push(event); - }); - - client.on('commandSucceeded', function (event) { - if (event.commandName === 'createIndexes') succeeded.push(event); - }); - - var db = client.db(configuration.db); - - db.collection('partialIndexes').createIndex( - { a: 1 }, - { partialFilterExpression: { a: 1 } }, - function (err) { - expect(err).to.not.exist; - test.deepEqual({ a: 1 }, started[0].command.indexes[0].partialFilterExpression); - client.close(done); - } - ); - } - }); - - it('should not retry partial index expression error', { - metadata: { - requires: { - topology: ['single', 'replicaset', 'sharded'], - mongodb: '>=3.1.8' - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - // Can't use $exists: false in partial filter expression, see - // https://jira.mongodb.org/browse/SERVER-17853 - var opts = { partialFilterExpression: { a: { $exists: false } } }; - db.collection('partialIndexes').createIndex({ a: 1 }, opts, function (err) { - test.ok(err); - test.equal(err.code, 67); - var msg = "key $exists must not start with '$'"; - test.ok(err.toString().indexOf(msg) === -1); - - client.close(done); - }); - } - }); - - it('should correctly create index on embedded key', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('embedded_key_indes'); - - collection.insertMany( - [ - { - a: { a: 1 } - }, - { - a: { a: 2 } - } - ], - function (err) { - expect(err).to.not.exist; - - collection.createIndex({ 'a.a': 1 }, function (err) { - expect(err).to.not.exist; - client.close(done); - }); - } - ); - } - }); - - it('should correctly create index using . keys', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('embedded_key_indes_1'); - collection.createIndex( - { 'key.external_id': 1, 'key.type': 1 }, - { unique: true, sparse: true, name: 'indexname' }, - function (err) { - expect(err).to.not.exist; - - client.close(done); - } - ); - } - }); - - it('error on duplicate key index', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('embedded_key_indes_2'); - collection.insertMany( - [ - { - key: { external_id: 1, type: 1 } - }, - { - key: { external_id: 1, type: 1 } - } - ], - function (err) { - expect(err).to.not.exist; - collection.createIndex( - { 'key.external_id': 1, 'key.type': 1 }, - { unique: true, sparse: true, name: 'indexname' }, - function (err) { - test.equal(11000, err.code); - - client.close(done); - } - ); - } - ); - } - }); - - it('should correctly create Index with sub element', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - // insert a doc - db.collection('messed_up_index').createIndex( - { temporary: 1, 'store.addressLines': 1, lifecycleStatus: 1 }, - configuration.writeConcernMax(), - function (err) { - expect(err).to.not.exist; - - client.close(done); - } - ); - } - }); - - it('should correctly fail detect error code 85 when performing createIndex', { - metadata: { - requires: { - topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'], - mongodb: '>=3.0.0 <=4.8.0' - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('messed_up_options'); - - collection.createIndex( - { 'a.one': 1, 'a.two': 1 }, - { name: 'n1', sparse: false }, - function (err) { - expect(err).to.not.exist; - - collection.createIndex( - { 'a.one': 1, 'a.two': 1 }, - { name: 'n2', sparse: true }, - function (err) { - test.ok(err); - test.equal(85, err.code); - - client.close(done); - } - ); - } - ); - } - }); - - it('should correctly fail by detecting error code 86 when performing createIndex', { - metadata: { - requires: { - topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'], - mongodb: '>=3.0.0' - } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - var collection = db.collection('messed_up_options'); - - collection.createIndex({ 'b.one': 1, 'b.two': 1 }, { name: 'test' }, function (err) { - expect(err).to.not.exist; - - collection.createIndex({ 'b.one': -1, 'b.two': -1 }, { name: 'test' }, function (err) { - test.ok(err); - test.equal(86, err.code); - - client.close(done); - }); - }); - } - }); - - it('should correctly create Index with sub element running in background', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - test: function (done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - var db = client.db(configuration.db); - // insert a doc - db.collection('messed_up_index_2').createIndex( - { 'accessControl.get': 1 }, - { background: true }, - function (err) { - expect(err).to.not.exist; - - client.close(done); - } - ); - } - }); - - context('commitQuorum', function () { - let client; - beforeEach(async function () { - client = this.configuration.newClient({ monitorCommands: true }); - }); - - afterEach(async function () { - await client.close(); - }); - - function throwErrorTest(testCommand) { - return { - metadata: { requires: { mongodb: '<4.4' } }, - test: function (done) { - const db = client.db('test'); - const collection = db.collection('commitQuorum'); - testCommand(db, collection, (err, result) => { - expect(err).to.exist; - expect(err.message).to.equal( - 'Option `commitQuorum` for `createIndexes` not supported on servers < 4.4' - ); - expect(result).to.not.exist; - done(); - }); - } - }; - } - it( - 'should throw an error if commitQuorum specified on db.createIndex', - throwErrorTest((db, collection, cb) => - db.createIndex(collection.collectionName, 'a', { commitQuorum: 'all' }, cb) - ) - ); - it( - 'should throw an error if commitQuorum specified on collection.createIndex', - throwErrorTest((db, collection, cb) => - collection.createIndex('a', { commitQuorum: 'all' }, cb) - ) - ); - it( - 'should throw an error if commitQuorum specified on collection.createIndexes', - throwErrorTest((db, collection, cb) => - collection.createIndexes( - [{ key: { a: 1 } }, { key: { b: 1 } }], - { commitQuorum: 'all' }, - cb - ) - ) - ); - - function commitQuorumTest(testCommand) { - return { - metadata: { requires: { mongodb: '>=4.4', topology: ['replicaset', 'sharded'] } }, - test: function (done) { - const events = []; - client.on('commandStarted', event => { - if (event.commandName === 'createIndexes') events.push(event); - }); - - const db = client.db('test'); - const collection = db.collection('commitQuorum'); - collection.insertOne({ a: 1 }, function (err) { - expect(err).to.not.exist; - testCommand(db, collection, err => { - expect(err).to.not.exist; - - expect(events).to.be.an('array').with.lengthOf(1); - expect(events[0]).nested.property('command.commitQuorum').to.equal(0); - collection.drop(err => { - expect(err).to.not.exist; - done(); - }); - }); - }); - } - }; - } - it( - 'should run command with commitQuorum if specified on db.createIndex', - commitQuorumTest((db, collection, cb) => - db.createIndex( - collection.collectionName, - 'a', - { writeConcern: { w: 'majority' }, commitQuorum: 0 }, - cb - ) - ) - ); - it( - 'should run command with commitQuorum if specified on collection.createIndex', - commitQuorumTest((db, collection, cb) => - collection.createIndex('a', { writeConcern: { w: 'majority' }, commitQuorum: 0 }, cb) - ) - ); - it( - 'should run command with commitQuorum if specified on collection.createIndexes', - commitQuorumTest((db, collection, cb) => - collection.createIndexes( - [{ key: { a: 1 } }], - { writeConcern: { w: 'majority' }, commitQuorum: 0 }, - cb - ) - ) - ); - }); - - it('should create index hidden', { - metadata: { requires: { mongodb: '>=4.4', topology: 'single' } }, - test: function (done) { - const configuration = this.configuration; - const client = configuration.newClient(configuration.writeConcernMax(), { maxPoolSize: 1 }); - const db = client.db(configuration.db); - db.createCollection('hidden_index_collection', (err, collection) => { - expect(err).to.not.exist; - collection.createIndex('a', { hidden: true }, (err, index) => { - expect(err).to.not.exist; - expect(index).to.equal('a_1'); - collection.listIndexes().toArray((err, indexes) => { - expect(err).to.not.exist; - expect(indexes).to.deep.equal([ - { v: 2, key: { _id: 1 }, name: '_id_' }, - { v: 2, key: { a: 1 }, name: 'a_1', hidden: true } - ]); - client.close(done); - }); - }); - }); - } - }); -}); diff --git a/test/integration/index_management.test.ts b/test/integration/index_management.test.ts new file mode 100644 index 0000000000..fab28b5890 --- /dev/null +++ b/test/integration/index_management.test.ts @@ -0,0 +1,743 @@ +import { expect } from 'chai'; + +import { + type Collection, + type CommandStartedEvent, + type CommandSucceededEvent, + type Db, + type MongoClient, + MongoServerError +} from '../mongodb'; +import { assert as test, setupDatabase } from './shared'; + +describe('Indexes', function () { + let client: MongoClient; + let db: Db; + let collection: Collection; + + before(function () { + return setupDatabase(this.configuration); + }); + + beforeEach(async function () { + client = this.configuration.newClient({}, { monitorCommands: true }); + db = client.db('indexes_test_db'); + collection = await db.createCollection('indexes_test_coll', { w: 1 }); + }); + + afterEach(async function () { + await db.dropDatabase({ writeConcern: { w: 1 } }).catch(() => null); + await client.close(); + }); + + it('Should correctly execute createIndex', async function () { + // Create an index + const response = await db.createIndex('promiseCollectionCollections1', { a: 1 }, { w: 1 }); + expect(response).to.exist; + }); + + it('shouldCorrectlyExtractIndexInformation', async function () { + const collection = await db.createCollection('test_index_information'); + await collection.insertMany([{ a: 1 }], this.configuration.writeConcernMax()); + + // Create an index on the collection + const indexName = await db.createIndex( + collection.collectionName, + 'a', + this.configuration.writeConcernMax() + ); + expect(indexName).to.equal('a_1'); + + // Let's fetch the index information + const collectionInfo = await db.indexInformation(collection.collectionName); + test.ok(collectionInfo['_id_'] != null); + test.equal('_id', collectionInfo['_id_'][0][0]); + test.ok(collectionInfo['a_1'] != null); + test.deepEqual([['a', 1]], collectionInfo['a_1']); + + const collectionInfo2 = await db.indexInformation(collection.collectionName); + const count1 = Object.keys(collectionInfo).length, + count2 = Object.keys(collectionInfo2).length; + + // Tests + test.ok(count2 >= count1); + test.ok(collectionInfo2['_id_'] != null); + test.equal('_id', collectionInfo2['_id_'][0][0]); + test.ok(collectionInfo2['a_1'] != null); + test.deepEqual([['a', 1]], collectionInfo2['a_1']); + test.ok(collectionInfo[indexName] != null); + test.deepEqual([['a', 1]], collectionInfo[indexName]); + }); + + it('shouldCorrectlyHandleMultipleColumnIndexes', async function () { + await collection.insertOne({ a: 1 }); + + const indexName = await db.createIndex( + collection.collectionName, + [ + ['a', -1], + ['b', 1], + ['c', -1] + ], + this.configuration.writeConcernMax() + ); + test.equal('a_-1_b_1_c_-1', indexName); + // Let's fetch the index information + const collectionInfo = await db.indexInformation(collection.collectionName); + const count1 = Object.keys(collectionInfo).length; + + // Test + test.equal(2, count1); + test.ok(collectionInfo[indexName] != null); + test.deepEqual( + [ + ['a', -1], + ['b', 1], + ['c', -1] + ], + collectionInfo[indexName] + ); + }); + + describe('Collection.indexes()', function () { + beforeEach(() => collection.createIndex({ age: 1 })); + afterEach(() => collection.dropIndexes()); + + context('when `full` is not provided', () => { + it('returns an array of indexes', async function () { + const indexes = await collection.indexes(); + expect(indexes).to.be.a('array'); + }); + }); + + context('when `full` is set to `true`', () => { + it('returns an array of indexes', async function () { + const indexes = await collection.indexes({ full: true }); + expect(indexes).to.be.a('array'); + }); + }); + + context('when `full` is set to `false`', () => { + it('returns a document mapping key to index definition', async function () { + const indexes = await collection.indexes({ full: false }); + expect(indexes).to.be.a('object'); + expect(indexes) + .to.have.property('age_1') + .to.deep.equal([['age', 1]]); + }); + }); + }); + + describe('Collection.indexInformation()', function () { + beforeEach(() => collection.createIndex({ age: 1 })); + afterEach(() => collection.dropIndexes()); + + context('when `full` is not provided', () => { + it('defaults to `false` and returns a document', async function () { + const indexes = await collection.indexInformation(); + expect(indexes).to.be.a('object'); + expect(indexes) + .to.have.property('age_1') + .to.deep.equal([['age', 1]]); + }); + }); + + context('when `full` is set to `true`', () => { + it('returns an array of indexes', async function () { + const indexes = await collection.indexInformation({ full: true }); + expect(indexes).to.be.a('array'); + }); + }); + + context('when `full` is set to `false`', () => { + it('returns a document mapping key to index definition', async function () { + const indexes = await collection.indexInformation({ full: false }); + expect(indexes).to.be.a('object'); + expect(indexes) + .to.have.property('age_1') + .to.deep.equal([['age', 1]]); + }); + }); + }); + + describe('Collection.indexExists()', function () { + beforeEach(() => collection.createIndex({ age: 1 })); + afterEach(() => collection.dropIndexes()); + + context('when provided a string index name', () => { + it('returns true when the index exists', async () => { + expect(await collection.indexExists('age_1')).to.be.true; + }); + + it('returns false when the index does not exist', async () => { + expect(await collection.indexExists('name_1')).to.be.false; + }); + }); + + context('when provided an array of index names', () => { + it('returns true when all indexes exists', async () => { + expect(await collection.indexExists(['age_1'])).to.be.true; + }); + + it('returns false when the none of the indexes exist', async () => { + expect(await collection.indexExists(['name_1'])).to.be.false; + }); + + it('returns false when only some of hte indexes exist', async () => { + expect(await collection.indexExists(['name_1', 'age_1'])).to.be.false; + }); + }); + }); + + it('shouldCorrectlyHandleUniqueIndex', async function () { + await db.createCollection('test_unique_index'); + await db.createIndex(collection.collectionName, 'hello', this.configuration.writeConcernMax()); + // Insert some docs + await collection.insertMany( + [{ hello: 'world' }, { hello: 'mike' }, { hello: 'world' }], + this.configuration.writeConcernMax() + ); + // Create a unique index and test that insert fails + { + const collection = await db.createCollection('test_unique_index2'); + await db.createIndex(collection.collectionName, 'hello', { + unique: true, + writeConcern: { w: 1 } + }); + // Insert some docs + const err = await collection + .insertMany( + [{ hello: 'world' }, { hello: 'mike' }, { hello: 'world' }], + this.configuration.writeConcernMax() + ) + .catch(e => e); + expect(err).to.be.instanceOf(Error).to.have.property('code', 11000); + } + }); + + it('shouldCorrectlyCreateSubfieldIndex', async function () { + await collection.insertMany( + [{ hello: { a: 4, b: 5 } }, { hello: { a: 7, b: 2 } }, { hello: { a: 4, b: 10 } }], + this.configuration.writeConcernMax() + ); + + // Create a unique subfield index and test that insert fails + const collection2 = await db.createCollection('test_index_on_subfield2'); + await collection2.createIndex('hello_a', { writeConcern: { w: 1 }, unique: true }); + const err = await collection2 + .insertMany( + [{ hello: { a: 4, b: 5 } }, { hello: { a: 7, b: 2 } }, { hello: { a: 4, b: 10 } }], + this.configuration.writeConcernMax() + ) + .catch(e => e); + expect(err).to.be.instanceOf(Error); + }); + + context('when dropIndexes succeeds', 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'); + }); + + 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 () { + beforeEach(async function () { + await collection.insertOne({ a: 1 }); + // Create an index on the collection + await collection.createIndex('a'); + await client + .db() + .admin() + .command({ + configureFailPoint: 'failCommand', + mode: { + times: 1 + }, + data: { + failCommands: ['dropIndexes'], + errorCode: 91 + } + }); + }); + + it( + 'should return false', + { + requires: { + mongodb: '>4.0' + } + }, + 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', async function () { + await collection.insertMany( + [ + { a: 0, b: { c: 'a' } }, + { a: 1, b: { c: 'b' } }, + { a: 1, b: { c: 'c' } }, + { a: 2, b: { c: 'a' } }, + { a: 3 }, + { a: 3 } + ], + this.configuration.writeConcernMax() + ); + { + const docs = await collection.distinct('a'); + expect(docs.sort()).to.deep.equal([0, 1, 2, 3]); + } + + { + const docs = await collection.distinct('b.c'); + expect(docs.sort()).to.deep.equal(['a', 'b', 'c']); + } + }); + + it('shouldCorrectlyCreateAndUseSparseIndex', async function () { + const db = client.db(this.configuration.db); + await db.createCollection('create_and_use_sparse_index_test'); + const collection = db.collection('create_and_use_sparse_index_test'); + await collection.createIndex({ title: 1 }, { sparse: true, writeConcern: { w: 1 } }); + await collection.insertMany( + [{ name: 'Jim' }, { name: 'Sarah', title: 'Princess' }], + this.configuration.writeConcernMax() + ); + const items = await collection + .find({ title: { $ne: null } }) + .sort({ title: 1 }) + .toArray(); + expect(items).to.have.lengthOf(1); + expect(items[0]).to.have.property('name', 'Sarah'); + + // Fetch the info for the indexes + const indexInfo = await collection.indexInformation({ full: true }); + expect(indexInfo).to.have.lengthOf(2); + }); + + it('shouldCorrectlyHandleGeospatialIndexes', async function () { + await collection.createIndex({ loc: '2d' }, this.configuration.writeConcernMax()); + await collection.insertOne({ loc: [-100, 100] }, this.configuration.writeConcernMax()); + + const err = await collection + .insertOne({ loc: [200, 200] }, this.configuration.writeConcernMax()) + .catch(e => e); + test.ok(err.errmsg.indexOf('point not in interval of') !== -1); + test.ok(err.errmsg.indexOf('-180') !== -1); + test.ok(err.errmsg.indexOf('180') !== -1); + }); + + it('shouldCorrectlyHandleGeospatialIndexesAlteredRange', async function () { + await collection.createIndex({ loc: '2d' }, { min: 0, max: 1024, writeConcern: { w: 1 } }); + await collection.insertOne({ loc: [100, 100] }, this.configuration.writeConcernMax()); + await collection.insertOne({ loc: [200, 200] }, this.configuration.writeConcernMax()); + const err = await collection + .insertOne({ loc: [-200, -200] }, this.configuration.writeConcernMax()) + .catch(e => e); + test.ok(err.errmsg.indexOf('point not in interval of') !== -1); + test.ok(err.errmsg.indexOf('0') !== -1); + test.ok(err.errmsg.indexOf('1024') !== -1); + }); + + it('shouldThrowDuplicateKeyErrorWhenCreatingIndex', async function () { + await collection.insertMany([{ a: 1 }, { a: 1 }], this.configuration.writeConcernMax()); + const err = await collection + .createIndex({ a: 1 }, { unique: true, writeConcern: { w: 1 } }) + .catch(e => e); + expect(err).to.exist; + }); + + it('shouldThrowDuplicateKeyErrorWhenDriverInStrictMode', async function () { + await collection.insertMany([{ a: 1 }, { a: 1 }], this.configuration.writeConcernMax()); + const err = await collection + .createIndex({ a: 1 }, { unique: true, writeConcern: { w: 1 } }) + .catch(e => e); + expect(err) + .to.be.instanceOf(MongoServerError) + .to.match(/duplicate key error/); + }); + + it('shouldCorrectlyUseMinMaxForSettingRangeInEnsureIndex', async function () { + await collection.createIndex({ loc: '2d' }, { min: 200, max: 1400, writeConcern: { w: 1 } }); + await collection.insertOne({ loc: [600, 600] }, this.configuration.writeConcernMax()); + }); + + it('Should correctly create an index with overriden name', async function () { + await collection.createIndex('name', { name: 'myfunky_name' }); + + // Fetch full index information + const indexInformation = await collection.indexInformation({ full: false }); + expect(indexInformation).to.have.property('myfunky_name'); + }); + + it('should correctly return error message when applying unique index to duplicate documents', async function () { + await collection.insertMany( + [{ a: 1 }, { a: 1 }, { a: 1 }], + this.configuration.writeConcernMax() + ); + + const err = await collection + .createIndex({ a: 1 }, { writeConcern: { w: 1 }, unique: true }) + .catch(e => e); + expect(err) + .to.be.instanceOf(MongoServerError) + .to.match(/duplicate key error/); + }); + + it('should correctly drop index with no callback', async function () { + await collection.insertMany([{ a: 1 }], this.configuration.writeConcernMax()); + + await collection.createIndex({ a: 1 }, { writeConcern: this.configuration.writeConcernMax() }); + await collection.dropIndex('a_1'); + }); + + it('should correctly apply hint to find', async function () { + await collection.insertMany([{ a: 1 }], this.configuration.writeConcernMax()); + + await collection.createIndex({ a: 1 }, { writeConcern: this.configuration.writeConcernMax() }); + await collection.indexInformation({ full: false }); + + const [doc] = await collection.find({}, { hint: 'a_1' }).toArray(); + expect(doc.a).to.equal(1); + }); + + it('should correctly set language_override option', async function () { + await collection.insertMany([{ text: 'Lorem ipsum dolor sit amet.', langua: 'italian' }]); + + await collection.createIndex( + { text: 'text' }, + { language_override: 'langua', name: 'language_override_index' } + ); + + const indexInformation = await collection.indexInformation({ full: true }); + for (let i = 0; i < indexInformation.length; i++) { + if (indexInformation[i].name === 'language_override_index') + test.equal(indexInformation[i].language_override, 'langua'); + } + }); + + it('should correctly use listIndexes to retrieve index list', async function () { + await db.collection('testListIndexes').createIndex({ a: 1 }); + + const indexes = await db.collection('testListIndexes').listIndexes().toArray(); + expect(indexes).to.have.lengthOf(2); + }); + + it('should correctly use listIndexes to retrieve index list using hasNext', async function () { + await db.collection('testListIndexes_2').createIndex({ a: 1 }); + + const result = await db.collection('testListIndexes_2').listIndexes().hasNext(); + expect(result).to.be.true; + }); + + it('should correctly ensureIndex for nested style index name c.d', async function () { + await db.collection('ensureIndexWithNestedStyleIndex').createIndex({ 'c.d': 1 }); + + // Get the list of indexes + const indexes = await db.collection('ensureIndexWithNestedStyleIndex').listIndexes().toArray(); + expect(indexes).to.have.lengthOf(2); + }); + + it('should correctly execute createIndexes with multiple indexes', async function () { + const r = await db + .collection('createIndexes') + .createIndexes([{ key: { a: 1 } }, { key: { b: 1 }, name: 'hello1' }]); + expect(r).to.deep.equal(['a_1', 'hello1']); + + const docs = await db.collection('createIndexes').listIndexes().toArray(); + const keys = {}; + + for (let i = 0; i < docs.length; i++) { + keys[docs[i].name] = true; + } + + test.ok(keys['a_1']); + test.ok(keys['hello1']); + }); + + it('should correctly execute createIndexes with one index', async function () { + const r = await db.collection('createIndexes').createIndexes([{ key: { a: 1 } }]); + expect(r).to.deep.equal(['a_1']); + + await collection.indexExists('a_1'); + }); + + it('shouldCorrectlyCreateTextIndex', async function () { + const r = await collection.createIndex({ '$**': 'text' }, { name: 'TextIndex' }); + expect(r).to.equal('TextIndex'); + }); + + it('should correctly pass partialIndexes through to createIndexCommand', async function () { + const configuration = this.configuration; + const started: Array = []; + const succeeded: Array = []; + + client.on('commandStarted', function (event) { + if (event.commandName === 'createIndexes') started.push(event); + }); + + client.on('commandSucceeded', function (event) { + if (event.commandName === 'createIndexes') succeeded.push(event); + }); + + const db = client.db(configuration.db); + + await db + .collection('partialIndexes') + .createIndex({ a: 1 }, { partialFilterExpression: { a: 1 } }); + expect(started[0].command.indexes[0].partialFilterExpression).to.deep.equal({ a: 1 }); + }); + + it('should not retry partial index expression error', async function () { + // Can't use $exists: false in partial filter expression, see + // https://jira.mongodb.org/browse/SERVER-17853 + const opts = { partialFilterExpression: { a: { $exists: false } } }; + const err = await db + .collection('partialIndexes') + .createIndex({ a: 1 }, opts) + .catch(e => e); + expect(err).to.be.instanceOf(Error).to.have.property('code', 67); + }); + + it('should correctly create index on embedded key', async function () { + await collection.insertMany([ + { + a: { a: 1 } + }, + { + a: { a: 2 } + } + ]); + + await collection.createIndex({ 'a.a': 1 }); + }); + + it('should correctly create index using . keys', async function () { + await collection.createIndex( + { 'key.external_id': 1, 'key.type': 1 }, + { unique: true, sparse: true, name: 'indexname' } + ); + }); + + it('error on duplicate key index', async function () { + await collection.insertMany([ + { + key: { external_id: 1, type: 1 } + }, + { + key: { external_id: 1, type: 1 } + } + ]); + const err = await collection + .createIndex( + { 'key.external_id': 1, 'key.type': 1 }, + { unique: true, sparse: true, name: 'indexname' } + ) + .catch(e => e); + + expect(err).to.be.instanceOf(Error).to.have.property('code', 11000); + }); + + it('should correctly create Index with sub element', async function () { + await collection.createIndex( + { temporary: 1, 'store.addressLines': 1, lifecycleStatus: 1 }, + this.configuration.writeConcernMax() + ); + }); + + it( + 'should correctly fail detect error code 85 when performing createIndex', + { + requires: { + mongodb: '<=4.8.0' + } + }, + async function () { + await collection.createIndex({ 'a.one': 1, 'a.two': 1 }, { name: 'n1', sparse: false }); + + const err = await collection + .createIndex({ 'a.one': 1, 'a.two': 1 }, { name: 'n2', sparse: true }) + .catch(e => e); + expect(err).to.be.instanceOf(Error).to.have.property('code', 85); + } + ); + + it('should correctly fail by detecting error code 86 when performing createIndex', async function () { + await collection.createIndex({ 'b.one': 1, 'b.two': 1 }, { name: 'test' }); + const err = await collection + .createIndex({ 'b.one': -1, 'b.two': -1 }, { name: 'test' }) + .catch(err => err); + + expect(err).to.be.instanceOf(Error).to.have.property('code', 86); + }); + + it('should correctly create Index with sub element running in background', async function () { + await collection.createIndex({ 'accessControl.get': 1 }, { background: true }); + }); + + context('commitQuorum', function () { + let client; + beforeEach(async function () { + client = this.configuration.newClient({ monitorCommands: true }); + }); + + afterEach(async function () { + await client.close(); + }); + + function throwErrorTest(testCommand: (db: Db, collection: Collection) => Promise) { + return { + metadata: { requires: { mongodb: '<4.4' } }, + test: async function () { + const db = client.db('test'); + const collection = db.collection('commitQuorum'); + const err = await testCommand(db, collection).catch(e => e); + expect(err.message).to.equal( + 'Option `commitQuorum` for `createIndexes` not supported on servers < 4.4' + ); + } + }; + } + it( + 'should throw an error if commitQuorum specified on db.createIndex', + throwErrorTest((db, collection) => + db.createIndex(collection.collectionName, 'a', { commitQuorum: 'all' }) + ) + ); + it( + 'should throw an error if commitQuorum specified on collection.createIndex', + throwErrorTest((db, collection) => collection.createIndex('a', { commitQuorum: 'all' })) + ); + it( + 'should throw an error if commitQuorum specified on collection.createIndexes', + throwErrorTest((db, collection) => + collection.createIndexes([{ key: { a: 1 } }, { key: { b: 1 } }], { commitQuorum: 'all' }) + ) + ); + + function commitQuorumTest( + testCommand: (db: Db, collection: Collection) => Promise + ): any { + return { + metadata: { requires: { mongodb: '>=4.4', topology: ['replicaset', 'sharded'] } }, + test: async function () { + const events: CommandStartedEvent[] = []; + client.on('commandStarted', event => { + if (event.commandName === 'createIndexes') events.push(event); + }); + + const db = client.db('test'); + const collection = db.collection('commitQuorum'); + await collection.insertOne({ a: 1 }); + await testCommand(db, collection); + + expect(events).to.be.an('array').with.lengthOf(1); + expect(events[0]).nested.property('command.commitQuorum').to.equal(0); + await collection.drop(err => { + expect(err).to.not.exist; + }); + } + }; + } + it( + 'should run command with commitQuorum if specified on db.createIndex', + commitQuorumTest((db, collection) => + db.createIndex(collection.collectionName, 'a', { + // @ts-expect-error revaluate this? + writeConcern: { w: 'majority' }, + commitQuorum: 0 + }) + ) + ); + it( + 'should run command with commitQuorum if specified on collection.createIndex', + commitQuorumTest((db, collection) => + // @ts-expect-error revaluate this? + collection.createIndex('a', { writeConcern: { w: 'majority' }, commitQuorum: 0 }) + ) + ); + it( + 'should run command with commitQuorum if specified on collection.createIndexes', + commitQuorumTest((db, collection) => + collection.createIndexes([{ key: { a: 1 } }], { + // @ts-expect-error revaluate this? + writeConcern: { w: 'majority' }, + commitQuorum: 0 + }) + ) + ); + }); + + it( + 'should create index hidden', + { + requires: { mongodb: '>=4.4', topology: 'single' } + }, + async function () { + const collection = await db.createCollection('hidden_index_collection'); + const indexName = await collection.createIndex('a', { hidden: true }); + expect(indexName).to.equal('a_1'); + const indexes = await collection.listIndexes().toArray(); + expect(indexes).to.deep.equal([ + { v: 2, key: { _id: 1 }, name: '_id_' }, + { v: 2, key: { a: 1 }, name: 'a_1', hidden: true } + ]); + } + ); +}); diff --git a/test/mongodb.ts b/test/mongodb.ts index 18986610e5..d2cbd3e16c 100644 --- a/test/mongodb.ts +++ b/test/mongodb.ts @@ -155,7 +155,6 @@ export * from '../src/operations/aggregate'; export * from '../src/operations/bulk_write'; export * from '../src/operations/collections'; export * from '../src/operations/command'; -export * from '../src/operations/common_functions'; export * from '../src/operations/count'; export * from '../src/operations/count_documents'; export * from '../src/operations/create_collection'; diff --git a/test/unit/operations/indexes.test.ts b/test/unit/operations/indexes.test.ts index deded3793b..8083cb5a6c 100644 --- a/test/unit/operations/indexes.test.ts +++ b/test/unit/operations/indexes.test.ts @@ -1,13 +1,13 @@ import { expect } from 'chai'; import { + CreateIndexesOperation, type CreateIndexesOptions, - CreateIndexOperation, type IndexDirection, ns } from '../../mongodb'; -describe('class CreateIndexOperation', () => { +describe('class CreateIndexesOperation', () => { const testCases = [ { description: 'single string', @@ -101,7 +101,12 @@ describe('class CreateIndexOperation', () => { ]; const makeIndexOperation = (input, options: CreateIndexesOptions = {}) => - new CreateIndexOperation({ s: { namespace: ns('a.b') } }, 'b', input, options); + CreateIndexesOperation.fromIndexSpecification( + { s: { namespace: ns('a.b') } }, + 'b', + input, + options + ); describe('#constructor()', () => { for (const { description, input, mapData, name } of testCases) {