Skip to content

Commit 58ef4a2

Browse files
committed
create ExplainableCommand class
1 parent d538439 commit 58ef4a2

File tree

10 files changed

+105
-109
lines changed

10 files changed

+105
-109
lines changed

src/cmap/wire_protocol/command.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { Topology } from '../../sdam/topology';
1010
import type { ReadPreferenceLike } from '../../read_preference';
1111
import type { WriteConcernOptions, WriteConcern, W } from '../../write_concern';
1212
import type { WriteCommandOptions } from './write_command';
13+
import { decorateWithExplain } from '../../explain';
1314

1415
/** @internal */
1516
export interface CommandOptions extends BSONSerializeOptions {
@@ -68,6 +69,11 @@ export function command(
6869
return callback(new MongoError(`command ${JSON.stringify(cmd)} does not return a cursor`));
6970
}
7071

72+
// TODO: should not modify the command here
73+
if (cmd.explain !== undefined) {
74+
cmd = decorateWithExplain(cmd, cmd.explain);
75+
}
76+
7177
if (!isClientEncryptionEnabled(server)) {
7278
_command(server, ns, cmd, options, callback);
7379
return;

src/cmap/wire_protocol/write_command.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { MongoError } from '../../error';
2-
import { collectionNamespace, Callback, decorateWithExplain } from '../../utils';
2+
import { collectionNamespace, Callback } from '../../utils';
33
import { command, CommandOptions } from './command';
44
import type { Server } from '../../sdam/server';
55
import type { Document, BSONSerializeOptions } from '../../bson';
66
import type { WriteConcern } from '../../write_concern';
7-
import type { ExplainOptions } from '../../explain';
7+
import { Explain, ExplainOptions } from '../../explain';
88

99
/** @public */
1010
export interface CollationOptions {
@@ -44,7 +44,7 @@ export function writeCommand(
4444
options = options || {};
4545
const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
4646
const writeConcern = options.writeConcern;
47-
let writeCommand: Document = {};
47+
const writeCommand: Document = {};
4848
writeCommand[type] = collectionNamespace(ns);
4949
writeCommand[opsField] = ops;
5050
writeCommand.ordered = ordered;
@@ -66,7 +66,7 @@ export function writeCommand(
6666
}
6767

6868
if (options.explain !== undefined) {
69-
writeCommand = decorateWithExplain(writeCommand, options);
69+
writeCommand.explain = Explain.fromOptions(options);
7070
}
7171

7272
const commandOptions = Object.assign(

src/explain.ts

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,50 @@
1+
import type { Callback, Document } from '.';
2+
import { MongoError } from './error';
3+
import { CommandOperation, CommandOperationOptions, OperationParent } from './operations/command';
14
import type { Server } from './sdam/server';
25
import { maxWireVersion } from './utils';
36

4-
export const SUPPORTS_EXPLAIN_WITH_REMOVE = 3;
5-
export const SUPPORTS_EXPLAIN_WITH_UPDATE = 3;
6-
export const SUPPORTS_EXPLAIN_WITH_DISTINCT = 3.2;
7-
export const SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY = 3.2;
8-
export const SUPPORTS_EXPLAIN_WITH_MAP_REDUCE = 4.4;
7+
const SUPPORTS_EXPLAIN_WITH_REMOVE = 3;
8+
const SUPPORTS_EXPLAIN_WITH_UPDATE = 3;
9+
const SUPPORTS_EXPLAIN_WITH_DISTINCT = 3.2;
10+
const SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY = 3.2;
11+
const SUPPORTS_EXPLAIN_WITH_MAP_REDUCE = 4.4;
12+
13+
/** @internal */
14+
export abstract class ExplainableCommand<
15+
T extends ExplainOptions = ExplainOptions,
16+
TResult = Document
17+
> extends CommandOperation<T, TResult> {
18+
explain?: Explain;
19+
20+
constructor(parent?: OperationParent, options?: T) {
21+
super(parent, options);
22+
this.explain = Explain.fromOptions(options);
23+
}
24+
25+
get canRetryWrite(): boolean {
26+
return this.explain === undefined;
27+
}
28+
29+
executeCommand(server: Server, cmd: Document, callback: Callback): void {
30+
if (this.explain) {
31+
if (!Explain.explainSupportedOnCmd(server, cmd)) {
32+
callback(new MongoError(`server ${server.name} does not support explain on this command`));
33+
return;
34+
}
35+
36+
cmd.explain = this.explain;
37+
}
38+
super.executeCommand(server, cmd, callback);
39+
}
40+
}
941

1042
/** @public */
11-
export interface ExplainOptions {
43+
export interface ExplainOptions extends CommandOperationOptions {
1244
explain?: VerbosityLike;
1345
}
1446

47+
/** @public */
1548
export enum Verbosity {
1649
queryPlanner = 'queryPlanner',
1750
queryPlannerExtended = 'queryPlannerExtended',
@@ -22,6 +55,7 @@ export enum Verbosity {
2255
/** @public */
2356
export type VerbosityLike = Verbosity | boolean;
2457

58+
/** @internal */
2559
export class Explain {
2660
explain: Verbosity;
2761

@@ -51,16 +85,43 @@ export class Explain {
5185
*/
5286
static explainSupported(server: Server, op: string): boolean {
5387
const wireVersion = maxWireVersion(server);
54-
if (
88+
return (
5589
(op === 'remove' && wireVersion >= SUPPORTS_EXPLAIN_WITH_REMOVE) ||
5690
(op === 'update' && wireVersion >= SUPPORTS_EXPLAIN_WITH_UPDATE) ||
5791
(op === 'distinct' && wireVersion >= SUPPORTS_EXPLAIN_WITH_DISTINCT) ||
5892
(op === 'findAndModify' && wireVersion >= SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY) ||
5993
(op === 'mapReduce' && wireVersion >= SUPPORTS_EXPLAIN_WITH_MAP_REDUCE)
60-
) {
61-
return true;
62-
}
94+
);
95+
}
6396

64-
return false;
97+
static explainSupportedOnCmd(server: Server, cmd: Document): boolean {
98+
const wireVersion = maxWireVersion(server);
99+
return (
100+
(cmd.remove && wireVersion >= SUPPORTS_EXPLAIN_WITH_REMOVE) ||
101+
(cmd.update && wireVersion >= SUPPORTS_EXPLAIN_WITH_UPDATE) ||
102+
(cmd.distinct && wireVersion >= SUPPORTS_EXPLAIN_WITH_DISTINCT) ||
103+
(cmd.findAndModify && wireVersion >= SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY) ||
104+
(cmd.mapReduce && wireVersion >= SUPPORTS_EXPLAIN_WITH_MAP_REDUCE)
105+
);
65106
}
66107
}
108+
109+
/**
110+
* Applies an explain to a given command.
111+
* @internal
112+
*
113+
* @param command - the command on which to apply the read concern
114+
* @param options - the options containing the explain verbosity
115+
*/
116+
export function decorateWithExplain(command: Document, options: ExplainOptions): Document {
117+
const explain = Explain.fromOptions(options);
118+
if (explain === undefined) return command;
119+
120+
// A command may not have an explain field on it
121+
if (command.explain !== undefined) {
122+
delete command.explain;
123+
}
124+
125+
command = { explain: command, verbosity: explain.explain };
126+
return command;
127+
}

src/operations/command.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Aspect, OperationBase, OperationOptions } from './operation';
22
import { ReadConcern } from '../read_concern';
33
import { WriteConcern, WriteConcernOptions } from '../write_concern';
4-
import { maxWireVersion, MongoDBNamespace, Callback, decorateWithExplain } from '../utils';
4+
import { maxWireVersion, MongoDBNamespace, Callback } from '../utils';
55
import { ReadPreference, ReadPreferenceLike } from '../read_preference';
66
import { commandSupportsReadConcern } from '../sessions';
77
import { MongoError } from '../error';
@@ -10,7 +10,6 @@ import type { Server } from '../sdam/server';
1010
import { BSONSerializeOptions, Document, resolveBSONOptions } from '../bson';
1111
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
1212
import type { ReadConcernLike } from './../read_concern';
13-
import type { Explain } from '../explain';
1413

1514
const SUPPORTS_WRITE_CONCERN_AND_COLLATION = 5;
1615

@@ -55,7 +54,6 @@ export abstract class CommandOperation<
5554
readPreference: ReadPreference;
5655
readConcern?: ReadConcern;
5756
writeConcern?: WriteConcern;
58-
explain?: Explain;
5957
fullResponse?: boolean;
6058
logger?: Logger;
6159

@@ -96,10 +94,6 @@ export abstract class CommandOperation<
9694
this.bsonOptions = resolveBSONOptions(options, parent);
9795
}
9896

99-
get canRetryWrite(): boolean {
100-
return this.explain === undefined;
101-
}
102-
10397
abstract execute(server: Server, callback: Callback<TResult>): void;
10498

10599
executeCommand(server: Server, cmd: Document, callback: Callback): void {
@@ -141,12 +135,6 @@ export abstract class CommandOperation<
141135
cmd.comment = options.comment;
142136
}
143137

144-
// If we have reached this point, then the explain verbosity must be valid
145-
// Note: explain inherits any comment from its command
146-
if (this.explain) {
147-
cmd = decorateWithExplain(cmd, { explain: this.explain.explain }); // TODO!
148-
}
149-
150138
if (this.logger && this.logger.isDebug()) {
151139
this.logger.debug(`executing command ${JSON.stringify(cmd)} against ${this.ns}`);
152140
}

src/operations/delete.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { defineAspects, Aspect, OperationBase, Hint } from './operation';
22
import { removeDocuments } from './common_functions';
3-
import { CommandOperation, CommandOperationOptions } from './command';
43
import { isObject } from 'util';
54
import type { Callback, MongoDBNamespace } from '../utils';
65
import type { Document } from '../bson';
76
import type { Server } from '../sdam/server';
87
import type { Collection } from '../collection';
98
import type { WriteCommandOptions } from '../cmap/wire_protocol/write_command';
109
import type { Connection } from '../cmap/connection';
11-
import { Explain, ExplainOptions } from '../explain';
10+
import { ExplainableCommand, ExplainOptions } from '../explain';
1211

1312
/** @public */
14-
export interface DeleteOptions extends CommandOperationOptions, ExplainOptions {
13+
export interface DeleteOptions extends ExplainOptions {
1514
single?: boolean;
1615
hint?: Hint;
1716
}
@@ -52,7 +51,7 @@ export class DeleteOperation extends OperationBase<DeleteOptions, Document> {
5251
}
5352
}
5453

55-
export class DeleteOneOperation extends CommandOperation<DeleteOptions, DeleteResult> {
54+
export class DeleteOneOperation extends ExplainableCommand<DeleteOptions, DeleteResult> {
5655
collection: Collection;
5756
filter: Document;
5857

@@ -61,7 +60,6 @@ export class DeleteOneOperation extends CommandOperation<DeleteOptions, DeleteRe
6160

6261
this.collection = collection;
6362
this.filter = filter;
64-
this.explain = Explain.fromOptions(options);
6563
}
6664

6765
execute(server: Server, callback: Callback<DeleteResult>): void {
@@ -77,16 +75,13 @@ export class DeleteOneOperation extends CommandOperation<DeleteOptions, DeleteRe
7775
return callback(undefined, { acknowledged: true, deletedCount: 0, result: { ok: 1 } });
7876
}
7977

80-
// If an explain option was executed, don't process the server results
81-
if (this.explain) return callback(undefined, r);
82-
8378
r.deletedCount = r.n;
8479
if (callback) callback(undefined, r);
8580
});
8681
}
8782
}
8883

89-
export class DeleteManyOperation extends CommandOperation<DeleteOptions, DeleteResult> {
84+
export class DeleteManyOperation extends ExplainableCommand<DeleteOptions, DeleteResult> {
9085
collection: Collection;
9186
filter: Document;
9287

@@ -99,7 +94,6 @@ export class DeleteManyOperation extends CommandOperation<DeleteOptions, DeleteR
9994

10095
this.collection = collection;
10196
this.filter = filter;
102-
this.explain = Explain.fromOptions(options);
10397
}
10498

10599
execute(server: Server, callback: Callback<DeleteResult>): void {
@@ -119,9 +113,6 @@ export class DeleteManyOperation extends CommandOperation<DeleteOptions, DeleteR
119113
return callback(undefined, { acknowledged: true, deletedCount: 0, result: { ok: 1 } });
120114
}
121115

122-
// If an explain option was executed, don't process the server results
123-
if (this.explain) return callback(undefined, r);
124-
125116
r.deletedCount = r.n;
126117
if (callback) callback(undefined, r);
127118
});

src/operations/distinct.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import { Aspect, defineAspects } from './operation';
2-
import { CommandOperation, CommandOperationOptions } from './command';
3-
import { decorateWithCollation, decorateWithReadConcern, Callback, maxWireVersion } from '../utils';
2+
import { decorateWithCollation, decorateWithReadConcern, Callback } from '../utils';
43
import type { Document } from '../bson';
54
import type { Server } from '../sdam/server';
65
import type { Collection } from '../collection';
7-
import { Explain, ExplainOptions, SUPPORTS_EXPLAIN_WITH_DISTINCT } from '../explain';
8-
import { MongoError } from '../error';
6+
import { ExplainableCommand, ExplainOptions } from '../explain';
97

108
/** @public */
11-
export interface DistinctOptions extends CommandOperationOptions, ExplainOptions {}
9+
export type DistinctOptions = ExplainOptions;
1210

1311
/** @internal Return a list of distinct values for the given key across a collection. */
14-
export class DistinctOperation extends CommandOperation<DistinctOptions, Document[]> {
12+
export class DistinctOperation extends ExplainableCommand<DistinctOptions, Document[]> {
1513
collection: Collection;
1614
/** Field of the document to find distinct values for. */
1715
key: string;
@@ -32,7 +30,6 @@ export class DistinctOperation extends CommandOperation<DistinctOptions, Documen
3230
this.collection = collection;
3331
this.key = key;
3432
this.query = query;
35-
this.explain = Explain.fromOptions(options);
3633
}
3734

3835
execute(server: Server, callback: Callback<Document[]>): void {
@@ -63,15 +60,6 @@ export class DistinctOperation extends CommandOperation<DistinctOptions, Documen
6360
return callback(err);
6461
}
6562

66-
if (this.explain) {
67-
if (maxWireVersion(server) < SUPPORTS_EXPLAIN_WITH_DISTINCT) {
68-
callback(
69-
new MongoError('the current topology does not support explain on distinct commands')
70-
);
71-
return;
72-
}
73-
}
74-
7563
super.executeCommand(server, cmd, (err, result) => {
7664
if (err) {
7765
callback(err);

src/operations/find_and_modify.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@ import {
88
Callback
99
} from '../utils';
1010
import { MongoError } from '../error';
11-
import { CommandOperation, CommandOperationOptions } from './command';
1211
import { defineAspects, Aspect } from './operation';
1312
import type { Document } from '../bson';
1413
import type { Server } from '../sdam/server';
1514
import type { Collection } from '../collection';
1615
import { Sort, formatSort } from '../sort';
17-
import { Explain, ExplainOptions, SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY } from '../explain';
16+
import { ExplainableCommand, ExplainOptions } from '../explain';
1817

1918
/** @public */
20-
export interface FindAndModifyOptions extends CommandOperationOptions, ExplainOptions {
19+
export interface FindAndModifyOptions extends ExplainOptions {
2120
/** When false, returns the updated document rather than the original. The default is true. */
2221
returnOriginal?: boolean;
2322
/** Upsert the document if it does not exist. */
@@ -42,7 +41,7 @@ export interface FindAndModifyOptions extends CommandOperationOptions, ExplainOp
4241
}
4342

4443
/** @internal */
45-
export class FindAndModifyOperation extends CommandOperation<FindAndModifyOptions, Document> {
44+
export class FindAndModifyOperation extends ExplainableCommand<FindAndModifyOptions, Document> {
4645
collection: Collection;
4746
query: Document;
4847
sort?: Sort;
@@ -64,7 +63,6 @@ export class FindAndModifyOperation extends CommandOperation<FindAndModifyOption
6463
this.query = query;
6564
this.sort = sort;
6665
this.doc = doc;
67-
this.explain = Explain.fromOptions(options);
6866
}
6967

7068
execute(server: Server, callback: Callback<Document>): void {
@@ -145,15 +143,6 @@ export class FindAndModifyOperation extends CommandOperation<FindAndModifyOption
145143
cmd.hint = options.hint;
146144
}
147145

148-
if (this.explain) {
149-
if (maxWireVersion(server) < SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY) {
150-
callback(
151-
new MongoError('the current topology does not support explain on findAndModify commands')
152-
);
153-
return;
154-
}
155-
}
156-
157146
// Execute the command
158147
super.executeCommand(server, cmd, (err, result) => {
159148
if (err) return callback(err);

0 commit comments

Comments
 (0)