-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: add explain support for non-cursor commands #2599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 23 commits
a8dfabe
5bded70
4374b6b
f00fbfe
1d937a8
2a9b1be
237a70f
c2d0a53
0126381
82715bd
3bf0d78
7090c39
82f0e60
f5e65ed
4bdf503
03333d7
20ef5da
c1a4ff9
2c7334b
d486214
ddc9826
6a17656
7235fb7
001ec2e
0b37761
31bb9eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import type { Document } from '.'; | ||
import { MongoError } from './error'; | ||
import type { Server } from './sdam/server'; | ||
import { maxWireVersion } from './utils'; | ||
|
||
/** @public */ | ||
export const ExplainVerbosity = { | ||
queryPlanner: 'queryPlanner', | ||
queryPlannerExtended: 'queryPlannerExtended', | ||
executionStats: 'executionStats', | ||
allPlansExecution: 'allPlansExecution' | ||
} as const; | ||
|
||
/** | ||
* For backwards compatibility, true is interpreted as | ||
* "allPlansExecution" and false as "queryPlanner". | ||
* @public | ||
*/ | ||
export type ExplainVerbosityLike = keyof typeof ExplainVerbosity | boolean; | ||
|
||
/** @public */ | ||
export interface ExplainOptions { | ||
/** Specifies the verbosity mode for the explain output. */ | ||
explain?: ExplainVerbosityLike; | ||
} | ||
|
||
// Minimum server versions which support explain with specific operations | ||
const SUPPORTS_EXPLAIN_WITH_REMOVE = 3; | ||
const SUPPORTS_EXPLAIN_WITH_UPDATE = 3; | ||
const SUPPORTS_EXPLAIN_WITH_DISTINCT = 4; | ||
const SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY = 4; | ||
const SUPPORTS_EXPLAIN_WITH_MAP_REDUCE = 9; | ||
|
||
/** @internal */ | ||
export class Explain { | ||
verbosity: keyof typeof ExplainVerbosity; | ||
|
||
constructor(verbosity: ExplainVerbosityLike) { | ||
if (typeof verbosity === 'boolean') { | ||
this.verbosity = verbosity | ||
? ExplainVerbosity.allPlansExecution | ||
: ExplainVerbosity.queryPlanner; | ||
} else { | ||
this.verbosity = ExplainVerbosity[verbosity]; | ||
} | ||
} | ||
|
||
static fromOptions(options?: ExplainOptions): Explain | undefined { | ||
if (options?.explain === undefined) return; | ||
|
||
const explain = options.explain; | ||
if (typeof explain === 'boolean' || explain in ExplainVerbosity) { | ||
return new Explain(explain); | ||
} | ||
|
||
throw new MongoError(`explain must be one of ${Object.keys(ExplainVerbosity)} or a boolean`); | ||
} | ||
|
||
/** Checks that the server supports explain on the given operation or command.*/ | ||
static explainSupported(server: Server, op: string | Document): boolean { | ||
const wireVersion = maxWireVersion(server); | ||
if (op === 'remove' || (typeof op === 'object' && op.remove)) { | ||
return wireVersion >= SUPPORTS_EXPLAIN_WITH_REMOVE; | ||
} else if (op === 'update' || (typeof op === 'object' && op.update)) { | ||
return wireVersion >= SUPPORTS_EXPLAIN_WITH_UPDATE; | ||
} else if (op === 'distinct' || (typeof op === 'object' && op.distinct)) { | ||
return wireVersion >= SUPPORTS_EXPLAIN_WITH_DISTINCT; | ||
} else if (op === 'findAndModify' || (typeof op === 'object' && op.findAndModify)) { | ||
return wireVersion >= SUPPORTS_EXPLAIN_WITH_FIND_AND_MODIFY; | ||
} else if (op === 'mapReduce' || (typeof op === 'object' && op.mapReduce)) { | ||
return wireVersion >= SUPPORTS_EXPLAIN_WITH_MAP_REDUCE; | ||
} | ||
|
||
return false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -163,6 +163,11 @@ export type { | |||||
export type { DbPrivate, DbOptions } from './db'; | ||||||
export type { AutoEncryptionOptions, AutoEncryptionLoggerLevels, AutoEncrypter } from './deps'; | ||||||
export type { AnyError, ErrorDescription } from './error'; | ||||||
export type { | ||||||
ExplainOptions, | ||||||
ExplainVerbosity as Verbosity, | ||||||
ExplainVerbosityLike as VerbosityLike | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} from './explain'; | ||||||
export type { | ||||||
GridFSBucketReadStream, | ||||||
GridFSBucketReadStreamOptions, | ||||||
|
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 } from '../utils'; | ||
import { maxWireVersion, MongoDBNamespace, Callback, decorateWithExplain } from '../utils'; | ||
import { ReadPreference, ReadPreferenceLike } from '../read_preference'; | ||
import { commandSupportsReadConcern } from '../sessions'; | ||
import { MongoError } from '../error'; | ||
|
@@ -10,11 +10,15 @@ 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 { Explain, ExplainOptions } from '../explain'; | ||
|
||
const SUPPORTS_WRITE_CONCERN_AND_COLLATION = 5; | ||
|
||
/** @public */ | ||
export interface CommandOperationOptions extends OperationOptions, WriteConcernOptions { | ||
export interface CommandOperationOptions | ||
extends OperationOptions, | ||
WriteConcernOptions, | ||
ExplainOptions { | ||
/** Return the full server response for the command */ | ||
fullResponse?: boolean; | ||
/** Specify a read concern and level for the collection. (only MongoDB 3.2 or higher supported) */ | ||
|
@@ -54,7 +58,7 @@ export abstract class CommandOperation< | |
readPreference: ReadPreference; | ||
readConcern?: ReadConcern; | ||
writeConcern?: WriteConcern; | ||
explain: boolean; | ||
explain?: Explain; | ||
fullResponse?: boolean; | ||
logger?: Logger; | ||
|
||
|
@@ -80,7 +84,6 @@ export abstract class CommandOperation< | |
this.writeConcern = WriteConcern.fromOptions(options); | ||
this.bsonOptions = resolveBSONOptions(options); | ||
|
||
this.explain = false; | ||
this.fullResponse = | ||
options && typeof options.fullResponse === 'boolean' ? options.fullResponse : false; | ||
|
||
|
@@ -92,6 +95,19 @@ export abstract class CommandOperation< | |
if (parent && parent.logger) { | ||
this.logger = parent.logger; | ||
} | ||
|
||
if (this.hasAspect(Aspect.EXPLAINABLE)) { | ||
this.explain = Explain.fromOptions(options); | ||
} else if (options?.explain !== undefined) { | ||
throw new MongoError(`explain is not supported on this command`); | ||
} | ||
} | ||
|
||
get canRetryWrite(): boolean { | ||
if (this.hasAspect(Aspect.EXPLAINABLE)) { | ||
return this.explain === undefined; | ||
} | ||
return true; | ||
} | ||
|
||
abstract execute(server: Server, callback: Callback<TResult>): void; | ||
|
@@ -139,6 +155,15 @@ export abstract class CommandOperation< | |
this.logger.debug(`executing command ${JSON.stringify(cmd)} against ${this.ns}`); | ||
} | ||
|
||
if (this.hasAspect(Aspect.EXPLAINABLE) && this.explain) { | ||
if (!Explain.explainSupported(server, cmd)) { | ||
callback(new MongoError(`server ${server.name} does not support explain on this command`)); | ||
return; | ||
} | ||
|
||
cmd = decorateWithExplain(cmd, this.explain); | ||
} | ||
|
||
server.command( | ||
this.ns.toString(), | ||
cmd, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a stylistic nit, but I think it'd be better to do |
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, not sure how that happened!