Skip to content

Commit

Permalink
create ExplainableCommand class
Browse files Browse the repository at this point in the history
  • Loading branch information
HanaPearlman committed Oct 30, 2020
1 parent d538439 commit 58ef4a2
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 109 deletions.
6 changes: 6 additions & 0 deletions src/cmap/wire_protocol/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { Topology } from '../../sdam/topology';
import type { ReadPreferenceLike } from '../../read_preference';
import type { WriteConcernOptions, WriteConcern, W } from '../../write_concern';
import type { WriteCommandOptions } from './write_command';
import { decorateWithExplain } from '../../explain';

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

// TODO: should not modify the command here
if (cmd.explain !== undefined) {
cmd = decorateWithExplain(cmd, cmd.explain);
}

if (!isClientEncryptionEnabled(server)) {
_command(server, ns, cmd, options, callback);
return;
Expand Down
8 changes: 4 additions & 4 deletions src/cmap/wire_protocol/write_command.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MongoError } from '../../error';
import { collectionNamespace, Callback, decorateWithExplain } from '../../utils';
import { collectionNamespace, Callback } from '../../utils';
import { command, CommandOptions } from './command';
import type { Server } from '../../sdam/server';
import type { Document, BSONSerializeOptions } from '../../bson';
import type { WriteConcern } from '../../write_concern';
import type { ExplainOptions } from '../../explain';
import { Explain, ExplainOptions } from '../../explain';

