From 669a089adfd11c5693b2adec123ebaf1a87b4189 Mon Sep 17 00:00:00 2001 From: bailey Date: Mon, 4 Aug 2025 14:05:54 -0600 Subject: [PATCH 1/6] count --- src/operations/count.ts | 45 +++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/operations/count.ts b/src/operations/count.ts index e3f9800d0e5..c1e5ac84f03 100644 --- a/src/operations/count.ts +++ b/src/operations/count.ts @@ -1,10 +1,10 @@ +import { type Connection } from '..'; import type { Document } from '../bson'; +import { MongoDBResponse } 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 type { MongoDBNamespace } from '../utils'; -import { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; /** @public */ @@ -21,8 +21,15 @@ export interface CountOptions extends CommandOperationOptions { hint?: string | Document; } +class CountResponse extends MongoDBResponse { + get n(): number { + return this.getNumber('n') ?? 0; + } +} + /** @internal */ -export class CountOperation extends CommandOperation { +export class CountOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = CountResponse; override options: CountOptions; collectionName?: string; query: Document; @@ -39,35 +46,33 @@ export class CountOperation extends CommandOperation { return 'count' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const options = this.options; + override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document { const cmd: Document = { count: this.collectionName, query: this.query }; - if (typeof options.limit === 'number') { - cmd.limit = options.limit; + if (typeof this.options.limit === 'number') { + cmd.limit = this.options.limit; } - if (typeof options.skip === 'number') { - cmd.skip = options.skip; + if (typeof this.options.skip === 'number') { + cmd.skip = this.options.skip; } - if (options.hint != null) { - cmd.hint = options.hint; + if (this.options.hint != null) { + cmd.hint = this.options.hint; } - if (typeof options.maxTimeMS === 'number') { - cmd.maxTimeMS = options.maxTimeMS; + if (typeof this.options.maxTimeMS === 'number') { + cmd.maxTimeMS = this.options.maxTimeMS; } - const result = await super.executeCommand(server, session, cmd, timeoutContext); - return result ? result.n : 0; + return cmd; + } + + override handleOk(response: InstanceType): number { + return response.n; } } From 6d52e39921bb4777e6f567cd8721ecf4f9e61be1 Mon Sep 17 00:00:00 2001 From: bailey Date: Mon, 4 Aug 2025 14:09:12 -0600 Subject: [PATCH 2/6] estimateddocumentcount --- src/operations/estimated_document_count.ts | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/operations/estimated_document_count.ts b/src/operations/estimated_document_count.ts index 5ab5aa4c305..dc8fa554d1d 100644 --- a/src/operations/estimated_document_count.ts +++ b/src/operations/estimated_document_count.ts @@ -1,9 +1,9 @@ +import { type Connection } from '..'; import type { Document } from '../bson'; +import { MongoDBResponse } 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 { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; /** @public */ @@ -16,8 +16,15 @@ export interface EstimatedDocumentCountOptions extends CommandOperationOptions { maxTimeMS?: number; } +class EstimatedDocumentCountResponse extends MongoDBResponse { + get n(): number { + return this.getNumber('n') ?? 0; + } +} + /** @internal */ -export class EstimatedDocumentCountOperation extends CommandOperation { +export class EstimatedDocumentCountOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = EstimatedDocumentCountResponse; override options: EstimatedDocumentCountOptions; collectionName: string; @@ -31,11 +38,7 @@ export class EstimatedDocumentCountOperation extends CommandOperation { return 'count' as const; } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { + override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document { const cmd: Document = { count: this.collectionName }; if (typeof this.options.maxTimeMS === 'number') { @@ -48,9 +51,11 @@ export class EstimatedDocumentCountOperation extends CommandOperation { cmd.comment = this.options.comment; } - const response = await super.executeCommand(server, session, cmd, timeoutContext); + return cmd; + } - return response?.n || 0; + override handleOk(response: InstanceType): number { + return response.n; } } From e25bdc30733071eaaeb579ba4e7d993c14a07943 Mon Sep 17 00:00:00 2001 From: bailey Date: Tue, 5 Aug 2025 15:15:07 -0600 Subject: [PATCH 3/6] insert --- src/operations/insert.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/operations/insert.ts b/src/operations/insert.ts index bbc324e65de..b26a32466df 100644 --- a/src/operations/insert.ts +++ b/src/operations/insert.ts @@ -5,9 +5,7 @@ import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; import { MongoServerError } from '../error'; import type { InferIdType } from '../mongo_types'; -import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; -import { type TimeoutContext } from '../timeout'; import { maybeAddIdToDocuments, type MongoDBNamespace } from '../utils'; import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; @@ -73,24 +71,6 @@ export class InsertOneOperation extends InsertOperation { super(collection.s.namespace, [maybeAddIdToDocuments(collection, doc, options)], options); } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const res = await super.execute(server, session, timeoutContext); - if (res.code) throw new MongoServerError(res); - if (res.writeErrors) { - // This should be a WriteError but we can't change it now because of error hierarchy - throw new MongoServerError(res.writeErrors[0]); - } - - return { - acknowledged: this.writeConcern?.w !== 0, - insertedId: this.documents[0]._id - }; - } - override handleOk(response: InstanceType): Document { const res = super.handleOk(response); if (res.code) throw new MongoServerError(res); From 69a3ec26789814390cec492dc7f93984dca81af3 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 6 Aug 2025 10:11:47 -0600 Subject: [PATCH 4/6] delete --- src/collection.ts | 8 ++++-- src/operations/delete.ts | 58 +++++++++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/collection.ts b/src/collection.ts index 0ad56d04f22..9cf23fc4a79 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -430,7 +430,7 @@ export class Collection { filter, replacement, resolveOptions(this, options) - ) + ) as TODO_NODE_3286 ); } @@ -489,7 +489,11 @@ export class Collection { ): Promise { return await executeOperation( this.client, - new DeleteManyOperation(this as TODO_NODE_3286, filter, resolveOptions(this, options)) + new DeleteManyOperation( + this as TODO_NODE_3286, + filter, + resolveOptions(this, options) + ) as TODO_NODE_3286 ); } diff --git a/src/operations/delete.ts b/src/operations/delete.ts index 0e93ead36a2..0de5421f2ca 100644 --- a/src/operations/delete.ts +++ b/src/operations/delete.ts @@ -1,4 +1,6 @@ import type { Document } from '../bson'; +import { type Connection } from '../cmap/connection'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; import { MongoCompatibilityError, MongoServerError } from '../error'; import { type TODO_NODE_3286 } from '../mongo_types'; @@ -7,7 +9,11 @@ import type { ClientSession } from '../sessions'; import { type TimeoutContext } from '../timeout'; import { type MongoDBNamespace } from '../utils'; import { type WriteConcernOptions } from '../write_concern'; -import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command'; +import { + type CollationOptions, + type CommandOperationOptions, + ModernizedCommandOperation +} from './command'; import { Aspect, defineAspects, type Hint } from './operation'; /** @public */ @@ -43,7 +49,8 @@ export interface DeleteStatement { } /** @internal */ -export class DeleteOperation extends CommandOperation { +export class DeleteOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; override options: DeleteOptions; statements: DeleteStatement[]; @@ -66,12 +73,9 @@ export class DeleteOperation extends CommandOperation { return this.statements.every(op => (op.limit != null ? op.limit > 0 : true)); } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const options = this.options ?? {}; + override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document { + const options = this.options; + const ordered = typeof options.ordered === 'boolean' ? options.ordered : true; const command: Document = { delete: this.ns.collection, @@ -97,13 +101,7 @@ export class DeleteOperation extends CommandOperation { } } - const res: TODO_NODE_3286 = await super.executeCommand( - server, - session, - command, - timeoutContext - ); - return res; + return command; } } @@ -127,19 +125,37 @@ export class DeleteOneOperation extends DeleteOperation { deletedCount: res.n }; } + + override handleOk( + response: InstanceType + ): DeleteResult { + const res = super.handleOk(response); + + // @ts-expect-error Explain commands have broken TS + if (this.explain) return res; + + if (res.code) throw new MongoServerError(res); + if (res.writeErrors) throw new MongoServerError(res.writeErrors[0]); + + return { + acknowledged: this.writeConcern?.w !== 0, + deletedCount: res.n + }; + } } export class DeleteManyOperation extends DeleteOperation { constructor(collection: Collection, filter: Document, options: DeleteOptions) { super(collection.s.namespace, [makeDeleteStatement(filter, options)], options); } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const res: TODO_NODE_3286 = await super.execute(server, session, timeoutContext); + override handleOk( + response: InstanceType + ): DeleteResult { + const res = super.handleOk(response); + + // @ts-expect-error Explain commands have broken TS if (this.explain) return res; + if (res.code) throw new MongoServerError(res); if (res.writeErrors) throw new MongoServerError(res.writeErrors[0]); From 9e936fbf64ebf5c6d4f56a4a0d759ccc3430fe67 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 6 Aug 2025 10:55:45 -0600 Subject: [PATCH 5/6] update & replace --- src/operations/update.ts | 55 +++++++++++++++------------------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/src/operations/update.ts b/src/operations/update.ts index f731e77945d..76c4142cbad 100644 --- a/src/operations/update.ts +++ b/src/operations/update.ts @@ -1,13 +1,17 @@ import type { Document } from '../bson'; +import { type Connection } from '../cmap/connection'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; import { MongoCompatibilityError, MongoInvalidArgumentError, MongoServerError } from '../error'; -import type { InferIdType, TODO_NODE_3286 } from '../mongo_types'; -import type { Server } from '../sdam/server'; +import type { InferIdType } from '../mongo_types'; import type { ClientSession } from '../sessions'; import { formatSort, type Sort, type SortForCmd } from '../sort'; -import { type TimeoutContext } from '../timeout'; import { hasAtomicOperators, type MongoDBNamespace } from '../utils'; -import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command'; +import { + type CollationOptions, + type CommandOperationOptions, + ModernizedCommandOperation +} from './command'; import { Aspect, defineAspects, type Hint } from './operation'; /** @public */ @@ -67,7 +71,8 @@ export interface UpdateStatement { * @internal * UpdateOperation is used in bulk write, while UpdateOneOperation and UpdateManyOperation are only used in the collections API */ -export class UpdateOperation extends CommandOperation { +export class UpdateOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; override options: UpdateOptions & { ordered?: boolean }; statements: UpdateStatement[]; @@ -95,17 +100,12 @@ export class UpdateOperation extends CommandOperation { return this.statements.every(op => op.multi == null || op.multi === false); } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const options = this.options ?? {}; - const ordered = typeof options.ordered === 'boolean' ? options.ordered : true; + override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document { + const options = this.options; const command: Document = { update: this.ns.collection, updates: this.statements, - ordered + ordered: options.ordered ?? true }; if (typeof options.bypassDocumentValidation === 'boolean') { @@ -122,7 +122,7 @@ export class UpdateOperation extends CommandOperation { command.comment = options.comment; } - const unacknowledgedWrite = this.writeConcern && this.writeConcern.w === 0; + const unacknowledgedWrite = this.writeConcern?.w === 0; if (unacknowledgedWrite) { if (this.statements.find((o: Document) => o.hint)) { // TODO(NODE-3541): fix error for hint with unacknowledged writes @@ -130,8 +130,7 @@ export class UpdateOperation extends CommandOperation { } } - const res = await super.executeCommand(server, session, command, timeoutContext); - return res; + return command; } } @@ -149,12 +148,8 @@ export class UpdateOneOperation extends UpdateOperation { } } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const res: TODO_NODE_3286 = await super.execute(server, session, timeoutContext); + override handleOk(response: InstanceType): Document { + const res = super.handleOk(response); if (this.explain != null) return res; if (res.code) throw new MongoServerError(res); if (res.writeErrors) throw new MongoServerError(res.writeErrors[0]); @@ -184,12 +179,8 @@ export class UpdateManyOperation extends UpdateOperation { } } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const res: TODO_NODE_3286 = await super.execute(server, session, timeoutContext); + override handleOk(response: InstanceType): Document { + const res = super.handleOk(response); if (this.explain != null) return res; if (res.code) throw new MongoServerError(res); if (res.writeErrors) throw new MongoServerError(res.writeErrors[0]); @@ -240,12 +231,8 @@ export class ReplaceOneOperation extends UpdateOperation { } } - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const res: TODO_NODE_3286 = await super.execute(server, session, timeoutContext); + override handleOk(response: InstanceType): Document { + const res = super.handleOk(response); if (this.explain != null) return res; if (res.code) throw new MongoServerError(res); if (res.writeErrors) throw new MongoServerError(res.writeErrors[0]); From ebc7975c707312274c65f58bbc2ea8b97914ae4e Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 6 Aug 2025 11:17:46 -0600 Subject: [PATCH 6/6] find and modify --- src/operations/find_and_modify.ts | 136 ++++++++++++++++++------------ 1 file changed, 80 insertions(+), 56 deletions(-) diff --git a/src/operations/find_and_modify.ts b/src/operations/find_and_modify.ts index 759cb02e72c..aaa07b136aa 100644 --- a/src/operations/find_and_modify.ts +++ b/src/operations/find_and_modify.ts @@ -1,14 +1,14 @@ +import { type Connection } from '..'; import type { Document } from '../bson'; +import { MongoDBResponse } from '../cmap/wire_protocol/responses'; import type { Collection } from '../collection'; import { MongoCompatibilityError, MongoInvalidArgumentError } from '../error'; import { ReadPreference } from '../read_preference'; -import type { Server } from '../sdam/server'; import type { ClientSession } from '../sessions'; import { formatSort, type Sort, type SortForCmd } from '../sort'; -import { type TimeoutContext } from '../timeout'; -import { decorateWithCollation, hasAtomicOperators, maxWireVersion } from '../utils'; +import { decorateWithCollation, hasAtomicOperators } from '../utils'; import { type WriteConcern, type WriteConcernSettings } from '../write_concern'; -import { CommandOperation, type CommandOperationOptions } from './command'; +import { type CommandOperationOptions, ModernizedCommandOperation } from './command'; import { Aspect, defineAspects } from './operation'; /** @public */ @@ -120,9 +120,9 @@ function configureFindAndModifyCmdBaseUpdateOpts( } /** @internal */ -export class FindAndModifyOperation extends CommandOperation { +export class FindAndModifyOperation extends ModernizedCommandOperation { + override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse; override options: FindOneAndReplaceOptions | FindOneAndUpdateOptions | FindOneAndDeleteOptions; - cmdBase: FindAndModifyCmdBase; collection: Collection; query: Document; doc?: Document; @@ -133,8 +133,26 @@ export class FindAndModifyOperation extends CommandOperation { options: FindOneAndReplaceOptions | FindOneAndUpdateOptions | FindOneAndDeleteOptions ) { super(collection, options); - this.options = options ?? {}; - this.cmdBase = { + this.options = options; + // force primary read preference + this.readPreference = ReadPreference.primary; + + this.collection = collection; + this.query = query; + } + + override get commandName() { + return 'findAndModify' as const; + } + + override buildCommandDocument( + _connection: Connection, + _session?: ClientSession + ): Document & FindAndModifyCmdBase { + const options = this.options; + const command: Document & FindAndModifyCmdBase = { + findAndModify: this.collection.collectionName, + query: this.query, remove: false, new: false, upsert: false @@ -144,77 +162,51 @@ export class FindAndModifyOperation extends CommandOperation { const sort = formatSort(options.sort); if (sort) { - this.cmdBase.sort = sort; + command.sort = sort; } if (options.projection) { - this.cmdBase.fields = options.projection; + command.fields = options.projection; } if (options.maxTimeMS) { - this.cmdBase.maxTimeMS = options.maxTimeMS; + command.maxTimeMS = options.maxTimeMS; } // Decorate the findAndModify command with the write Concern if (options.writeConcern) { - this.cmdBase.writeConcern = options.writeConcern; + command.writeConcern = options.writeConcern; } if (options.let) { - this.cmdBase.let = options.let; + command.let = options.let; } // we check for undefined specifically here to allow falsy values // eslint-disable-next-line no-restricted-syntax if (options.comment !== undefined) { - this.cmdBase.comment = options.comment; + command.comment = options.comment; } - // force primary read preference - this.readPreference = ReadPreference.primary; - - this.collection = collection; - this.query = query; - } - - override get commandName() { - return 'findAndModify' as const; - } - - override async execute( - server: Server, - session: ClientSession | undefined, - timeoutContext: TimeoutContext - ): Promise { - const coll = this.collection; - const query = this.query; - const options = { ...this.options, ...this.bsonOptions }; - - // Create findAndModify command object - const cmd: Document = { - findAndModify: coll.collectionName, - query: query, - ...this.cmdBase - }; - - decorateWithCollation(cmd, coll, options); + decorateWithCollation(command, this.collection, options); if (options.hint) { - // TODO: once this method becomes a CommandOperation we will have the server - // in place to check. const unacknowledgedWrite = this.writeConcern?.w === 0; - if (unacknowledgedWrite || maxWireVersion(server) < 8) { + if (unacknowledgedWrite) { throw new MongoCompatibilityError( 'The current topology does not support a hint on findAndModify commands' ); } - cmd.hint = options.hint; + command.hint = options.hint; } - // Execute the command - const result = await super.executeCommand(server, session, cmd, timeoutContext); - return options.includeResultMetadata ? result : (result.value ?? null); + return command; + } + + override handleOk(response: InstanceType): Document { + const result = super.handleOk(response); + return this.options.includeResultMetadata ? result : (result.value ?? null); } } @@ -227,12 +219,21 @@ export class FindOneAndDeleteOperation extends FindAndModifyOperation { } super(collection, filter, options); - this.cmdBase.remove = true; + } + + override buildCommandDocument( + connection: Connection, + session?: ClientSession + ): Document & FindAndModifyCmdBase { + const document = super.buildCommandDocument(connection, session); + document.remove = true; + return document; } } /** @internal */ export class FindOneAndReplaceOperation extends FindAndModifyOperation { + private replacement: Document; constructor( collection: Collection, filter: Document, @@ -252,13 +253,25 @@ export class FindOneAndReplaceOperation extends FindAndModifyOperation { } super(collection, filter, options); - this.cmdBase.update = replacement; - configureFindAndModifyCmdBaseUpdateOpts(this.cmdBase, options); + this.replacement = replacement; + } + + override buildCommandDocument( + connection: Connection, + session?: ClientSession + ): Document & FindAndModifyCmdBase { + const document = super.buildCommandDocument(connection, session); + document.update = this.replacement; + configureFindAndModifyCmdBaseUpdateOpts(document, this.options); + return document; } } /** @internal */ export class FindOneAndUpdateOperation extends FindAndModifyOperation { + override options: FindOneAndUpdateOptions; + + private update: Document; constructor( collection: Collection, filter: Document, @@ -278,12 +291,23 @@ export class FindOneAndUpdateOperation extends FindAndModifyOperation { } super(collection, filter, options); - this.cmdBase.update = update; - configureFindAndModifyCmdBaseUpdateOpts(this.cmdBase, options); + this.update = update; + this.options = options; + } - if (options.arrayFilters) { - this.cmdBase.arrayFilters = options.arrayFilters; + override buildCommandDocument( + connection: Connection, + session?: ClientSession + ): Document & FindAndModifyCmdBase { + const document = super.buildCommandDocument(connection, session); + document.update = this.update; + configureFindAndModifyCmdBaseUpdateOpts(document, this.options); + + if (this.options.arrayFilters) { + document.arrayFilters = this.options.arrayFilters; } + + return document; } }