Skip to content
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

feat(schema): add schema-level readConcern option to apply default readConcern for all queries #14579

Merged
merged 2 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ Valid options:
* [methods](#methods)
* [query](#query-helpers)
* [autoSearchIndex](#autoSearchIndex)
* [readConcern](#readConcern)

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

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

<h2 id="readConcern">
<a href="#readConcern">
option: readConcern
</a>
</h2>

[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
}
);
```

<h2 id="es6-classes"><a href="#es6-classes">With ES6 Classes</a></h2>

Schemas have a [`loadClass()` method](api/schema.html#schema_Schema-loadClass)
Expand Down
22 changes: 22 additions & 0 deletions lib/helpers/schema/applyReadConcern.js
Original file line number Diff line number Diff line change
@@ -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 };
}
};
3 changes: 3 additions & 0 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions test/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
});
4 changes: 4 additions & 0 deletions types/schemaoptions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down