Skip to content

Commit 61aca0d

Browse files
committed
refactor: move find command building to FindOperation
The primary work of this ticket was to make the `Collection#find` method behave like other read operations by moving the command construction to the `FindOperation` itself. Along the way a number of improvements were made: - cursors now know they are tailable and/or awaitData upon construction, rather than observing the internal `cmd` they are built around. As much as possible the cursor implementation now no longer alters behavior after initialization based on what is in the internal `cmd` document - a number of supported, but undocumented options were added to the `find` method itself - `bsonOptions` were introduced to the `OperationBase` class as a first step towards improving our story around overriding these values and passing them to wire protocol methods NODE-2830
1 parent dfca70d commit 61aca0d

File tree

14 files changed

+372
-316
lines changed

14 files changed

+372
-316
lines changed

src/bson.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,24 @@ export interface BSONSerializeOptions extends SerializeOptions {
4848

4949
raw?: boolean;
5050
}
51+
52+
export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSerializeOptions {
53+
const {
54+
fieldsAsRaw,
55+
promoteValues,
56+
promoteBuffers,
57+
promoteLongs,
58+
serializeFunctions,
59+
ignoreUndefined,
60+
raw
61+
} = options;
62+
return {
63+
fieldsAsRaw,
64+
promoteValues,
65+
promoteBuffers,
66+
promoteLongs,
67+
serializeFunctions,
68+
ignoreUndefined,
69+
raw
70+
};
71+
}

