Skip to content

Commit 80a6673

Browse files
add support for version
1 parent 6005836 commit 80a6673

File tree

2 files changed

+122
-20
lines changed

2 files changed

+122
-20
lines changed

src/operations/indexes.ts

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,11 @@ function isSingleIndexTuple(t: unknown): t is [string, IndexDirection] {
169169
return Array.isArray(t) && t.length === 2 && isIndexDirection(t[1]);
170170
}
171171

172-
function makeIndexSpec(
173-
indexSpec: IndexSpecification,
174-
options?: CreateIndexesOptions
175-
): IndexDescription {
172+
/**
173+
* Converts an `IndexSpecification`, which can be specified in multiple formats, into a
174+
* valid `key` for the createIndexes command.
175+
*/
176+
function constructIndexDescriptionMap(indexSpec: IndexSpecification): Map<string, IndexDirection> {
176177
const key: Map<string, IndexDirection> = new Map();
177178

178179
const indexSpecs =
@@ -195,14 +196,46 @@ function makeIndexSpec(
195196
}
196197
}
197198

198-
return { ...options, key };
199+
return key;
199200
}
200201

202+
/**
203+
* Receives an index description and returns a modified index description which has had invalid options removed
204+
* from the description and has mapped the `version` option to the `v` option.
205+
*/
206+
function resolveIndexDescription(
207+
description: IndexDescription
208+
): Omit<ResolvedIndexDescription, 'key'> {
209+
const validProvidedOptions = Object.entries({ ...description }).filter(([optionName]) =>
210+
VALID_INDEX_OPTIONS.has(optionName)
211+
);
212+
213+
return Object.fromEntries(
214+
// we support the `version` option, but the `createIndexes` command expects it to be the `v`
215+
validProvidedOptions.map(([name, value]) => (name === 'version' ? ['v', value] : [name, value]))
216+
);
217+
}
218+
219+
/**
220+
* @internal
221+
*
222+
* Internally, the driver represents index description keys with `Map`s to preserve key ordering.
223+
* We don't require users to specify maps, so we transform user provided descriptions into
224+
* "resolved" by converting the `key` into a JS `Map`, if it isn't already a map.
225+
*
226+
* Additionally, we support the `version` option, but the `createIndexes` command uses the field `v`
227+
* to specify the index version so we map the value of `version` to `v`, if provided.
228+
*/
229+
type ResolvedIndexDescription = Omit<IndexDescription, 'key' | 'version'> & {
230+
key: Map<string, IndexDirection>;
231+
v?: IndexDescription['version'];
232+
};
233+
201234
/** @internal */
202235
export class CreateIndexesOperation extends CommandOperation<string[]> {
203236
override options: CreateIndexesOptions;
204237
collectionName: string;
205-
indexes: ReadonlyArray<Omit<IndexDescription, 'key'> & { key: Map<string, IndexDirection> }>;
238+
indexes: ReadonlyArray<ResolvedIndexDescription>;
206239

207240
private constructor(
208241
parent: OperationParent,
@@ -214,16 +247,12 @@ export class CreateIndexesOperation extends CommandOperation<string[]> {
214247

215248
this.options = options ?? {};
216249
this.collectionName = collectionName;
217-
this.indexes = indexes.map(userIndex => {
250+
this.indexes = indexes.map((userIndex: IndexDescription): ResolvedIndexDescription => {
218251
// Ensure the key is a Map to preserve index key ordering
219252
const key =
220253
userIndex.key instanceof Map ? userIndex.key : new Map(Object.entries(userIndex.key));
221254
const name = userIndex.name != null ? userIndex.name : Array.from(key).flat().join('_');
222-
const validIndexOptions = Object.fromEntries(
223-
Object.entries({ ...userIndex }).filter(([optionName]) =>
224-
VALID_INDEX_OPTIONS.has(optionName)
225-
)
226-
);
255+
const validIndexOptions = resolveIndexDescription(userIndex);
227256
return {
228257
...validIndexOptions,
229258
name,
@@ -245,14 +274,11 @@ export class CreateIndexesOperation extends CommandOperation<string[]> {
245274
parent: OperationParent,
246275
collectionName: string,
247276
indexSpec: IndexSpecification,
248-
options?: CreateIndexesOptions
277+
options: CreateIndexesOptions = {}
249278
): CreateIndexesOperation {
250-
return new CreateIndexesOperation(
251-
parent,
252-
collectionName,
253-
[makeIndexSpec(indexSpec, options)],
254-
options
255-
);
279+
const key = constructIndexDescriptionMap(indexSpec);
280+
const description: IndexDescription = { ...options, key };
281+
return new CreateIndexesOperation(parent, collectionName, [description], options);
256282
}
257283

258284
override get commandName() {

test/integration/index_management.test.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
type MongoClient,
99
MongoServerError
1010
} from '../mongodb';
11-
import { assert as test, setupDatabase } from './shared';
11+
import { assert as test, filterForCommands, setupDatabase } from './shared';
1212

1313
describe('Indexes', function () {
1414
let client: MongoClient;
@@ -160,6 +160,82 @@ describe('Indexes', function () {
160160
});
161161
});
162162

163+
describe.only('Collection.createIndex()', function () {
164+
const started: CommandStartedEvent[] = [];
165+
beforeEach(() => {
166+
started.length = 0;
167+
client.on('commandStarted', filterForCommands('createIndexes', started));
168+
});
169+
170+
context('when version is not specified as an option', function () {
171+
it('does not attach `v` to the command', async () => {
172+
await collection.createIndex({ age: 1 });
173+
const { command } = started[0];
174+
expect(command).to.exist;
175+
expect(command.indexes[0]).not.to.have.property('v');
176+
});
177+
});
178+
179+
context('when version is specified as an option', function () {
180+
it('attaches `v` to the command with the value of `version`', async () => {
181+
await collection.createIndex({ age: 1 }, { version: 1 });
182+
const { command } = started[0];
183+
expect(command).to.exist;
184+
expect(command.indexes[0]).to.have.property('v', 1);
185+
});
186+
});
187+
});
188+
189+
describe.only('Collection.createIndexes()', function () {
190+
const started: CommandStartedEvent[] = [];
191+
beforeEach(() => {
192+
started.length = 0;
193+
client.on('commandStarted', filterForCommands('createIndexes', started));
194+
});
195+
196+
context('when version is not specified as an option', function () {
197+
it('does not attach `v` to the command', async () => {
198+
await collection.createIndexes([{ key: { age: 1 } }]);
199+
const { command } = started[0];
200+
expect(command).to.exist;
201+
expect(command.indexes[0]).not.to.have.property('v');
202+
});
203+
});
204+
205+
context('when version is specified as an option', function () {
206+
it('does not attach `v` to the command', async () => {
207+
await collection.createIndexes([{ key: { age: 1 } }], { version: 1 });
208+
const { command } = started[0];
209+
expect(command).to.exist;
210+
expect(command.indexes[0]).not.to.have.property('v', 1);
211+
});
212+
});
213+
214+
context('when version is provided in the index description and the options', function () {
215+
it('the value in the description takes precedence', async () => {
216+
await collection.createIndexes([{ key: { age: 1 }, version: 1 }], { version: 0 });
217+
const { command } = started[0];
218+
expect(command).to.exist;
219+
expect(command.indexes[0]).to.have.property('v', 1);
220+
});
221+
});
222+
223+
context(
224+
'when version is provided in some of the index descriptions and the options',
225+
function () {
226+
it('does not specify a version from the `version` provided in the options', async () => {
227+
await collection.createIndexes([{ key: { age: 1 }, version: 1 }, { key: { date: 1 } }], {
228+
version: 0
229+
});
230+
const { command } = started[0];
231+
expect(command).to.exist;
232+
expect(command.indexes[0]).to.have.property('v', 1);
233+
expect(command.indexes[1]).not.to.have.property('v');
234+
});
235+
}
236+
);
237+
});
238+
163239
describe('Collection.indexExists()', function () {
164240
beforeEach(() => collection.createIndex({ age: 1 }));
165241
afterEach(() => collection.dropIndexes());

0 commit comments

Comments
 (0)