Skip to content

Commit

Permalink
asdf
Browse files Browse the repository at this point in the history
  • Loading branch information
baileympearson committed Oct 17, 2024
1 parent 00cb86b commit aaa24e4
Show file tree
Hide file tree
Showing 11 changed files with 607 additions and 58 deletions.
9 changes: 4 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"mocha": "^10.4.0",
"mocha-sinon": "^2.1.2",
"mongodb-client-encryption": "^6.1.0",
"mongodb-legacy": "^6.1.2",
"mongodb-legacy": "^6.1.3",
"nyc": "^15.1.0",
"prettier": "^3.3.3",
"semver": "^7.6.3",
Expand Down
55 changes: 50 additions & 5 deletions src/cursor/aggregation_cursor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { Document } from '../bson';
import { MongoAPIError } from '../error';
import type { ExplainCommandOptions, ExplainVerbosityLike } from '../explain';
import {
Explain,
type ExplainCommandOptions,
type ExplainVerbosityLike,
validateExplainTimeoutOptions
} from '../explain';
import type { MongoClient } from '../mongo_client';
import { AggregateOperation, type AggregateOptions } from '../operations/aggregate';
import { executeOperation } from '../operations/execute_operation';
Expand Down Expand Up @@ -65,26 +70,66 @@ export class AggregationCursor<TSchema = any> extends AbstractCursor<TSchema> {

/** @internal */
async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, {
const options = {
...this.aggregateOptions,
...this.cursorOptions,
session
});
};
try {
validateExplainTimeoutOptions(options, Explain.fromOptions(options));
} catch {
throw new MongoAPIError(
'timeoutMS cannot be used with explain when explain is specified in aggregateOptions'
);
}

const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, options);

const response = await executeOperation(this.client, aggregateOperation, this.timeoutContext);

return { server: aggregateOperation.server, session, response };
}

