Skip to content

Commit 3393c96

Browse files
authored
Merge pull request #14251 from Automattic/vkarpov15/gh-14232
Add Atlas search index helpers to Models and Schemas
2 parents dad0da9 + 33b3bba commit 3393c96

File tree

10 files changed

+167
-8
lines changed

10 files changed

+167
-8
lines changed

docs/guide.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ Valid options:
558558
* [collectionOptions](#collectionOptions)
559559
* [methods](#methods)
560560
* [query](#query-helpers)
561+
* [autoSearchIndex](#autoSearchIndex)
561562

562563
<h2 id="autoIndex"><a href="#autoIndex">option: autoIndex</a></h2>
563564

@@ -1453,6 +1454,25 @@ const Test = mongoose.model('Test', schema);
14531454
await Test.createCollection();
14541455
```
14551456

1457+
<h2 id="autoSearchIndex">
1458+
<a href="#autoSearchIndex">
1459+
option: autoSearchIndex
1460+
</a>
1461+
</h2>
1462+
1463+
Similar to [`autoIndex`](#autoIndex), except for automatically creates any [Atlas search indexes](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) defined in your schema.
1464+
Unlike `autoIndex`, this option defaults to false.
1465+
1466+
```javascript
1467+
const schema = new Schema({ name: String }, { autoSearchIndex: true });
1468+
schema.searchIndex({
1469+
name: 'my-index',
1470+
definition: { mappings: { dynamic: true } }
1471+
});
1472+
// Will automatically attempt to create the `my-index` search index.
1473+
const Test = mongoose.model('Test', schema);
1474+
``
1475+
14561476
<h2 id="es6-classes"><a href="#es6-classes">With ES6 Classes</a></h2>
14571477

14581478
Schemas have a [`loadClass()` method](api/schema.html#schema_Schema-loadClass)

lib/connection.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,8 @@ Connection.prototype.onClose = function(force) {
10031003
Connection.prototype.collection = function(name, options) {
10041004
const defaultOptions = {
10051005
autoIndex: this.config.autoIndex != null ? this.config.autoIndex : this.base.options.autoIndex,
1006-
autoCreate: this.config.autoCreate != null ? this.config.autoCreate : this.base.options.autoCreate
1006+
autoCreate: this.config.autoCreate != null ? this.config.autoCreate : this.base.options.autoCreate,
1007+
autoSearchIndex: this.config.autoSearchIndex != null ? this.config.autoSearchIndex : this.base.options.autoSearchIndex
10071008
};
10081009
options = Object.assign({}, defaultOptions, options ? clone(options) : {});
10091010
options.$wasForceClosed = this.$wasForceClosed;

lib/drivers/node-mongodb-native/connection.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ NativeConnection.prototype.createClient = async function createClient(uri, optio
246246
delete options.sanitizeFilter;
247247
}
248248

249+
if ('autoSearchIndex' in options) {
250+
this.config.autoSearchIndex = options.autoSearchIndex;
251+
delete options.autoSearchIndex;
252+
}
253+
249254
// Backwards compat
250255
if (options.user || options.pass) {
251256
options.auth = options.auth || {};

lib/model.js

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,10 +1273,14 @@ for (const i in EventEmitter.prototype) {
12731273
}
12741274

12751275
/**
1276-
* This function is responsible for building [indexes](https://www.mongodb.com/docs/manual/indexes/),
1277-
* unless [`autoIndex`](https://mongoosejs.com/docs/guide.html#autoIndex) is turned off.
1276+
* This function is responsible for initializing the underlying connection in MongoDB based on schema options.
1277+
* This function performs the following operations:
12781278
*
1279-
* Mongoose calls this function automatically when a model is created using
1279+
* - `createCollection()` unless [`autoCreate`](https://mongoosejs.com/docs/guide.html#autoCreate) option is turned off
1280+
* - `ensureIndexes()` unless [`autoIndex`](https://mongoosejs.com/docs/guide.html#autoIndex) option is turned off
1281+
* - `createSearchIndex()` on all schema search indexes if `autoSearchIndex` is enabled.
1282+
*
1283+
* Mongoose calls this function automatically when a model is a created using
12801284
* [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) or
12811285
* [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()), so you
12821286
* don't need to call `init()` to trigger index builds.
@@ -1324,6 +1328,23 @@ Model.init = function init() {
13241328
}
13251329
return await this.ensureIndexes({ _automatic: true });
13261330
};
1331+
const _createSearchIndexes = async() => {
1332+
const autoSearchIndex = utils.getOption(
1333+
'autoSearchIndex',
1334+
this.schema.options,
1335+
conn.config,
1336+
conn.base.options
1337+
);
1338+
if (!autoSearchIndex) {
1339+
return;
1340+
}
1341+
1342+
const results = [];
1343+
for (const searchIndex of this.schema._searchIndexes) {
1344+
results.push(await this.createSearchIndex(searchIndex));
1345+
}
1346+
return results;
1347+
};
13271348
const _createCollection = async() => {
13281349
if ((conn.readyState === STATES.connecting || conn.readyState === STATES.disconnected) && conn._shouldBufferCommands()) {
13291350
await new Promise(resolve => {
@@ -1342,7 +1363,9 @@ Model.init = function init() {
13421363
return await this.createCollection();
13431364
};
13441365

1345-
this.$init = _createCollection().then(() => _ensureIndexes());
1366+
this.$init = _createCollection().
1367+
then(() => _ensureIndexes()).
1368+
then(() => _createSearchIndexes());
13461369

13471370
const _catch = this.$init.catch;
13481371
const _this = this;
@@ -1506,6 +1529,72 @@ Model.syncIndexes = async function syncIndexes(options) {
15061529
return dropped;
15071530
};
15081531

1532+
/**
1533+
* Create an [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
1534+
* This function only works when connected to MongoDB Atlas.
1535+
*
1536+
* #### Example:
1537+
*
1538+
* const schema = new Schema({ name: { type: String, unique: true } });
1539+
* const Customer = mongoose.model('Customer', schema);
1540+
* await Customer.createSearchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
1541+
*
1542+
* @param {Object} description index options, including `name` and `definition`
1543+
* @param {String} description.name
1544+
* @param {Object} description.definition
1545+
* @return {Promise}
1546+
* @api public
1547+
*/
1548+
1549+
Model.createSearchIndex = async function createSearchIndex(description) {
1550+
_checkContext(this, 'createSearchIndex');
1551+
1552+
return await this.$__collection.createSearchIndex(description);
1553+
};
1554+
1555+
/**
1556+
* Update an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
1557+
* This function only works when connected to MongoDB Atlas.
1558+
*
1559+
* #### Example:
1560+
*
1561+
* const schema = new Schema({ name: { type: String, unique: true } });
1562+
* const Customer = mongoose.model('Customer', schema);
1563+
* await Customer.updateSearchIndex('test', { mappings: { dynamic: true } });
1564+
*
1565+
* @param {String} name
1566+
* @param {Object} definition
1567+
* @return {Promise}
1568+
* @api public
1569+
*/
1570+
1571+
Model.updateSearchIndex = async function updateSearchIndex(name, definition) {
1572+
_checkContext(this, 'updateSearchIndex');
1573+
1574+
return await this.$__collection.updateSearchIndex(name, definition);
1575+
};
1576+
1577+
/**
1578+
* Delete an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) by name.
1579+
* This function only works when connected to MongoDB Atlas.
1580+
*
1581+
* #### Example:
1582+
*
1583+
* const schema = new Schema({ name: { type: String, unique: true } });
1584+
* const Customer = mongoose.model('Customer', schema);
1585+
* await Customer.dropSearchIndex('test');
1586+
*
1587+
* @param {String} name
1588+
* @return {Promise}
1589+
* @api public
1590+
*/
1591+
1592+
Model.dropSearchIndex = async function dropSearchIndex(name) {
1593+
_checkContext(this, 'dropSearchIndex');
1594+
1595+
return await this.$__collection.dropSearchIndex(name);
1596+
};
1597+
15091598
/**
15101599
* Does a dry-run of `Model.syncIndexes()`, returning the indexes that `syncIndexes()` would drop and create if you were to run `syncIndexes()`.
15111600
*

lib/mongoose.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ function Mongoose(options) {
6565
this.options = Object.assign({
6666
pluralization: true,
6767
autoIndex: true,
68-
autoCreate: true
68+
autoCreate: true,
69+
autoSearchIndex: false
6970
}, options);
7071
const createInitialConnection = utils.getOption('createInitialConnection', this.options);
7172
if (createInitialConnection == null || createInitialConnection) {

lib/schema.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ function Schema(obj, options) {
116116
this.inherits = {};
117117
this.callQueue = [];
118118
this._indexes = [];
119+
this._searchIndexes = [];
119120
this.methods = (options && options.methods) || {};
120121
this.methodOptions = {};
121122
this.statics = (options && options.statics) || {};
@@ -411,6 +412,7 @@ Schema.prototype._clone = function _clone(Constructor) {
411412
s.query = clone(this.query);
412413
s.plugins = Array.prototype.slice.call(this.plugins);
413414
s._indexes = clone(this._indexes);
415+
s._searchIndexes = clone(this._searchIndexes);
414416
s.s.hooks = this.s.hooks.clone();
415417

416418
s.tree = clone(this.tree);
@@ -908,6 +910,28 @@ Schema.prototype.clearIndexes = function clearIndexes() {
908910
return this;
909911
};
910912

913+
/**
914+
* Add an [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) that Mongoose will create using `Model.createSearchIndex()`.
915+
* This function only works when connected to MongoDB Atlas.
916+
*
917+
* #### Example:
918+
*
919+
* const ToySchema = new Schema({ name: String, color: String, price: Number });
920+
* ToySchema.searchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
921+
*
922+
* @param {Object} description index options, including `name` and `definition`
923+
* @param {String} description.name
924+
* @param {Object} description.definition
925+
* @return {Schema} the Schema instance
926+
* @api public
927+
*/
928+
929+
Schema.prototype.searchIndex = function searchIndex(description) {
930+
this._searchIndexes.push(description);
931+
932+
return this;
933+
};
934+
911935
/**
912936
* Reserved document keys.
913937
*

lib/validOptions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const VALID_OPTIONS = Object.freeze([
1111
'applyPluginsToDiscriminators',
1212
'autoCreate',
1313
'autoIndex',
14+
'autoSearchIndex',
1415
'bufferCommands',
1516
'bufferTimeoutMS',
1617
'cloneSchemas',

types/connection.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ declare module 'mongoose' {
4848
pass?: string;
4949
/** Set to false to disable automatic index creation for all models associated with this connection. */
5050
autoIndex?: boolean;
51-
/** Set to `true` to make Mongoose automatically call `createCollection()` on every model created on this connection. */
51+
/** Set to `false` to disable Mongoose automatically calling `createCollection()` on every model created on this connection. */
5252
autoCreate?: boolean;
5353
}
5454

types/inferschematype.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ type ResolvePathType<PathValueType, Options extends SchemaTypeOptions<PathValueT
271271
IfEquals<PathValueType, Types.Decimal128> extends true ? Types.Decimal128 :
272272
IfEquals<PathValueType, Schema.Types.BigInt> extends true ? bigint :
273273
IfEquals<PathValueType, BigInt> extends true ? bigint :
274-
PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint :
274+
PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint :
275275
PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer :
276276
IfEquals<PathValueType, Schema.Types.UUID> extends true ? Buffer :
277277
PathValueType extends MapConstructor | 'Map' ? Map<string, ResolvePathType<Options['of']>> :

types/models.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ declare module 'mongoose' {
245245
*/
246246
createCollection<T extends mongodb.Document>(options?: mongodb.CreateCollectionOptions & Pick<SchemaOptions, 'expires'>): Promise<mongodb.Collection<T>>;
247247

248+
/**
249+
* Create an [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
250+
* This function only works when connected to MongoDB Atlas.
251+
*/
252+
createSearchIndex(description: mongodb.SearchIndexDescription): Promise<string>;
253+
248254
/** Connection the model uses. */
249255
db: Connection;
250256

@@ -298,6 +304,12 @@ declare module 'mongoose' {
298304
'deleteOne'
299305
>;
300306

307+
/**
308+
* Delete an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) by name.
309+
* This function only works when connected to MongoDB Atlas.
310+
*/
311+
dropSearchIndex(name: string): Promise<void>;
312+
301313
/**
302314
* Event emitter that reports any errors that occurred. Useful for global error
303315
* handling.
@@ -473,6 +485,12 @@ declare module 'mongoose' {
473485
doc: any, options: PopulateOptions | Array<PopulateOptions> | string
474486
): Promise<MergeType<THydratedDocumentType, Paths>>;
475487

488+
/**
489+
* Update an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
490+
* This function only works when connected to MongoDB Atlas.
491+
*/
492+
updateSearchIndex(name: string, definition: AnyObject): Promise<void>;
493+
476494
/** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */
477495
validate(): Promise<void>;
478496
validate(obj: any): Promise<void>;

0 commit comments

Comments
 (0)