-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
Showing
20 changed files
with
3,146 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import type { BSONSerializeOptions, Document, Long } from '../bson'; | ||
import type { Db } from '../db'; | ||
import { MongoAPIError, MongoUnexpectedServerResponseError } from '../error'; | ||
import { executeOperation, ExecutionResult } from '../operations/execute_operation'; | ||
import { GetMoreOperation } from '../operations/get_more'; | ||
import { RunCommandOperation } from '../operations/run_command'; | ||
import type { ReadConcernLike } from '../read_concern'; | ||
import type { ReadPreferenceLike } from '../read_preference'; | ||
import type { ClientSession } from '../sessions'; | ||
import { Callback, ns } from '../utils'; | ||
import { AbstractCursor } from './abstract_cursor'; | ||
|
||
/** @public */ | ||
export type RunCursorCommandOptions = { | ||
readPreference?: ReadPreferenceLike; | ||
session?: ClientSession; | ||
} & BSONSerializeOptions; | ||
|
||
/** @internal */ | ||
type RunCursorCommandResponse = { | ||
cursor: { id: bigint | Long | number; ns: string; firstBatch: Document[] }; | ||
ok: 1; | ||
}; | ||
|
||
/** @public */ | ||
export class RunCommandCursor extends AbstractCursor { | ||
public readonly command: Readonly<Record<string, any>>; | ||
public readonly getMoreOptions: { | ||
comment?: any; | ||
maxAwaitTimeMS?: number; | ||
batchSize?: number; | ||
} = {}; | ||
|
||
/** | ||
* Controls the `getMore.comment` field | ||
* @param comment - any BSON value | ||
*/ | ||
public setComment(comment: any): this { | ||
this.getMoreOptions.comment = comment; | ||
return this; | ||
} | ||
|
||
/** | ||
* Controls the `getMore.maxTimeMS` field. Only valid when cursor is tailable await | ||
* @param maxTimeMS - the number of milliseconds to wait for new data | ||
*/ | ||
public setMaxTimeMS(maxTimeMS: number): this { | ||
this.getMoreOptions.maxAwaitTimeMS = maxTimeMS; | ||
return this; | ||
} | ||
|
||
/** | ||
* Controls the `getMore.batchSize` field | ||
* @param maxTimeMS - the number documents to return in the `nextBatch` | ||
*/ | ||
public setBatchSize(batchSize: number): this { | ||
this.getMoreOptions.batchSize = batchSize; | ||
return this; | ||
} | ||
|
||
/** Unsupported for RunCommandCursor */ | ||
public override clone(): never { | ||
throw new MongoAPIError('Clone not supported, create a new cursor with db.runCursorCommand'); | ||
} | ||
|
||
/** Unsupported for RunCommandCursor: readConcern must be configured directly on command document */ | ||
public override withReadConcern(_: ReadConcernLike): never { | ||
throw new MongoAPIError( | ||
'RunCommandCursor does not support readConcern it must be attached to the command being run' | ||
); | ||
} | ||
|
||
/** Unsupported for RunCommandCursor: various cursor flags must be configured directly on command document */ | ||
public override addCursorFlag(_: string, __: boolean): never { | ||
throw new MongoAPIError( | ||
'RunCommandCursor does not support cursor flags, they must be attached to the command being run' | ||
); | ||
} | ||
|
||
/** Unsupported for RunCommandCursor: maxTimeMS must be configured directly on command document */ | ||
public override maxTimeMS(_: number): never { | ||
throw new MongoAPIError( | ||
'maxTimeMS must be configured on the command document directly, to configure getMore.maxTimeMS use cursor.setMaxTimeMS()' | ||
); | ||
} | ||
|
||
/** Unsupported for RunCommandCursor: batchSize must be configured directly on command document */ | ||
public override batchSize(_: number): never { | ||
throw new MongoAPIError( | ||
'batchSize must be configured on the command document directly, to configure getMore.batchSize use cursor.setBatchSize()' | ||
); | ||
} | ||
|
||
/** @internal */ | ||
private db: Db; | ||
|
||
/** @internal */ | ||
constructor(db: Db, command: Document, options: RunCursorCommandOptions = {}) { | ||
super(db.s.client, ns(db.namespace), options); | ||
this.db = db; | ||
this.command = Object.freeze({ ...command }); | ||
} | ||
|
||
/** @internal */ | ||
protected _initialize(session: ClientSession, callback: Callback<ExecutionResult>) { | ||
const operation = new RunCommandOperation<RunCursorCommandResponse>(this.db, this.command, { | ||
...this.cursorOptions, | ||
session: session, | ||
readPreference: this.cursorOptions.readPreference | ||
}); | ||
executeOperation(this.client, operation).then( | ||
response => { | ||
if (response.cursor == null) { | ||
callback( | ||
new MongoUnexpectedServerResponseError('Expected server to respond with cursor') | ||
); | ||
return; | ||
} | ||
callback(undefined, { | ||
server: operation.server, | ||
session, | ||
response | ||
}); | ||
}, | ||
err => callback(err) | ||
); | ||
} | ||
|
||
/** @internal */ | ||
override _getMore(_batchSize: number, callback: Callback<Document>) { | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const getMoreOperation = new GetMoreOperation(this.namespace, this.id!, this.server!, { | ||
...this.cursorOptions, | ||
session: this.session, | ||
...this.getMoreOptions | ||
}); | ||
|
||
executeOperation(this.client, getMoreOperation, callback); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { expect } from 'chai'; | ||
|
||
import { Db, MongoClient } from '../../mongodb'; | ||
|
||
describe('runCursorCommand API', () => { | ||
let client: MongoClient; | ||
let db: Db; | ||
|
||
beforeEach(async function () { | ||
client = this.configuration.newClient({}, { monitorCommands: true }); | ||
db = client.db(); | ||
await db.dropDatabase().catch(() => null); | ||
await db | ||
.collection<{ _id: number }>('collection') | ||
.insertMany([{ _id: 0 }, { _id: 1 }, { _id: 2 }]); | ||
}); | ||
|
||
afterEach(async function () { | ||
await client.close(); | ||
}); | ||
|
||
it('returns each document only once across multiple iterators', async () => { | ||
const cursor = db.runCursorCommand({ find: 'collection', filter: {}, batchSize: 1 }); | ||
cursor.setBatchSize(1); | ||
|
||
const a = cursor[Symbol.asyncIterator](); | ||
const b = cursor[Symbol.asyncIterator](); | ||
|
||
// Interleaving calls to A and B | ||
const results = [ | ||
await a.next(), // find, first doc | ||
await b.next(), // getMore, second doc | ||
|
||
await a.next(), // getMore, third doc | ||
await b.next(), // getMore, no doc & exhausted id, a.k.a. done | ||
|
||
await a.next(), // done | ||
await b.next() // done | ||
]; | ||
|
||
expect(results).to.deep.equal([ | ||
{ value: { _id: 0 }, done: false }, | ||
{ value: { _id: 1 }, done: false }, | ||
{ value: { _id: 2 }, done: false }, | ||
{ value: undefined, done: true }, | ||
{ value: undefined, done: true }, | ||
{ value: undefined, done: true } | ||
]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.