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)
@@ -1473,6 +1474,24 @@ schema.searchIndex({
const Test = mongoose.model('Test', schema);
```
+
+
+[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
+ }
+);
+```
+
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. */