-
Notifications
You must be signed in to change notification settings - Fork 84
feat: output full bson objects MONGOSH-1285 #2584
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
40a8727
WIP
addaleax 4b2991c
use DeepInspectServiceProviderWrapper, install our own inspect functi…
lerouxb f85dc9f
you cannot extend the return value of initializeBulkOp
lerouxb 24ef043
apparently inspect passed as the final parameter isn't always a thing
lerouxb cda077c
better name
lerouxb 7d008f7
inspect a shallow copy
lerouxb a27b349
fix comment
lerouxb 8c523bb
leave bson values alone, don't accidentally override existing custom …
lerouxb aa2e830
don't remove things
lerouxb 6307dd3
wrap every type of cursor
lerouxb 70aa3ee
don't accidentally override our custom Date and RegExp inpect functions
lerouxb 8624b68
don't depend on inspect
lerouxb cae4017
more indirection
lerouxb 7d2d0a3
fill out more things on the stub
lerouxb d12548c
how did this work before?
lerouxb 12fc59b
Merge branch 'main' into print-output-full
lerouxb 22e67f8
add the custom inspect symbol as not-enumerable
lerouxb f4f64bc
pull bsonLibrary off _sp rather
lerouxb 709de79
Merge branch 'main' into print-output-full
lerouxb 77f64f2
don't wrap the service provider in java land
lerouxb 72d8679
Update packages/shell-api/src/custom-inspect.ts
lerouxb a93b8fd
some unit tests
lerouxb 0ce95c3
more unit tests
lerouxb 69d3d13
adjust runtime indepdendence tests
lerouxb 209c9f5
Update packages/shell-api/src/custom-inspect.ts
lerouxb c023d46
fixup: remove wrappable flag, skip async iter support for now, use fn…
addaleax e31ba35
fixup: merge cursor implementations, move to separate subdir
addaleax 2bc07c3
fixup: fix runtime independence test again
addaleax 370288f
fixup: add e2e tests
addaleax 28dd205
fixup: oidc test
addaleax 7202eb2
fixup: stub out more cli-repl test sp usage
addaleax 1b72c5b
feat(cli-repl): add flag to control deep inspect behavior
addaleax f783da4
fixup: prettier doesnt run on subdirs?
addaleax 0a9c1bf
fixup: add missing waitForPrompt call
addaleax caaa723
fixup: simplify using waitForCleanOutput
addaleax 02e70c7
Merge remote-tracking branch 'origin/main' into print-output-full
addaleax File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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
257 changes: 257 additions & 0 deletions
257
packages/shell-api/src/deep-inspect-service-provider-wrapper.ts
This file contains hidden or 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,257 @@ | ||
| import type { | ||
| ServiceProvider, | ||
| ServiceProviderAbstractCursor, | ||
| } from '@mongosh/service-provider-core'; | ||
| import { ServiceProviderCore } from '@mongosh/service-provider-core'; | ||
| import type { InspectOptions, inspect as _inspect } from 'util'; | ||
| import type { Document } from '@mongosh/service-provider-core'; | ||
|
|
||
| export class DeepInspectServiceProviderWrapper | ||
| extends ServiceProviderCore | ||
| implements ServiceProvider | ||
| { | ||
| _sp: ServiceProvider; | ||
|
|
||
| constructor(sp: ServiceProvider) { | ||
| super(sp.bsonLibrary); | ||
| this._sp = sp; | ||
|
|
||
| for (const prop of Object.keys(this)) { | ||
| if (typeof (this as any)[prop] === 'function' && !(prop in sp)) { | ||
| (this as any)[prop] = undefined; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| aggregate = cursorMethod('aggregate'); | ||
| aggregateDb = cursorMethod('aggregateDb'); | ||
| count = forwardedMethod('count'); | ||
| estimatedDocumentCount = forwardedMethod('estimatedDocumentCount'); | ||
| countDocuments = forwardedMethod('countDocuments'); | ||
| distinct = bsonMethod('distinct'); | ||
| find = cursorMethod('find'); | ||
| findOneAndDelete = bsonMethod('findOneAndDelete'); | ||
| findOneAndReplace = bsonMethod('findOneAndReplace'); | ||
| findOneAndUpdate = bsonMethod('findOneAndUpdate'); | ||
| getTopologyDescription = forwardedMethod('getTopologyDescription'); | ||
| getIndexes = bsonMethod('getIndexes'); | ||
| listCollections = bsonMethod('listCollections'); | ||
| readPreferenceFromOptions = forwardedMethod('readPreferenceFromOptions'); | ||
| // TODO: this should be a cursor method, but the types are incompatible | ||
| watch = forwardedMethod('watch'); | ||
| getSearchIndexes = bsonMethod('getSearchIndexes'); | ||
| runCommand = bsonMethod('runCommand'); | ||
| runCommandWithCheck = bsonMethod('runCommandWithCheck'); | ||
| runCursorCommand = cursorMethod('runCursorCommand'); | ||
| dropDatabase = bsonMethod('dropDatabase'); | ||
| dropCollection = forwardedMethod('dropCollection'); | ||
| bulkWrite = bsonMethod('bulkWrite'); | ||
| clientBulkWrite = bsonMethod('clientBulkWrite'); | ||
| deleteMany = bsonMethod('deleteMany'); | ||
| updateMany = bsonMethod('updateMany'); | ||
| updateOne = bsonMethod('updateOne'); | ||
| deleteOne = bsonMethod('deleteOne'); | ||
| createIndexes = bsonMethod('createIndexes'); | ||
| insertMany = bsonMethod('insertMany'); | ||
| insertOne = bsonMethod('insertOne'); | ||
| replaceOne = bsonMethod('replaceOne'); | ||
| initializeBulkOp = bsonMethod('initializeBulkOp'); | ||
| createSearchIndexes = forwardedMethod('createSearchIndexes'); | ||
| close = forwardedMethod('close'); | ||
| suspend = forwardedMethod('suspend'); | ||
| renameCollection = forwardedMethod('renameCollection'); | ||
| dropSearchIndex = forwardedMethod('dropSearchIndex'); | ||
| updateSearchIndex = forwardedMethod('updateSearchIndex'); | ||
| listDatabases = bsonMethod('listDatabases'); | ||
| authenticate = forwardedMethod('authenticate'); | ||
| createCollection = forwardedMethod('createCollection'); | ||
| getReadPreference = forwardedMethod('getReadPreference'); | ||
| getReadConcern = forwardedMethod('getReadConcern'); | ||
| getWriteConcern = forwardedMethod('getWriteConcern'); | ||
|
|
||
| get platform() { | ||
| return this._sp.platform; | ||
| } | ||
| get initialDb() { | ||
| return this._sp.initialDb; | ||
| } | ||
|
|
||
| getURI = forwardedMethod('getURI'); | ||
| getConnectionInfo = forwardedMethod('getConnectionInfo'); | ||
| resetConnectionOptions = forwardedMethod('resetConnectionOptions'); | ||
| startSession = forwardedMethod('startSession'); | ||
| getRawClient = forwardedMethod('getRawClient'); | ||
| createClientEncryption = forwardedMethod('createClientEncryption'); | ||
| getFleOptions = forwardedMethod('getFleOptions'); | ||
| createEncryptedCollection = forwardedMethod('createEncryptedCollection'); | ||
|
|
||
| async getNewConnection( | ||
| ...args: Parameters<ServiceProvider['getNewConnection']> | ||
| ): Promise<ServiceProvider> { | ||
| const sp = await this._sp.getNewConnection(...args); | ||
| return new DeepInspectServiceProviderWrapper(sp as ServiceProvider); | ||
| } | ||
| } | ||
|
|
||
| type PickMethodsByReturnType<T, R> = { | ||
| [k in keyof T as NonNullable<T[k]> extends (...args: any[]) => R | ||
| ? k | ||
| : never]: T[k]; | ||
| }; | ||
|
|
||
| function cursorMethod< | ||
| K extends keyof PickMethodsByReturnType< | ||
| ServiceProvider, | ||
| ServiceProviderAbstractCursor | ||
| > | ||
| >( | ||
| key: K | ||
| ): ( | ||
| ...args: Parameters<Required<ServiceProvider>[K]> | ||
| ) => ReturnType<Required<ServiceProvider>[K]> { | ||
| return function ( | ||
| this: DeepInspectServiceProviderWrapper, | ||
| ...args: Parameters<ServiceProvider[K]> | ||
| ): ReturnType<ServiceProvider[K]> { | ||
| // The problem here is that ReturnType<ServiceProvider[K]> results in | ||
| // ServiceProviderAnyCursor which includes ServiceProviderChangeStream which | ||
| // doesn't have readBufferedDocuments or toArray. We can try cast things to | ||
| // ServiceProviderAbstractCursor, but then that's not assignable to | ||
| // ServiceProviderAnyCursor. And that's why there's so much casting below. | ||
| const cursor = (this._sp[key] as any)(...args) as any; | ||
|
|
||
| cursor.next = cursorNext( | ||
lerouxb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| cursor.next.bind(cursor) as () => Promise<Document | null> | ||
| ); | ||
| cursor.tryNext = cursorTryNext( | ||
| cursor.tryNext.bind(cursor) as () => Promise<Document | null> | ||
| ); | ||
|
|
||
| if (cursor.readBufferedDocuments) { | ||
| cursor.readBufferedDocuments = cursorReadBufferedDocuments( | ||
| cursor.readBufferedDocuments.bind(cursor) as ( | ||
| number?: number | ||
| ) => Document[] | ||
| ); | ||
| } | ||
| if (cursor.toArray) { | ||
| cursor.toArray = cursorToArray( | ||
| cursor.toArray.bind(cursor) as () => Promise<Document[]> | ||
| ); | ||
| } | ||
|
|
||
| return cursor; | ||
| }; | ||
| } | ||
|
|
||
| const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); | ||
|
|
||
| function cursorNext( | ||
| original: () => Promise<Document | null> | ||
lerouxb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ): () => Promise<Document | null> { | ||
| return async function (): Promise<Document | null> { | ||
| const result = await original(); | ||
| if (result) { | ||
| replaceWithCustomInspect(result); | ||
lerouxb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| return result; | ||
| }; | ||
| } | ||
|
|
||
| const cursorTryNext = cursorNext; | ||
|
|
||
| function cursorReadBufferedDocuments( | ||
| original: (number?: number) => Document[] | ||
| ): (number?: number) => Document[] { | ||
| return function (number?: number): Document[] { | ||
| const results = original(number); | ||
|
|
||
| replaceWithCustomInspect(results); | ||
|
|
||
| return results; | ||
| }; | ||
| } | ||
|
|
||
| function cursorToArray( | ||
| original: () => Promise<Document[]> | ||
| ): () => Promise<Document[]> { | ||
| return async function (): Promise<Document[]> { | ||
| const results = await original(); | ||
|
|
||
| replaceWithCustomInspect(results); | ||
|
|
||
| return results; | ||
| }; | ||
| } | ||
lerouxb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| function bsonMethod< | ||
| K extends keyof PickMethodsByReturnType<ServiceProvider, Promise<any>> | ||
| >( | ||
| key: K | ||
| ): ( | ||
| ...args: Parameters<Required<ServiceProvider>[K]> | ||
| ) => ReturnType<Required<ServiceProvider>[K]> { | ||
| return async function ( | ||
| this: DeepInspectServiceProviderWrapper, | ||
| ...args: Parameters<Required<ServiceProvider>[K]> | ||
| ): // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| // @ts-ignore The returntype already contains a promise | ||
| ReturnType<Required<ServiceProvider>[K]> { | ||
| const result = await (this._sp[key] as any)(...args); | ||
| replaceWithCustomInspect(result); | ||
| return result; | ||
| }; | ||
| } | ||
|
|
||
| function forwardedMethod< | ||
| K extends keyof PickMethodsByReturnType<ServiceProvider, any> | ||
| >( | ||
| key: K | ||
| ): ( | ||
| ...args: Parameters<Required<ServiceProvider>[K]> | ||
| ) => ReturnType<Required<ServiceProvider>[K]> { | ||
| return function ( | ||
| this: DeepInspectServiceProviderWrapper, | ||
| ...args: Parameters<Required<ServiceProvider>[K]> | ||
| ): ReturnType<Required<ServiceProvider>[K]> { | ||
| // not wrapping the result at all because forwardedMethod() is for simple | ||
| // values only | ||
| return (this._sp[key] as any)(...args); | ||
| }; | ||
| } | ||
|
|
||
| function customDocumentInspect( | ||
| this: Document, | ||
| depth: number, | ||
| inspectOptions: InspectOptions, | ||
| inspect: typeof _inspect | ||
| ) { | ||
| const newInspectOptions = { | ||
| ...inspectOptions, | ||
| depth: Infinity, | ||
| maxArrayLength: Infinity, | ||
| maxStringLength: Infinity, | ||
| }; | ||
|
|
||
| // reuse the standard inpect logic for an object without causing infinite | ||
lerouxb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // recursion | ||
| const inspectBackup = (this as any)[customInspectSymbol]; | ||
| delete (this as any)[customInspectSymbol]; | ||
| const result = inspect(this, newInspectOptions); | ||
| (this as any)[customInspectSymbol] = inspectBackup; | ||
| return result; | ||
| } | ||
|
|
||
| function replaceWithCustomInspect(obj: any) { | ||
lerouxb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (Array.isArray(obj)) { | ||
| (obj as any)[customInspectSymbol] = customDocumentInspect; | ||
lerouxb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for (const item of obj) { | ||
| replaceWithCustomInspect(item); | ||
| } | ||
| } else if (obj && typeof obj === 'object' && obj !== null) { | ||
lerouxb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| obj[customInspectSymbol] = customDocumentInspect; | ||
| for (const value of Object.values(obj)) { | ||
| replaceWithCustomInspect(value); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -51,6 +51,7 @@ import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete' | |
| import type { JSONSchema } from 'mongodb-schema'; | ||
| import { analyzeDocuments } from 'mongodb-schema'; | ||
| import type { BaseCursor } from './abstract-cursor'; | ||
| import { DeepInspectServiceProviderWrapper } from './deep-inspect-service-provider-wrapper'; | ||
|
|
||
| /** | ||
| * The subset of CLI options that is relevant for the shell API's behavior itself. | ||
|
|
@@ -203,7 +204,9 @@ export class ShellInstanceState { | |
| cliOptions: ShellCliOptions = {}, | ||
| bsonLibrary: BSONLibrary = initialServiceProvider.bsonLibrary | ||
| ) { | ||
| this.initialServiceProvider = initialServiceProvider; | ||
| this.initialServiceProvider = new DeepInspectServiceProviderWrapper( | ||
| initialServiceProvider | ||
| ); | ||
| this.bsonLibrary = bsonLibrary; | ||
| this.messageBus = messageBus; | ||
| this.shellApi = new ShellApi(this); | ||
|
|
@@ -220,11 +223,11 @@ export class ShellInstanceState { | |
| undefined, | ||
| undefined, | ||
| undefined, | ||
| initialServiceProvider | ||
| this.initialServiceProvider | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw it is VERY easy to accidentally pass |
||
| ); | ||
| this.mongos.push(mongo); | ||
| this.currentDb = mongo.getDB( | ||
| initialServiceProvider.initialDb || DEFAULT_DB | ||
| this.initialServiceProvider.initialDb || DEFAULT_DB | ||
| ); | ||
| } else { | ||
| this.currentDb = new NoDatabase() as DatabaseWithSchema; | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.