Skip to content

Commit 8ed6411

Browse files
command op too
1 parent bef8cd8 commit 8ed6411

File tree

5 files changed

+119
-13
lines changed

5 files changed

+119
-13
lines changed

src/operations/command.ts

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { type Connection } from '..';
12
import type { BSONSerializeOptions, Document } from '../bson';
23
import { type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses';
34
import { MongoInvalidArgumentError } from '../error';
@@ -9,14 +10,14 @@ import {
910
} from '../explain';
1011
import { ReadConcern } from '../read_concern';
1112
import type { ReadPreference } from '../read_preference';
12-
import type { Server } from '../sdam/server';
13+
import type { Server, ServerCommandOptions } from '../sdam/server';
1314
import { MIN_SECONDARY_WRITE_WIRE_VERSION } from '../sdam/server_selection';
1415
import type { ClientSession } from '../sessions';
1516
import { type TimeoutContext } from '../timeout';
1617
import { commandSupportsReadConcern, maxWireVersion, MongoDBNamespace } from '../utils';
1718
import { WriteConcern, type WriteConcernOptions } from '../write_concern';
1819
import type { ReadConcernLike } from './../read_concern';
19-
import { AbstractOperation, Aspect, type OperationOptions } from './operation';
20+
import { AbstractOperation, Aspect, ModernizedOperation, type OperationOptions } from './operation';
2021

2122
/** @public */
2223
export interface CollationOptions {
@@ -183,3 +184,93 @@ export abstract class CommandOperation<T> extends AbstractOperation<T> {
183184
return await server.command(this.ns, cmd, options, responseType);
184185
}
185186
}
187+
188+
export abstract class ModernizedCommandOperation<T> extends ModernizedOperation<T> {
189+
override options: CommandOperationOptions;
190+
readConcern?: ReadConcern;
191+
writeConcern?: WriteConcern;
192+
explain?: Explain;
193+
194+
constructor(parent?: OperationParent, options?: CommandOperationOptions) {
195+
super(options);
196+
this.options = options ?? {};
197+
198+
// NOTE: this was explicitly added for the add/remove user operations, it's likely
199+
// something we'd want to reconsider. Perhaps those commands can use `Admin`
200+
// as a parent?
201+
const dbNameOverride = options?.dbName || options?.authdb;
202+
if (dbNameOverride) {
203+
this.ns = new MongoDBNamespace(dbNameOverride, '$cmd');
204+
} else {
205+
this.ns = parent
206+
? parent.s.namespace.withCollection('$cmd')
207+
: new MongoDBNamespace('admin', '$cmd');
208+
}
209+
210+
this.readConcern = ReadConcern.fromOptions(options);
211+
this.writeConcern = WriteConcern.fromOptions(options);
212+
213+
if (this.hasAspect(Aspect.EXPLAINABLE)) {
214+
this.explain = Explain.fromOptions(options);
215+
if (this.explain) validateExplainTimeoutOptions(this.options, this.explain);
216+
} else if (options?.explain != null) {
217+
throw new MongoInvalidArgumentError(`Option "explain" is not supported on this command`);
218+
}
219+
}
220+
221+
override get canRetryWrite(): boolean {
222+
if (this.hasAspect(Aspect.EXPLAINABLE)) {
223+
return this.explain == null;
224+
}
225+
return super.canRetryWrite;
226+
}
227+
228+
abstract buildCommandDocument(connection: Connection, session?: ClientSession): Document;
229+
230+
override buildOptions(timeoutContext: TimeoutContext): ServerCommandOptions {
231+
return {
232+
...this.options,
233+
...this.bsonOptions,
234+
timeoutContext,
235+
readPreference: this.readPreference,
236+
session: this.session
237+
};
238+
}
239+
240+
override buildCommand(connection: Connection, session?: ClientSession): Document {
241+
const command = this.buildCommandDocument(connection, session);
242+
243+
const serverWireVersion = maxWireVersion(connection);
244+
const inTransaction = this.session && this.session.inTransaction();
245+
246+
if (this.readConcern && commandSupportsReadConcern(command) && !inTransaction) {
247+
Object.assign(command, { readConcern: this.readConcern });
248+
}
249+
250+
if (this.trySecondaryWrite && serverWireVersion < MIN_SECONDARY_WRITE_WIRE_VERSION) {
251+
command.omitReadPreference = true;
252+
}
253+
254+
if (this.writeConcern && this.hasAspect(Aspect.WRITE_OPERATION) && !inTransaction) {
255+
WriteConcern.apply(command, this.writeConcern);
256+
}
257+
258+
if (
259+
this.options.collation &&
260+
typeof this.options.collation === 'object' &&
261+
!this.hasAspect(Aspect.SKIP_COLLATION)
262+
) {
263+
Object.assign(command, { collation: this.options.collation });
264+
}
265+
266+
if (typeof this.options.maxTimeMS === 'number') {
267+
command.maxTimeMS = this.options.maxTimeMS;
268+
}
269+
270+
if (this.hasAspect(Aspect.EXPLAINABLE) && this.explain) {
271+
return decorateWithExplain(command, this.explain);
272+
}
273+
274+
return command;
275+
}
276+
}

src/operations/execute_operation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ async function tryOperation<
279279
}
280280
}
281281