/** Execute the explain for the cursor */
async explain(verbosity?: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document> {
async explain(): Promise<Document>;
async explain(verbosity: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document>;
async explain(options: { timeoutMS?: number }): Promise<Document>;
async explain(
verbosity: ExplainVerbosityLike | ExplainCommandOptions,
options: { timeoutMS?: number }
): Promise<Document>;
async explain(
verbosity?: ExplainVerbosityLike | ExplainCommandOptions | { timeoutMS?: number },
options?: { timeoutMS?: number }
): Promise<Document> {
let explain: ExplainVerbosityLike | ExplainCommandOptions | undefined;
let timeout: { timeoutMS?: number } | undefined;
if (verbosity == null && options == null) {
explain = true;
timeout = undefined;
} else if (verbosity != null && options == null) {
explain =
typeof verbosity !== 'object'
? verbosity
: 'verbosity' in verbosity
? verbosity
: undefined;

timeout = typeof verbosity === 'object' && 'timeoutMS' in verbosity ? verbosity : undefined;
} else {
// @ts-expect-error TS isn't smart enough to determine that if both options are provided, the first is explain options
explain = verbosity;
timeout = options;
}

return (
await executeOperation(
this.client,
new AggregateOperation(this.namespace, this.pipeline, {
...this.aggregateOptions, // NOTE: order matters here, we may need to refine this
...this.cursorOptions,
explain: verbosity ?? true
...timeout,
explain: explain ?? true
})
)
).shift(this.deserializationOptions);
Expand Down
57 changes: 51 additions & 6 deletions src/cursor/find_cursor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { type Document } from '../bson';
import { CursorResponse } from '../cmap/wire_protocol/responses';
import { MongoInvalidArgumentError, MongoTailableCursorError } from '../error';
import { type ExplainCommandOptions, type ExplainVerbosityLike } from '../explain';
import { MongoAPIError, MongoInvalidArgumentError, MongoTailableCursorError } from '../error';
import {
Explain,
type ExplainCommandOptions,
type ExplainVerbosityLike,
validateExplainTimeoutOptions
} from '../explain';
import type { MongoClient } from '../mongo_client';
import type { CollationOptions } from '../operations/command';
import { CountOperation, type CountOptions } from '../operations/count';
Expand Down Expand Up @@ -63,11 +68,21 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {

/** @internal */
async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
const findOperation = new FindOperation(this.namespace, this.cursorFilter, {
const options = {
...this.findOptions, // NOTE: order matters here, we may need to refine this
...this.cursorOptions,
session
});
};

try {
validateExplainTimeoutOptions(options, Explain.fromOptions(options));
} catch {
throw new MongoAPIError(
'timeoutMS cannot be used with explain when explain is specified in findOptions'
);
}

const findOperation = new FindOperation(this.namespace, this.cursorFilter, options);

const response = await executeOperation(this.client, findOperation, this.timeoutContext);

Expand Down Expand Up @@ -133,14 +148,44 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
}

/** Execute the explain for the cursor */
async explain(verbosity?: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document> {
async explain(): Promise<Document>;
async explain(verbosity: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document>;
async explain(options: { timeoutMS?: number }): Promise<Document>;
async explain(
verbosity: ExplainVerbosityLike | ExplainCommandOptions,
options: { timeoutMS?: number }
): Promise<Document>;
async explain(
verbosity?: ExplainVerbosityLike | ExplainCommandOptions | { timeoutMS?: number },
options?: { timeoutMS?: number }
): Promise<Document> {
let explain: ExplainVerbosityLike | ExplainCommandOptions | undefined;
let timeout: { timeoutMS?: number } | undefined;
if (verbosity == null && options == null) {
explain = true;
timeout = undefined;
} else if (verbosity != null && options == null) {
explain =
typeof verbosity !== 'object'
? verbosity
: 'verbosity' in verbosity
? verbosity
: undefined;
timeout = typeof verbosity === 'object' && 'timeoutMS' in verbosity ? verbosity : undefined;
} else {
// @ts-expect-error TS isn't smart enough to determine that if both options are provided, the first is explain options
explain = verbosity;
timeout = options;
}

return (
await executeOperation(
this.client,
new FindOperation(this.namespace, this.cursorFilter, {
...this.findOptions, // NOTE: order matters here, we may need to refine this
...this.cursorOptions,
explain: verbosity ?? true
...timeout,
explain: explain ?? true
})
)
).shift(this.deserializationOptions);
Expand Down
37 changes: 37 additions & 0 deletions src/explain.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { type Document } from 'bson';

import { MongoAPIError } from './error';

/** @public */
export const ExplainVerbosity = Object.freeze({
queryPlanner: 'queryPlanner',
Expand Down Expand Up @@ -86,3 +90,36 @@ export class Explain {
return new Explain(verbosity, maxTimeMS);
}
}

export function validateExplainTimeoutOptions(options: Document, explain?: Explain) {
const { maxTimeMS, timeoutMS } = options;
if (timeoutMS != null && (maxTimeMS != null || explain?.maxTimeMS != null)) {
throw new MongoAPIError('Cannot use maxTimeMS with timeoutMS for explain commands.');
}
}

/**
* Applies an explain to a given command.
* @internal
*
* @param command - the command on which to apply the explain
* @param options - the options containing the explain verbosity
*/
export function decorateWithExplain(
command: Document,
explain: Explain
): {
explain: Document;
verbosity: ExplainVerbosity;
maxTimeMS?: number;
} {
type ExplainCommand = ReturnType<typeof decorateWithExplain>;
const { verbosity, maxTimeMS } = explain;
const baseCommand: ExplainCommand = { explain: command, verbosity };

if (typeof maxTimeMS === 'number') {
baseCommand.maxTimeMS = maxTimeMS;
}

return baseCommand;
}
15 changes: 8 additions & 7 deletions src/operations/command.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import type { BSONSerializeOptions, Document } from '../bson';
import { type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses';
import { MongoInvalidArgumentError } from '../error';
import { Explain, type ExplainOptions } from '../explain';
import {
decorateWithExplain,
Explain,
type ExplainOptions,
validateExplainTimeoutOptions
} from '../explain';
import { ReadConcern } from '../read_concern';
import type { ReadPreference } from '../read_preference';
import type { Server } from '../sdam/server';
import { MIN_SECONDARY_WRITE_WIRE_VERSION } from '../sdam/server_selection';
import type { ClientSession } from '../sessions';
import { type TimeoutContext } from '../timeout';
import {
commandSupportsReadConcern,
decorateWithExplain,
maxWireVersion,
MongoDBNamespace
} from '../utils';
import { commandSupportsReadConcern, maxWireVersion, MongoDBNamespace } from '../utils';
import { WriteConcern, type WriteConcernOptions } from '../write_concern';
import type { ReadConcernLike } from './../read_concern';
import { AbstractOperation, Aspect, type OperationOptions } from './operation';
Expand Down Expand Up @@ -97,6 +97,7 @@ export abstract class CommandOperation<T> extends AbstractOperation<T> {

if (this.hasAspect(Aspect.EXPLAINABLE)) {
this.explain = Explain.fromOptions(options);
validateExplainTimeoutOptions(this.options, this.explain);
} else if (options?.explain != null) {
throw new MongoInvalidArgumentError(`Option "explain" is not supported on this command`);
}
Expand Down
9 changes: 7 additions & 2 deletions src/operations/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import type { Document } from '../bson';
import { CursorResponse, ExplainedCursorResponse } from '../cmap/wire_protocol/responses';
import { type AbstractCursorOptions, type CursorTimeoutMode } from '../cursor/abstract_cursor';
import { MongoInvalidArgumentError } from '../error';
import { type ExplainOptions } from '../explain';
import {
decorateWithExplain,
type ExplainOptions,
validateExplainTimeoutOptions
} from '../explain';
import { ReadConcern } from '../read_concern';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { formatSort, type Sort } from '../sort';
import { type TimeoutContext } from '../timeout';
import { decorateWithExplain, type MongoDBNamespace, normalizeHintField } from '../utils';
import { type MongoDBNamespace, normalizeHintField } from '../utils';
import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command';
import { Aspect, defineAspects, type Hint } from './operation';

Expand Down Expand Up @@ -119,6 +123,7 @@ export class FindOperation extends CommandOperation<CursorResponse> {

let findCommand = makeFindCommand(this.ns, this.filter, options);
if (this.explain) {
validateExplainTimeoutOptions(this.options, this.explain);
findCommand = decorateWithExplain(findCommand, this.explain);
}

Expand Down
27 changes: 0 additions & 27 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
MongoParseError,
MongoRuntimeError
} from './error';
import type { Explain, ExplainVerbosity } from './explain';
import type { MongoClient } from './mongo_client';
import type { CommandOperationOptions, OperationParent } from './operations/command';
import type { Hint, OperationOptions } from './operations/operation';
Expand Down Expand Up @@ -245,32 +244,6 @@ export function decorateWithReadConcern(
}
}

/**
* Applies an explain to a given command.
* @internal
*
* @param command - the command on which to apply the explain
* @param options - the options containing the explain verbosity
*/
export function decorateWithExplain(
command: Document,
explain: Explain
): {
explain: Document;
verbosity: ExplainVerbosity;
maxTimeMS?: number;
} {
type ExplainCommand = ReturnType<typeof decorateWithExplain>;
const { verbosity, maxTimeMS } = explain;
const baseCommand: ExplainCommand = { explain: command, verbosity };

if (typeof maxTimeMS === 'number') {
baseCommand.maxTimeMS = maxTimeMS;
}

return baseCommand;
}

/**
* @internal
*/
Expand Down
Loading

0 comments on commit aaa24e4

Please sign in to comment.