Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
import {
CreateIndexesOperation,
type CreateIndexesOptions,
type CreateIndexesCommandOptions,
type DropIndexesOptions,
DropIndexOperation,
type IndexDescription,
Expand Down Expand Up @@ -609,7 +610,7 @@ export class Collection<TSchema extends Document = Document> {
* Creates an index on the db and collection collection.
*
* @param indexSpec - The field name or index specification to create an index for
* @param options - Optional settings for the command
* @param indexOptions - Optional settings for the command
*
* @example
* ```ts
Expand All @@ -635,16 +636,32 @@ export class Collection<TSchema extends Document = Document> {
*/
async createIndex(
indexSpec: IndexSpecification,
options?: CreateIndexesOptions
options?: CreateIndexesOptions,
commandOptions?: CreateIndexesCommandOptions
): Promise<string> {
const indexes = await executeOperation(
this.client,
CreateIndexesOperation.fromIndexSpecification(
let operation;
if (commandOptions) {
// v2 path: index and command options are separated
let indexOptions = options;
operation = CreateIndexesOperation.fromIndexSpecification(
this,
this.collectionName,
indexSpec,
indexOptions,
resolveOptions(this, commandOptions)
);
} else {
// v1 path: index and command options are merged in the indexOptions
operation = CreateIndexesOperation.fromIndexSpecification(
this,
this.collectionName,
indexSpec,
resolveOptions(this, options)
)
);
}
const indexes = await executeOperation(
this.client,
operation
);

return indexes[0];
Expand Down Expand Up @@ -683,15 +700,17 @@ export class Collection<TSchema extends Document = Document> {
*/
async createIndexes(
indexSpecs: IndexDescription[],
options?: CreateIndexesOptions
options?: CreateIndexesOptions,
commandOptions?: CreateIndexesCommandOptions
): Promise<string[]> {
return await executeOperation(
this.client,
CreateIndexesOperation.fromIndexDescriptionArray(
this,
this.collectionName,
indexSpecs,
resolveOptions(this, { ...options, maxTimeMS: undefined })
resolveOptions(this, { ...options, maxTimeMS: undefined }),
commandOptions
)
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/gridfs/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ async function checkChunksIndex(stream: GridFSBucketWriteStream): Promise<void>
remainingTimeMS = stream.timeoutContext?.getRemainingTimeMSOrThrow(
`Upload timed out after ${stream.timeoutContext?.timeoutMS}ms`
);
// TODO(NODE-6893): this is a mixed bag of index options (background, unique) and command
// options (the write concern fields, timeoutMS), which the two parameter overload sorts via
// the index option allowlist. When validateOptions defaults to false, move the command
// options into the third parameter.
await stream.chunks.createIndex(index, {
...stream.writeConcern,
background: true,
Expand Down Expand Up @@ -379,6 +383,8 @@ async function checkIndexes(stream: GridFSBucketWriteStream): Promise<void> {
`Upload timed out after ${stream.timeoutContext?.timeoutMS}ms`
);

// TODO(NODE-6893): timeoutMS is a command option; move it into the third parameter when
// validateOptions defaults to false.
await stream.files.createIndex(index, { background: false, timeoutMS: remainingTimeMS });
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ export type {
export type { IndexInformationOptions } from './operations/indexes';
export type {
CreateIndexesOptions,
CreateIndexesCommandOptions,
DropIndexesOptions,
IndexDescription,
IndexDescriptionCompact,
Expand Down
64 changes: 48 additions & 16 deletions src/operations/indexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ export interface CreateIndexesOptions extends Omit<CommandOperationOptions, 'wri
hidden?: boolean;
}

/** @public */
export interface CreateIndexesCommandOptions
extends Omit<CommandOperationOptions, 'collation' | 'maxTimeMS' | 'explain'> {
/** ...votingMembers etc. */
commitQuorum?: number | string;
/** @deprecated will default to false in a future release */
validateOptions?: boolean;
}

function isSingleIndexTuple(t: unknown): t is [string, IndexDirection] {
return Array.isArray(t) && t.length === 2 && isIndexDirection(t[1]);
}
Expand Down Expand Up @@ -198,19 +207,24 @@ function constructIndexDescriptionMap(indexSpec: IndexSpecification): Map<string
}

/**
* Receives an index description and returns a modified index description which has had invalid options removed
* from the description and has mapped the `version` option to the `v` option.
* Receives an index description and returns a modified index description which has mapped the
* `version` option to the `v` option.
*
* When `allowUnknownIndexOptions` is `false` (the default), options that are not in the driver's
* `VALID_INDEX_OPTIONS` allowlist are removed from the description. When `true`, all options are
* retained and passed through to the server for validation.
*/
function resolveIndexDescription(
description: IndexDescription
description: IndexDescription,
allowUnknownIndexOptions: boolean
): Omit<ResolvedIndexDescription, 'key'> {
const validProvidedOptions = Object.entries(description).filter(([optionName]) =>
VALID_INDEX_OPTIONS.has(optionName)
const providedOptions = Object.entries(description).filter(
([optionName]) => allowUnknownIndexOptions || VALID_INDEX_OPTIONS.has(optionName)
);

return Object.fromEntries(
// we support the `version` option, but the `createIndexes` command expects it to be the `v`
validProvidedOptions.map(([name, value]) => (name === 'version' ? ['v', value] : [name, value]))
providedOptions.map(([name, value]) => (name === 'version' ? ['v', value] : [name, value]))
);
}

Expand Down Expand Up @@ -252,11 +266,17 @@ export class CreateIndexesOperation extends CommandOperation<string[]> {
parent: OperationParent,
collectionName: string,
indexes: IndexDescription[],
options?: CreateIndexesOptions
indexOptions?: CreateIndexesOptions,
commandOptions?: CreateIndexesCommandOptions
) {
super(parent, options);
// When commandOptions is supplied the caller has separated the two kinds of options, so the
// command reads from it exclusively. Otherwise indexOptions is both, and the command reads
// from it as it always has. There is deliberately no fallback between the two.
const optionsForCommand: CreateIndexesOptions = commandOptions ?? indexOptions ?? {};

this.options = options ?? {};
super(parent, optionsForCommand);

this.options = { ...optionsForCommand };
// collation is set on each index, it should not be defined at the root
this.options.collation = undefined;
this.collectionName = collectionName;
Expand All @@ -265,9 +285,13 @@ export class CreateIndexesOperation extends CommandOperation<string[]> {
const key =
userIndex.key instanceof Map ? userIndex.key : new Map(Object.entries(userIndex.key));
const name = userIndex.name ?? Array.from(key).flat().join('_');
const validIndexOptions = resolveIndexDescription(userIndex);
const validIndexOptions = resolveIndexDescription(
userIndex,
// TODO(seanrmilligan): set to false in a future release
commandOptions?.validateOptions ?? true
);
return {
...validIndexOptions,
...indexOptions,
name,
key
};
Expand All @@ -279,20 +303,28 @@ export class CreateIndexesOperation extends CommandOperation<string[]> {
parent: OperationParent,
collectionName: string,
indexes: IndexDescription[],
options?: CreateIndexesOptions
indexOptions?: CreateIndexesOptions,
commandOptions?: CreateIndexesCommandOptions
): CreateIndexesOperation {
return new CreateIndexesOperation(parent, collectionName, indexes, options);
return new CreateIndexesOperation(parent, collectionName, indexes, indexOptions, commandOptions);
}

static fromIndexSpecification(
parent: OperationParent,
collectionName: string,
indexSpec: IndexSpecification,
options: CreateIndexesOptions = {}
indexOptions: CreateIndexesOptions = {},
commandOptions?: CreateIndexesCommandOptions
): CreateIndexesOperation {
const key = constructIndexDescriptionMap(indexSpec);
const description: IndexDescription = { ...options, key };
return new CreateIndexesOperation(parent, collectionName, [description], options);
const description: IndexDescription = { ...indexOptions, key };
return new CreateIndexesOperation(
parent,
collectionName,
[description],
indexOptions,
commandOptions
);
}

override get commandName() {
Expand Down
6 changes: 3 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ export class BufferPool {
// TODO(NODE-4732): alloc API should change based on raw option
const result = ByteUtils.allocateUnsafe(size);

for (let bytesRead = 0; bytesRead < size; ) {
for (let bytesRead = 0; bytesRead < size;) {
const buffer = this.buffers.shift();
if (buffer == null) {
break;
Expand Down Expand Up @@ -1445,8 +1445,8 @@ export async function abortable<T>(
const abortListener = signal.aborted
? reject(signal.reason)
: addAbortListener(signal, function () {
reject(this.reason);
});
reject(this.reason);
});

try {
return await Promise.race([promise, aborted]);
Expand Down
Loading
Loading