-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(NODE-3517): improve index spec handling and type definitions #3315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
65a67e9
6fc9cc1
053c047
1fa6b63
0df78c0
af386dc
c6989f2
a136417
7eddd4b
359dd57
2d0dde7
453679b
91910ee
572a8c7
3a017d1
e9f6edb
8bf8b11
eb78d8e
79babb2
02c5a1e
7b3f2fd
4ee2651
4a1d509
c770dce
4100dbc
c681cba
4ed0db3
92b1967
784c8c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ import type { OneOrMore } from '../mongo_types'; | |
import { ReadPreference } from '../read_preference'; | ||
import type { Server } from '../sdam/server'; | ||
import type { ClientSession } from '../sessions'; | ||
import { Callback, maxWireVersion, MongoDBNamespace, parseIndexOptions } from '../utils'; | ||
import { Callback, isObject, maxWireVersion, MongoDBNamespace } from '../utils'; | ||
import { | ||
CollationOptions, | ||
CommandOperation, | ||
|
@@ -51,14 +51,17 @@ const VALID_INDEX_OPTIONS = new Set([ | |
|
||
/** @public */ | ||
export type IndexDirection = -1 | 1 | '2d' | '2dsphere' | 'text' | 'geoHaystack' | number; | ||
|
||
function isIndexDirection(x: unknown): x is IndexDirection { | ||
return ( | ||
typeof x === 'number' || x === '2d' || x === '2dsphere' || x === 'text' || x === 'geoHaystack' | ||
); | ||
} | ||
/** @public */ | ||
export type IndexSpecification = OneOrMore< | ||
| string | ||
| [string, IndexDirection] | ||
| { [key: string]: IndexDirection } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing the array options is a breaking change, because the following used to be permitted, even though it's not a valid index specification according to MongoDB: const invalid: IndexSpecification = [[["name", 1]]] Our precedent is to release breaking TS changes as bug fixes when applicable, which I am okay with, but maybe we should consider pulling this change (the deletion of lines 60/61) into a separate bug fix PR to not mix the bug fix in with this other work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of the three we're addressing here should we pull them each out and try and make this PR only about the refactor (i.e. why this bug but not the others)? (I think the tuple one might be difficult but we can see). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what "three" are you referencing here? three bugs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think whether we pull these out into separate PRs/fixes or not, we should make sure that the PR description accurately reflects whatever fixes are included and that we are super clear in our release notes about the changes in behavior. It's also worth noting that the types in the documentation won't get updated on a patch, unless we manually regenerate the 4.8, which could be confusing. I think because of the scope of the changes here, this set of improvements that's coming in as a bug fix is better marked as a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh and let's triple check to make sure that every single one of those things has full regression test coverage There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Collected a summary in the description and between the type/fle/indexes.test.ts additions I see coverage for each point. |
||
| [string, IndexDirection][] | ||
| { [key: string]: IndexDirection }[] | ||
| Map<string, IndexDirection> | ||
baileympearson marked this conversation as resolved.
Show resolved
Hide resolved
nbbeeken marked this conversation as resolved.
Show resolved
Hide resolved
|
||
>; | ||
|
||
/** @public */ | ||
|
@@ -86,7 +89,7 @@ export interface IndexDescription | |
> { | ||
collation?: CollationOptions; | ||
name?: string; | ||
key: Document; | ||
key: { [key: string]: IndexDirection } | Map<string, IndexDirection>; | ||
} | ||
|
||
/** @public */ | ||
|
@@ -130,23 +133,37 @@ export interface CreateIndexesOptions extends CommandOperationOptions { | |
hidden?: boolean; | ||
} | ||
|
||
function makeIndexSpec(indexSpec: IndexSpecification, options: any): IndexDescription { | ||
const indexParameters = parseIndexOptions(indexSpec); | ||
|
||
// Generate the index name | ||
const name = typeof options.name === 'string' ? options.name : indexParameters.name; | ||
|
||
// Set up the index | ||
const finalIndexSpec: Document = { name, key: indexParameters.fieldHash }; | ||
function isSingleIndexTuple(t: unknown): t is [string, IndexDirection] { | ||
return Array.isArray(t) && t.length === 2 && isIndexDirection(t[1]); | ||
} | ||
|
||
// merge valid index options into the index spec | ||
for (const optionName in options) { | ||
if (VALID_INDEX_OPTIONS.has(optionName)) { | ||
finalIndexSpec[optionName] = options[optionName]; | ||
function makeIndexSpec( | ||
indexSpec: IndexSpecification, | ||
options?: CreateIndexesOptions | ||
): IndexDescription { | ||
const key: Map<string, IndexDirection> = new Map(); | ||
|
||
const indexSpecs = | ||
!Array.isArray(indexSpec) || isSingleIndexTuple(indexSpec) ? [indexSpec] : indexSpec; | ||
|
||
// Iterate through array and handle different types | ||
for (const spec of indexSpecs) { | ||
if (typeof spec === 'string') { | ||
key.set(spec, 1); | ||
} else if (Array.isArray(spec)) { | ||
key.set(spec[0], spec[1] ?? 1); | ||
} else if (spec instanceof Map) { | ||
for (const [property, value] of spec) { | ||
key.set(property, value); | ||
} | ||
} else if (isObject(spec)) { | ||
for (const [property, value] of Object.entries(spec)) { | ||
key.set(property, value); | ||
} | ||
} | ||
} | ||
|
||
return finalIndexSpec as IndexDescription; | ||
return { ...options, key }; | ||
} | ||
|
||
/** @internal */ | ||
|
@@ -183,7 +200,7 @@ export class CreateIndexesOperation< | |
> extends CommandOperation<T> { | ||
override options: CreateIndexesOptions; | ||
collectionName: string; | ||
indexes: IndexDescription[]; | ||
indexes: ReadonlyArray<Omit<IndexDescription, 'key'> & { key: Map<string, IndexDirection> }>; | ||
|
||
constructor( | ||
parent: OperationParent, | ||
|
@@ -195,8 +212,22 @@ export class CreateIndexesOperation< | |
|
||
this.options = options ?? {}; | ||
this.collectionName = collectionName; | ||
|
||
this.indexes = indexes; | ||
this.indexes = indexes.map(userIndex => { | ||
// Ensure the key is a Map to preserve index key ordering | ||
const key = | ||
userIndex.key instanceof Map ? userIndex.key : new Map(Object.entries(userIndex.key)); | ||
const name = userIndex.name != null ? userIndex.name : Array.from(key).flat().join('_'); | ||
const validIndexOptions = Object.fromEntries( | ||
Object.entries({ ...userIndex }).filter(([optionName]) => | ||
VALID_INDEX_OPTIONS.has(optionName) | ||
) | ||
); | ||
return { | ||
...validIndexOptions, | ||
name, | ||
key | ||
}; | ||
}); | ||
} | ||
|
||
override execute( | ||
|
@@ -209,31 +240,6 @@ export class CreateIndexesOperation< | |
|
||
const serverWireVersion = maxWireVersion(server); | ||
|
||
// Ensure we generate the correct name if the parameter is not set | ||
for (let i = 0; i < indexes.length; i++) { | ||
// Did the user pass in a collation, check if our write server supports it | ||
if (indexes[i].collation && serverWireVersion < 5) { | ||
callback( | ||
new MongoCompatibilityError( | ||
`Server ${server.name}, which reports wire version ${serverWireVersion}, ` + | ||
'does not support collation' | ||
) | ||
); | ||
return; | ||
} | ||
|
||
if (indexes[i].name == null) { | ||
const keys = []; | ||
|
||
for (const name in indexes[i].key) { | ||
keys.push(`${name}_${indexes[i].key[name]}`); | ||
} | ||
|
||
// Set the name | ||
indexes[i].name = keys.join('_'); | ||
} | ||
} | ||
|
||
const cmd: Document = { createIndexes: this.collectionName, indexes }; | ||
|
||
if (options.commitQuorum != null) { | ||
|
@@ -271,12 +277,6 @@ export class CreateIndexOperation extends CreateIndexesOperation<string> { | |
indexSpec: IndexSpecification, | ||
options?: CreateIndexesOptions | ||
) { | ||
// createIndex can be called with a variety of styles: | ||
// coll.createIndex('a'); | ||
// coll.createIndex({ a: 1 }); | ||
// coll.createIndex([['a', 1]]); | ||
// createIndexes is always called with an array of index spec objects | ||
|
||
super(parent, collectionName, [makeIndexSpec(indexSpec, options)], options); | ||
} | ||
override execute( | ||
|
Uh oh!
There was an error while loading. Please reload this page.