src/cmap/wire_protocol/get_more.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { GetMore } from '../commands';
2-
import { Long, Document } from '../../bson';
2+
import { Long, Document, pluckBSONSerializeOptions } from '../../bson';
33
import { MongoError, MongoNetworkError } from '../../error';
44
import { applyCommonQueryOptions } from './shared';
55
import { maxWireVersion, collectionNamespace, Callback } from '../../utils';
@@ -71,7 +71,11 @@ export function getMore(
7171

7272
if (wireVersion < 4) {
7373
const getMoreOp = new GetMore(ns, cursorId, { numberToReturn: batchSize });
74-
const queryOptions = applyCommonQueryOptions({}, cursorState);
74+
const queryOptions = applyCommonQueryOptions(
75+
{},
76+
Object.assign({ bsonOptions: pluckBSONSerializeOptions(options) }, cursorState)
77+
);
78+
7579
queryOptions.fullResult = true;
7680
queryOptions.command = true;
7781
server.s.pool.write(getMoreOp, queryOptions, queryCallback);

src/cmap/wire_protocol/query.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Query } from '../commands';
33
import { MongoError } from '../../error';
44
import { maxWireVersion, collectionNamespace, Callback } from '../../utils';
55
import { getReadPreference, isSharded, applyCommonQueryOptions } from './shared';
6-
import type { Document } from '../../bson';
6+
import { Document, pluckBSONSerializeOptions } from '../../bson';
77
import type { Server } from '../../sdam/server';
88
import type { ReadPreferenceLike } from '../../read_preference';
99
import type { InternalCursorState } from '../../cursor/core_cursor';
@@ -32,9 +32,12 @@ export function query(
3232

3333
if (maxWireVersion(server) < 4) {
3434
const query = prepareLegacyFindQuery(server, ns, cmd, cursorState, options);
35-
const queryOptions = applyCommonQueryOptions({}, cursorState);
36-
queryOptions.fullResult = true;
35+
const queryOptions = applyCommonQueryOptions(
36+
{},
37+
Object.assign({ bsonOptions: pluckBSONSerializeOptions(options) }, cursorState)
38+
);
3739

40+
queryOptions.fullResult = true;
3841
if (typeof query.documentsReturnedIn === 'string') {
3942
queryOptions.documentsReturnedIn = query.documentsReturnedIn;
4043
}

src/cmap/wire_protocol/shared.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { TopologyDescription } from '../../sdam/topology_description';
33
import { MongoError } from '../../error';
44
import { ReadPreference } from '../../read_preference';
55
import type { Document } from '../../bson';
6-
import type { CommandOptions } from './command';
76
import type { OpQueryOptions } from '../commands';
87
import type { Topology } from '../../sdam/topology';
98
import type { Server } from '../../sdam/server';
109
import type { ServerDescription } from '../../sdam/server_description';
1110
import type { ReadPreferenceLike } from '../../read_preference';
11+
import type { InternalCursorState } from '../../cursor/core_cursor';
1212

1313
export interface ReadPreferenceOption {
1414
readPreference?: ReadPreferenceLike;
@@ -35,27 +35,28 @@ export function getReadPreference(cmd: Document, options: ReadPreferenceOption):
3535

3636
export function applyCommonQueryOptions(
3737
queryOptions: OpQueryOptions,
38-
options: CommandOptions
38+
cursorState: InternalCursorState
3939
): OpQueryOptions {
40-
Object.assign(queryOptions, {
41-
raw: typeof options.raw === 'boolean' ? options.raw : false,
42-
promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
43-
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
44-
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
45-
monitoring: typeof options.monitoring === 'boolean' ? options.monitoring : false,
46-
fullResult: typeof options.fullResult === 'boolean' ? options.fullResult : false
47-
});
48-
49-
if (typeof options.socketTimeout === 'number') {
50-
queryOptions.socketTimeout = options.socketTimeout;
51-
}
52-
53-
if (options.session) {
54-
queryOptions.session = options.session;
40+
if (cursorState.bsonOptions) {
41+
Object.assign(queryOptions, {
42+
raw: typeof cursorState.bsonOptions.raw === 'boolean' ? cursorState.bsonOptions.raw : false,
43+
promoteLongs:
44+
typeof cursorState.bsonOptions.promoteLongs === 'boolean'
45+
? cursorState.bsonOptions.promoteLongs
46+
: true,
47+
promoteValues:
48+
typeof cursorState.bsonOptions.promoteValues === 'boolean'
49+
? cursorState.bsonOptions.promoteValues
50+
: true,
51+
promoteBuffers:
52+
typeof cursorState.bsonOptions.promoteBuffers === 'boolean'
53+
? cursorState.bsonOptions.promoteBuffers
54+
: false
55+
});
5556
}
5657

57-
if (typeof options.documentsReturnedIn === 'string') {
58-
queryOptions.documentsReturnedIn = options.documentsReturnedIn;
58+
if (cursorState.session) {
59+
queryOptions.session = cursorState.session;
5960
}
6061

6162
return queryOptions;

src/collection.ts

Lines changed: 13 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ import { ReadPreference, ReadPreferenceLike } from './read_preference';
33
import { deprecate } from 'util';
44
import {
55
normalizeHintField,
6-
decorateCommand,
7-
decorateWithCollation,
8-
decorateWithReadConcern,
9-
formattedOrderClause,
106
checkCollectionName,
117
deprecateOptions,
128
MongoDBNamespace,
@@ -91,8 +87,6 @@ import type { Topology } from './sdam/topology';
9187
import type { Logger, LoggerOptions } from './logger';
9288
import type { OperationParent } from './operations/command';
9389

94-
const mergeKeys = ['ignoreUndefined'];
95-
9690
/** @public */
9791
export interface Collection {
9892
/** @deprecated Use {@link Collection.dropIndexes#Class} instead */
@@ -138,6 +132,7 @@ export interface CollectionPrivate {
138132
promoteLongs?: boolean;
139133
promoteValues?: boolean;
140134
promoteBuffers?: boolean;
135+
ignoreUndefined?: boolean;
141136
collectionHint?: Hint;
142137
readConcern?: ReadConcern;
143138
writeConcern?: WriteConcern;
@@ -212,7 +207,11 @@ export class Collection implements OperationParent {
212207
promoteBuffers:
213208
options == null || options.promoteBuffers == null
214209
? db.s.options?.promoteBuffers
215-
: options.promoteBuffers
210+
: options.promoteBuffers,
211+
ignoreUndefined:
212+
options == null || options.ignoreUndefined == null
213+
? db.s.options?.ignoreUndefined
214+
: options.ignoreUndefined
216215
};
217216
}
218217

@@ -680,155 +679,24 @@ export class Collection implements OperationParent {
680679
/**
681680
* Creates a cursor for a query that can be used to iterate over results from MongoDB
682681
*
683-
* @param query - The cursor query object.
684-
* @param options - Optional settings for the command
682+
* @param filter - The query predicate. If unspecified, then all documents in the collection will match the predicate
685683
*/
686684
find(): Cursor;
687-
find(query: Document): Cursor;
688-
find(query: Document, options: FindOptions): Cursor;
689-
find(query?: Document, options?: FindOptions): Cursor {
685+
find(filter: Document): Cursor;
686+
find(filter: Document, options: FindOptions): Cursor;
687+
find(filter?: Document, options?: FindOptions): Cursor {
690688
if (arguments.length > 2) {
691689
throw new TypeError('Third parameter to `collection.find()` must be undefined');
692690
}
693-
if (typeof query === 'function') {
694-
throw new TypeError('`query` parameter must not be function');
695-
}
696691
if (typeof options === 'function') {
697692
throw new TypeError('`options` parameter must not be function');
698693
}
699694

700-
let selector =
701-
query !== null && typeof query === 'object' && Array.isArray(query) === false ? query : {};
702-
703-
// Validate correctness off the selector
704-
const object = selector;
705-
if (Buffer.isBuffer(object)) {
706-
const object_size = object[0] | (object[1] << 8) | (object[2] << 16) | (object[3] << 24);
707-
if (object_size !== object.length) {
708-
const error = new Error(
709-
'query selector raw message size does not match message header size [' +
710-
object.length +
711-
'] != [' +
712-
object_size +
713-
']'
714-
);
715-
error.name = 'MongoError';
716-
throw error;
717-
}
718-
}
719-
720-
// Check special case where we are using an objectId
721-
if (selector != null && selector._bsontype === 'ObjectID') {
722-
selector = { _id: selector };
723-
}
724-
725-
if (!options) {
726-
options = {};
727-
}
728-
729-
let projection = options.projection || options.fields;
730-
731-
if (projection && !Buffer.isBuffer(projection) && Array.isArray(projection)) {
732-
projection = projection.length
733-
? projection.reduce((result, field) => {
734-
result[field] = 1;
735-
return result;
736-
}, {})
737-
: { _id: 1 };
738-
}
739-
740-
// Make a shallow copy of options
741-
const newOptions: Document = Object.assign({}, options);
742-
743-
// Make a shallow copy of the collection options
744-
for (const key in this.s.options) {
745-
if (mergeKeys.indexOf(key) !== -1) {
746-
newOptions[key] = this.s.options[key];
747-
}
748-
}
749-
750-
// Unpack options
751-
newOptions.skip = options.skip ? options.skip : 0;
752-
newOptions.limit = options.limit ? options.limit : 0;
753-
newOptions.raw = typeof options.raw === 'boolean' ? options.raw : this.s.raw;
754-
newOptions.hint =
755-
options.hint != null ? normalizeHintField(options.hint) : this.s.collectionHint;
756-
newOptions.timeout = typeof options.timeout === 'undefined' ? undefined : options.timeout;
757-
// // If we have overridden slaveOk otherwise use the default db setting
758-
newOptions.slaveOk = options.slaveOk != null ? options.slaveOk : this.s.db.slaveOk;
759-
760-
// Add read preference if needed
761-
newOptions.readPreference = ReadPreference.resolve(this, newOptions);
762-
763-
// Set slave ok to true if read preference different from primary
764-
if (
765-
newOptions.readPreference != null &&
766-
(newOptions.readPreference !== 'primary' || newOptions.readPreference.mode !== 'primary')
767-
) {
768-
newOptions.slaveOk = true;
769-
}
770-
771-
// Ensure the query is an object
772-
if (selector != null && typeof selector !== 'object') {
773-
throw new MongoError('query selector must be an object');
774-
}
775-
776-
// Build the find command
777-
const findCommand: Document = {
778-
find: this.s.namespace.toString(),
779-
limit: newOptions.limit,
780-
skip: newOptions.skip,
781-
query: selector
782-
};
783-
784-
if (typeof options.allowDiskUse === 'boolean') {
785-
findCommand.allowDiskUse = options.allowDiskUse;
786-
}
787-
788-
// Ensure we use the right await data option
789-
if (typeof newOptions.awaitdata === 'boolean') {
790-
newOptions.awaitData = newOptions.awaitdata;
791-
}
792-
793-
// Translate to new command option noCursorTimeout
794-
if (typeof newOptions.timeout === 'boolean') newOptions.noCursorTimeout = newOptions.timeout;
795-
796-
decorateCommand(findCommand, newOptions, ['session', 'collation']);
797-
798-
if (projection) findCommand.fields = projection;
799-
800-
// Add db object to the new options
801-
newOptions.db = this.s.db;
802-
803-
// Set raw if available at collection level
804-
if (newOptions.raw == null && typeof this.s.raw === 'boolean') newOptions.raw = this.s.raw;
805-
// Set promoteLongs if available at collection level
806-
if (newOptions.promoteLongs == null && typeof this.s.promoteLongs === 'boolean')
807-
newOptions.promoteLongs = this.s.promoteLongs;
808-
if (newOptions.promoteValues == null && typeof this.s.promoteValues === 'boolean')
809-
newOptions.promoteValues = this.s.promoteValues;
810-
if (newOptions.promoteBuffers == null && typeof this.s.promoteBuffers === 'boolean')
811-
newOptions.promoteBuffers = this.s.promoteBuffers;
812-
813-
// Sort options
814-
if (findCommand.sort) {
815-
findCommand.sort = formattedOrderClause(findCommand.sort);
816-
}
817-
818-
// Set the readConcern
819-
decorateWithReadConcern(findCommand, this, options);
820-
821-
// Decorate find command with collation options
822-
823-
decorateWithCollation(findCommand, this, options);
824-
825-
const cursor = new Cursor(
695+
return new Cursor(
826696
this.s.topology,
827-
new FindOperation(this, this.s.namespace, findCommand, newOptions),
828-
newOptions
697+
new FindOperation(this, this.s.namespace, filter, options),
698+
options
829699
);
830-
831-
return cursor;
832700
}
833701

834702
/**

0 commit comments

Comments
 (0)