From 4b95aac1742c1afeb4fe3e35d94f4c2f8d36fd48 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 9 Apr 2024 16:48:57 +0200 Subject: [PATCH] WIP --- packages/cli-repl/src/cli-repl.ts | 6 +- packages/cli-repl/src/mongosh-repl.ts | 7 +- packages/cli-repl/src/smoke-tests-fle.ts | 80 +---- packages/cli-repl/src/smoke-tests.ts | 4 +- packages/service-provider-core/src/index.ts | 6 +- .../service-provider-core/src/readable.ts | 2 +- .../src/service-provider.ts | 8 + .../src/cli-service-provider.ts | 327 +++++++++++++++++- packages/service-provider-server/src/index.ts | 10 +- packages/shell-api/src/abstract-cursor.ts | 56 +-- .../shell-api/src/aggregate-or-find-cursor.ts | 5 +- packages/shell-api/src/collection.ts | 45 +-- packages/shell-api/src/cursor.ts | 21 +- packages/shell-api/src/database.ts | 43 +-- packages/shell-api/src/helpers.ts | 6 +- packages/shell-api/src/mongo.ts | 16 +- .../shell-api/src/shell-instance-state.ts | 17 +- .../shell-evaluator/src/shell-evaluator.ts | 25 +- 18 files changed, 481 insertions(+), 203 deletions(-) diff --git a/packages/cli-repl/src/cli-repl.ts b/packages/cli-repl/src/cli-repl.ts index a86d6cac58..d53a7a4c73 100644 --- a/packages/cli-repl/src/cli-repl.ts +++ b/packages/cli-repl/src/cli-repl.ts @@ -7,7 +7,7 @@ import { redactURICredentials } from '@mongosh/history'; import i18n from '@mongosh/i18n'; import type { AutoEncryptionOptions } from '@mongosh/service-provider-core'; import { bson } from '@mongosh/service-provider-core'; -import { CliServiceProvider } from '@mongosh/service-provider-server'; +import { SynchronousCliServiceProvider } from '@mongosh/service-provider-server'; import type { CliOptions, DevtoolsConnectOptions } from '@mongosh/arg-parser'; import { SnippetManager } from '@mongosh/snippet-manager'; import { Editor } from '@mongosh/editor'; @@ -792,7 +792,7 @@ export class CliRepl implements MongoshIOProvider { async connect( driverUri: string, driverOptions: DevtoolsConnectOptions - ): Promise { + ): Promise { const { quiet } = CliRepl.getFileAndEvalInfo(this.cliOptions); if (!this.cliOptions.nodb && !quiet) { this.output.write( @@ -802,7 +802,7 @@ export class CliRepl implements MongoshIOProvider { '\n' ); } - return await CliServiceProvider.connect( + return await SynchronousCliServiceProvider.connect( driverUri, driverOptions, this.cliOptions, diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index fd3e742db1..1acaf802ff 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -3,7 +3,7 @@ import { MongoshInternalError, MongoshWarning } from '@mongosh/errors'; import { changeHistory } from '@mongosh/history'; import type { AutoEncryptionOptions, - ServiceProvider, + //ServiceProvider, } from '@mongosh/service-provider-core'; import type { EvaluationListener, @@ -42,6 +42,7 @@ import type { FormatOptions } from './format-output'; import { markTime } from './startup-timing'; import type { Context } from 'vm'; import { Script, createContext, runInContext } from 'vm'; +import type { SynchronousServiceProvider } from '@mongosh/service-provider-core'; declare const __non_webpack_require__: any; @@ -176,7 +177,7 @@ class MongoshNodeRepl implements EvaluationListener { * or print any user prompt. */ async initialize( - serviceProvider: ServiceProvider, + serviceProvider: SynchronousServiceProvider, moreRecentMongoshVersion?: string | null ): Promise { const instanceState = new ShellInstanceState( @@ -198,7 +199,7 @@ class MongoshNodeRepl implements EvaluationListener { let mongodVersion = extraInfo?.is_stream ? 'Atlas Stream Processing' : buildInfo?.version; - const apiVersion = serviceProvider.getRawClient()?.serverApi?.version; + const apiVersion = undefined; //serviceProvider.getRawClient()?.serverApi?.version; if (apiVersion) { mongodVersion = (mongodVersion ? mongodVersion + ' ' : '') + diff --git a/packages/cli-repl/src/smoke-tests-fle.ts b/packages/cli-repl/src/smoke-tests-fle.ts index 938bbc0dc7..f4a2ec4520 100644 --- a/packages/cli-repl/src/smoke-tests-fle.ts +++ b/packages/cli-repl/src/smoke-tests-fle.ts @@ -4,82 +4,4 @@ * to create an auto-encryption-aware connection. */ -export default String.raw` -const assert = function(value, message) { - if (!value) { - console.error('assertion failed:', message); - unencryptedDb.dropDatabase(); - process.exit(1); - } -}; -if (db.version().startsWith('4.0.') || - !db.runCommand({buildInfo:1}).modules.includes('enterprise')) { - // No FLE on mongod < 4.2 or community - print('Test skipped') - process.exit(0) -} - -const dbname = 'testdb_fle' + new Date().getTime(); -use(dbname); -unencryptedDb = db; -assert(db.getName() === dbname, 'db name must match'); - -const local = { key: Buffer.from('kh4Gv2N8qopZQMQYMEtww/AkPsIrXNmEMxTrs3tUoTQZbZu4msdRUaR8U5fXD7A7QXYHcEvuu4WctJLoT+NvvV3eeIg3MD+K8H9SR794m/safgRHdIfy6PD+rFpvmFbY', 'base64') }; - -const keyMongo = Mongo(db.getMongo(), { - keyVaultNamespace: dbname + '.__keyVault', - kmsProviders: { local } -}); - -const keyVault = keyMongo.getKeyVault(); -const keyId = keyVault.createKey('local'); -sleep(100); - -const schemaMap = {}; -schemaMap[dbname + '.employees'] = { - bsonType: 'object', - properties: { - taxid: { - encrypt: { - keyId: [keyId], - bsonType: 'string', - algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random' - } - } - } -}; - -console.log('Using schema map', schemaMap); - -const autoMongo = Mongo(db.getMongo(), { - keyVaultNamespace: dbname + '.__keyVault', - kmsProviders: { local }, - schemaMap -}); - -db = autoMongo.getDB(dbname); -db.employees.insertOne({ taxid: 'abc' }); - -// If there is some failure that is not related to the assert() calls, we still -// want to make sure that we only print the success message if everything -// has worked so far, because the shell keeps evaluating statements after errors. -let verifiedEncrypted = false -let verifiedUnencrypted = false -{ - const document = db.employees.find().toArray()[0]; - console.log('auto-decrypted document', document); - verifiedEncrypted = document.taxid === 'abc'; - assert(verifiedEncrypted, 'Must do automatic decryption'); -} -db = unencryptedDb; -{ - const document = db.employees.find().toArray()[0]; - console.log('non-decrypted document', document); - verifiedUnencrypted = document.taxid instanceof Binary && document.taxid.sub_type === 6; - assert(verifiedUnencrypted, 'Must not do decryption without keys'); -} -if (verifiedEncrypted && verifiedUnencrypted) { - print('Test succeeded') -} -db.dropDatabase(); -`; +export default String.raw`print('Test skipped')`; diff --git a/packages/cli-repl/src/smoke-tests.ts b/packages/cli-repl/src/smoke-tests.ts index 2d2938ad35..f6a20591a2 100644 --- a/packages/cli-repl/src/smoke-tests.ts +++ b/packages/cli-repl/src/smoke-tests.ts @@ -160,7 +160,7 @@ export async function runSmokeTests({ input: 'crypto.createHash("md5").update("hello").digest("hex")', output: expectFipsSupport ? /disabled for FIPS|digital envelope routines::unsupported/i - : /disabled for FIPS|digital envelope routines::unsupported|Could not enable FIPS mode/i, + : /disabled for FIPS|digital envelope routines::unsupported|Could not enable FIPS mode|Assertion failed: crypto::CSPRNG/i, includeStderr: true, testArgs: ['--tlsFIPSMode', '--nodb'], perfTestIterations: 0, @@ -170,7 +170,7 @@ export async function runSmokeTests({ input: 'crypto.createHash("sha256").update("hello").digest("hex")', output: expectFipsSupport ? /2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824/i - : /2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824|digital envelope routines::unsupported|Could not enable FIPS mode/i, + : /2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824|digital envelope routines::unsupported|Could not enable FIPS mode|Assertion failed: crypto::CSPRNG/i, includeStderr: true, testArgs: ['--tlsFIPSMode', '--nodb'], perfTestIterations: 0, diff --git a/packages/service-provider-core/src/index.ts b/packages/service-provider-core/src/index.ts index 91ad7881c1..0816c672b5 100644 --- a/packages/service-provider-core/src/index.ts +++ b/packages/service-provider-core/src/index.ts @@ -1,5 +1,8 @@ import './textencoder-polyfill'; // for mongodb-connection-string-url in the java-shell -import ServiceProvider, { ServiceProviderCore } from './service-provider'; +import ServiceProvider, { + ServiceProviderCore, + SynchronousServiceProvider, +} from './service-provider'; import getConnectInfo, { ConnectInfo } from './connect-info'; import type { ReplPlatform } from './platform'; const DEFAULT_DB = 'test'; @@ -18,6 +21,7 @@ export { export { bson } from './bson-export'; export { + SynchronousServiceProvider, ServiceProvider, ShellAuthOptions, getConnectInfo, diff --git a/packages/service-provider-core/src/readable.ts b/packages/service-provider-core/src/readable.ts index ff7f341eaf..ca0da4dd90 100644 --- a/packages/service-provider-core/src/readable.ts +++ b/packages/service-provider-core/src/readable.ts @@ -157,7 +157,7 @@ export default interface Readable { /** * Get currently known topology information. */ - getTopology(): any; + getTopology?(): any; /** * Returns an array that holds a list of documents that identify and diff --git a/packages/service-provider-core/src/service-provider.ts b/packages/service-provider-core/src/service-provider.ts index 74d9c5bede..06c16ecede 100644 --- a/packages/service-provider-core/src/service-provider.ts +++ b/packages/service-provider-core/src/service-provider.ts @@ -15,6 +15,14 @@ export default interface ServiceProvider Closable, Admin {} +export type SynchronousServiceProvider = { + [k in keyof ServiceProvider]: ServiceProvider[k] extends ( + ...args: infer A + ) => Promise + ? (...args: A) => R + : ServiceProvider[k]; +}; + export class ServiceProviderCore { public bsonLibrary: typeof BSON; constructor(bsonLibrary?: typeof BSON) { diff --git a/packages/service-provider-server/src/cli-service-provider.ts b/packages/service-provider-server/src/cli-service-provider.ts index b79fd305c6..c1647f60e9 100644 --- a/packages/service-provider-server/src/cli-service-provider.ts +++ b/packages/service-provider-server/src/cli-service-provider.ts @@ -62,6 +62,7 @@ import type { ChangeStream, AutoEncryptionOptions, ClientEncryption as MongoCryptClientEncryption, + SynchronousServiceProvider, } from '@mongosh/service-provider-core'; import { getConnectInfo, @@ -77,7 +78,7 @@ import { ConnectionString, CommaAndColonSeparatedRecord, } from 'mongodb-connection-string-url'; -import { EventEmitter } from 'events'; +import { EventEmitter, once } from 'events'; import type { CreateEncryptedCollectionOptions } from '@mongosh/service-provider-core'; import type { DevtoolsConnectionState } from '@mongodb-js/devtools-connect'; import { isDeepStrictEqual } from 'util'; @@ -88,6 +89,15 @@ import { ClientEncryption, } from 'mongodb'; import { connectMongoClient } from '@mongodb-js/devtools-connect'; +import type { MessagePort } from 'worker_threads'; +import { + MessageChannel, + Worker, + parentPort, + receiveMessageOnPort, + workerData, +} from 'worker_threads'; +import assert from 'assert'; const bsonlib = () => { const { @@ -132,7 +142,7 @@ type DropDatabaseResult = { type ConnectionInfo = { buildInfo: any; - topology: any; + //topology: any; extraInfo: ExtraConnectionInfo; }; type ExtraConnectionInfo = ReturnType & { fcv?: string }; @@ -189,6 +199,317 @@ interface DependencyVersionInfo { kerberosVersion?: string; } +export class SynchronousCliServiceProvider + extends ServiceProviderCore + implements SynchronousServiceProvider +{ + static async connect( + this: typeof SynchronousCliServiceProvider, + uri: string, + driverOptions: DevtoolsConnectOptions, + cliOptions: { nodb?: boolean } = {}, + bus: MongoshBus = new EventEmitter() + ): Promise { + const sp = new this(bus); + await sp.start({ uri, driverOptions, cliOptions }); + return sp; + } + + private bus: MongoshBus; + private worker: Worker; + private callPort: MessagePort; + private remotePort: MessagePort; + private flag: Int32Array; + public readonly platform = 'CLI'; + public readonly initialDb = 'test'; + + constructor(bus: MongoshBus) { + super(bsonlib()); + const sab = new SharedArrayBuffer(4); + this.flag = new Int32Array(sab); + const channel = new MessageChannel(); + this.callPort = channel.port1; + this.remotePort = channel.port2; + + this.bus = bus; + this.worker = new Worker( + `require(${JSON.stringify( + __filename + )}).SynchronousCliServiceProvider.runWorker(); + `, + { eval: true, workerData: { flag: this.flag } } + ); + const origEmit: any = this.bus.emit; + this.worker.on('message', (msg: any) => { + if (msg.msg === 'BUS') origEmit.call(this.bus, ...msg.args); + }); + this.bus.emit = (...args: any) => { + this.worker.postMessage({ msg: 'BUS', args }); + return origEmit.call(this.bus, ...args); + }; + } + + async start(options: { + uri: string; + driverOptions: DevtoolsConnectOptions; + cliOptions: { nodb?: boolean }; + }) { + this.worker.postMessage( + { + msg: 'START', + options: JSON.parse(JSON.stringify(options)), + callPort: this.remotePort, + }, + [this.remotePort] + ); + + let msg; + do { + [msg] = await once(this.worker, 'message'); + } while (msg.msg !== 'STARTED'); + } + + close(): void { + void this.worker.terminate(); + } + + static async runWorker(): Promise { + if (!parentPort || !workerData) + throw new Error('Can only call runWorker inside a Worker thread'); + const flag = workerData.flag as Int32Array; + + const [msg] = await once(parentPort, 'message'); + assert.strictEqual(msg.msg, 'START'); + const { uri, driverOptions, cliOptions } = msg.options; + const callPort: MessagePort = msg.callPort; + + const ee = new EventEmitter(); + const forwardingBus = { + on(...args: Parameters<(typeof ee)['on']>) { + return ee.on(...args); + }, + once(...args: Parameters<(typeof ee)['once']>) { + return ee.once(...args); + }, + emit(...args: Parameters<(typeof ee)['emit']>) { + const ret = ee.emit(...args); + parentPort?.postMessage({ + msg: 'BUS', + args, + }); + Atomics.add(flag, 0, 1); + Atomics.notify(flag, 0); + return ret; + }, + }; + + // eslint-disable-next-line prefer-const + let realSp: CliServiceProvider | undefined; + parentPort.on('message', (msg: any) => { + if (msg.msg === 'BUS') { + (ee as any).emit(...msg.args); + } + }); + const savedObjects: any[] = []; + callPort.on('message', (msg: any) => { + if (msg.msg === 'CALL') { + void (async () => { + try { + if (!realSp) { + throw new Error('No SP initialized yet'); + } + const target: any = + msg.self === null ? realSp : savedObjects[msg.self]; + let result = await target[msg.fn](...msg.args); + if (msg.save) { + result = savedObjects.push(result) - 1; + } + callPort.postMessage({ + msg: 'RESPONSE', + result, + }); + } catch (err: any) { + callPort.postMessage({ + msg: 'RESPONSE', + err: { + base: err, + props: { + name: err.name, + message: err.message, + stack: err.stack, + }, + }, + }); + } + Atomics.add(flag, 0, 1); + Atomics.notify(flag, 0); + })(); + } + }); + + realSp = await CliServiceProvider.connect( + uri, + driverOptions, + cliOptions, + forwardingBus + ); + parentPort.postMessage({ msg: 'STARTED' }); + } + + _call< + K extends keyof { + [k in keyof CliServiceProvider as CliServiceProvider[k] extends ( + ...args: any + ) => any + ? k + : never]: CliServiceProvider[k]; + } + >( + save: boolean, + self: null | string, + fn: K, + ...args: Parameters + ) { + this.callPort.postMessage({ + msg: 'CALL', + fn, + args, + save, + self, + }); + do { + const value = Atomics.load(this.flag, 0); + const { message } = receiveMessageOnPort(this.callPort) ?? {}; + if (!message) Atomics.wait(this.flag, 0, value); + else { + if (message.err) { + const err = + Object.prototype.toString.call(message.err.base) === + '[object Error]' + ? message.err.base + : new Error(message.err.props.message); + Object.assign(err, message.err.props); + throw err; + } + return message.result; + } + // eslint-disable-next-line no-constant-condition + } while (true); + } + + aggregate = makeCursorCall('aggregate'); + aggregateDb = makeCursorCall('aggregateDb'); + count = makeCall('count'); + countDocuments = makeCall('countDocuments'); + distinct = makeCall('distinct'); + estimatedDocumentCount = makeCall('estimatedDocumentCount'); + find = makeCursorCall('find'); + findOneAndDelete = makeCall('findOneAndDelete'); + findOneAndReplace = makeCall('findOneAndReplace'); + findOneAndUpdate = makeCall('findOneAndUpdate'); + getIndexes = makeCall('getIndexes'); + listCollections = makeCall('listCollections'); + readPreferenceFromOptions = makeCall('readPreferenceFromOptions'); + watch = makeCall('watch'); + getSearchIndexes = makeCall('getSearchIndexes'); + runCommand = makeCall('runCommand'); + runCommandWithCheck = makeCall('runCommandWithCheck'); + runCursorCommand = makeCursorCall('runCursorCommand'); + dropCollection = makeCall('dropCollection'); + dropDatabase = makeCall('dropDatabase'); + dropSearchIndex = makeCall('dropSearchIndex'); + bulkWrite = makeCall('bulkWrite'); + deleteMany = makeCall('deleteMany'); + deleteOne = makeCall('deleteOne'); + insertMany = makeCall('insertMany'); + insertOne = makeCall('insertOne'); + replaceOne = makeCall('replaceOne'); + updateMany = makeCall('updateMany'); + updateSearchIndex = makeCall('updateSearchIndex'); + updateOne = makeCall('updateOne'); + createSearchIndexes = makeCall('createSearchIndexes'); + createIndexes = makeCall('createIndexes') as any; + renameCollection = makeCall('renameCollection'); + initializeBulkOp = makeCall('initializeBulkOp'); + suspend = makeCall('suspend'); + listDatabases = makeCall('listDatabases'); + getURI = makeCall('getURI'); + getConnectionInfo = makeCall('getConnectionInfo'); + authenticate = makeCall('authenticate'); + createCollection = makeCall('createCollection'); + getReadConcern = makeCall('getReadConcern'); + getReadPreference = makeCall('getReadPreference'); + getWriteConcern = makeCall('getWriteConcern'); + resetConnectionOptions = makeCall('resetConnectionOptions'); + startSession = makeCall('startSession'); + + getNewConnection(): never { + throw new Error('not implemented '); + } + getRawClient(): never { + throw new Error('not implemented '); + } +} +function makeCall< + K extends keyof { + [k in keyof CliServiceProvider as CliServiceProvider[k] extends ( + ...args: any + ) => any + ? k + : never]: CliServiceProvider[k]; + } +>( + fn: K +): ( + this: SynchronousCliServiceProvider, + ...args: Parameters +) => ReturnType extends Promise ? R : never { + return function (...args: Parameters) { + return this._call(false, null, fn, ...args); + }; +} +function makeCursorCall< + K extends keyof { + [k in keyof CliServiceProvider as CliServiceProvider[k] extends ( + ...args: any + ) => driver.AbstractCursor + ? k + : never]: CliServiceProvider[k]; + } +>( + fn: K +): ( + this: SynchronousCliServiceProvider, + ...args: Parameters +) => ReturnType extends Promise ? R : never { + return function (...args: Parameters) { + const cursor = this._call(true, null, fn, ...args); + return { + next: (...args: any) => { + return (this as any)._call(false, cursor, 'next', ...args); + }, + tryNext: (...args: any) => { + return (this as any)._call(false, cursor, 'tryNext', ...args); + }, + limit: (...args: any) => { + return (this as any)._call(false, cursor, 'limit', ...args); + }, + skip: (...args: any) => { + return (this as any)._call(false, cursor, 'skip', ...args); + }, + count: (...args: any) => { + return (this as any)._call(false, cursor, 'count', ...args); + }, + hasNext: (...args: any) => { + return (this as any)._call(false, cursor, 'hasNext', ...args); + }, + close: (...args: any) => { + return (this as any)._call(false, cursor, 'close', ...args); + }, + } as any; + }; +} + /** * Encapsulates logic for the service provider for the mongosh CLI. */ @@ -447,7 +768,7 @@ class CliServiceProvider return { buildInfo: buildInfo, - topology: topology, + //topology: topology, extraInfo: { ...extraConnectionInfo, fcv: fcv?.featureCompatibilityVersion?.version, diff --git a/packages/service-provider-server/src/index.ts b/packages/service-provider-server/src/index.ts index bdd49e3434..387d7ab765 100644 --- a/packages/service-provider-server/src/index.ts +++ b/packages/service-provider-server/src/index.ts @@ -1,4 +1,10 @@ -import CliServiceProvider from './cli-service-provider'; +import CliServiceProvider, { + SynchronousCliServiceProvider, +} from './cli-service-provider'; import CompassServiceProvider from './compass/compass-service-provider'; export type { DevtoolsConnectOptions } from '@mongodb-js/devtools-connect'; -export { CliServiceProvider, CompassServiceProvider }; +export { + CliServiceProvider, + CompassServiceProvider, + SynchronousCliServiceProvider, +}; diff --git a/packages/shell-api/src/abstract-cursor.ts b/packages/shell-api/src/abstract-cursor.ts index b3af0ec8a4..af4ba59f7d 100644 --- a/packages/shell-api/src/abstract-cursor.ts +++ b/packages/shell-api/src/abstract-cursor.ts @@ -59,9 +59,9 @@ export abstract class AbstractCursor< return this; } - @returnsPromise - async close(): Promise { - await this._cursor.close(); + //@returnsPromise + /*async*/ close(): /*Promise<*/ void /*>*/ { + /*await*/ void this._cursor.close(); } @returnsPromise @@ -76,20 +76,20 @@ export abstract class AbstractCursor< } } - @returnsPromise - async hasNext(): Promise { - return this._cursor.hasNext(); + //@returnsPromise + /*async*/ hasNext(): /*Promise<*/ boolean /*>*/ { + return this._cursor.hasNext() as any; } - @returnsPromise - async tryNext(): Promise { + //@returnsPromise + /*async*/ tryNext(): /*Promise<*/ Document | null /*>*/ { return this._tryNext(); } - async _tryNext(): Promise { - let result = await this._cursor.tryNext(); + /*async*/ _tryNext(): /*Promise<*/ Document | null /*>*/ { + let result = /*await*/ this._cursor.tryNext(); if (result !== null && this._transform !== null) { - result = await this._transform(result); + result = /*await*/ this._transform(result); } return result; } @@ -102,19 +102,19 @@ export abstract class AbstractCursor< return true; } - async *[Symbol.asyncIterator]() { - if ( - this._cursor[Symbol.asyncIterator] && + /* async*/ *[Symbol.iterator]() { + /*if ( + this._cursor[Symbol.iterator] && this._canDelegateIterationToUnderlyingCursor() ) { yield* this._cursor; - return; - } + //return; + }*/ let doc; // !== null should suffice, but some stubs in our tests return 'undefined' // eslint-disable-next-line eqeqeq - while ((doc = await this._tryNext()) != null) { + while ((doc = /*await*/ this._tryNext()) != null) { yield doc; } } @@ -127,28 +127,28 @@ export abstract class AbstractCursor< return this.isClosed() && this.objsLeftInBatch() === 0; } - @returnsPromise - async itcount(): Promise { + //@returnsPromise + /*async */ itcount(): /*Promise<*/ number /*>*/ { let count = 0; - while (await this._tryNext()) { + while (/*await*/ this._tryNext()) { count++; } return count; } - @returnsPromise - async toArray(): Promise { + //@returnsPromise + /*async*/ toArray(): /*Promise<*/ Document[] /*>*/ { // toArray is always defined for driver cursors, but not necessarily // in tests if ( typeof this._cursor.toArray === 'function' && this._canDelegateIterationToUnderlyingCursor() ) { - return await this._cursor.toArray(); + return /*await*/ this._cursor.toArray() as any; } const result = []; - for await (const doc of this) { + for (/*await*/ const doc of this) { result.push(doc); } return result; @@ -176,11 +176,11 @@ export abstract class AbstractCursor< return this; } - @returnsPromise - async next(): Promise { - let result = await this._cursor.next(); + //@returnsPromise + /*async*/ next(): /*Promise<*/ Document | null /*>*/ { + let result = /*await*/ this._cursor.next(); if (result !== null && this._transform !== null) { - result = await this._transform(result); + result = /*await*/ this._transform(result); } return result; } diff --git a/packages/shell-api/src/aggregate-or-find-cursor.ts b/packages/shell-api/src/aggregate-or-find-cursor.ts index 716a7f6e38..b155007b12 100644 --- a/packages/shell-api/src/aggregate-or-find-cursor.ts +++ b/packages/shell-api/src/aggregate-or-find-cursor.ts @@ -37,12 +37,13 @@ export abstract class AggregateOrFindCursor< @returnsPromise @apiVersions([1]) - async explain(verbosity?: ExplainVerbosityLike): Promise { + /*async */ + explain(verbosity?: ExplainVerbosityLike): /*Promise<*/ any /*>*/ { // TODO: @maurizio we should probably move this in the Explain class? // NOTE: the node driver always returns the full explain plan // for Cursor and the queryPlanner explain for AggregationCursor. verbosity = validateExplainableVerbosity(verbosity); - const fullExplain: any = await this._cursor.explain(verbosity); + const fullExplain: any = /*await*/ this._cursor.explain(verbosity); const explain: any = { ...fullExplain, diff --git a/packages/shell-api/src/collection.ts b/packages/shell-api/src/collection.ts index e73664d793..65f062c2db 100644 --- a/packages/shell-api/src/collection.ts +++ b/packages/shell-api/src/collection.ts @@ -155,7 +155,7 @@ export default class Collection extends ShellApiWithMongoClass { * * @returns {Promise} The promise of aggregation results. */ - async aggregate( + /*async aggregate( pipeline: Document[], options: Document & { explain?: never } ): Promise; @@ -163,11 +163,12 @@ export default class Collection extends ShellApiWithMongoClass { pipeline: Document[], options: Document & { explain: ExplainVerbosityLike } ): Promise; - async aggregate(...stages: Document[]): Promise; - @returnsPromise + async aggregate(...stages: Document[]): Promise;*/ + //@returnsPromise @returnType('AggregationCursor') @apiVersions([1]) - async aggregate(...args: any[]): Promise { + /*async*/ + aggregate(...args: any[]): /*Promise<*/ any /*>*/ { let options; let pipeline; if (args.length === 0 || Array.isArray(args[0])) { @@ -184,15 +185,15 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, pipeline, - { ...(await this._database._baseOptions()), ...aggOptions }, + { .../*await*/ this._database._baseOptions(), ...aggOptions }, dbOptions ); const cursor = new AggregationCursor(this._mongo, providerCursor); if (explain) { - return await cursor.explain(explain); + return /*await*/ cursor.explain(explain); } else if (shouldRunAggregationImmediately(pipeline)) { - await cursor.hasNext(); + /*await*/ cursor.hasNext(); } this._mongo._instanceState.currentCursor = cursor; @@ -409,17 +410,18 @@ export default class Collection extends ShellApiWithMongoClass { * * @returns {Integer} The promise of the count. */ - @returnsPromise + //@returnsPromise @serverVersions(['4.0.3', ServerVersions.latest]) @apiVersions([1]) - async estimatedDocumentCount( + /*async*/ + estimatedDocumentCount( options: EstimatedDocumentCountOptions = {} - ): Promise { + ): /*Promise<*/ number /*>*/ { this._emitCollectionApiCall('estimatedDocumentCount', { options }); return this._mongo._serviceProvider.estimatedDocumentCount( this._database._name, this._name, - { ...(await this._database._baseOptions()), ...options } + { .../*await*/ this._database._baseOptions(), ...options } ); } @@ -436,12 +438,13 @@ export default class Collection extends ShellApiWithMongoClass { */ @returnType('Cursor') @apiVersions([1]) - @returnsPromise - async find( + //@returnsPromise + /*async*/ + find( query?: Document, projection?: Document, options: FindOptions = {} - ): Promise { + ): /*Promise<*/ Cursor /*>*/ { if (projection) { options.projection = projection; } @@ -453,7 +456,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, query, - { ...(await this._database._baseOptions()), ...options } + { .../*await*/ this._database._baseOptions(), ...options } ) ); @@ -507,14 +510,15 @@ export default class Collection extends ShellApiWithMongoClass { * * @returns {Cursor} The promise of the cursor. */ - @returnsPromise + //@returnsPromise @returnType('Document') @apiVersions([1]) - async findOne( + /*async*/ + findOne( query: Document = {}, projection?: Document, options: FindOptions = {} - ): Promise { + ): /*Promise<*/ Document | null /*>*/ { if (projection) { options.projection = projection; } @@ -526,7 +530,7 @@ export default class Collection extends ShellApiWithMongoClass { this._database._name, this._name, query, - { ...(await this._database._baseOptions()), ...options } + { .../*await*/ this._database._baseOptions(), ...options } ) ) .limit(1) @@ -2057,6 +2061,7 @@ export default class Collection extends ShellApiWithMongoClass { @topologies([Topologies.Sharded]) @apiVersions([]) async getShardDistribution(): Promise { + throw new Error(); /* this._emitCollectionApiCall('getShardDistribution', {}); const result = {} as Document; @@ -2169,7 +2174,7 @@ export default class Collection extends ShellApiWithMongoClass { ]; } result.Totals = totalValue; - return new CommandResult('StatsResult', result); + return new CommandResult('StatsResult', result);*/ } @serverVersions(['3.1.0', ServerVersions.latest]) diff --git a/packages/shell-api/src/cursor.ts b/packages/shell-api/src/cursor.ts index 425be16074..ce622a1fed 100644 --- a/packages/shell-api/src/cursor.ts +++ b/packages/shell-api/src/cursor.ts @@ -97,16 +97,17 @@ export default class Cursor extends AggregateOrFindCursor } @serverVersions([ServerVersions.earliest, '4.0.0']) - @returnsPromise + //@returnsPromise @deprecated - async count(): Promise { + /*async*/ + count(): Promise { return this._cursor.count(); } - @returnsPromise - async hasNext(): Promise { + //@returnsPromise + /*async*/ hasNext(): /*Promise<*/ boolean /*>*/ { if (this._tailable) { - await this._instanceState.printWarning( + /*await */ void this._instanceState.printWarning( 'If this is a tailable cursor with awaitData, and there are no documents in the batch, this method ' + 'will will block. Use tryNext if you want to check if there are any documents without waiting.' ); @@ -145,10 +146,10 @@ export default class Cursor extends AggregateOrFindCursor return this; } - @returnsPromise - async next(): Promise { + // @returnsPromise + /*async*/ next(): /*Promise<*/ Document | null /*>*/ { if (this._tailable) { - await this._instanceState.printWarning( + /*await*/ void this._instanceState.printWarning( 'If this is a tailable cursor with awaitData, and there are no documents in the batch, this' + ' method will will block. Use tryNext if you want to check if there are any documents without waiting.' ); @@ -197,8 +198,8 @@ export default class Cursor extends AggregateOrFindCursor return this; } - @returnsPromise - async size(): Promise { + //@returnsPromise + /*async*/ size(): Promise { return this._cursor.count(); } diff --git a/packages/shell-api/src/database.ts b/packages/shell-api/src/database.ts index 842cb4628d..18f1147f52 100644 --- a/packages/shell-api/src/database.ts +++ b/packages/shell-api/src/database.ts @@ -105,17 +105,17 @@ export default class Database extends ShellApiWithMongoClass { return proxy; } - async _baseOptions(): Promise { + /*async*/ _baseOptions(): /*Promise<*/ CommandOperationOptions /*>*/ { const options: CommandOperationOptions = {}; if (this._session) { options.session = this._session._session; } - const maxTimeMS = await this._instanceState.shellApi.config.get( + /*const maxTimeMS = await this._instanceState.shellApi.config.get( 'maxTimeMS' ); if (typeof maxTimeMS === 'number') { options.maxTimeMS = maxTimeMS; - } + }*/ return options; } @@ -165,16 +165,16 @@ export default class Database extends ShellApiWithMongoClass { } // Private helpers to avoid sending telemetry events for internal calls. Public so rs/sh can use them - public async _runReadCommand( + public /* async*/ _runReadCommand( cmd: Document, options: CommandOperationOptions = {} - ): Promise { + ): /*Promise<*/ Document /*>*/ { return this._mongo._serviceProvider.runCommandWithCheck( this._name, adjustRunCommand(cmd, this._instanceState.shellBson), { ...this._mongo._getExplicitlyRequestedReadPref(), - ...(await this._baseOptions()), + .../*await*/ this._baseOptions(), ...options, } ); @@ -185,7 +185,7 @@ export default class Database extends ShellApiWithMongoClass { options: CommandOperationOptions = {} ): Promise { return this.getSiblingDB('admin')._runCommand(cmd, { - ...(await this._baseOptions()), + .../*await*/ this._baseOptions(), ...options, }); } @@ -414,13 +414,14 @@ export default class Database extends ShellApiWithMongoClass { * @param options * @returns {Promise} The promise of aggregation results. */ - @returnsPromise + //@returnsPromise @returnType('AggregationCursor') @apiVersions([1]) - async aggregate( + /*async*/ + aggregate( pipeline: Document[], options?: Document - ): Promise { + ): /*Promise<*/ AggregationCursor /*>*/ { assertArgsDefinedType([pipeline], [true], 'Database.aggregate'); this._emitDatabaseApiCall('aggregate', { options, pipeline }); @@ -429,15 +430,15 @@ export default class Database extends ShellApiWithMongoClass { const providerCursor = this._mongo._serviceProvider.aggregateDb( this._name, pipeline, - { ...(await this._baseOptions()), ...aggOptions }, + { .../*await*/ this._baseOptions(), ...aggOptions }, dbOptions ); const cursor = new AggregationCursor(this._mongo, providerCursor); if (explain) { - return await cursor.explain(explain); + return /*await*/ cursor.explain(explain); } else if (shouldRunAggregationImmediately(pipeline)) { - await cursor.hasNext(); + /*await*/ cursor.hasNext(); } this._mongo._instanceState.currentCursor = cursor; @@ -1155,30 +1156,32 @@ export default class Database extends ShellApiWithMongoClass { return info.bits; } - @returnsPromise + //@returnsPromise @apiVersions([]) - async isMaster(): Promise { + /*async */ + isMaster(): /*Promise<*/ Document /*>*/ { this._emitDatabaseApiCall('isMaster', {}); - const result = await this._runReadCommand({ + const result = /* await*/ this._runReadCommand({ isMaster: 1, }); result.isWritablePrimary = result.ismaster; return result; } - @returnsPromise + //@returnsPromise @apiVersions([1]) @serverVersions(['5.0.0', ServerVersions.latest]) - async hello(): Promise { + /*async*/ + hello(): /*Promise<*/ Document /*>*/ { this._emitDatabaseApiCall('hello', {}); try { - this._cachedHello = await this._runReadCommand({ + this._cachedHello = /* await*/ this._runReadCommand({ hello: 1, }); return this._cachedHello; } catch (err: any) { if (err?.codeName === 'CommandNotFound') { - const result = await this.isMaster(); + const result = /*await*/ this.isMaster(); delete result.ismaster; this._cachedHello = result; return this._cachedHello; diff --git a/packages/shell-api/src/helpers.ts b/packages/shell-api/src/helpers.ts index 06d2253d9d..4df9c0c0ee 100644 --- a/packages/shell-api/src/helpers.ts +++ b/packages/shell-api/src/helpers.ts @@ -228,6 +228,7 @@ export async function getPrintableShardStatus( configDB: Database, verbose: boolean ): Promise { + throw new Error(); /* const result = {} as any; // configDB is a DB object that contains the sharding metadata of interest. @@ -637,7 +638,7 @@ export async function getPrintableShardStatus( ).filter((dbEntry) => !!dbEntry); delete result.shardingVersion.currentVersion; - return result; + return result;*/ } export async function getConfigDB(db: Database): Promise { @@ -899,6 +900,7 @@ export function assertCLI(platform: ReplPlatform, features: string): void { export function processFLEOptions( fleOptions: ClientSideFieldLevelEncryptionOptions ): AutoEncryptionOptions { + throw new Error('No FLE support'); /* assertKeysDefined(fleOptions, ['keyVaultNamespace', 'kmsProviders']); Object.keys(fleOptions).forEach((k) => { if ( @@ -957,7 +959,7 @@ export function processFLEOptions( if (fleOptions.tlsOptions !== undefined) { autoEncryption.tlsOptions = fleOptions.tlsOptions; } - return autoEncryption; + return autoEncryption;*/ } // The then?: never check is to make sure this doesn't accidentally get applied diff --git a/packages/shell-api/src/mongo.ts b/packages/shell-api/src/mongo.ts index 49e003423e..9386af9b6d 100644 --- a/packages/shell-api/src/mongo.ts +++ b/packages/shell-api/src/mongo.ts @@ -29,13 +29,14 @@ import type { ReadPreference, ReadPreferenceLike, ReadPreferenceMode, - ServiceProvider, + //ServiceProvider, TransactionOptions, MongoClientOptions, AutoEncryptionOptions as SPAutoEncryption, ServerApi, ServerApiVersion, WriteConcern, + SynchronousServiceProvider, } from '@mongosh/service-provider-core'; import type { ConnectionInfo } from '@mongosh/arg-parser'; import { @@ -70,7 +71,7 @@ type Mutable = { @shellApiClassDefault @classPlatforms(['CLI']) export default class Mongo extends ShellApiClass { - private __serviceProvider: ServiceProvider | null = null; + private __serviceProvider: SynchronousServiceProvider | null = null; public readonly _databases: Record = Object.create(null); public _instanceState: ShellInstanceState; public _connectionInfo: ConnectionInfo; @@ -85,7 +86,7 @@ export default class Mongo extends ShellApiClass { uri?: string | Mongo, fleOptions?: ClientSideFieldLevelEncryptionOptions, otherOptions?: { api?: ServerApi | ServerApiVersion }, - sp?: ServiceProvider + sp?: SynchronousServiceProvider ) { super(); this._instanceState = instanceState; @@ -156,7 +157,7 @@ export default class Mongo extends ShellApiClass { // generally speaking, it's always there, so instead of using a type of // `ServiceProvider | null` and a data property, we use a getter that throws // if used too early. - get _serviceProvider(): ServiceProvider { + get _serviceProvider(): SynchronousServiceProvider { if (this.__serviceProvider === null) { throw new MongoshInternalError( 'No ServiceProvider available for this mongo', @@ -167,7 +168,7 @@ export default class Mongo extends ShellApiClass { } // For testing. - set _serviceProvider(sp: ServiceProvider) { + set _serviceProvider(sp: SynchronousServiceProvider) { this.__serviceProvider = sp; } @@ -228,6 +229,7 @@ export default class Mongo extends ShellApiClass { }; } const parentProvider = this._instanceState.initialServiceProvider; + // eslint-disable-next-line no-useless-catch try { this.__serviceProvider = await parentProvider.getNewConnection( this._uri, @@ -237,13 +239,13 @@ export default class Mongo extends ShellApiClass { // If the initial provider had TLS enabled, and we're not able to connect, // and the new URL does not contain a SSL/TLS indicator, we add a notice // about the fact that the behavior differs from the legacy shell here. - if ( + /*if ( e?.name === 'MongoServerSelectionError' && parentProvider.getRawClient()?.options?.tls && !/\b(ssl|tls)=/.exec(this._uri) ) { e.message += ' (is ?tls=true missing from the connection string?)'; - } + }*/ throw e; } } diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index 3ec67224c6..777e98166c 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -3,7 +3,8 @@ import type { AutoEncryptionOptions, ConnectInfo, ServerApi, - ServiceProvider, + //ServiceProvider, + SynchronousServiceProvider, TopologyDescription, } from '@mongosh/service-provider-core'; import { DEFAULT_DB } from '@mongosh/service-provider-core'; @@ -141,7 +142,7 @@ export default class ShellInstanceState { | null; public currentDb: Database; public messageBus: MongoshBus; - public initialServiceProvider: ServiceProvider; // the initial service provider + public initialServiceProvider: SynchronousServiceProvider; // the initial service provider public connectionInfo: any; public context: any; public mongos: Mongo[]; @@ -166,7 +167,7 @@ export default class ShellInstanceState { private alreadyTransformedErrors = new WeakMap(); constructor( - initialServiceProvider: ServiceProvider, + initialServiceProvider: SynchronousServiceProvider, messageBus: any = new EventEmitter(), cliOptions: ShellCliOptions = {} ) { @@ -308,7 +309,7 @@ export default class ShellInstanceState { this.messageBus.emit('mongosh:setCtx', { method: 'setCtx', arguments: {} }); } - get currentServiceProvider(): ServiceProvider { + get currentServiceProvider(): SynchronousServiceProvider { try { return this.currentDb._mongo._serviceProvider; } catch (err: any) { @@ -338,7 +339,7 @@ export default class ShellInstanceState { return { topology: () => { let topology: Topologies; - const topologyDescription = this.currentServiceProvider.getTopology() + const topologyDescription = this.currentServiceProvider.getTopology?.() ?.description as TopologyDescription; // TODO: once a driver with NODE-3011 is available set type to TopologyType | undefined const topologyType: string | undefined = topologyDescription?.type; @@ -429,11 +430,12 @@ export default class ShellInstanceState { } apiVersionInfo(): Required | undefined { + return undefined; /* const { serverApi } = this.currentServiceProvider.getRawClient()?.options ?? {}; return serverApi?.version ? { strict: false, deprecationErrors: false, ...serverApi } - : undefined; + : undefined;*/ } async onInterruptExecution(): Promise { @@ -518,7 +520,8 @@ export default class ShellInstanceState { private getTopologySpecificPrompt(): string { // TODO: once a driver with NODE-3011 is available set type to TopologyDescription - const description = this.currentServiceProvider.getTopology()?.description; + const description = + this.currentServiceProvider.getTopology?.()?.description; if (!description) { return ''; } diff --git a/packages/shell-evaluator/src/shell-evaluator.ts b/packages/shell-evaluator/src/shell-evaluator.ts index 75ac1966a3..cd7c648042 100644 --- a/packages/shell-evaluator/src/shell-evaluator.ts +++ b/packages/shell-evaluator/src/shell-evaluator.ts @@ -4,7 +4,7 @@ import { ShellResult, EvaluationListener, } from '@mongosh/shell-api'; -import AsyncWriter from '@mongosh/async-rewriter2'; +//import AsyncWriter from '@mongosh/async-rewriter2'; type EvaluationFunction = ( input: string, @@ -15,13 +15,13 @@ type EvaluationFunction = ( import { HIDDEN_COMMANDS, redactSensitiveData } from '@mongosh/history'; import { TimingCategories, type TimingCategory } from '@mongosh/types'; -let hasAlreadyRunGlobalRuntimeSupportEval = false; +/*let hasAlreadyRunGlobalRuntimeSupportEval = false; // `v8.startupSnapshot` is currently untyped, might as well use `any`. let v8: any; try { v8 = require('v8'); } catch { - /* not Node.js */ + /* not Node.js * / } if (v8?.startupSnapshot?.isBuildingSnapshot?.()) { v8.startupSnapshot.addSerializeCallback(() => { @@ -30,7 +30,7 @@ if (v8?.startupSnapshot?.isBuildingSnapshot?.()) { eval(new AsyncWriter().process('1+1')); hasAlreadyRunGlobalRuntimeSupportEval = true; }); -} +}*/ type ResultHandler = ( value: any @@ -38,8 +38,8 @@ type ResultHandler = ( class ShellEvaluator { private instanceState: ShellInstanceState; private resultHandler: ResultHandler; - private hasAppliedAsyncWriterRuntimeSupport = true; - private asyncWriter: AsyncWriter; + //private hasAppliedAsyncWriterRuntimeSupport = true; + //private asyncWriter: AsyncWriter; private markTime?: (category: TimingCategory, label: string) => void; private exposeAsyncRewriter: boolean; @@ -51,8 +51,8 @@ class ShellEvaluator { ) { this.instanceState = instanceState; this.resultHandler = resultHandler; - this.asyncWriter = new AsyncWriter(); - this.hasAppliedAsyncWriterRuntimeSupport = false; + //this.asyncWriter = new AsyncWriter(); + //this.hasAppliedAsyncWriterRuntimeSupport = false; this.exposeAsyncRewriter = !!exposeAsyncRewriter; this.markTime = markTime; } @@ -93,12 +93,11 @@ class ShellEvaluator { } if (this.exposeAsyncRewriter) { - (context as any).__asyncRewrite = (rewriteInput: string) => - this.asyncWriter.process(rewriteInput); + (context as any).__asyncRewrite = (rewriteInput: string) => rewriteInput; //this.asyncWriter.process(rewriteInput); } this.markTime?.(TimingCategories.AsyncRewrite, 'start async rewrite'); - let rewrittenInput = this.asyncWriter.process(input); + const rewrittenInput = input; // this.asyncWriter.process(input); this.markTime?.(TimingCategories.AsyncRewrite, 'done async rewrite'); const hiddenCommands = RegExp(HIDDEN_COMMANDS, 'g'); @@ -107,7 +106,7 @@ class ShellEvaluator { input: redactSensitiveData(trimmedInput), }); } - + /* if (!this.hasAppliedAsyncWriterRuntimeSupport) { this.hasAppliedAsyncWriterRuntimeSupport = true; this.markTime?.( @@ -128,7 +127,7 @@ class ShellEvaluator { 'done global runtimeSupportCode processing' ); rewrittenInput = supportCode + ';\n' + rewrittenInput; - } + }*/ try { this.markTime?.(