Skip to content

Commit d35862e

Browse files
committed
fix(NODE-4936)!: remove unsupported options from db.command and admin.command
1 parent c44af11 commit d35862e

File tree

9 files changed

+87
-104
lines changed

9 files changed

+87
-104
lines changed

Diff for: src/admin.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Document } from './bson';
1+
import { type Document, resolveBSONOptions } from './bson';
22
import type { Db } from './db';
33
import { AddUserOperation, type AddUserOptions } from './operations/add_user';
44
import type { CommandOperationOptions } from './operations/command';
@@ -9,7 +9,7 @@ import {
99
type ListDatabasesResult
1010
} from './operations/list_databases';
1111
import { RemoveUserOperation, type RemoveUserOptions } from './operations/remove_user';
12-
import { RunCommandOperation, type RunCommandOptions } from './operations/run_command';
12+
import { RunAdminCommandOperation, type RunCommandOptions } from './operations/run_command';
1313
import {
1414
ValidateCollectionOperation,
1515
type ValidateCollectionOptions
@@ -76,7 +76,11 @@ export class Admin {
7676
async command(command: Document, options?: RunCommandOptions): Promise<Document> {
7777
return executeOperation(
7878
this.s.db.client,
79-
new RunCommandOperation(this.s.db, command, { dbName: 'admin', ...options })
79+
new RunAdminCommandOperation(command, {
80+
...resolveBSONOptions(options),
81+
session: options?.session,
82+
readPreference: options?.readPreference
83+
})
8084
);
8185
}
8286

Diff for: src/db.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,14 @@ export class Db {
259259
*/
260260
async command(command: Document, options?: RunCommandOptions): Promise<Document> {
261261
// Intentionally, we do not inherit options from parent for this operation.
262-
return executeOperation(this.client, new RunCommandOperation(this, command, options));
262+
return executeOperation(
263+
this.client,
264+
new RunCommandOperation(this, command, {
265+
...resolveBSONOptions(options),
266+
session: options?.session,
267+
readPreference: options?.readPreference
268+
})
269+
);
263270
}
264271

265272
/**

Diff for: src/mongo_client.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import type { Encrypter } from './encrypter';
2222
import { MongoInvalidArgumentError } from './error';
2323
import { MongoLogger, type MongoLoggerOptions } from './mongo_logger';
2424
import { TypedEventEmitter } from './mongo_types';
25+
import { executeOperation } from './operations/execute_operation';
26+
import { RunAdminCommandOperation } from './operations/run_command';
2527
import type { ReadConcern, ReadConcernLevel, ReadConcernLike } from './read_concern';
2628
import { ReadPreference, type ReadPreferenceMode } from './read_preference';
2729
import type { TagSet } from './sdam/server_description';
@@ -519,12 +521,13 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
519521
if (servers.length !== 0) {
520522
const endSessions = Array.from(this.s.sessionPool.sessions, ({ id }) => id);
521523
if (endSessions.length !== 0) {
522-
await this.db('admin')
523-
.command(
524+
await executeOperation(
525+
this,
526+
new RunAdminCommandOperation(
524527
{ endSessions },
525528
{ readPreference: ReadPreference.primaryPreferred, noResponse: true }
526529
)
527-
.catch(() => null); // outcome does not matter
530+
).catch(() => null); // outcome does not matter;
528531
}
529532
}
530533

Diff for: src/operations/operation.ts

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ const kSession = Symbol('session');
4949
*/
5050
export abstract class AbstractOperation<TResult = any> {
5151
ns!: MongoDBNamespace;
52-
cmd!: Document;
5352
readPreference: ReadPreference;
5453
server!: Server;
5554
bypassPinningCheck: boolean;

Diff for: src/operations/rename.ts

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import type { Document } from '../bson';
22
import { Collection } from '../collection';
3-
import { MongoServerError } from '../error';
43
import type { Server } from '../sdam/server';
54
import type { ClientSession } from '../sessions';
6-
import { checkCollectionName } from '../utils';
7-
import type { CommandOperationOptions } from './command';
5+
import { checkCollectionName, MongoDBNamespace } from '../utils';
6+
import { CommandOperation, type CommandOperationOptions } from './command';
87
import { Aspect, defineAspects } from './operation';
9-
import { RunAdminCommandOperation } from './run_command';
108

119
/** @public */
1210
export interface RenameOptions extends CommandOperationOptions {
@@ -17,39 +15,41 @@ export interface RenameOptions extends CommandOperationOptions {
1715
}
1816

1917
/** @internal */
20-
export class RenameOperation extends RunAdminCommandOperation {
18+
export class RenameOperation extends CommandOperation<Document> {
2119
override options: RenameOptions;
2220
collection: Collection;
2321
newName: string;
2422

2523
constructor(collection: Collection, newName: string, options: RenameOptions) {
2624
// Check the collection name
2725
checkCollectionName(newName);
26+
super(collection, options);
2827

29-
// Build the command
30-
const renameCollection = collection.namespace;
31-
const toCollection = collection.s.namespace.withCollection(newName).toString();
32-
const dropTarget = typeof options.dropTarget === 'boolean' ? options.dropTarget : false;
33-
const cmd = { renameCollection: renameCollection, to: toCollection, dropTarget: dropTarget };
34-
35-
super(collection, cmd, options);
28+
this.ns = new MongoDBNamespace('admin', '$cmd');
3629
this.options = options;
3730
this.collection = collection;
3831
this.newName = newName;
3932
}
4033

4134
override async execute(server: Server, session: ClientSession | undefined): Promise<Collection> {
42-
const coll = this.collection;
43-
44-
const doc = await super.execute(server, session);
45-
// We have an error
46-
if (doc?.errmsg) {
47-
throw new MongoServerError(doc);
48-
}
49-
50-
const newColl: Collection<Document> = new Collection(coll.s.db, this.newName, coll.s.options);
35+
// Build the command
36+
const renameCollection = this.collection.namespace;
37+
const toCollection = this.collection.s.namespace.withCollection(this.newName).toString();
38+
const dropTarget =
39+
typeof this.options.dropTarget === 'boolean' ? this.options.dropTarget : false;
40+
41+
const command = {
42+
renameCollection: renameCollection,
43+
to: toCollection,
44+
dropTarget: dropTarget
45+
};
46+
47+
await super.executeCommand(server, session, command);
48+
return new Collection(this.collection.s.db, this.newName, this.collection.s.options);
49+
}
5150

52-
return newColl;
51+
protected executeCallback(_server: any, _session: any, _callback: any): void {
52+
throw new Error('Method not implemented.');
5353
}
5454
}
5555

Diff for: src/operations/run_command.ts

+33-48
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,58 @@
11
import type { BSONSerializeOptions, Document } from '../bson';
2+
import { type Db } from '../db';
3+
import { type TODO_NODE_3286 } from '../mongo_types';
24
import type { ReadPreferenceLike } from '../read_preference';
35
import type { Server } from '../sdam/server';
46
import type { ClientSession } from '../sessions';
5-
import { type Callback, MongoDBNamespace } from '../utils';
6-
import { CommandCallbackOperation, type OperationParent } from './command';
7+
import { MongoDBNamespace } from '../utils';
8+
import { AbstractOperation } from './operation';
79

810
/** @public */
911
export type RunCommandOptions = {
1012
/** Specify ClientSession for this command */
1113
session?: ClientSession;
1214
/** The read preference */
1315
readPreference?: ReadPreferenceLike;
14-
15-
// The following options were "accidentally" supported
16-
// Since the options are generally supported through inheritance
17-
18-
/** @deprecated This is an internal option that has undefined behavior for this API */
19-
willRetryWrite?: any;
20-
/** @deprecated This is an internal option that has undefined behavior for this API */
21-
omitReadPreference?: any;
22-
/** @deprecated This is an internal option that has undefined behavior for this API */
23-
writeConcern?: any;
24-
/** @deprecated This is an internal option that has undefined behavior for this API */
25-
explain?: any;
26-
/** @deprecated This is an internal option that has undefined behavior for this API */
27-
readConcern?: any;
28-
/** @deprecated This is an internal option that has undefined behavior for this API */
29-
collation?: any;
30-
/** @deprecated This is an internal option that has undefined behavior for this API */
31-
maxTimeMS?: any;
32-
/** @deprecated This is an internal option that has undefined behavior for this API */
33-
comment?: any;
34-
/** @deprecated This is an internal option that has undefined behavior for this API */
35-
retryWrites?: any;
36-
/** @deprecated This is an internal option that has undefined behavior for this API */
37-
dbName?: any;
38-
/** @deprecated This is an internal option that has undefined behavior for this API */
39-
authdb?: any;
40-
/** @deprecated This is an internal option that has undefined behavior for this API */
41-
noResponse?: any;
42-
43-
/** @internal Used for transaction commands */
44-
bypassPinningCheck?: boolean;
4516
} & BSONSerializeOptions;
4617

4718
/** @internal */
48-
export class RunCommandOperation<T = Document> extends CommandCallbackOperation<T> {
19+
export class RunCommandOperation<T = Document> extends AbstractOperation<T> {
4920
override options: RunCommandOptions;
5021
command: Document;
5122

52-
constructor(parent: OperationParent | undefined, command: Document, options?: RunCommandOptions) {
53-
super(parent, options);
54-
this.options = options ?? {};
23+
constructor(parent: Db, command: Document, options: RunCommandOptions) {
24+
super(options);
25+
this.options = options;
5526
this.command = command;
27+
this.ns = parent.s.namespace.withCollection('$cmd');
5628
}
5729

58-
override executeCallback(
59-
server: Server,
60-
session: ClientSession | undefined,
61-
callback: Callback<T>
62-
): void {
63-
const command = this.command;
64-
this.executeCommandCallback(server, session, command, callback);
30+
override async execute(server: Server, session: ClientSession | undefined): Promise<T> {
31+
return server.commandAsync(this.ns, this.command, {
32+
...this.options,
33+
readPreference: this.readPreference,
34+
session
35+
}) as TODO_NODE_3286;
6536
}
6637
}
6738

68-
export class RunAdminCommandOperation<T = Document> extends RunCommandOperation<T> {
69-
constructor(parent: OperationParent | undefined, command: Document, options?: RunCommandOptions) {
70-
super(parent, command, options);
71-
this.ns = new MongoDBNamespace('admin');
39+
export class RunAdminCommandOperation<T = Document> extends AbstractOperation<T> {
40+
command: Document;
41+
42+
constructor(
43+
command: Document,
44+
options: RunCommandOptions & { noResponse?: boolean; bypassPinningCheck?: boolean }
45+
) {
46+
super(options);
47+
this.command = command;
48+
this.ns = new MongoDBNamespace('admin', '$cmd');
49+
}
50+
51+
override async execute(server: Server, session: ClientSession | undefined): Promise<T> {
52+
return server.commandAsync(this.ns, this.command, {
53+
...this.options,
54+
readPreference: this.readPreference,
55+
session
56+
}) as TODO_NODE_3286;
7257
}
7358
}

Diff for: src/sessions.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ function endTransaction(
754754
// send the command
755755
executeOperation(
756756
session.client,
757-
new RunAdminCommandOperation(undefined, command, {
757+
new RunAdminCommandOperation(command, {
758758
session,
759759
readPreference: ReadPreference.primary,
760760
bypassPinningCheck: true
@@ -778,7 +778,7 @@ function endTransaction(
778778

779779
return executeOperation(
780780
session.client,
781-
new RunAdminCommandOperation(undefined, command, {
781+
new RunAdminCommandOperation(command, {
782782
session,
783783
readPreference: ReadPreference.primary,
784784
bypassPinningCheck: true
@@ -989,7 +989,7 @@ export function applySession(
989989
if (
990990
session.supports.causalConsistency &&
991991
session.operationTime &&
992-
commandSupportsReadConcern(command, options)
992+
commandSupportsReadConcern(command)
993993
) {
994994
command.readConcern = command.readConcern || {};
995995
Object.assign(command.readConcern, { afterClusterTime: session.operationTime });

Diff for: src/utils.ts

+1-10
Original file line numberDiff line numberDiff line change
@@ -1155,20 +1155,11 @@ export function shuffle<T>(sequence: Iterable<T>, limit = 0): Array<T> {
11551155

11561156
// TODO(NODE-4936): read concern eligibility for commands should be codified in command construction
11571157
// @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.rst#read-concern
1158-
export function commandSupportsReadConcern(command: Document, options?: Document): boolean {
1158+
export function commandSupportsReadConcern(command: Document): boolean {
11591159
if (command.aggregate || command.count || command.distinct || command.find || command.geoNear) {
11601160
return true;
11611161
}
11621162

1163-
if (
1164-
command.mapReduce &&
1165-
options &&
1166-
options.out &&
1167-
(options.out.inline === 1 || options.out === 'inline')
1168-
) {
1169-
return true;
1170-
}
1171-
11721163
return false;
11731164
}
11741165

Diff for: test/integration/run-command/run_command.test.ts

+7-13
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,11 @@ describe('RunCommand API', () => {
4242
expect(command).to.not.have.property('writeConcern');
4343
});
4444

45-
// TODO(NODE-4936): We do support readConcern in options, the spec forbids this
46-
it.skip(
47-
'does not support readConcern in options',
48-
{ requires: { mongodb: '>=5.0' } },
49-
async () => {
50-
const command = Object.freeze({ find: 'test', filter: {} });
51-
const res = await db.command(command, { readConcern: ReadConcern.MAJORITY });
52-
expect(res).to.have.property('ok', 1);
53-
expect(commandsStarted).to.not.have.nested.property('[0].command.readConcern');
54-
expect(command).to.not.have.property('readConcern');
55-
}
56-
).skipReason =
57-
'TODO(NODE-4936): Enable this test when readConcern support has been removed from runCommand';
45+
it('does not support readConcern in options', { requires: { mongodb: '>=5.0' } }, async () => {
46+
const command = Object.freeze({ find: 'test', filter: {} });
47+
const res = await db.command(command, { readConcern: ReadConcern.MAJORITY });
48+
expect(res).to.have.property('ok', 1);
49+
expect(commandsStarted).to.not.have.nested.property('[0].command.readConcern');
50+
expect(command).to.not.have.property('readConcern');
51+
});
5852
});

0 commit comments

Comments
 (0)