Skip to content

Commit 58e4252

Browse files
authored
refactor: move find command building all into the find operation (#2643)
One of the changes made in merging the core and native drivers was to merge the wire protocol methods for executing find operations. A vestigial piece of this was that the find command was being built in the wire protocol layer, rather than the operation layer. This patch teases these two apart, so that the `query` wire protocol method only creates and executes an `OP_QUERY` message, and the modern and legacy find commands are built in the find operation's definition itself. NODE-2900
1 parent 577d6eb commit 58e4252

File tree

8 files changed

+292
-353
lines changed

8 files changed

+292
-353
lines changed

src/cmap/connection.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import { StreamDescription, StreamDescriptionOptions } from './stream_descriptio
44
import * as wp from './wire_protocol';
55
import { CommandStartedEvent, CommandFailedEvent, CommandSucceededEvent } from './events';
66
import { updateSessionFromResponse } from '../sessions';
7-
import { uuidV4, ClientMetadata, now, calculateDurationInMs, Callback } from '../utils';
7+
import {
8+
uuidV4,
9+
ClientMetadata,
10+
now,
11+
calculateDurationInMs,
12+
Callback,
13+
MongoDBNamespace
14+
} from '../utils';
815
import {
916
MongoError,
1017
MongoNetworkError,
@@ -23,7 +30,7 @@ import type { GetMoreOptions } from './wire_protocol/get_more';
2330
import type { InsertOptions, UpdateOptions, RemoveOptions } from './wire_protocol/index';
2431
import type { Stream } from './connect';
2532
import type { LoggerOptions } from '../logger';
26-
import type { FindOptions } from '../operations/find';
33+
import type { QueryOptions } from './wire_protocol/query';
2734

2835
const kStream = Symbol('stream');
2936
const kQueue = Symbol('queue');
@@ -246,7 +253,7 @@ export class Connection extends EventEmitter {
246253
wp.command(makeServerTrampoline(this), ns, cmd, options as CommandOptions, callback);
247254
}
248255

249-
query(ns: string, cmd: Document, options: FindOptions, callback: Callback): void {
256+
query(ns: MongoDBNamespace, cmd: Document, options: QueryOptions, callback: Callback): void {
250257
wp.query(makeServerTrampoline(this), ns, cmd, options, callback);
251258
}
252259

src/cmap/wire_protocol/query.ts

Lines changed: 66 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,40 @@
1-
import { command, CommandOptions } from './command';
2-
import { Query } from '../commands';
3-
import { MongoError } from '../../error';
4-
import { maxWireVersion, collectionNamespace, Callback, decorateWithExplain } from '../../utils';
5-
import { getReadPreference, isSharded, applyCommonQueryOptions } from './shared';
6-
import { Document, pluckBSONSerializeOptions } from '../../bson';
1+
import { OpQueryOptions, Query } from '../commands';
2+
import type { Callback, MongoDBNamespace } from '../../utils';
3+
import { BSONSerializeOptions, Document, pluckBSONSerializeOptions } from '../../bson';
74
import type { Server } from '../../sdam/server';
8-
import type { ReadPreferenceLike } from '../../read_preference';
9-
import type { FindOptions } from '../../operations/find';
10-
import { Explain } from '../../explain';
5+
import { ReadPreference } from '../../read_preference';
116

127
/** @internal */
13-
export interface QueryOptions extends CommandOptions {
14-
readPreference?: ReadPreferenceLike;
8+
export interface QueryOptions extends BSONSerializeOptions {
9+
readPreference: ReadPreference;
10+
documentsReturnedIn?: string;
11+
batchSize?: number;
12+
limit?: number;
13+
skip?: number;
14+
projection?: Document;
15+
tailable?: boolean;
16+
awaitData?: boolean;
17+
noCursorTimeout?: boolean;
18+
/** @deprecated use `noCursorTimeout` instead */
19+
timeout?: boolean;
20+
partial?: boolean;
21+
oplogReplay?: boolean;
1522
}
1623

1724
export function query(
1825
server: Server,
19-
ns: string,
20-
cmd: Document,
21-
options: FindOptions,
26+
ns: MongoDBNamespace,
27+
findCommand: Document,
28+
options: QueryOptions,
2229
callback: Callback
2330
): void {
2431
options = options || {};
2532

26-
if (cmd == null) {
27-
return callback(new MongoError(`command ${JSON.stringify(cmd)} does not return a cursor`));
28-
}
29-
30-
if (maxWireVersion(server) < 4) {
31-
const query = prepareLegacyFindQuery(server, ns, cmd, options);
32-
const queryOptions = applyCommonQueryOptions(
33-
{},
34-
Object.assign(options, { ...pluckBSONSerializeOptions(options) })
35-
);
36-
37-
queryOptions.fullResult = true;
38-
if (typeof query.documentsReturnedIn === 'string') {
39-
queryOptions.documentsReturnedIn = query.documentsReturnedIn;
40-
}
41-
42-
server.s.pool.write(query, queryOptions, callback);
43-
return;
44-
}
45-
46-
const readPreference = getReadPreference(cmd, options);
47-
let findCmd = prepareFindCommand(server, ns, cmd);
48-
49-
// If we have explain, we need to rewrite the find command
50-
// to wrap it in the explain command
51-
const explain = Explain.fromOptions(options);
52-
if (explain) {
53-
findCmd = decorateWithExplain(findCmd, explain);
54-
}
55-
56-
// NOTE: This actually modifies the passed in cmd, and our code _depends_ on this
57-
// side-effect. Change this ASAP
58-
cmd.virtual = false;
59-
60-
const commandOptions = Object.assign(
61-
{
62-
documentsReturnedIn: 'firstBatch',
63-
numberToReturn: 1,
64-
slaveOk: readPreference.slaveOk()
65-
},
66-
options
67-
);
68-
69-
command(server, ns, findCmd, commandOptions, callback);
70-
}
71-
72-
function prepareFindCommand(server: Server, ns: string, cmd: Document) {
73-
const findCmd: Document = {
74-
find: collectionNamespace(ns)
75-
};
76-
77-
if (cmd.query) {
78-
if (cmd.query['$query']) {
79-
findCmd.filter = cmd.query['$query'];
80-
} else {
81-
findCmd.filter = cmd.query;
82-
}
83-
}
84-
85-
let sortValue = cmd.sort;
86-
if (Array.isArray(sortValue)) {
87-
const sortObject: Document = {};
88-
89-
if (sortValue.length > 0 && !Array.isArray(sortValue[0])) {
90-
let sortDirection = sortValue[1];
91-
if (sortDirection === 'asc') {
92-
sortDirection = 1;
93-
} else if (sortDirection === 'desc') {
94-
sortDirection = -1;
95-
}
96-
97-
sortObject[sortValue[0]] = sortDirection;
98-
} else {
99-
for (let i = 0; i < sortValue.length; i++) {
100-
let sortDirection = sortValue[i][1];
101-
if (sortDirection === 'asc') {
102-
sortDirection = 1;
103-
} else if (sortDirection === 'desc') {
104-
sortDirection = -1;
105-
}
106-
107-
sortObject[sortValue[i][0]] = sortDirection;
108-
}
109-
}
110-
111-
sortValue = sortObject;
112-
}
113-
114-
if (typeof cmd.allowDiskUse === 'boolean') {
115-
findCmd.allowDiskUse = cmd.allowDiskUse;
116-
}
117-
118-
if (cmd.sort) findCmd.sort = sortValue;
119-
if (cmd.fields) findCmd.projection = cmd.fields;
120-
if (cmd.hint) findCmd.hint = cmd.hint;
121-
if (cmd.skip) findCmd.skip = cmd.skip;
122-
if (cmd.limit) findCmd.limit = cmd.limit;
123-
if (cmd.limit < 0) {
124-
findCmd.limit = Math.abs(cmd.limit);
125-
findCmd.singleBatch = true;
126-
}
127-
128-
if (typeof cmd.batchSize === 'number') {
129-
if (cmd.batchSize < 0) {
130-
if (cmd.limit !== 0 && Math.abs(cmd.batchSize) < Math.abs(cmd.limit)) {
131-
findCmd.limit = Math.abs(cmd.batchSize);
132-
}
133-
134-
findCmd.singleBatch = true;
135-
}
136-
137-
findCmd.batchSize = Math.abs(cmd.batchSize);
138-
}
139-
140-
if (cmd.comment) findCmd.comment = cmd.comment;
141-
if (cmd.maxScan) findCmd.maxScan = cmd.maxScan;
142-
if (cmd.maxTimeMS) findCmd.maxTimeMS = cmd.maxTimeMS;
143-
if (cmd.min) findCmd.min = cmd.min;
144-
if (cmd.max) findCmd.max = cmd.max;
145-
findCmd.returnKey = cmd.returnKey ? cmd.returnKey : false;
146-
findCmd.showRecordId = cmd.showDiskLoc ? cmd.showDiskLoc : false;
147-
if (cmd.snapshot) findCmd.snapshot = cmd.snapshot;
148-
if (cmd.tailable) findCmd.tailable = cmd.tailable;
149-
if (cmd.oplogReplay) findCmd.oplogReplay = cmd.oplogReplay;
150-
if (cmd.noCursorTimeout) findCmd.noCursorTimeout = cmd.noCursorTimeout;
151-
if (cmd.awaitData) findCmd.awaitData = cmd.awaitData;
152-
if (cmd.awaitdata) findCmd.awaitData = cmd.awaitdata;
153-
if (cmd.partial) findCmd.partial = cmd.partial;
154-
if (cmd.collation) findCmd.collation = cmd.collation;
155-
if (cmd.readConcern) findCmd.readConcern = cmd.readConcern;
156-
157-
return findCmd;
158-
}
159-
160-
function prepareLegacyFindQuery(
161-
server: Server,
162-
ns: string,
163-
cmd: Document,
164-
options: FindOptions
165-
): Query {
166-
options = options || {};
167-
168-
const readPreference = getReadPreference(cmd, options);
169-
const batchSize = cmd.batchSize || options.batchSize || 0;
170-
const limit = cmd.limit || options.limit;
171-
const numberToSkip = cmd.skip || options.skip || 0;
172-
33+
const isExplain = typeof findCommand.$explain !== 'undefined';
34+
const readPreference = options.readPreference ?? ReadPreference.primary;
35+
const batchSize = options.batchSize || 0;
36+
const limit = options.limit;
37+
const numberToSkip = options.skip || 0;
17338
let numberToReturn = 0;
17439
if (
17540
limit &&
@@ -180,66 +45,57 @@ function prepareLegacyFindQuery(
18045
numberToReturn = batchSize;
18146
}
18247

183-
const findCmd: Document = {};
184-
if (isSharded(server) && readPreference) {
185-
findCmd['$readPreference'] = readPreference.toJSON();
48+
if (isExplain) {
49+
// nToReturn must be 0 (match all) or negative (match N and close cursor)
50+
// nToReturn > 0 will give explain results equivalent to limit(0)
51+
numberToReturn = -Math.abs(limit || 0);
18652
}
18753

188-
if (cmd.sort) findCmd['$orderby'] = cmd.sort;
189-
if (cmd.hint) findCmd['$hint'] = cmd.hint;
190-
if (cmd.snapshot) findCmd['$snapshot'] = cmd.snapshot;
191-
if (typeof cmd.returnKey !== 'undefined') findCmd['$returnKey'] = cmd.returnKey;
192-
if (cmd.maxScan) findCmd['$maxScan'] = cmd.maxScan;
193-
if (cmd.min) findCmd['$min'] = cmd.min;
194-
if (cmd.max) findCmd['$max'] = cmd.max;
195-
if (typeof cmd.showDiskLoc !== 'undefined') {
196-
findCmd['$showDiskLoc'] = cmd.showDiskLoc;
197-
} else if (typeof cmd.showRecordId !== 'undefined') {
198-
findCmd['$showDiskLoc'] = cmd.showRecordId;
54+
const queryOptions: OpQueryOptions = {
55+
numberToSkip,
56+
numberToReturn,
57+
pre32Limit: typeof limit === 'number' ? limit : undefined,
58+
checkKeys: false,
59+
slaveOk: readPreference.slaveOk()
60+
};
61+
62+
if (options.projection) {
63+
queryOptions.returnFieldSelector = options.projection;
19964
}
20065

201-
if (cmd.comment) findCmd['$comment'] = cmd.comment;
202-
if (cmd.maxTimeMS) findCmd['$maxTimeMS'] = cmd.maxTimeMS;
203-
if (options.explain) {
204-
// nToReturn must be 0 (match all) or negative (match N and close cursor)
205-
// nToReturn > 0 will give explain results equivalent to limit(0)
206-
numberToReturn = -Math.abs(cmd.limit || 0);
207-
findCmd['$explain'] = true;
66+
const query = new Query(ns.toString(), findCommand, queryOptions);
67+
if (typeof options.tailable === 'boolean') {
68+
query.tailable = options.tailable;
20869
}
20970

210-
findCmd['$query'] = cmd.query;
211-
if (cmd.readConcern && cmd.readConcern.level !== 'local') {
212-
throw new MongoError(
213-
`server find command does not support a readConcern level of ${cmd.readConcern.level}`
214-
);
71+
if (typeof options.oplogReplay === 'boolean') {
72+
query.oplogReplay = options.oplogReplay;
21573
}
21674

217-
if (cmd.readConcern) {
218-
cmd = Object.assign({}, cmd);
219-
delete cmd['readConcern'];
75+
if (typeof options.timeout === 'boolean') {
76+
query.noCursorTimeout = options.timeout;
77+
} else if (typeof options.noCursorTimeout === 'boolean') {
78+
query.noCursorTimeout = options.noCursorTimeout;
22079
}
22180

222-
const serializeFunctions =
223-
typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
224-
const ignoreUndefined =
225-
typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : false;
81+
if (typeof options.awaitData === 'boolean') {
82+
query.awaitData = options.awaitData;
83+
}
22684

227-
const query = new Query(ns, findCmd, {
228-
numberToSkip,
229-
numberToReturn,
230-
pre32Limit: typeof limit === 'number' ? limit : undefined,
231-
checkKeys: false,
232-
returnFieldSelector: cmd.fields,
233-
serializeFunctions,
234-
ignoreUndefined
235-
});
85+
if (typeof options.partial === 'boolean') {
86+
query.partial = options.partial;
87+
}
23688

237-
if (typeof cmd.tailable === 'boolean') query.tailable = cmd.tailable;
238-
if (typeof cmd.oplogReplay === 'boolean') query.oplogReplay = cmd.oplogReplay;
239-
if (typeof cmd.noCursorTimeout === 'boolean') query.noCursorTimeout = cmd.noCursorTimeout;
240-
if (typeof cmd.awaitData === 'boolean') query.awaitData = cmd.awaitData;
241-
if (typeof cmd.partial === 'boolean') query.partial = cmd.partial;
89+
server.s.pool.write(
90+
query,
91+
{ fullResult: true, ...pluckBSONSerializeOptions(options) },
92+
(err, result) => {
93+
if (err || !result) return callback(err, result);
94+
if (isExplain && result.documents && result.documents[0]) {
95+
return callback(undefined, result.documents[0]);
96+
}
24297

243-
query.slaveOk = readPreference.slaveOk();
244-
return query;
98+
callback(undefined, result);
99+
}
100+
);
245101
}

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ export type {
143143
UpdateOptions as WireUpdateOptions,
144144
RemoveOptions as WireRemoveOptions
145145
} from './cmap/wire_protocol/index';
146-
export type { QueryOptions } from './cmap/wire_protocol/query';
147146
export type { CollationOptions, WriteCommandOptions } from './cmap/wire_protocol/write_command';
148147
export type { CollectionPrivate, CollectionOptions } from './collection';
149148
export type { AggregationCursorOptions } from './cursor/aggregation_cursor';

0 commit comments

Comments
 (0)