diff --git a/docs/guide.md b/docs/guide.md index 2ed451b08c3..9c2766c311b 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -559,6 +559,7 @@ Valid options: * [methods](#methods) * [query](#query-helpers) * [autoSearchIndex](#autoSearchIndex) +* [readConcern](#readConcern)

option: autoIndex

@@ -1473,6 +1474,24 @@ schema.searchIndex({ const Test = mongoose.model('Test', schema); ``` +

+ + option: readConcern + +

+ +[Read concerns](https://www.mongodb.com/docs/manual/reference/read-concern/) are similar to [`writeConcern`](#writeConcern), but for read operations like `find()` and `findOne()`. +To set a default `readConcern`, pass the `readConcern` option to the schema constructor as follows. + +```javascript +const eventSchema = new mongoose.Schema( + { name: String }, + { + readConcern: { level: 'available' } // <-- set default readConcern for all queries + } +); +``` +

With ES6 Classes

Schemas have a [`loadClass()` method](api/schema.html#schema_Schema-loadClass) diff --git a/lib/helpers/schema/applyReadConcern.js b/lib/helpers/schema/applyReadConcern.js new file mode 100644 index 00000000000..80d4da6eb20 --- /dev/null +++ b/lib/helpers/schema/applyReadConcern.js @@ -0,0 +1,22 @@ +'use strict'; + +const get = require('../get'); + +module.exports = function applyReadConcern(schema, options) { + if (options.readConcern !== undefined) { + return; + } + + // Don't apply default read concern to operations in transactions, + // because you shouldn't set read concern on individual operations + // within a transaction. + // See: https://www.mongodb.com/docs/manual/reference/read-concern/ + if (options && options.session && options.session.transaction) { + return; + } + + const level = get(schema, 'options.readConcern.level', null); + if (level != null) { + options.readConcern = { level }; + } +}; diff --git a/lib/model.js b/lib/model.js index eaadf894b91..3e28d4f18c6 100644 --- a/lib/model.js +++ b/lib/model.js @@ -27,6 +27,7 @@ const applyEmbeddedDiscriminators = require('./helpers/discriminator/applyEmbedd const applyHooks = require('./helpers/model/applyHooks'); const applyMethods = require('./helpers/model/applyMethods'); const applyProjection = require('./helpers/projection/applyProjection'); +const applyReadConcern = require('./helpers/schema/applyReadConcern'); const applySchemaCollation = require('./helpers/indexes/applySchemaCollation'); const applyStaticHooks = require('./helpers/model/applyStaticHooks'); const applyStatics = require('./helpers/model/applyStatics'); @@ -417,6 +418,8 @@ Model.prototype.$__handleSave = function(options, callback) { where[key] = val; } } + + applyReadConcern(this.$__schema, optionsWithCustomValues); this.constructor.collection.findOne(where, optionsWithCustomValues) .then(documentExists => { const matchedCount = !documentExists ? 0 : 1; diff --git a/lib/query.js b/lib/query.js index 22956fb818f..dbf022ebf54 100644 --- a/lib/query.js +++ b/lib/query.js @@ -13,6 +13,7 @@ const QueryCursor = require('./cursor/queryCursor'); const ValidationError = require('./error/validation'); const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption'); const handleReadPreferenceAliases = require('./helpers/query/handleReadPreferenceAliases'); +const applyReadConcern = require('./helpers/schema/applyReadConcern'); const applyWriteConcern = require('./helpers/schema/applyWriteConcern'); const cast = require('./cast'); const castArrayFilters = require('./helpers/update/castArrayFilters'); @@ -1944,6 +1945,7 @@ Query.prototype._optionsForExec = function(model) { if (!model) { return options; } + applyReadConcern(model.schema, options); // Apply schema-level `writeConcern` option applyWriteConcern(model.schema, options); diff --git a/lib/schema.js b/lib/schema.js index 04c631eb799..97c64a38e1c 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -66,6 +66,7 @@ const numberRE = /^\d+$/; * - [_id](https://mongoosejs.com/docs/guide.html#_id): bool - defaults to true * - [minimize](https://mongoosejs.com/docs/guide.html#minimize): bool - controls [document#toObject](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()) behavior when called manually - defaults to true * - [read](https://mongoosejs.com/docs/guide.html#read): string + * - [readConcern](https://mongoosejs.com/docs/guide.html#readConcern): object - defaults to null, use to set a default [read concern](https://www.mongodb.com/docs/manual/reference/read-concern/) for all queries. * - [writeConcern](https://mongoosejs.com/docs/guide.html#writeConcern): object - defaults to null, use to override [the MongoDB server's default write concern settings](https://www.mongodb.com/docs/manual/reference/write-concern/) * - [shardKey](https://mongoosejs.com/docs/guide.html#shardKey): object - defaults to `null` * - [strict](https://mongoosejs.com/docs/guide.html#strict): bool - defaults to true diff --git a/test/schema.test.js b/test/schema.test.js index 0092a44a4ee..8cd58ba7b9f 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -3237,4 +3237,25 @@ describe('schema', function() { assert.equal(doc.element, '#hero'); assert.ok(doc instanceof ClickedModel); }); + + it('supports schema-level readConcern (gh-14511)', async function() { + const eventSchema = new mongoose.Schema({ + name: String + }, { readConcern: { level: 'available' } }); + const Event = db.model('Test', eventSchema); + + let q = Event.find(); + let options = q._optionsForExec(); + assert.deepStrictEqual(options.readConcern, { level: 'available' }); + + q = Event.find().setOptions({ readConcern: { level: 'local' } }); + options = q._optionsForExec(); + assert.deepStrictEqual(options.readConcern, { level: 'local' }); + + q = Event.find().setOptions({ readConcern: null }); + options = q._optionsForExec(); + assert.deepStrictEqual(options.readConcern, null); + + await q; + }); }); diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 31795187cf0..4df87a806ea 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -120,6 +120,10 @@ declare module 'mongoose' { * to all queries derived from a model. */ read?: string; + /** + * Set a default readConcern for all queries at the schema level + */ + readConcern?: { level: 'local' | 'available' | 'majority' | 'snapshot' | 'linearizable' } /** Allows setting write concern at the schema level. */ writeConcern?: WriteConcern; /** defaults to true. */