Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions packages/browser-repl/src/components/utils/inspect.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { CustomInspectFunction } from 'util';
import { inspect as utilInspect } from 'util';
import { bsonStringifiers } from '@mongosh/service-provider-core';

Expand All @@ -14,18 +15,16 @@ import { bsonStringifiers } from '@mongosh/service-provider-core';
const customInspect = utilInspect.custom || 'inspect';
const visitedObjects = new WeakSet();

function tryAddInspect(
obj: any,
stringifier: (this: any, depth: any, options: any) => string
): void {
function tryAddInspect(obj: unknown, stringifier: CustomInspectFunction): void {
try {
Object.defineProperty(obj, customInspect, {
writable: true,
configurable: true,
enumerable: false,
value: function (...args: [any, any]) {
value: function (...args: Parameters<CustomInspectFunction>): string {
try {
return stringifier.call(this, ...args);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
// eslint-disable-next-line no-console
console.warn('Could not inspect bson object', { obj: this, err });
Expand Down
28 changes: 28 additions & 0 deletions packages/service-provider-core/src/printable-bson.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,34 @@ describe('BSON printers', function () {
).to.equal("MD5('0123456789abcdef0123456789abcdef')");
});

describe('with Vector types', function () {
it('formats Int8Array correctly', function () {
expect(
inspect(bson.Binary.fromInt8Array(new Int8Array([1, 2, 3])))
).to.equal('Binary.fromInt8Array(new Int8Array([ 1, 2, 3 ]))');
});

it('formats PackedBits correctly', function () {
expect(
inspect(bson.Binary.fromPackedBits(new Uint8Array([1, 2, 3])))
).to.equal('Binary.fromPackedBits(new Uint8Array([ 1, 2, 3 ]))');
});

it('formats Float32Array correctly', function () {
expect(
inspect(
bson.Binary.fromFloat32Array(
new Float32Array([1.1111, 2.2222, 3.3333])
),
{ compact: true }
)
).matches(
// Precision is lost because of float handling, so we use regex to match
/Binary.fromFloat32Array\(new Float32Array\(\[ 1\.1\d*, 2\.2\d*, 3\.3\d* \]\)\)/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we're meant to be dealing with float precision weirdness in JS in this case. can definitely see how it'd be weird to see imprecise floats while trying to inspect data. we could do something like https://github.com/mongodb-js/compass/pull/6834/files#diff-3d4b298fbb2cfe42467edc59277483eb9bcfdc46b07ea658ab3dc4f66903da88R174

);
});
});

it('formats any other value with the new format using createfromBase64', function () {
expect(
inspect(bson.Binary.createFromBase64('SGVsbG8sIFdvcmxkIQo='))
Expand Down
68 changes: 60 additions & 8 deletions packages/service-provider-core/src/printable-bson.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { bson as BSON } from './bson-export';
import type { InspectOptionsStylized, CustomInspectFunction } from 'util';
import { inspect as utilInspect } from 'util';
const inspectCustom = Symbol.for('nodejs.util.inspect.custom');
type BSONClassKey = (typeof BSON)[Exclude<
keyof typeof BSON,
Expand All @@ -7,26 +9,73 @@ type BSONClassKey = (typeof BSON)[Exclude<

// Turn e.g. 'new Double(...)' into 'Double(...)' but preserve possible leading whitespace
function removeNewFromInspectResult(str: string): string {
return String(str).replace(/^(\s*)(new )/, '$1');
return str.replace(/^(\s*)(new )/, '$1');
}

/** Typed array such as Int8Array have a format like 'Int8Array(3) [1, 2, 3]'
* and we want to remove the prefix and keep just the array contents. */
function removeTypedArrayPrefixFromInspectResult(str: string): string {
return str.replace(/^\s*\S+\s*\(\d+\)\s*/, '');
}

// Create a Node.js-util-inspect() style custom inspect function that
// strips 'new ' from inspect results but otherwise uses the Node.js
// driver's bson library's inspect functions.
function makeClasslessInspect<K extends BSONClassKey>(className: K) {
function makeClasslessInspect<K extends BSONClassKey>(
className: K
): CustomInspectFunction {
const originalInspect = BSON[className].prototype.inspect;
return function (
this: (typeof BSON)[typeof className]['prototype'],
...args: any
...args: Parameters<typeof originalInspect>
) {
return removeNewFromInspectResult(originalInspect.apply(this, args as any));
};
return removeNewFromInspectResult(originalInspect.apply(this, args));
} satisfies CustomInspectFunction;
}

const binaryInspect = makeClasslessInspect('Binary');

const binaryVectorInspect = function (
this: typeof BSON.Binary.prototype,
depth: number,
options: InspectOptionsStylized
): string {
switch (this.buffer[0]) {
case BSON.Binary.VECTOR_TYPE.Int8:
return `Binary.fromInt8Array(new Int8Array(${removeTypedArrayPrefixFromInspectResult(
utilInspect(this.toInt8Array(), {
depth,
...options,
// These arrays can be very large, so would prefer to use the default options instead.
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
})
)}))`;
case BSON.Binary.VECTOR_TYPE.Float32:
return `Binary.fromFloat32Array(new Float32Array(${removeTypedArrayPrefixFromInspectResult(
utilInspect(this.toFloat32Array(), {
depth,
...options,
// These arrays can be very large, so would prefer to use the default options instead.
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we want this, could remove. it just seems possible that these arrays can be very large I'd imagine so the {maxArrayLength: Infinity} that we're getting from

return formatCursor(value, { ...options, ...fullDepthInspectOptions });
can be problematic

})
)}))`;
case BSON.Binary.VECTOR_TYPE.PackedBit:
return `Binary.fromPackedBits(new Uint8Array(${removeTypedArrayPrefixFromInspectResult(
utilInspect(this.toPackedBits(), {
depth,
...options,
// These arrays can be very large, so would prefer to use the default options instead.
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
})
)}))`;
default:
return binaryInspect.call(this, depth, options);
}
} satisfies CustomInspectFunction;

export const bsonStringifiers: Record<
BSONClassKey | 'ObjectID',
(this: any, depth: any, options: any) => string
CustomInspectFunction
> = {
ObjectId: makeClasslessInspect('ObjectId'),
ObjectID: makeClasslessInspect('ObjectId'),
Expand All @@ -43,10 +92,13 @@ export const bsonStringifiers: Record<
BSONRegExp: makeClasslessInspect('BSONRegExp'),
Binary: function (
this: typeof BSON.Binary.prototype,
...args: any[]
...args: Parameters<CustomInspectFunction>
): string {
const hexString = this.toString('hex');

switch (this.sub_type) {
case BSON.Binary.SUBTYPE_VECTOR:
return binaryVectorInspect.apply(this, args);
case BSON.Binary.SUBTYPE_MD5:
return `MD5('${hexString}')`;
case BSON.Binary.SUBTYPE_UUID:
Expand All @@ -64,7 +116,7 @@ export const bsonStringifiers: Record<
default:
return binaryInspect.apply(this, args);
}
},
} satisfies CustomInspectFunction,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💙

};

/**
Expand Down