feat(NODE-6893): Allow passthrough options on createIndexes - #5012
feat(NODE-6893): Allow passthrough options on createIndexes#5012seanrmilligan wants to merge 2 commits into
Conversation
93afa52 to
756f24d
Compare
756f24d to
5304629
Compare
| 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 |
There was a problem hiding this comment.
This TODO should be a future NODE task, not the one that adds this comment.
Also applies on line 386 to that comment.
| * | ||
| * @remarks This option is ignored by the server. | ||
| * @see https://www.mongodb.com/docs/manual/reference/command/createIndexes/ | ||
| * @deprecated 4.2 |
There was a problem hiding this comment.
We removed 4.2 support recently, so we may not need this method at all. Can you verify?
|
|
||
| const validIndexOptions = resolveIndexDescription( | ||
| userIndex, | ||
| // TODO(seanrmilligan): Add NODE ticket to set to remove allowUnknownIndexOptions with |
| client.on('commandStarted', ev => { | ||
| if (ev.commandName === 'createIndexes') commands.push(ev); | ||
| }); | ||
| db = client.db('node6893_create_index'); |
There was a problem hiding this comment.
Nit: no reason to encode a random NODE task into a test.
| // http://github.com/mongodb/specifications/blob/6f64d0ee3ae49edbdb30eb995f3e29549e8cfa6a/source/index-management/index-management.md#standard-api | ||
| // This represents the options for the COMMAND, not the INDEX. | ||
| export interface CreateIndexesCommandOptions | ||
| extends Pick<CommandOperationOptions, 'comment' | 'maxTimeMS' | 'rawData'> { |
There was a problem hiding this comment.
rawData may need to be specified explicitly, since it is marked @internal on CommandOperationOptions.
| collectionName: string, | ||
| indexes: IndexDescription[], | ||
| options?: CreateIndexesOptions | ||
| indexOptions?: CreateIndexesOptions, |
There was a problem hiding this comment.
This parameter is not used in this function, think line 518 should be commandOptions ?? indexOptions.
| * | ||
| * @internal | ||
| */ | ||
| export function resolveCommandOptions<T extends Document>( |
There was a problem hiding this comment.
This function seems logically identical to existing resolveOptions method. Do we need this addition?
| collectionName: string, | ||
| indexes: IndexDescription[], | ||
| allowUnknownIndexOptions: boolean, | ||
| commandOptions?: CreateIndexesOptions | CreateIndexOptions |
There was a problem hiding this comment.
Optional params on a private constructor doesn't buy us anything and is detrimental, suggest making this mandatory. (Detrimental because "I forgot to pass a parameter" and "I don't have anything to specify" become indistinguishable.)
5304629 to
991884f
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate issues affect overload typing, command-option typing, and serialization of language options.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds opt-in passthrough for unknown index options while separating index and command options for createIndex.
Changes:
- Adds
allowUnknownIndexOptionshandling. - Introduces
IndexOptionsandCreateIndexOptions. - Updates operation construction, exports, and test coverage.
File summaries
| File | Summary |
|---|---|
test/unit/operations/indexes.test.ts |
Tests option filtering and passthrough behavior. |
test/unit/collection.test.ts |
Tests command and index option separation. |
test/integration/index-management/create_indexes_option_validation.test.ts |
Adds integration validation coverage. |
test/integration/index_management.test.ts |
Tests unknown-option handling. |
test/integration/crud/abstract_operation.test.ts |
Updates operation construction tests. |
src/utils.ts |
Resolves inherited command options. |
src/operations/indexes.ts |
Defines option types, filtering, and passthrough logic. |
src/operations/create_collection.ts |
Updates internal index creation. |
src/index.ts |
Exports new public option types. |
src/gridfs/upload.ts |
Documents future option migration. |
src/db.ts |
Updates createIndex operation construction. |
src/collection.ts |
Adds overloads and passthrough support. |
Review details
Suppressed comments (7)
src/collection.ts:739
- This new TODO also has no NODE/DRIVERS ticket identifier, unlike the repository's established TODO convention. Please link the future default-change work to a concrete ticket before merging.
// TODO(seanrmilligan): default this to true and remove the parameter in a future major
// release. Index options live on each index description, so nothing on this path
// contaminates them -- but flipping it turns today's silently dropped unknown option into
// a server error.
src/collection.ts:655
- The passthrough overload leaves
commandOptionsoptional, but the implementation usescommandOptions == nullto select the legacy allowlist path. A valid two-argument call using the newIndexOptionsshape (for example{ defaultLanguage: 'english' }) is therefore accepted by TypeScript and then silently drops the option. Require the third argument for this overload, or use an unambiguous runtime discriminator.
indexOptions?: IndexOptions,
commandOptions?: CreateIndexOptions
src/gridfs/upload.ts:278
- This TODO refers to
validateOptions, but that is not the option controlling this code path; the new API usesallowUnknownIndexOptions. The stale name will mislead anyone implementing the GridFS migration, so update the comment to the actual overload/flag.
// the index option allowlist. When validateOptions defaults to false, move the command
// options into the third parameter.
src/gridfs/upload.ts:387
- This TODO also names the nonexistent
validateOptionssetting. Refer to the legacy two-parametercreateIndexpath instead so the follow-up work is tied to the API that actually controls the behavior.
// TODO(NODE-6893): timeoutMS is a command option; move it into the third parameter when
// validateOptions defaults to false.
src/operations/indexes.ts:479
- A command comment is normally any BSON value (
CommandOperationOptions.commentisunknown), but this new public type narrows it toDocument. The string comments used by the addedcreateIndextests are consequently not typeable through the new overload; use the existingunknowncomment type.
comment?: Document;
src/operations/indexes.ts:479
- This new public option is documented as enabling comments, but
CreateIndexesOperation.buildCommandDocumentstill emits onlycommitQuorumfrom the command options, socommentis silently dropped. The added unit test currently asserts the opposite behavior; either add comment to the command document or remove it from this API until it is supported.
/**
* Enables users to specify an arbitrary comment to help trace the operation through
* the database profiler, currentOp and logs. The default is to not send a value.
*
* @see https://www.mongodb.com/docs/manual/reference/command/createIndexes/
*
* @sinceServerVersion 4.4
*/
comment?: Document;
src/operations/indexes.ts:610
- Repository TODOs consistently carry a NODE/DRIVERS ticket identifier, but this TODO explicitly asks for a future NODE ticket without one. Please attach the follow-up ticket so the planned default change remains traceable.
// TODO(seanrmilligan): Add NODE ticket to set to remove allowUnknownIndexOptions with
// a default behavior of true in a future 8.0.0 release
- Files reviewed: 12/12 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| * | ||
| * This options is only supported by servers \>= 6.0. | ||
| */ | ||
| clustered?: boolean; |
There was a problem hiding this comment.
Good point. If clustered is only every present on the response*, why do we need to expose it on a request object.
*Side question: how/when do we return clustered to the user? I'm not finding it yet.
| } | ||
|
|
||
| /** @public */ | ||
| export interface CreateIndexOptions { |
There was a problem hiding this comment.
I think the better approach here is something like this:
export interface CreateIndexOptions extends Omit<CommandOperationOptions, 'collation' | 'explain'> {
commitQuorum?: number | string;
}
| 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])) |
There was a problem hiding this comment.
Good find, but the suggestion leaves something to be desired. Suggest going with a map like this:
const INDEX_OPTION_RENAMES = new Map([
['version', 'v'],
['defaultLanguage', 'default_language'],
['languageOverride', 'language_override']
]);
| * `CommandOperationOptions`. The command-level fields this resolves (read/write concern, | ||
| * read preference, timeoutMS, BSON serialization) are added to the return type, and any | ||
| * field `T` declares itself takes precedence -- so an option type that narrows a field | ||
| * (ex. `CreateIndexOptions.maxTimeMS`, which is a `bigint`) keeps its own declaration. |
There was a problem hiding this comment.
Agreed, the type is wrong, should be fixed.
| indexSpecs: IndexDescription[], | ||
| options?: CreateIndexesOptions | ||
| commandOptions?: CreateIndexesOptions, | ||
| allowUnknownIndexOptions = false |
There was a problem hiding this comment.
When calling createIndex, a user opts-in to the new behavior by specifying an object.
This is not the same approach for createIndexes, where opt-in behavior is signaled with a boolean.
Should we have the same approaches in both spots, that is, specify an object to get the new behavior.
|
|
||
| it( | ||
| 'creates an index using a server option the driver does not know about', | ||
| { metadata: { requires: { mongodb: '>=5.3' } } }, |
There was a problem hiding this comment.
Remove metadata wrapper.
|
|
||
| it( | ||
| 'creates an index using a server option the driver does not know about', | ||
| { metadata: { requires: { mongodb: '>=5.3' } } }, |
There was a problem hiding this comment.
Remove metadata wrapper.
PavelSafronov
left a comment
There was a problem hiding this comment.
There are a number of type additions, but no new types tests. You'll need to add these.
| } | ||
|
|
||
| /** @public */ | ||
| export interface CreateIndexOptions { |
There was a problem hiding this comment.
I think the better approach here is something like this:
export interface CreateIndexOptions extends Omit<CommandOperationOptions, 'collation' | 'explain'> {
commitQuorum?: number | string;
}
| 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])) |
There was a problem hiding this comment.
Good find, but the suggestion leaves something to be desired. Suggest going with a map like this:
const INDEX_OPTION_RENAMES = new Map([
['version', 'v'],
['defaultLanguage', 'default_language'],
['languageOverride', 'language_override']
]);
| * | ||
| * This options is only supported by servers \>= 6.0. | ||
| */ | ||
| clustered?: boolean; |
There was a problem hiding this comment.
Good point. If clustered is only every present on the response*, why do we need to expose it on a request object.
*Side question: how/when do we return clustered to the user? I'm not finding it yet.
| 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) |
There was a problem hiding this comment.
Think we need to test optionName !== 'key' here.
| * `CommandOperationOptions`. The command-level fields this resolves (read/write concern, | ||
| * read preference, timeoutMS, BSON serialization) are added to the return type, and any | ||
| * field `T` declares itself takes precedence -- so an option type that narrows a field | ||
| * (ex. `CreateIndexOptions.maxTimeMS`, which is a `bigint`) keeps its own declaration. |
There was a problem hiding this comment.
Agreed, the type is wrong, should be fixed.
Description
Summary of Changes
Notes for Reviewers
What is the motivation for this change?
Release Highlight
Release notes highlight
Double check the following
npm run check:lint)type(NODE-xxxx)[!]: descriptionfeat(NODE-1234)!: rewriting everything in coffeescript