282+
operation.server = server;
283+
282284
try {
283285
// If tries > 0 and we are command batching we need to reset the batch.
284286
if (tries > 0 && operation.hasAspect(Aspect.COMMAND_BATCHING)) {

src/operations/insert.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1+
import { type Connection } from '..';
12
import type { Document } from '../bson';
23
import type { BulkWriteOptions } from '../bulk/common';
4+
import { MongoDBResponse } from '../cmap/wire_protocol/responses';
35
import type { Collection } from '../collection';
46
import { MongoServerError } from '../error';
57
import type { InferIdType } from '../mongo_types';
68
import type { Server } from '../sdam/server';
79
import type { ClientSession } from '../sessions';
810
import { type TimeoutContext } from '../timeout';
911
import { maybeAddIdToDocuments, type MongoDBNamespace } from '../utils';
10-
import { CommandOperation, type CommandOperationOptions } from './command';
12+
import { type CommandOperationOptions, ModernizedCommandOperation } from './command';
1113
import { Aspect, defineAspects } from './operation';
12-
1314
/** @internal */
14-
export class InsertOperation extends CommandOperation<Document> {
15+
export class InsertOperation extends ModernizedCommandOperation<Document> {
16+
override SERVER_COMMAND_RESPONSE_TYPE = MongoDBResponse;
1517
override options: BulkWriteOptions;
18+
1619
documents: Document[];
1720

1821
constructor(ns: MongoDBNamespace, documents: Document[], options: BulkWriteOptions) {
@@ -26,11 +29,7 @@ export class InsertOperation extends CommandOperation<Document> {
2629
return 'insert' as const;
2730
}
2831

29-
override async execute(
30-
server: Server,
31-
session: ClientSession | undefined,
32-
timeoutContext: TimeoutContext
33-
): Promise<Document> {
32+
override buildCommandDocument(_connection: Connection, _session?: ClientSession): Document {
3433
const options = this.options ?? {};
3534
const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
3635
const command: Document = {
@@ -49,7 +48,7 @@ export class InsertOperation extends CommandOperation<Document> {
4948
command.comment = options.comment;
5049
}
5150

52-
return await super.executeCommand(server, session, command, timeoutContext);
51+
return command;
5352
}
5453
}
5554

@@ -91,6 +90,20 @@ export class InsertOneOperation extends InsertOperation {
9190
insertedId: this.documents[0]._id
9291
};
9392
}
93+
94+
override handleOk(response: InstanceType<typeof this.SERVER_COMMAND_RESPONSE_TYPE>): Document {
95+
const res = super.handleOk(response);
96+
if (res.code) throw new MongoServerError(res);
97+
if (res.writeErrors) {
98+
// This should be a WriteError but we can't change it now because of error hierarchy
99+
throw new MongoServerError(res.writeErrors[0]);
100+
}
101+
102+
return {
103+
acknowledged: this.writeConcern?.w !== 0,
104+
insertedId: this.documents[0]._id
105+
};
106+
}
94107
}
95108

96109
/** @public */

src/operations/search_indexes/drop.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { type Connection, type MongoError } from '../..';
22
import type { Document } from '../../bson';
3+
import { MongoDBResponse } from '../../cmap/wire_protocol/responses';
34
import type { Collection } from '../../collection';
45
import { MONGODB_ERROR_CODES, MongoServerError } from '../../error';
56
import type { ServerCommandOptions } from '../../sdam/server';
67
import type { ClientSession } from '../../sessions';
78
import { type TimeoutContext } from '../../timeout';
89
import { ModernizedOperation } from '../operation';
9-
import { MongoDBResponse } from '../../cmap/wire_protocol/responses'
1010

1111
/** @internal */
1212
export class DropSearchIndexOperation extends ModernizedOperation<void> {

test/integration/retryable-writes/non-server-retryable_writes.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('Non Server Retryable Writes', function () {
3232
'returns the original error with a PoolRequstedRetry label after encountering a WriteConcernError',
3333
{ requires: { topology: 'replicaset' } },
3434
async () => {
35-
const serverCommandStub = sinon.stub(Server.prototype, 'command');
35+
const serverCommandStub = sinon.stub(Server.prototype, 'modernCommand');
3636
serverCommandStub.onCall(0).rejects(new PoolClearedError('error'));
3737
serverCommandStub.onCall(1).returns(
3838
Promise.reject(

0 commit comments

Comments
 (0)