/** @public */
export interface CollationOptions {
Expand Down Expand Up @@ -44,7 +44,7 @@ export function writeCommand(
options = options || {};
const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
const writeConcern = options.writeConcern;
let writeCommand: Document = {};
const writeCommand: Document = {};
writeCommand[type] = collectionNamespace(ns);
writeCommand[opsField] = ops;
writeCommand.ordered = ordered;
Expand All @@ -66,7 +66,7 @@ export function writeCommand(
}

if (options.explain !== undefined) {
writeCommand = decorateWithExplain(writeCommand, options);
writeCommand.explain = Explain.fromOptions(options);
}

const commandOptions = Object.assign(
Expand Down
83 changes: 72 additions & 11 deletions src/explain.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
import type { Callback, Document } from '.';
import { MongoError } from './error';
import { CommandOperation, CommandOperationOptions, OperationParent } from './operations/command';
import type { Server } from './sdam/server';
import { maxWireVersion } from './utils';

export const SUPPORTS_EXPLAIN_WITH_REMOVE = 3;
export const SUPPORTS_EXPLAIN_WITH_UPDATE = 3;
export const SUPPORTS_EXPLAIN_WITH_DISTINCT = 3.2;
export const SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY = 3.2;
export const SUPPORTS_EXPLAIN_WITH_MAP_REDUCE = 4.4;
const SUPPORTS_EXPLAIN_WITH_REMOVE = 3;
const SUPPORTS_EXPLAIN_WITH_UPDATE = 3;
const SUPPORTS_EXPLAIN_WITH_DISTINCT = 3.2;
const SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY = 3.2;
const SUPPORTS_EXPLAIN_WITH_MAP_REDUCE = 4.4;

/** @internal */
export abstract class ExplainableCommand<
T extends ExplainOptions = ExplainOptions,
TResult = Document
> extends CommandOperation<T, TResult> {
explain?: Explain;

constructor(parent?: OperationParent, options?: T) {
super(parent, options);
this.explain = Explain.fromOptions(options);
}

get canRetryWrite(): boolean {
return this.explain === undefined;
}

executeCommand(server: Server, cmd: Document, callback: Callback): void {
if (this.explain) {
if (!Explain.explainSupportedOnCmd(server, cmd)) {
callback(new MongoError(`server ${server.name} does not support explain on this command`));
return;
}

cmd.explain = this.explain;
}
super.executeCommand(server, cmd, callback);
}
}

/** @public */
export interface ExplainOptions {
export interface ExplainOptions extends CommandOperationOptions {
explain?: VerbosityLike;
}

/** @public */
export enum Verbosity {
queryPlanner = 'queryPlanner',
queryPlannerExtended = 'queryPlannerExtended',
Expand All @@ -22,6 +55,7 @@ export enum Verbosity {
/** @public */
export type VerbosityLike = Verbosity | boolean;

/** @internal */
export class Explain {
explain: Verbosity;

Expand Down Expand Up @@ -51,16 +85,43 @@ export class Explain {
*/
static explainSupported(server: Server, op: string): boolean {
const wireVersion = maxWireVersion(server);
if (
return (
(op === 'remove' && wireVersion >= SUPPORTS_EXPLAIN_WITH_REMOVE) ||
(op === 'update' && wireVersion >= SUPPORTS_EXPLAIN_WITH_UPDATE) ||
(op === 'distinct' && wireVersion >= SUPPORTS_EXPLAIN_WITH_DISTINCT) ||
(op === 'findAndModify' && wireVersion >= SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY) ||
(op === 'mapReduce' && wireVersion >= SUPPORTS_EXPLAIN_WITH_MAP_REDUCE)
) {
return true;
}
);
}

return false;
static explainSupportedOnCmd(server: Server, cmd: Document): boolean {
const wireVersion = maxWireVersion(server);
return (
(cmd.remove && wireVersion >= SUPPORTS_EXPLAIN_WITH_REMOVE) ||
(cmd.update && wireVersion >= SUPPORTS_EXPLAIN_WITH_UPDATE) ||
(cmd.distinct && wireVersion >= SUPPORTS_EXPLAIN_WITH_DISTINCT) ||
(cmd.findAndModify && wireVersion >= SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY) ||
(cmd.mapReduce && wireVersion >= SUPPORTS_EXPLAIN_WITH_MAP_REDUCE)
);
}
}

/**
* Applies an explain to a given command.
* @internal
*
* @param command - the command on which to apply the read concern
* @param options - the options containing the explain verbosity
*/
export function decorateWithExplain(command: Document, options: ExplainOptions): Document {
const explain = Explain.fromOptions(options);
if (explain === undefined) return command;

// A command may not have an explain field on it
if (command.explain !== undefined) {
delete command.explain;
}

command = { explain: command, verbosity: explain.explain };
return command;
}
14 changes: 1 addition & 13 deletions src/operations/command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Aspect, OperationBase, OperationOptions } from './operation';
import { ReadConcern } from '../read_concern';
import { WriteConcern, WriteConcernOptions } from '../write_concern';
import { maxWireVersion, MongoDBNamespace, Callback, decorateWithExplain } from '../utils';
import { maxWireVersion, MongoDBNamespace, Callback } from '../utils';
import { ReadPreference, ReadPreferenceLike } from '../read_preference';
import { commandSupportsReadConcern } from '../sessions';
import { MongoError } from '../error';
Expand All @@ -10,7 +10,6 @@ import type { Server } from '../sdam/server';
import { BSONSerializeOptions, Document, resolveBSONOptions } from '../bson';
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
import type { ReadConcernLike } from './../read_concern';
import type { Explain } from '../explain';

const SUPPORTS_WRITE_CONCERN_AND_COLLATION = 5;

Expand Down Expand Up @@ -55,7 +54,6 @@ export abstract class CommandOperation<
readPreference: ReadPreference;
readConcern?: ReadConcern;
writeConcern?: WriteConcern;
explain?: Explain;
fullResponse?: boolean;
logger?: Logger;

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

get canRetryWrite(): boolean {
return this.explain === undefined;
}

abstract execute(server: Server, callback: Callback<TResult>): void;

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

// If we have reached this point, then the explain verbosity must be valid
// Note: explain inherits any comment from its command
if (this.explain) {
cmd = decorateWithExplain(cmd, { explain: this.explain.explain }); // TODO!
}

if (this.logger && this.logger.isDebug()) {
this.logger.debug(`executing command ${JSON.stringify(cmd)} against ${this.ns}`);
}
Expand Down
17 changes: 4 additions & 13 deletions src/operations/delete.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { defineAspects, Aspect, OperationBase, Hint } from './operation';
import { removeDocuments } from './common_functions';
import { CommandOperation, CommandOperationOptions } from './command';
import { isObject } from 'util';
import type { Callback, MongoDBNamespace } from '../utils';
import type { Document } from '../bson';
import type { Server } from '../sdam/server';
import type { Collection } from '../collection';
import type { WriteCommandOptions } from '../cmap/wire_protocol/write_command';
import type { Connection } from '../cmap/connection';
import { Explain, ExplainOptions } from '../explain';
import { ExplainableCommand, ExplainOptions } from '../explain';

/** @public */
export interface DeleteOptions extends CommandOperationOptions, ExplainOptions {
export interface DeleteOptions extends ExplainOptions {
single?: boolean;
hint?: Hint;
}
Expand Down Expand Up @@ -52,7 +51,7 @@ export class DeleteOperation extends OperationBase<DeleteOptions, Document> {
}
}

export class DeleteOneOperation extends CommandOperation<DeleteOptions, DeleteResult> {
export class DeleteOneOperation extends ExplainableCommand<DeleteOptions, DeleteResult> {
collection: Collection;
filter: Document;

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

this.collection = collection;
this.filter = filter;
this.explain = Explain.fromOptions(options);
}

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

// If an explain option was executed, don't process the server results
if (this.explain) return callback(undefined, r);

r.deletedCount = r.n;
if (callback) callback(undefined, r);
});
}
}

export class DeleteManyOperation extends CommandOperation<DeleteOptions, DeleteResult> {
export class DeleteManyOperation extends ExplainableCommand<DeleteOptions, DeleteResult> {
collection: Collection;
filter: Document;

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

this.collection = collection;
this.filter = filter;
this.explain = Explain.fromOptions(options);
}

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

// If an explain option was executed, don't process the server results
if (this.explain) return callback(undefined, r);

r.deletedCount = r.n;
if (callback) callback(undefined, r);
});
Expand Down
20 changes: 4 additions & 16 deletions src/operations/distinct.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { Aspect, defineAspects } from './operation';
import { CommandOperation, CommandOperationOptions } from './command';
import { decorateWithCollation, decorateWithReadConcern, Callback, maxWireVersion } from '../utils';
import { decorateWithCollation, decorateWithReadConcern, Callback } from '../utils';
import type { Document } from '../bson';
import type { Server } from '../sdam/server';
import type { Collection } from '../collection';
import { Explain, ExplainOptions, SUPPORTS_EXPLAIN_WITH_DISTINCT } from '../explain';
import { MongoError } from '../error';
import { ExplainableCommand, ExplainOptions } from '../explain';

/** @public */
export interface DistinctOptions extends CommandOperationOptions, ExplainOptions {}
export type DistinctOptions = ExplainOptions;

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

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

if (this.explain) {
if (maxWireVersion(server) < SUPPORTS_EXPLAIN_WITH_DISTINCT) {
callback(
new MongoError('the current topology does not support explain on distinct commands')
);
return;
}
}

super.executeCommand(server, cmd, (err, result) => {
if (err) {
callback(err);
Expand Down
17 changes: 3 additions & 14 deletions src/operations/find_and_modify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ import {
Callback
} from '../utils';
import { MongoError } from '../error';
import { CommandOperation, CommandOperationOptions } from './command';
import { defineAspects, Aspect } from './operation';
import type { Document } from '../bson';
import type { Server } from '../sdam/server';
import type { Collection } from '../collection';
import { Sort, formatSort } from '../sort';
import { Explain, ExplainOptions, SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY } from '../explain';
import { ExplainableCommand, ExplainOptions } from '../explain';

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

/** @internal */
export class FindAndModifyOperation extends CommandOperation<FindAndModifyOptions, Document> {
export class FindAndModifyOperation extends ExplainableCommand<FindAndModifyOptions, Document> {
collection: Collection;
query: Document;
sort?: Sort;
Expand All @@ -64,7 +63,6 @@ export class FindAndModifyOperation extends CommandOperation<FindAndModifyOption
this.query = query;
this.sort = sort;
this.doc = doc;
this.explain = Explain.fromOptions(options);
}

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

if (this.explain) {
if (maxWireVersion(server) < SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY) {
callback(
new MongoError('the current topology does not support explain on findAndModify commands')
);
return;
}
}

// Execute the command
super.executeCommand(server, cmd, (err, result) => {
if (err) return callback(err);
Expand Down
Loading

0 comments on commit 58ef4a2

Please sign in to comment.