From 8b1463b2e04999fcfda3f83e461eaf5baa9c7f18 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Tue, 5 Aug 2025 21:37:32 +0200 Subject: [PATCH 01/21] refactor(NODE-7086): refactor misc operations --- src/cmap/wire_protocol/responses.ts | 12 ++++++ src/operations/distinct.ts | 59 ++++++--------------------- src/operations/profiling_level.ts | 26 ++++++------ src/operations/remove_user.ts | 22 +++++----- src/operations/set_profiling_level.ts | 28 +++++++------ src/operations/stats.ts | 19 ++++----- 6 files changed, 71 insertions(+), 95 deletions(-) diff --git a/src/cmap/wire_protocol/responses.ts b/src/cmap/wire_protocol/responses.ts index d5549aea545..e42bc31f6cb 100644 --- a/src/cmap/wire_protocol/responses.ts +++ b/src/cmap/wire_protocol/responses.ts @@ -391,3 +391,15 @@ export class ClientBulkWriteCursorResponse extends CursorResponse { return this.get('writeConcernError', BSONType.object, false); } } + +export class ProfilingLevelResponse extends MongoDBResponse { + get was() { + return this.get('was', BSONType.int, true); + } +} + +export class DistinctResponse extends MongoDBResponse { + get values() { + return this.get('values', BSONType.array, true); + } +} diff --git a/src/operations/distinct.ts b/src/operations/distinct.ts index cb6891fc475..e83b18392c3 100644 --- a/src/operations/distinct.ts +++ b/src/operations/distinct.ts @@ -1,10 +1,8 @@ import type { Document } from '../bson'; +import { type Connection } from '../cmap/connection'; +import { DistinctResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; -import { type TimeoutContext } from '../timeout'; -import { decorateWithCollation, decorateWithReadConcern } from '../utils'; -import { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; /** @public */ @@ -27,7 +25,8 @@ export type DistinctOptions = CommandOperationOptions & { * Return a list of distinct values for the given key across a collection. * @internal */ -export class DistinctOperation extends CommandOperation { +export class DistinctOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = DistinctResponse; override options: DistinctOptions; collection: Collection; /** Field of the document to find distinct values for. */ @@ -56,48 +55,16 @@ export class DistinctOperation extends CommandOperation { return 'distinct' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const coll = this.collection; - const key = this.key; - const query = this.query; - const options = this.options; - - // Distinct command - const cmd: Document = { - distinct: coll.collectionName, - key: key, - query: query + override buildCommandDocument(_connection: Connection): Document { + return { + distinct: this.collection.collectionName, + key: this.key, + query: this.query }; + } - // Add maxTimeMS if defined - if (typeof options.maxTimeMS === 'number') { - cmd.maxTimeMS = options.maxTimeMS; - } - - // we check for undefined specifically here to allow falsy values - // eslint-disable-next-line no-restricted-syntax - if (typeof options.comment !== 'undefined') { - cmd.comment = options.comment; - } - - if (options.hint != null) { - cmd.hint = options.hint; - } - - // Do we have a readConcern specified - decorateWithReadConcern(cmd, coll, options); - - // Have we specified collation - decorateWithCollation(cmd, coll, options); - - const result = await super.executeCommand(server, session, cmd, timeoutContext); - - // @ts-expect-error: Explain always returns a document - return this.explain ? result : result.values; + override handleOk(response: InstanceType): any[] { + this.explain ? response.toObject() : response.values; } } diff --git a/src/operations/profiling_level.ts b/src/operations/profiling_level.ts index 7c860a244b7..2024311bd96 100644 --- a/src/operations/profiling_level.ts +++ b/src/operations/profiling_level.ts @@ -1,15 +1,16 @@ +import { type Document } from '../bson'; +import { type Connection } from '../cmap/connection'; +import { ProfilingLevelResponse } from '../cmap/wire_protocol/responses'; import type { Db } from '../db'; import { MongoUnexpectedServerResponseError } from '../error'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; -import { type TimeoutContext } from '../timeout'; -import { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; /** @public */ export type ProfilingLevelOptions = CommandOperationOptions; /** @internal */ -export class ProfilingLevelOperation extends CommandOperation { +export class ProfilingLevelOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = ProfilingLevelResponse; override options: ProfilingLevelOptions; constructor(db: Db, options: ProfilingLevelOptions) { @@ -21,14 +22,13 @@ export class ProfilingLevelOperation extends CommandOperation { return 'profile' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const doc = await super.executeCommand(server, session, { profile: -1 }, timeoutContext); - if (doc.ok === 1) { - const was = doc.was; + override buildCommandDocument(_connection: Connection): Document { + return { profile: -1 }; + } + + override handleOk(response: InstanceType): string { + if (response.ok === 1) { + const was = response.was; if (was === 0) return 'off'; if (was === 1) return 'slow_only'; if (was === 2) return 'all'; diff --git a/src/operations/remove_user.ts b/src/operations/remove_user.ts index 7f484ba89a3..e0c73d29aa4 100644 --- a/src/operations/remove_user.ts +++ b/src/operations/remove_user.ts @@ -1,15 +1,16 @@ +import { type Document } from '../bson'; +import { type Connection } from '../cmap/connection'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Db } from '../db'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; -import { type TimeoutContext } from '../timeout'; -import { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; /** @public */ export type RemoveUserOptions = CommandOperationOptions; /** @internal */ -export class RemoveUserOperation extends CommandOperation { +export class RemoveUserOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; override options: RemoveUserOptions; username: string; @@ -23,12 +24,11 @@ export class RemoveUserOperation extends CommandOperation { return 'dropUser' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - await super.executeCommand(server, session, { dropUser: this.username }, timeoutContext); + override buildCommandDocument(_connection: Connection): Document { + return { dropUser: this.username }; + } + + override handleOk(_response: InstanceType): boolean { return true; } } diff --git a/src/operations/set_profiling_level.ts b/src/operations/set_profiling_level.ts index d76473f2632..e3460bbfce6 100644 --- a/src/operations/set_profiling_level.ts +++ b/src/operations/set_profiling_level.ts @@ -1,10 +1,10 @@ +import { type Document } from '../bson'; +import { type Connection } from '../cmap/connection'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Db } from '../db'; import { MongoInvalidArgumentError } from '../error'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; -import { type TimeoutContext } from '../timeout'; import { enumToString } from '../utils'; -import { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; const levelValues = new Set(['off', 'slow_only', 'all']); @@ -22,7 +22,8 @@ export type ProfilingLevel = (typeof ProfilingLevel)[keyof typeof ProfilingLevel export type SetProfilingLevelOptions = CommandOperationOptions; /** @internal */ -export class SetProfilingLevelOperation extends CommandOperation { +export class SetProfilingLevelOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; override options: SetProfilingLevelOptions; level: ProfilingLevel; profile: 0 | 1 | 2; @@ -52,21 +53,22 @@ export class SetProfilingLevelOperation extends CommandOperation return 'profile' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { + override buildCommandDocument(_connection: Connection): Document { const level = this.level; if (!levelValues.has(level)) { + // TODO(NODE-3483): Determine error to put here throw new MongoInvalidArgumentError( `Profiling level must be one of "${enumToString(ProfilingLevel)}"` ); } - // TODO(NODE-3483): Determine error to put here - await super.executeCommand(server, session, { profile: this.profile }, timeoutContext); - return level; + return { profile: this.profile }; + } + + override handleOk( + _response: InstanceType + ): ProfilingLevel { + return this.level; } } diff --git a/src/operations/stats.ts b/src/operations/stats.ts index aafd3bf1bac..399bb9c5311 100644 --- a/src/operations/stats.ts +++ b/src/operations/stats.ts @@ -1,9 +1,8 @@ import type { Document } from '../bson'; +import { type Connection } from '../cmap/connection'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Db } from '../db'; -import type { Server } from '../sdam/server'; -import type { ClientSession } from '../sessions'; -import { type TimeoutContext } from '../timeout'; -import { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; /** @public */ @@ -13,7 +12,8 @@ export interface DbStatsOptions extends CommandOperationOptions { } /** @internal */ -export class DbStatsOperation extends CommandOperation { +export class DbStatsOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; override options: DbStatsOptions; constructor(db: Db, options: DbStatsOptions) { @@ -25,17 +25,12 @@ export class DbStatsOperation extends CommandOperation { return 'dbStats' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { + override buildCommandDocument(_connection: Connection): Document { const command: Document = { dbStats: true }; if (this.options.scale != null) { command.scale = this.options.scale; } - - return await super.executeCommand(server, session, command, timeoutContext); + return command; } } From ff0a56fe91ae7b57db05f08188c2a860953f059c Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 6 Aug 2025 14:07:15 +0200 Subject: [PATCH 02/21] chore: move responses --- src/cmap/wire_protocol/responses.ts | 12 ------------ src/collection.ts | 18 +++++++++++------- src/operations/distinct.ts | 25 ++++++++++++++++++++----- src/operations/profiling_level.ts | 10 ++++++++-- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/cmap/wire_protocol/responses.ts b/src/cmap/wire_protocol/responses.ts index e42bc31f6cb..d5549aea545 100644 --- a/src/cmap/wire_protocol/responses.ts +++ b/src/cmap/wire_protocol/responses.ts @@ -391,15 +391,3 @@ export class ClientBulkWriteCursorResponse extends CursorResponse { return this.get('writeConcernError', BSONType.object, false); } } - -export class ProfilingLevelResponse extends MongoDBResponse { - get was() { - return this.get('was', BSONType.int, true); - } -} - -export class DistinctResponse extends MongoDBResponse { - get values() { - return this.get('values', BSONType.array, true); - } -} diff --git a/src/collection.ts b/src/collection.ts index 223737c32a9..cc5381b3523 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -853,27 +853,31 @@ export class Collection { */ distinct>( key: Key - ): Promise[Key]>>>; + ): Promise[Key]>> | Document>; distinct>( key: Key, filter: Filter - ): Promise[Key]>>>; + ): Promise[Key]>> | Document>; distinct>( key: Key, filter: Filter, options: DistinctOptions - ): Promise[Key]>>>; + ): Promise[Key]>> | Document>; // Embedded documents overload - distinct(key: string): Promise; - distinct(key: string, filter: Filter): Promise; - distinct(key: string, filter: Filter, options: DistinctOptions): Promise; + distinct(key: string): Promise; + distinct(key: string, filter: Filter): Promise; + distinct( + key: string, + filter: Filter, + options: DistinctOptions + ): Promise; async distinct>( key: Key, filter: Filter = {}, options: DistinctOptions = {} - ): Promise { + ): Promise { return await executeOperation( this.client, new DistinctOperation( diff --git a/src/operations/distinct.ts b/src/operations/distinct.ts index e83b18392c3..98418e71509 100644 --- a/src/operations/distinct.ts +++ b/src/operations/distinct.ts @@ -1,6 +1,6 @@ -import type { Document } from '../bson'; +import { BSONType, type Document, parseUtf8ValidationOption } from '../bson'; import { type Connection } from '../cmap/connection'; -import { DistinctResponse } from '../cmap/wire_protocol/responses'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; @@ -21,11 +21,17 @@ export type DistinctOptions = CommandOperationOptions & { hint?: Document | string; }; +class DistinctResponse extends MongoDBResponse { + get values() { + return this.get('values', BSONType.array, true); + } +} + /** * Return a list of distinct values for the given key across a collection. * @internal */ -export class DistinctOperation extends ModernizedCommandOperation { +export class DistinctOperation extends ModernizedCommandOperation { override SERVER_COMMAND_RESPONSE_TYPE = DistinctResponse; override options: DistinctOptions; collection: Collection; @@ -63,8 +69,17 @@ export class DistinctOperation extends ModernizedCommandOperation { }; } - override handleOk(response: InstanceType): any[] { - this.explain ? response.toObject() : response.values; + override handleOk( + response: InstanceType + ): any[] | Document { + if (this.explain) { + return response.toObject(this.bsonOptions); + } + const values = response.values.toObject({ + ...this.bsonOptions, + validation: parseUtf8ValidationOption(this.bsonOptions) + }); + return Object.entries(values).map(([_key, val]) => val); } } diff --git a/src/operations/profiling_level.ts b/src/operations/profiling_level.ts index 2024311bd96..90b310b8afe 100644 --- a/src/operations/profiling_level.ts +++ b/src/operations/profiling_level.ts @@ -1,6 +1,6 @@ -import { type Document } from '../bson'; +import { BSONType, type Document } from '../bson'; import { type Connection } from '../cmap/connection'; -import { ProfilingLevelResponse } from '../cmap/wire_protocol/responses'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Db } from '../db'; import { MongoUnexpectedServerResponseError } from '../error'; import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; @@ -8,6 +8,12 @@ import { type CommandOperationOptions, ModernizedCommandOperation } from './comm /** @public */ export type ProfilingLevelOptions = CommandOperationOptions; +class ProfilingLevelResponse extends MongoDBResponse { + get was() { + return this.get('was', BSONType.int, true); + } +} + /** @internal */ export class ProfilingLevelOperation extends ModernizedCommandOperation { override SERVER_COMMAND_RESPONSE_TYPE = ProfilingLevelResponse; From aa3b5ee059b7fbd5b15ba77e6a873e43a25e5241 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 6 Aug 2025 14:13:10 +0200 Subject: [PATCH 03/21] refactor: run command --- src/operations/run_command.ts | 56 ++++++++++++----------------------- 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 4b967ee3cd5..cdedd2d67fa 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -1,13 +1,13 @@ import type { BSONSerializeOptions, Document } from '../bson'; -import { type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses'; +import { type Connection } from '../cmap/connection'; +import { MongoDBResponse, type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses'; import { type Db } from '../db'; -import { type TODO_NODE_3286 } from '../mongo_types'; import type { ReadPreferenceLike } from '../read_preference'; -import type { Server } from '../sdam/server'; +import type { ServerCommandOptions } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { type TimeoutContext } from '../timeout'; import { MongoDBNamespace } from '../utils'; -import { AbstractOperation } from './operation'; +import { ModernizedOperation } from './operation'; /** @public */ export type RunCommandOptions = { @@ -25,7 +25,8 @@ export type RunCommandOptions = { } & BSONSerializeOptions; /** @internal */ -export class RunCommandOperation extends AbstractOperation { +export class RunCommandOperation extends ModernizedOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; command: Document; override options: RunCommandOptions & { responseType?: MongoDBResponseConstructor }; @@ -44,29 +45,17 @@ export class RunCommandOperation extends AbstractOperation { return 'runCommand' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - this.server = server; - const res: TODO_NODE_3286 = await server.command( - this.ns, - this.command, - { - ...this.options, - readPreference: this.readPreference, - session, - timeoutContext - }, - this.options.responseType - ); + override buildCommand(_connection: Connection, _session?: ClientSession): Document { + return this.command; + } - return res; + override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { + return { session: this.session, timeoutContext }; } } -export class RunAdminCommandOperation extends AbstractOperation { +export class RunAdminCommandOperation extends ModernizedOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; command: Document; override options: RunCommandOptions & { noResponse?: boolean; @@ -90,18 +79,11 @@ export class RunAdminCommandOperation extends AbstractOperation return 'runCommand' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - this.server = server; - const res: TODO_NODE_3286 = await server.command(this.ns, this.command, { - ...this.options, - readPreference: this.readPreference, - session, - timeoutContext - }); - return res; + override buildCommand(_connection: Connection, _session?: ClientSession): Document { + return this.command; + } + + override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { + return { session: this.session, timeoutContext }; } } From f5fca0690b078e42eda8997761eeb2b86a7ee9c2 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 7 Aug 2025 15:57:02 +0200 Subject: [PATCH 04/21] fix: distinct typing --- src/collection.ts | 24 +++++++++++++----------- src/operations/distinct.ts | 16 +++------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/collection.ts b/src/collection.ts index cc5381b3523..37339881c3b 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -17,6 +17,7 @@ import { } from './cursor/list_search_indexes_cursor'; import type { Db } from './db'; import { MongoAPIError, MongoInvalidArgumentError, MongoOperationTimeoutError } from './error'; +import { type ExplainCommandOptions, type ExplainVerbosityLike } from './explain'; import type { MongoClient, PkFactory } from './mongo_client'; import type { Abortable, @@ -853,31 +854,32 @@ export class Collection { */ distinct>( key: Key - ): Promise[Key]>> | Document>; + ): Promise[Key]>>>; distinct>( key: Key, filter: Filter - ): Promise[Key]>> | Document>; + ): Promise[Key]>>>; distinct>( key: Key, filter: Filter, options: DistinctOptions - ): Promise[Key]>> | Document>; + ): Promise[Key]>>>; + distinct>( + key: Key, + filter: Filter, + options: DistinctOptions & { explain: ExplainVerbosityLike | ExplainCommandOptions } + ): Promise; // Embedded documents overload - distinct(key: string): Promise; - distinct(key: string, filter: Filter): Promise; - distinct( - key: string, - filter: Filter, - options: DistinctOptions - ): Promise; + distinct(key: string): Promise; + distinct(key: string, filter: Filter): Promise; + distinct(key: string, filter: Filter, options: DistinctOptions): Promise; async distinct>( key: Key, filter: Filter = {}, options: DistinctOptions = {} - ): Promise { + ): Promise { return await executeOperation( this.client, new DistinctOperation( diff --git a/src/operations/distinct.ts b/src/operations/distinct.ts index 98418e71509..2302a3c0812 100644 --- a/src/operations/distinct.ts +++ b/src/operations/distinct.ts @@ -1,4 +1,4 @@ -import { BSONType, type Document, parseUtf8ValidationOption } from '../bson'; +import { type Document } from '../bson'; import { type Connection } from '../cmap/connection'; import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; @@ -21,18 +21,12 @@ export type DistinctOptions = CommandOperationOptions & { hint?: Document | string; }; -class DistinctResponse extends MongoDBResponse { - get values() { - return this.get('values', BSONType.array, true); - } -} - /** * Return a list of distinct values for the given key across a collection. * @internal */ export class DistinctOperation extends ModernizedCommandOperation { - override SERVER_COMMAND_RESPONSE_TYPE = DistinctResponse; + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; override options: DistinctOptions; collection: Collection; /** Field of the document to find distinct values for. */ @@ -75,11 +69,7 @@ export class DistinctOperation extends ModernizedCommandOperation val); + return response.toObject(this.bsonOptions).values; } } From 973717b678cccad6abdbb8c7cff06b773809b22f Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Mon, 11 Aug 2025 15:31:43 +0200 Subject: [PATCH 05/21] chore: split out run command cursor op --- src/cursor/run_command_cursor.ts | 4 ++-- src/operations/run_command.ts | 37 +++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 0e9992a2aa4..b8969b4c44c 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -4,7 +4,7 @@ import type { Db } from '../db'; import { MongoAPIError, MongoRuntimeError } from '../error'; import { executeOperation } from '../operations/execute_operation'; import { GetMoreOperation } from '../operations/get_more'; -import { RunCommandOperation } from '../operations/run_command'; +import { RunCommandOperation, RunCursorCommandOperation } from '../operations/run_command'; import type { ReadConcernLike } from '../read_concern'; import type { ReadPreferenceLike } from '../read_preference'; import type { ClientSession } from '../sessions'; @@ -143,7 +143,7 @@ export class RunCommandCursor extends AbstractCursor { /** @internal */ protected async _initialize(session: ClientSession): Promise { - const operation = new RunCommandOperation(this.db, this.command, { + const operation = new RunCursorCommandOperation(this.db, this.command, { ...this.cursorOptions, session: session, readPreference: this.cursorOptions.readPreference, diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index cdedd2d67fa..506ae70a8ee 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -1,6 +1,6 @@ import type { BSONSerializeOptions, Document } from '../bson'; import { type Connection } from '../cmap/connection'; -import { MongoDBResponse, type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses'; +import { CursorResponse, MongoDBResponse, type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses'; import { type Db } from '../db'; import type { ReadPreferenceLike } from '../read_preference'; import type { ServerCommandOptions } from '../sdam/server'; @@ -54,6 +54,41 @@ export class RunCommandOperation extends ModernizedOperation { } } +/** @internal */ +export class RunCursorCommandOperation extends ModernizedOperation { + override SERVER_COMMAND_RESPONSE_TYPE = CursorResponse; + command: Document; + override options: RunCommandOptions & { responseType?: MongoDBResponseConstructor }; + + constructor( + parent: Db, + command: Document, + options: RunCommandOptions & { responseType?: MongoDBResponseConstructor } + ) { + super(options); + this.command = command; + this.options = options; + this.ns = parent.s.namespace.withCollection('$cmd'); + } + + override get commandName() { + return 'runCommand' as const; + } + + override buildCommand(_connection: Connection, _session?: ClientSession): Document { + return this.command; + } + + override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { + return { session: this.session, timeoutContext }; + } + override handleOk( + response: InstanceType + ): CursorResponse { + return response; + } +} + export class RunAdminCommandOperation extends ModernizedOperation { override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; command: Document; From 46d0141b6e7e66ea8bba9f0747df37cebecc5b73 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 13 Aug 2025 15:34:10 +0200 Subject: [PATCH 06/21] chore: lint + debug --- src/cursor/run_command_cursor.ts | 2 +- src/operations/run_command.ts | 6 +++++- test/unit/collection.test.ts | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index b8969b4c44c..186841339f2 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -4,7 +4,7 @@ import type { Db } from '../db'; import { MongoAPIError, MongoRuntimeError } from '../error'; import { executeOperation } from '../operations/execute_operation'; import { GetMoreOperation } from '../operations/get_more'; -import { RunCommandOperation, RunCursorCommandOperation } from '../operations/run_command'; +import { RunCursorCommandOperation } from '../operations/run_command'; import type { ReadConcernLike } from '../read_concern'; import type { ReadPreferenceLike } from '../read_preference'; import type { ClientSession } from '../sessions'; diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 506ae70a8ee..1c4f8fa2485 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -1,6 +1,10 @@ import type { BSONSerializeOptions, Document } from '../bson'; import { type Connection } from '../cmap/connection'; -import { CursorResponse, MongoDBResponse, type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses'; +import { + CursorResponse, + MongoDBResponse, + type MongoDBResponseConstructor +} from '../cmap/wire_protocol/responses'; import { type Db } from '../db'; import type { ReadPreferenceLike } from '../read_preference'; import type { ServerCommandOptions } from '../sdam/server'; diff --git a/test/unit/collection.test.ts b/test/unit/collection.test.ts index b050e69dc92..09a34f67fe1 100644 --- a/test/unit/collection.test.ts +++ b/test/unit/collection.test.ts @@ -25,6 +25,7 @@ describe('Collection', function () { server.setMessageHandler(request => { const doc = request.document; + console.log('doc', doc); if (isHello(doc)) { return request.reply(Object.assign({}, HELLO)); From 2f511c338e7c3cb73973194df7d6a3841a3c5bf2 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 14 Aug 2025 16:00:03 +0200 Subject: [PATCH 07/21] fix: comment on distinct --- src/operations/distinct.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/operations/distinct.ts b/src/operations/distinct.ts index 2302a3c0812..a50ed476d3b 100644 --- a/src/operations/distinct.ts +++ b/src/operations/distinct.ts @@ -56,11 +56,17 @@ export class DistinctOperation extends ModernizedCommandOperation Date: Thu, 14 Aug 2025 16:29:29 +0200 Subject: [PATCH 08/21] test: fix handshake tests --- src/operations/operation.ts | 1 + .../mongodb-handshake.prose.test.ts | 53 ++++++++++--------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/operations/operation.ts b/src/operations/operation.ts index 0804ec6486f..4ad41c26806 100644 --- a/src/operations/operation.ts +++ b/src/operations/operation.ts @@ -167,6 +167,7 @@ export abstract class ModernizedOperation extends AbstractOperation): TResult { + console.log(response); return response.toObject(this.bsonOptions) as TResult; } diff --git a/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts b/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts index b2d127fc9ca..4fa72aa41e3 100644 --- a/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts +++ b/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts @@ -6,6 +6,7 @@ import { getFAASEnv, Int32, LEGACY_HELLO_COMMAND, + MongoDBResponse, type MongoClient } from '../../mongodb'; import { sleep } from '../../tools/utils'; @@ -239,19 +240,21 @@ describe('Client Metadata Update Prose Tests', function () { } ); - sinon.stub(Connection.prototype, 'command').callsFake(async function (ns, cmd, options) { - // @ts-expect-error: sinon will place wrappedMethod on the command method. - const command = Connection.prototype.command.wrappedMethod.bind(this); - - if (cmd.hello || cmd[LEGACY_HELLO_COMMAND]) { - if (!initialClientMetadata) { - initialClientMetadata = cmd.client; - } else { - updatedClientMetadata = cmd.client; + sinon + .stub(Connection.prototype, 'command') + .callsFake(async function (ns, cmd, options, responseType) { + // @ts-expect-error: sinon will place wrappedMethod on the command method. + const command = Connection.prototype.command.wrappedMethod.bind(this); + + if (cmd.hello || cmd[LEGACY_HELLO_COMMAND]) { + if (!initialClientMetadata) { + initialClientMetadata = cmd.client; + } else { + updatedClientMetadata = cmd.client; + } } - } - return command(ns, cmd, options); - }); + return command(ns, cmd, options, responseType); + }); await client.db('test').command({ ping: 1 }); await sleep(5); @@ -311,19 +314,21 @@ describe('Client Metadata Update Prose Tests', function () { client = this.configuration.newClient({}, { maxIdleTimeMS: 1 }); client.appendMetadata({ name: 'library', version: '1.2', platform: 'Library Platform' }); - sinon.stub(Connection.prototype, 'command').callsFake(async function (ns, cmd, options) { - // @ts-expect-error: sinon will place wrappedMethod on the command method. - const command = Connection.prototype.command.wrappedMethod.bind(this); - - if (cmd.hello || cmd[LEGACY_HELLO_COMMAND]) { - if (!initialClientMetadata) { - initialClientMetadata = cmd.client; - } else { - updatedClientMetadata = cmd.client; + sinon + .stub(Connection.prototype, 'command') + .callsFake(async function (ns, cmd, options, responseType) { + // @ts-expect-error: sinon will place wrappedMethod on the command method. + const command = Connection.prototype.command.wrappedMethod.bind(this); + + if (cmd.hello || cmd[LEGACY_HELLO_COMMAND]) { + if (!initialClientMetadata) { + initialClientMetadata = cmd.client; + } else { + updatedClientMetadata = cmd.client; + } } - } - return command(ns, cmd, options); - }); + return command(ns, cmd, options, responseType); + }); await client.db('test').command({ ping: 1 }); await sleep(5); From beaba58b9412cff323822ed449cd932f9e81d606 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 14 Aug 2025 19:40:08 +0200 Subject: [PATCH 09/21] fix: lint --- .../mongodb-handshake/mongodb-handshake.prose.test.ts | 1 - test/unit/collection.test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts b/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts index 4fa72aa41e3..da5b98227be 100644 --- a/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts +++ b/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts @@ -6,7 +6,6 @@ import { getFAASEnv, Int32, LEGACY_HELLO_COMMAND, - MongoDBResponse, type MongoClient } from '../../mongodb'; import { sleep } from '../../tools/utils'; diff --git a/test/unit/collection.test.ts b/test/unit/collection.test.ts index 09a34f67fe1..b050e69dc92 100644 --- a/test/unit/collection.test.ts +++ b/test/unit/collection.test.ts @@ -25,7 +25,6 @@ describe('Collection', function () { server.setMessageHandler(request => { const doc = request.document; - console.log('doc', doc); if (isHello(doc)) { return request.reply(Object.assign({}, HELLO)); From c580aa1fee37dc91c40689004b18e50886e35745 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 14 Aug 2025 19:57:53 +0200 Subject: [PATCH 10/21] chore: remove console log --- src/operations/operation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/operations/operation.ts b/src/operations/operation.ts index 4ad41c26806..0804ec6486f 100644 --- a/src/operations/operation.ts +++ b/src/operations/operation.ts @@ -167,7 +167,6 @@ export abstract class ModernizedOperation extends AbstractOperation): TResult { - console.log(response); return response.toObject(this.bsonOptions) as TResult; } From 4ebf755e32743531c5b133d1ec98ef756d57528d Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 14 Aug 2025 20:16:24 +0200 Subject: [PATCH 11/21] chore: async close --- test/integration/read-write-concern/write_concern.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/read-write-concern/write_concern.test.ts b/test/integration/read-write-concern/write_concern.test.ts index 2610288b6ff..d72dd48de0e 100644 --- a/test/integration/read-write-concern/write_concern.test.ts +++ b/test/integration/read-write-concern/write_concern.test.ts @@ -276,9 +276,9 @@ describe('Write Concern', function () { await client.connect(); }); - afterEach(function () { + afterEach(async function () { sinon.restore(); - client.close(); + await client.close(); }); it( From 69371e833ccaf8bc684bc1b6326e82418210e63b Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 14 Aug 2025 21:05:54 +0200 Subject: [PATCH 12/21] fix: run admin command --- src/mongo_client.ts | 2 +- src/operations/run_command.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 74163da4611..83a3addd5f1 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -794,7 +794,7 @@ export class MongoClient extends TypedEventEmitter implements this, new RunAdminCommandOperation( { endSessions }, - { readPreference: ReadPreference.primaryPreferred, noResponse: true } + { readPreference: ReadPreference.primaryPreferred, writeConcern: { w: 0 } } ) ); } catch (error) { diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 1c4f8fa2485..75fa09608ce 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -11,6 +11,8 @@ import type { ServerCommandOptions } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { type TimeoutContext } from '../timeout'; import { MongoDBNamespace } from '../utils'; +import { type WriteConcern } from '../write_concern'; +import { ModernizedCommandOperation } from './command'; import { ModernizedOperation } from './operation'; /** @public */ @@ -93,22 +95,22 @@ export class RunCursorCommandOperation extends ModernizedOperation extends ModernizedOperation { +export class RunAdminCommandOperation extends ModernizedCommandOperation { override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; command: Document; override options: RunCommandOptions & { - noResponse?: boolean; + writeConcern?: WriteConcern; bypassPinningCheck?: boolean; }; constructor( command: Document, options: RunCommandOptions & { - noResponse?: boolean; + writeConcern?: WriteConcern; bypassPinningCheck?: boolean; } ) { - super(options); + super(undefined, options); this.command = command; this.options = options; this.ns = new MongoDBNamespace('admin', '$cmd'); @@ -118,7 +120,7 @@ export class RunAdminCommandOperation extends ModernizedOperation< return 'runCommand' as const; } - override buildCommand(_connection: Connection, _session?: ClientSession): Document { + override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document { return this.command; } From 79011e89dbda9c5d44a3a2ef2398a75611c258a8 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 14 Aug 2025 21:30:23 +0200 Subject: [PATCH 13/21] fix: run command operation --- src/operations/run_command.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 75fa09608ce..73d3b2f3c05 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -31,7 +31,7 @@ export type RunCommandOptions = { } & BSONSerializeOptions; /** @internal */ -export class RunCommandOperation extends ModernizedOperation { +export class RunCommandOperation extends ModernizedCommandOperation { override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; command: Document; override options: RunCommandOptions & { responseType?: MongoDBResponseConstructor }; @@ -41,7 +41,7 @@ export class RunCommandOperation extends ModernizedOperation { command: Document, options: RunCommandOptions & { responseType?: MongoDBResponseConstructor } ) { - super(options); + super(undefined, options); this.command = command; this.options = options; this.ns = parent.s.namespace.withCollection('$cmd'); @@ -51,7 +51,7 @@ export class RunCommandOperation extends ModernizedOperation { return 'runCommand' as const; } - override buildCommand(_connection: Connection, _session?: ClientSession): Document { + override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document { return this.command; } From 90b2dba80802443b0e326cfc30538e18377ffa34 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 14 Aug 2025 23:18:13 +0200 Subject: [PATCH 14/21] test: fix index test --- src/operations/command.ts | 6 ++- .../node-specific/mongo_client.test.ts | 2 +- test/unit/collection.test.ts | 38 ++++++++----------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/operations/command.ts b/src/operations/command.ts index 223a5a57fc1..050ac96e06c 100644 --- a/src/operations/command.ts +++ b/src/operations/command.ts @@ -223,13 +223,15 @@ export abstract class ModernizedCommandOperation extends ModernizedOperation< abstract buildCommandDocument(connection: Connection, session?: ClientSession): Document; override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { - return { + const options = { ...this.options, ...this.bsonOptions, timeoutContext, readPreference: this.readPreference, session: this.session }; + console.log(options); + return options; } override buildCommand(connection: Connection, session?: ClientSession): Document { @@ -261,6 +263,8 @@ export abstract class ModernizedCommandOperation extends ModernizedOperation< return decorateWithExplain(command, this.explain); } + console.log(command); + return command; } } diff --git a/test/integration/node-specific/mongo_client.test.ts b/test/integration/node-specific/mongo_client.test.ts index 9e0394013cd..db0a7f684de 100644 --- a/test/integration/node-specific/mongo_client.test.ts +++ b/test/integration/node-specific/mongo_client.test.ts @@ -916,7 +916,7 @@ describe('class MongoClient', function () { expect(result2).to.have.property('ok', 1); }); - it('sends endSessions with noResponse set', async () => { + it.only('sends endSessions with noResponse set', async () => { const session = client.startSession(); // make a session to be ended await client.db('test').command({ ping: 1 }, { session }); await session.endSession(); diff --git a/test/unit/collection.test.ts b/test/unit/collection.test.ts index b050e69dc92..5c98611d4e9 100644 --- a/test/unit/collection.test.ts +++ b/test/unit/collection.test.ts @@ -15,7 +15,17 @@ describe('Collection', function () { }); context('#createIndex', () => { - it('should error when createIndex fails', function (done) { + let client; + + before(function () { + client = new MongoClient(`mongodb://${server.uri()}`); + }); + + after(async function () { + await client.close(); + }); + + it('should error when createIndex fails', async function () { const ERROR_RESPONSE = { ok: 0, errmsg: @@ -39,27 +49,11 @@ describe('Collection', function () { } }); - const client = new MongoClient(`mongodb://${server.uri()}`); - - const close = e => client.close().then(() => done(e)); - - client - .connect() - .then(() => client.db('foo').collection('bar')) - .then(coll => coll.createIndex({ a: 1 })) - .then( - () => close('Expected createIndex to fail, but it succeeded'), - e => { - try { - expect(e).to.have.property('ok', ERROR_RESPONSE.ok); - expect(e).to.have.property('errmsg', ERROR_RESPONSE.errmsg); - expect(e).to.have.property('code', ERROR_RESPONSE.code); - close(null); - } catch (err) { - close(err); - } - } - ); + const coll = client.db('foo').collection('bar'); + const e = await coll.createIndex({ a: 1 }).catch(e => e); + expect(e).to.have.property('ok', ERROR_RESPONSE.ok); + expect(e).to.have.property('errmsg', ERROR_RESPONSE.errmsg); + expect(e).to.have.property('code', ERROR_RESPONSE.code); }); }); From 5f9a61a1ba186e32b43991c40e3b1018db4162d4 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 14 Aug 2025 23:36:46 +0200 Subject: [PATCH 15/21] chore: fix lint --- src/operations/command.ts | 1 - test/integration/node-specific/mongo_client.test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/operations/command.ts b/src/operations/command.ts index 050ac96e06c..d2d714dff91 100644 --- a/src/operations/command.ts +++ b/src/operations/command.ts @@ -230,7 +230,6 @@ export abstract class ModernizedCommandOperation extends ModernizedOperation< readPreference: this.readPreference, session: this.session }; - console.log(options); return options; } diff --git a/test/integration/node-specific/mongo_client.test.ts b/test/integration/node-specific/mongo_client.test.ts index db0a7f684de..9e0394013cd 100644 --- a/test/integration/node-specific/mongo_client.test.ts +++ b/test/integration/node-specific/mongo_client.test.ts @@ -916,7 +916,7 @@ describe('class MongoClient', function () { expect(result2).to.have.property('ok', 1); }); - it.only('sends endSessions with noResponse set', async () => { + it('sends endSessions with noResponse set', async () => { const session = client.startSession(); // make a session to be ended await client.db('test').command({ ping: 1 }, { session }); await session.endSession(); From 6aa31ff1adf34e83351800eed6642159dc03a6ff Mon Sep 17 00:00:00 2001 From: bailey Date: Tue, 19 Aug 2025 10:49:44 -0600 Subject: [PATCH 16/21] don't do chagnes to submodule --- src/mongo_client.ts | 2 +- src/operations/command.ts | 2 -- src/operations/distinct.ts | 5 +++ src/operations/run_command.ts | 10 ++++-- test/integration/index_management.test.ts | 14 ++++++++ test/unit/collection.test.ts | 43 ----------------------- 6 files changed, 27 insertions(+), 49 deletions(-) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 83a3addd5f1..74163da4611 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -794,7 +794,7 @@ export class MongoClient extends TypedEventEmitter implements this, new RunAdminCommandOperation( { endSessions }, - { readPreference: ReadPreference.primaryPreferred, writeConcern: { w: 0 } } + { readPreference: ReadPreference.primaryPreferred, noResponse: true } ) ); } catch (error) { diff --git a/src/operations/command.ts b/src/operations/command.ts index d2d714dff91..c50164a0045 100644 --- a/src/operations/command.ts +++ b/src/operations/command.ts @@ -262,8 +262,6 @@ export abstract class ModernizedCommandOperation extends ModernizedOperation< return decorateWithExplain(command, this.explain); } - console.log(command); - return command; } } diff --git a/src/operations/distinct.ts b/src/operations/distinct.ts index a50ed476d3b..ff8ee796ec9 100644 --- a/src/operations/distinct.ts +++ b/src/operations/distinct.ts @@ -66,6 +66,11 @@ export class DistinctOperation extends ModernizedCommandOperation extends ModernizedCommandOperation { @@ -56,7 +58,7 @@ export class RunCommandOperation extends ModernizedCommandOperatio } override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { - return { session: this.session, timeoutContext }; + return { session: this.session, timeoutContext, signal: this.options.signal }; } } @@ -101,6 +103,7 @@ export class RunAdminCommandOperation extends ModernizedCommandOpe override options: RunCommandOptions & { writeConcern?: WriteConcern; bypassPinningCheck?: boolean; + noResponse?: boolean; }; constructor( @@ -108,6 +111,7 @@ export class RunAdminCommandOperation extends ModernizedCommandOpe options: RunCommandOptions & { writeConcern?: WriteConcern; bypassPinningCheck?: boolean; + noResponse?: boolean; } ) { super(undefined, options); @@ -125,6 +129,6 @@ export class RunAdminCommandOperation extends ModernizedCommandOpe } override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { - return { session: this.session, timeoutContext }; + return { session: this.session, timeoutContext, noResponse: this.options.noResponse }; } } diff --git a/test/integration/index_management.test.ts b/test/integration/index_management.test.ts index ac7354ce995..be10562e6eb 100644 --- a/test/integration/index_management.test.ts +++ b/test/integration/index_management.test.ts @@ -8,6 +8,7 @@ import { type MongoClient, MongoServerError } from '../mongodb'; +import { type FailPoint } from '../tools/utils'; import { assert as test, filterForCommands, setupDatabase } from './shared'; describe('Indexes', function () { @@ -36,6 +37,19 @@ describe('Indexes', function () { expect(response).to.exist; }); + it('createIndex() throws an error error when createIndex fails', async function () { + await client.db('admin').command({ + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: ['createIndexes'], + errorCode: 10 + } + }); + const error = await db.createIndex('promiseCollectionCollections1', { a: 1 }).catch(e => e); + expect(error).to.be.instanceOf(MongoServerError); + }); + it('shouldCorrectlyExtractIndexInformation', async function () { const collection = await db.createCollection('test_index_information'); await collection.insertMany([{ a: 1 }], this.configuration.writeConcernMax()); diff --git a/test/unit/collection.test.ts b/test/unit/collection.test.ts index 5c98611d4e9..c45712abebc 100644 --- a/test/unit/collection.test.ts +++ b/test/unit/collection.test.ts @@ -14,49 +14,6 @@ describe('Collection', function () { await cleanup(); }); - context('#createIndex', () => { - let client; - - before(function () { - client = new MongoClient(`mongodb://${server.uri()}`); - }); - - after(async function () { - await client.close(); - }); - - it('should error when createIndex fails', async function () { - const ERROR_RESPONSE = { - ok: 0, - errmsg: - 'WiredTigerIndex::insert: key too large to index, failing 1470 { : "56f37cb8e4b089e98d52ab0e", : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..." }', - code: 17280 - }; - - server.setMessageHandler(request => { - const doc = request.document; - - if (isHello(doc)) { - return request.reply(Object.assign({}, HELLO)); - } - - if (doc.createIndexes) { - return request.reply(ERROR_RESPONSE); - } - - if (doc.insert === 'system.indexes') { - return request.reply(ERROR_RESPONSE); - } - }); - - const coll = client.db('foo').collection('bar'); - const e = await coll.createIndex({ a: 1 }).catch(e => e); - expect(e).to.have.property('ok', ERROR_RESPONSE.ok); - expect(e).to.have.property('errmsg', ERROR_RESPONSE.errmsg); - expect(e).to.have.property('code', ERROR_RESPONSE.code); - }); - }); - context('#aggregate', () => { // general test for aggregate function function testAggregate(config, done) { From e12412b2d1886fa0e3dc1bdb871ce461f3c8fccd Mon Sep 17 00:00:00 2001 From: bailey Date: Tue, 19 Aug 2025 11:16:30 -0600 Subject: [PATCH 17/21] run command test --- src/operations/run_command.ts | 40 +++++++++++------------------------ 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 6018726a063..1491f9459dd 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -14,7 +14,6 @@ import { type TimeoutContext } from '../timeout'; import { MongoDBNamespace } from '../utils'; import { type WriteConcern } from '../write_concern'; import { ModernizedCommandOperation } from './command'; -import { ModernizedOperation } from './operation'; /** @public */ export type RunCommandOptions = { @@ -58,38 +57,23 @@ export class RunCommandOperation extends ModernizedCommandOperatio } override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { - return { session: this.session, timeoutContext, signal: this.options.signal }; + return { + session: this.session, + timeoutContext, + signal: this.options.signal, + readPreference: this.options.readPreference + }; } } -/** @internal */ -export class RunCursorCommandOperation extends ModernizedOperation { +/** + * @internal + * + * A specialized subclass of RunCommandOperation for cursor-creating commands. + */ +export class RunCursorCommandOperation extends RunCommandOperation { override SERVER_COMMAND_RESPONSE_TYPE = CursorResponse; - command: Document; - override options: RunCommandOptions & { responseType?: MongoDBResponseConstructor }; - - constructor( - parent: Db, - command: Document, - options: RunCommandOptions & { responseType?: MongoDBResponseConstructor } - ) { - super(options); - this.command = command; - this.options = options; - this.ns = parent.s.namespace.withCollection('$cmd'); - } - - override get commandName() { - return 'runCommand' as const; - } - override buildCommand(_connection: Connection, _session?: ClientSession): Document { - return this.command; - } - - override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { - return { session: this.session, timeoutContext }; - } override handleOk( response: InstanceType ): CursorResponse { From 2f0d38a6ea4f4a11e854034a83c8d6fabbab375c Mon Sep 17 00:00:00 2001 From: bailey Date: Tue, 19 Aug 2025 20:13:16 -0600 Subject: [PATCH 18/21] cleanups --- src/operations/command.ts | 3 +-- src/operations/run_command.ts | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/operations/command.ts b/src/operations/command.ts index c50164a0045..223a5a57fc1 100644 --- a/src/operations/command.ts +++ b/src/operations/command.ts @@ -223,14 +223,13 @@ export abstract class ModernizedCommandOperation extends ModernizedOperation< abstract buildCommandDocument(connection: Connection, session?: ClientSession): Document; override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { - const options = { + return { ...this.options, ...this.bsonOptions, timeoutContext, readPreference: this.readPreference, session: this.session }; - return options; } override buildCommand(connection: Connection, session?: ClientSession): Document { diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 1491f9459dd..657fe935b75 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -12,7 +12,6 @@ import type { ServerCommandOptions } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { type TimeoutContext } from '../timeout'; import { MongoDBNamespace } from '../utils'; -import { type WriteConcern } from '../write_concern'; import { ModernizedCommandOperation } from './command'; /** @public */ @@ -85,17 +84,15 @@ export class RunAdminCommandOperation extends ModernizedCommandOpe override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; command: Document; override options: RunCommandOptions & { - writeConcern?: WriteConcern; - bypassPinningCheck?: boolean; noResponse?: boolean; + bypassPinningCheck?: boolean; }; constructor( command: Document, options: RunCommandOptions & { - writeConcern?: WriteConcern; - bypassPinningCheck?: boolean; noResponse?: boolean; + bypassPinningCheck?: boolean; } ) { super(undefined, options); From 2b54a11eba71069b3b3eb78638b11219a75d6423 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 20 Aug 2025 08:50:33 -0600 Subject: [PATCH 19/21] fix run command stuff --- src/admin.ts | 5 +- src/cursor/run_command_cursor.ts | 7 +- src/db.ts | 2 +- src/mongo_client.ts | 30 ++++++--- src/operations/run_command.ts | 67 +++++-------------- src/sessions.ts | 9 +-- .../crud/abstract_operation.test.ts | 6 -- 7 files changed, 49 insertions(+), 77 deletions(-) diff --git a/src/admin.ts b/src/admin.ts index 0f03023a95c..c75a51fa9dd 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -8,11 +8,12 @@ import { type ListDatabasesResult } from './operations/list_databases'; import { RemoveUserOperation, type RemoveUserOptions } from './operations/remove_user'; -import { RunAdminCommandOperation, type RunCommandOptions } from './operations/run_command'; +import { RunCommandOperation, type RunCommandOptions } from './operations/run_command'; import { ValidateCollectionOperation, type ValidateCollectionOptions } from './operations/validate_collection'; +import { MongoDBNamespace } from './utils'; /** @internal */ export interface AdminPrivate { @@ -75,7 +76,7 @@ export class Admin { async command(command: Document, options?: RunCommandOptions): Promise { return await executeOperation( this.s.db.client, - new RunAdminCommandOperation(command, { + new RunCommandOperation(new MongoDBNamespace('admin'), command, { ...resolveBSONOptions(options), session: options?.session, readPreference: options?.readPreference, diff --git a/src/cursor/run_command_cursor.ts b/src/cursor/run_command_cursor.ts index 186841339f2..e6ceee1060c 100644 --- a/src/cursor/run_command_cursor.ts +++ b/src/cursor/run_command_cursor.ts @@ -1,5 +1,5 @@ import type { BSONSerializeOptions, Document } from '../bson'; -import { CursorResponse } from '../cmap/wire_protocol/responses'; +import { type CursorResponse } from '../cmap/wire_protocol/responses'; import type { Db } from '../db'; import { MongoAPIError, MongoRuntimeError } from '../error'; import { executeOperation } from '../operations/execute_operation'; @@ -143,11 +143,10 @@ export class RunCommandCursor extends AbstractCursor { /** @internal */ protected async _initialize(session: ClientSession): Promise { - const operation = new RunCursorCommandOperation(this.db, this.command, { + const operation = new RunCursorCommandOperation(this.db.s.namespace, this.command, { ...this.cursorOptions, session: session, - readPreference: this.cursorOptions.readPreference, - responseType: CursorResponse + readPreference: this.cursorOptions.readPreference }); const response = await executeOperation(this.client, operation, this.timeoutContext); diff --git a/src/db.ts b/src/db.ts index 49b094bc100..a5c678f4f1e 100644 --- a/src/db.ts +++ b/src/db.ts @@ -272,7 +272,7 @@ export class Db { return await executeOperation( this.client, new RunCommandOperation( - this, + this.s.namespace, command, resolveOptions(undefined, { ...resolveBSONOptions(options), diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 74163da4611..2d0e7ca5c09 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -2,6 +2,7 @@ import { promises as fs } from 'fs'; import type { TcpNetConnectOpts } from 'net'; import type { ConnectionOptions as TLSConnectionOptions, TLSSocketOptions } from 'tls'; +import { type ServerCommandOptions, type TimeoutContext } from '.'; import { type BSONSerializeOptions, type Document, resolveBSONOptions } from './bson'; import { ChangeStream, type ChangeStreamDocument, type ChangeStreamOptions } from './change_stream'; import type { AutoEncrypter, AutoEncryptionOptions } from './client-side-encryption/auto_encrypter'; @@ -20,6 +21,7 @@ import { makeClientMetadata } from './cmap/handshake/client_metadata'; import type { CompressorName } from './cmap/wire_protocol/compression'; +import { MongoDBResponse } from './cmap/wire_protocol/responses'; import { parseOptions, resolveSRVRecord } from './connection_string'; import { MONGO_CLIENT_EVENTS } from './constants'; import { type AbstractCursor } from './cursor/abstract_cursor'; @@ -42,7 +44,7 @@ import { } from './operations/client_bulk_write/common'; import { ClientBulkWriteExecutor } from './operations/client_bulk_write/executor'; import { executeOperation } from './operations/execute_operation'; -import { RunAdminCommandOperation } from './operations/run_command'; +import { ModernizedOperation } from './operations/operation'; import type { ReadConcern, ReadConcernLevel, ReadConcernLike } from './read_concern'; import { ReadPreference, type ReadPreferenceMode } from './read_preference'; import { type AsyncDisposable, configureResourceManagement } from './resource_management'; @@ -790,13 +792,25 @@ export class MongoClient extends TypedEventEmitter implements const endSessions = Array.from(this.s.sessionPool.sessions, ({ id }) => id); if (endSessions.length !== 0) { try { - await executeOperation( - this, - new RunAdminCommandOperation( - { endSessions }, - { readPreference: ReadPreference.primaryPreferred, noResponse: true } - ) - ); + class EndSessionsOperation extends ModernizedOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; + override buildCommand(_connection: Connection, _session?: ClientSession): Document { + return { + endSessions + }; + } + override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { + return { + timeoutContext, + readPreference: ReadPreference.primaryPreferred, + noResponse: true + }; + } + override get commandName(): string { + return 'endSessions'; + } + } + await executeOperation(this, new EndSessionsOperation()); } catch (error) { squashError(error); } diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 657fe935b75..61b77792b52 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -1,18 +1,13 @@ import { type Abortable } from '..'; import type { BSONSerializeOptions, Document } from '../bson'; import { type Connection } from '../cmap/connection'; -import { - CursorResponse, - MongoDBResponse, - type MongoDBResponseConstructor -} from '../cmap/wire_protocol/responses'; -import { type Db } from '../db'; +import { CursorResponse, MongoDBResponse } from '../cmap/wire_protocol/responses'; +import { ModernizedOperation } from '../operations/operation'; import type { ReadPreferenceLike } from '../read_preference'; import type { ServerCommandOptions } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { type TimeoutContext } from '../timeout'; -import { MongoDBNamespace } from '../utils'; -import { ModernizedCommandOperation } from './command'; +import { type MongoDBNamespace } from '../utils'; /** @public */ export type RunCommandOptions = { @@ -27,31 +22,33 @@ export type RunCommandOptions = { timeoutMS?: number; /** @internal */ omitMaxTimeMS?: boolean; + + /** + * @internal Hints to `executeOperation` that this operation should not unpin on an ended transaction + * This is only used by the driver for transaction commands + */ + bypassPinningCheck?: boolean; } & BSONSerializeOptions & Abortable; /** @internal */ -export class RunCommandOperation extends ModernizedCommandOperation { +export class RunCommandOperation extends ModernizedOperation { override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; command: Document; - override options: RunCommandOptions & { responseType?: MongoDBResponseConstructor }; + override options: RunCommandOptions; - constructor( - parent: Db, - command: Document, - options: RunCommandOptions & { responseType?: MongoDBResponseConstructor } - ) { - super(undefined, options); + constructor(namespace: MongoDBNamespace, command: Document, options: RunCommandOptions) { + super(options); this.command = command; this.options = options; - this.ns = parent.s.namespace.withCollection('$cmd'); + this.ns = namespace.withCollection('$cmd'); } override get commandName() { return 'runCommand' as const; } - override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document { + override buildCommand(_connection: Connection, _session?: ClientSession): Document { return this.command; } @@ -79,37 +76,3 @@ export class RunCursorCommandOperation extends RunCommandOperation { return response; } } - -export class RunAdminCommandOperation extends ModernizedCommandOperation { - override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; - command: Document; - override options: RunCommandOptions & { - noResponse?: boolean; - bypassPinningCheck?: boolean; - }; - - constructor( - command: Document, - options: RunCommandOptions & { - noResponse?: boolean; - bypassPinningCheck?: boolean; - } - ) { - super(undefined, options); - this.command = command; - this.options = options; - this.ns = new MongoDBNamespace('admin', '$cmd'); - } - - override get commandName() { - return 'runCommand' as const; - } - - override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document { - return this.command; - } - - override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { - return { session: this.session, timeoutContext, noResponse: this.options.noResponse }; - } -} diff --git a/src/sessions.ts b/src/sessions.ts index ee1f13171bf..abd63468da2 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -24,7 +24,7 @@ import { import type { MongoClient, MongoOptions } from './mongo_client'; import { TypedEventEmitter } from './mongo_types'; import { executeOperation } from './operations/execute_operation'; -import { RunAdminCommandOperation } from './operations/run_command'; +import { RunCommandOperation } from './operations/run_command'; import { ReadConcernLevel } from './read_concern'; import { ReadPreference } from './read_preference'; import { type AsyncDisposable, configureResourceManagement } from './resource_management'; @@ -43,6 +43,7 @@ import { isPromiseLike, List, maxWireVersion, + MongoDBNamespace, noop, now, squashError, @@ -505,7 +506,7 @@ export class ClientSession command.recoveryToken = this.transaction.recoveryToken; } - const operation = new RunAdminCommandOperation(command, { + const operation = new RunCommandOperation(new MongoDBNamespace('admin'), command, { session: this, readPreference: ReadPreference.primary, bypassPinningCheck: true @@ -536,7 +537,7 @@ export class ClientSession try { await executeOperation( this.client, - new RunAdminCommandOperation(command, { + new RunCommandOperation(new MongoDBNamespace('admin'), command, { session: this, readPreference: ReadPreference.primary, bypassPinningCheck: true @@ -637,7 +638,7 @@ export class ClientSession command.recoveryToken = this.transaction.recoveryToken; } - const operation = new RunAdminCommandOperation(command, { + const operation = new RunCommandOperation(new MongoDBNamespace('admin'), command, { session: this, readPreference: ReadPreference.primary, bypassPinningCheck: true diff --git a/test/integration/crud/abstract_operation.test.ts b/test/integration/crud/abstract_operation.test.ts index fe4d1d47586..a6ea5681c23 100644 --- a/test/integration/crud/abstract_operation.test.ts +++ b/test/integration/crud/abstract_operation.test.ts @@ -192,12 +192,6 @@ describe('abstract operation', function () { subclassType: mongodb.RunCommandOperation, correctCommandName: 'runCommand' }, - { - subclassCreator: () => - new mongodb.RunAdminCommandOperation({ dummyCommand: 'dummyCommand' }, {}), - subclassType: mongodb.RunAdminCommandOperation, - correctCommandName: 'runCommand' - }, { subclassCreator: () => new mongodb.CreateSearchIndexesOperation(collection, [{ definition: { a: 1 } }]), From 165da894b14746771f4924592a5cdb7bcd5eaa84 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 20 Aug 2025 09:05:16 -0600 Subject: [PATCH 20/21] run endSessions on admin. --- src/mongo_client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 2d0e7ca5c09..54562147f0b 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -62,7 +62,7 @@ import { type HostAddress, hostMatchesWildcards, isHostMatch, - type MongoDBNamespace, + MongoDBNamespace, noop, ns, resolveOptions, @@ -793,6 +793,7 @@ export class MongoClient extends TypedEventEmitter implements if (endSessions.length !== 0) { try { class EndSessionsOperation extends ModernizedOperation { + override ns = MongoDBNamespace.fromString('admin.$cmd'); override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; override buildCommand(_connection: Connection, _session?: ClientSession): Document { return { From 8e4912a69c92ab6cf352b0c5c6b56d1296af29dd Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 20 Aug 2025 09:27:56 -0600 Subject: [PATCH 21/21] fix operation constructor test --- src/operations/run_command.ts | 1 + test/integration/crud/abstract_operation.test.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/operations/run_command.ts b/src/operations/run_command.ts index 61b77792b52..fd40bc3062a 100644 --- a/src/operations/run_command.ts +++ b/src/operations/run_command.ts @@ -54,6 +54,7 @@ export class RunCommandOperation extends ModernizedOperation { override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions { return { + ...this.options, session: this.session, timeoutContext, signal: this.options.signal, diff --git a/test/integration/crud/abstract_operation.test.ts b/test/integration/crud/abstract_operation.test.ts index a6ea5681c23..cce3ac9ec37 100644 --- a/test/integration/crud/abstract_operation.test.ts +++ b/test/integration/crud/abstract_operation.test.ts @@ -188,7 +188,11 @@ describe('abstract operation', function () { }, { subclassCreator: () => - new mongodb.RunCommandOperation(db, { dummyCommand: 'dummyCommand' }, {}), + new mongodb.RunCommandOperation( + new mongodb.MongoDBNamespace('foo', 'bar'), + { dummyCommand: 'dummyCommand' }, + {} + ), subclassType: mongodb.RunCommandOperation, correctCommandName: 'runCommand' },