diff --git a/src/cmap/commands.ts b/src/cmap/commands.ts index 7234127238..87acf9caa7 100644 --- a/src/cmap/commands.ts +++ b/src/cmap/commands.ts @@ -12,7 +12,7 @@ let _requestId = 0; // Query flags const OPTS_TAILABLE_CURSOR = 2; -const OPTS_SLAVE = 4; +const OPTS_SECONDARY = 4; const OPTS_OPLOG_REPLAY = 8; const OPTS_NO_CURSOR_TIMEOUT = 16; const OPTS_AWAIT_DATA = 32; @@ -41,7 +41,7 @@ export interface OpQueryOptions extends CommandOptions { ignoreUndefined?: boolean; maxBsonSize?: number; checkKeys?: boolean; - slaveOk?: boolean; + secondaryOk?: boolean; requestId?: number; moreToCome?: boolean; @@ -67,7 +67,7 @@ export class Query { checkKeys: boolean; batchSize: number; tailable: boolean; - slaveOk: boolean; + secondaryOk: boolean; oplogReplay: boolean; noCursorTimeout: boolean; awaitData: boolean; @@ -112,7 +112,7 @@ export class Query { // Flags this.tailable = false; - this.slaveOk = typeof options.slaveOk === 'boolean' ? options.slaveOk : false; + this.secondaryOk = typeof options.secondaryOk === 'boolean' ? options.secondaryOk : false; this.oplogReplay = false; this.noCursorTimeout = false; this.awaitData = false; @@ -146,8 +146,8 @@ export class Query { flags |= OPTS_TAILABLE_CURSOR; } - if (this.slaveOk) { - flags |= OPTS_SLAVE; + if (this.secondaryOk) { + flags |= OPTS_SECONDARY; } if (this.oplogReplay) { diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 63eb6f5dbe..77e106d666 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -96,7 +96,7 @@ export interface QueryOptions extends BSONSerializeOptions { /** @internal */ export interface CommandOptions extends BSONSerializeOptions { command?: boolean; - slaveOk?: boolean; + secondaryOk?: boolean; /** Specify read preference if command supports it */ readPreference?: ReadPreferenceLike; raw?: boolean; @@ -427,7 +427,7 @@ export class Connection extends TypedEventEmitter { numberToReturn: -1, checkKeys: false, // This value is not overridable - slaveOk: readPreference.slaveOk() + secondaryOk: readPreference.secondaryOk() }, options ); @@ -472,7 +472,7 @@ export class Connection extends TypedEventEmitter { numberToReturn, pre32Limit: typeof limit === 'number' ? limit : undefined, checkKeys: false, - slaveOk: readPreference.slaveOk() + secondaryOk: readPreference.secondaryOk() }; if (options.projection) { diff --git a/src/collection.ts b/src/collection.ts index f69d368d0b..2cc9165a62 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -112,6 +112,9 @@ export interface CollectionOptions extends BSONSerializeOptions, WriteConcernOptions, LoggerOptions { + /** + * @deprecated Use readPreference instead + */ slaveOk?: boolean; /** Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) */ readConcern?: ReadConcernLike; @@ -127,7 +130,6 @@ export interface CollectionPrivate { namespace: MongoDBNamespace; readPreference?: ReadPreference; bsonOptions: BSONSerializeOptions; - slaveOk?: boolean; collectionHint?: Hint; readConcern?: ReadConcern; writeConcern?: WriteConcern; @@ -181,8 +183,7 @@ export class Collection { readPreference: ReadPreference.fromOptions(options), bsonOptions: resolveBSONOptions(options, db), readConcern: ReadConcern.fromOptions(options), - writeConcern: WriteConcern.fromOptions(options), - slaveOk: options == null || options.slaveOk == null ? db.slaveOk : options.slaveOk + writeConcern: WriteConcern.fromOptions(options) }; } diff --git a/src/db.ts b/src/db.ts index 4d686d4457..9aa71abbf4 100644 --- a/src/db.ts +++ b/src/db.ts @@ -185,8 +185,18 @@ export class Db { return this.s.options; } - // slaveOk specified + /** + * slaveOk specified + * @deprecated Use secondaryOk instead + */ get slaveOk(): boolean { + return this.secondaryOk; + } + + /** + * Check if a secondary can be used (because the read preference is *not* set to primary) + */ + get secondaryOk(): boolean { return this.s.readPreference?.preference !== 'primary' || false; } diff --git a/src/read_preference.ts b/src/read_preference.ts index 5a5538e939..554bf23412 100644 --- a/src/read_preference.ts +++ b/src/read_preference.ts @@ -232,19 +232,27 @@ export class ReadPreference { } /** - * Indicates that this readPreference needs the "slaveOk" bit when sent over the wire - * + * Indicates that this readPreference needs the "secondaryOk" bit when sent over the wire + * @deprecated Use secondaryOk instead * @see https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op-query */ slaveOk(): boolean { - const NEEDS_SLAVEOK = new Set([ + return this.secondaryOk(); + } + + /** + * Indicates that this readPreference needs the "SecondaryOk" bit when sent over the wire + * @see https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/#op-query + */ + secondaryOk(): boolean { + const NEEDS_SECONDARYOK = new Set([ ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST ]); - return NEEDS_SLAVEOK.has(this.mode); + return NEEDS_SECONDARYOK.has(this.mode); } /** diff --git a/test/functional/cursor.test.js b/test/functional/cursor.test.js index 3156eb5008..2921ff4bd8 100644 --- a/test/functional/cursor.test.js +++ b/test/functional/cursor.test.js @@ -11,7 +11,6 @@ const { Writable } = require('stream'); const { ReadPreference } = require('../../src/read_preference'); const { ServerType } = require('../../src/sdam/common'); const { formatSort } = require('../../src/sort'); -const { FindCursor } = require('../../src/cursor/find_cursor'); describe('Cursor', function () { before(function () { @@ -3732,38 +3731,6 @@ describe('Cursor', function () { } }); - // NOTE: This is skipped because I don't think its correct or adds value. The expected error - // is not an error with hasNext (from server), but rather a local TypeError which should - // be caught anyway. The only solution here would be to wrap the entire top level call - // in a try/catch which is not going to happen. - it.skip('Should propagate hasNext errors when using a callback', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - test: function (done) { - const configuration = this.configuration; - var client = configuration.newClient({ w: 1 }, { maxPoolSize: 1 }); - client.connect((err, client) => { - expect(err).to.not.exist; - this.defer(() => client.close()); - - const db = client.db(configuration.db); - const cursor = new FindCursor( - db.s.topology, - db.s.namespace, - {}, - { limit: 0, skip: 0, slaveOk: false, readPreference: 42 } - ); - - cursor.hasNext(err => { - test.ok(err !== null); - test.equal(err.message, 'readPreference must be a ReadPreference instance'); - done(); - }); - }); - } - }); - it( 'should return implicit session to pool when client-side cursor exhausts results on initial query', { diff --git a/test/unit/db.test.ts b/test/unit/db.test.ts new file mode 100644 index 0000000000..48dfbb527e --- /dev/null +++ b/test/unit/db.test.ts @@ -0,0 +1,53 @@ +import { expect } from 'chai'; + +import { MongoClient } from '../../src'; +import { Db, DbOptions } from '../../src/db'; +import { ReadPreference } from '../../src/read_preference'; + +describe('class Db', function () { + describe('secondaryOk', function () { + const client = new MongoClient('mongodb://localhost:27017'); + const legacy_secondary_ok = 'slaveOk'; + const secondary_ok = 'secondaryOk'; + + it('should be false when readPreference is Primary', function () { + const options: DbOptions = { readPreference: ReadPreference.PRIMARY }; + const mydb = new Db(client, 'mydb', options); + + expect(mydb).property(secondary_ok).to.be.false; + expect(mydb).property(legacy_secondary_ok).to.be.false; + }); + + it('should be true when readPreference is Primary Preferred', function () { + const options: DbOptions = { readPreference: ReadPreference.PRIMARY_PREFERRED }; + const mydb = new Db(client, 'mydb', options); + + expect(mydb).property(secondary_ok).to.be.true; + expect(mydb).property(legacy_secondary_ok).to.be.true; + }); + + it('should be true when readPreference is Secondary', function () { + const options: DbOptions = { readPreference: ReadPreference.SECONDARY }; + const mydb = new Db(client, 'mydb', options); + + expect(mydb).property(secondary_ok).to.be.true; + expect(mydb).property(legacy_secondary_ok).to.be.true; + }); + + it('should be true when readPreference is Secondary Preferred', function () { + const options: DbOptions = { readPreference: ReadPreference.SECONDARY_PREFERRED }; + const mydb = new Db(client, 'mydb', options); + + expect(mydb).property(secondary_ok).to.be.true; + expect(mydb).property(legacy_secondary_ok).to.be.true; + }); + + it('should be true when readPreference is Nearest', function () { + const options: DbOptions = { readPreference: ReadPreference.NEAREST }; + const mydb = new Db(client, 'mydb', options); + + expect(mydb).property(secondary_ok).to.be.true; + expect(mydb).property(legacy_secondary_ok).to.be.true; + }); + }); +}); diff --git a/test/unit/read_preference.test.ts b/test/unit/read_preference.test.ts index 8f18aa22e8..1d96821485 100644 --- a/test/unit/read_preference.test.ts +++ b/test/unit/read_preference.test.ts @@ -4,7 +4,7 @@ import { ReadPreference } from '../../src'; describe('class ReadPreference', function () { const maxStalenessSeconds = 1234; - const { PRIMARY, SECONDARY, NEAREST } = ReadPreference; + const { PRIMARY, PRIMARY_PREFERRED, SECONDARY, SECONDARY_PREFERRED, NEAREST } = ReadPreference; const TAGS = [{ loc: 'dc' }]; describe('::constructor', function () { it('should accept (mode)', function () { @@ -132,4 +132,46 @@ describe('class ReadPreference', function () { ).to.throw('Primary read preference cannot be combined with maxStalenessSeconds'); }); }); + + describe('secondaryOk()', function () { + it('should be false when readPreference is Primary', function () { + const readPreference = ReadPreference.fromOptions({ + readPreference: PRIMARY + }); + expect(readPreference.secondaryOk()).to.be.false; + expect(readPreference.slaveOk()).to.be.false; + }); + + it('should be true when readPreference is Primary Preferred', function () { + const readPreference = ReadPreference.fromOptions({ + readPreference: PRIMARY_PREFERRED + }); + expect(readPreference.secondaryOk()).to.be.true; + expect(readPreference.slaveOk()).to.be.true; + }); + + it('should be true when readPreference is Secondary', function () { + const readPreference = ReadPreference.fromOptions({ + readPreference: SECONDARY + }); + expect(readPreference.secondaryOk()).to.be.true; + expect(readPreference.slaveOk()).to.be.true; + }); + + it('should be true when readPreference is Secondary Preferred', function () { + const readPreference = ReadPreference.fromOptions({ + readPreference: SECONDARY_PREFERRED + }); + expect(readPreference.secondaryOk()).to.be.true; + expect(readPreference.slaveOk()).to.be.true; + }); + + it('should be true when readPreference is Nearest', function () { + const readPreference = ReadPreference.fromOptions({ + readPreference: NEAREST + }); + expect(readPreference.secondaryOk()).to.be.true; + expect(readPreference.slaveOk()).to.be.true; + }); + }); });