Skip to content

Commit 999208b

Browse files
authored
feat(service-provider-core): support new BSON vector types in mongosh MONGOSH-1873 (#2425)
1 parent 3804535 commit 999208b

File tree

3 files changed

+92
-13
lines changed

3 files changed

+92
-13
lines changed

packages/browser-repl/src/components/utils/inspect.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { CustomInspectFunction } from 'util';
12
import { inspect as utilInspect } from 'util';
23
import { bsonStringifiers } from '@mongosh/service-provider-core';
34

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

17-
function tryAddInspect(
18-
obj: any,
19-
stringifier: (this: any, depth: any, options: any) => string
20-
): void {
18+
function tryAddInspect(obj: unknown, stringifier: CustomInspectFunction): void {
2119
try {
2220
Object.defineProperty(obj, customInspect, {
2321
writable: true,
2422
configurable: true,
2523
enumerable: false,
26-
value: function (...args: [any, any]) {
24+
value: function (...args: Parameters<CustomInspectFunction>): string {
2725
try {
2826
return stringifier.call(this, ...args);
27+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2928
} catch (err: any) {
3029
// eslint-disable-next-line no-console
3130
console.warn('Could not inspect bson object', { obj: this, err });

packages/service-provider-core/src/printable-bson.spec.ts

+28
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,34 @@ describe('BSON printers', function () {
9595
).to.equal("MD5('0123456789abcdef0123456789abcdef')");
9696
});
9797

98+
describe('with Vector types', function () {
99+
it('formats Int8Array correctly', function () {
100+
expect(
101+
inspect(bson.Binary.fromInt8Array(new Int8Array([1, 2, 3])))
102+
).to.equal('Binary.fromInt8Array(new Int8Array([ 1, 2, 3 ]))');
103+
});
104+
105+
it('formats PackedBits correctly', function () {
106+
expect(
107+
inspect(bson.Binary.fromPackedBits(new Uint8Array([1, 2, 3])))
108+
).to.equal('Binary.fromPackedBits(new Uint8Array([ 1, 2, 3 ]))');
109+
});
110+
111+
it('formats Float32Array correctly', function () {
112+
expect(
113+
inspect(
114+
bson.Binary.fromFloat32Array(
115+
new Float32Array([1.1111, 2.2222, 3.3333])
116+
),
117+
{ compact: true }
118+
)
119+
).matches(
120+
// Precision is lost because of float handling, so we use regex to match
121+
/Binary.fromFloat32Array\(new Float32Array\(\[ 1\.1\d*, 2\.2\d*, 3\.3\d* \]\)\)/
122+
);
123+
});
124+
});
125+
98126
it('formats any other value with the new format using createfromBase64', function () {
99127
expect(
100128
inspect(bson.Binary.createFromBase64('SGVsbG8sIFdvcmxkIQo='))

packages/service-provider-core/src/printable-bson.ts

+60-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { bson as BSON } from './bson-export';
2+
import type { InspectOptionsStylized, CustomInspectFunction } from 'util';
3+
import { inspect as utilInspect } from 'util';
24
const inspectCustom = Symbol.for('nodejs.util.inspect.custom');
35
type BSONClassKey = (typeof BSON)[Exclude<
46
keyof typeof BSON,
@@ -7,26 +9,73 @@ type BSONClassKey = (typeof BSON)[Exclude<
79

810
// Turn e.g. 'new Double(...)' into 'Double(...)' but preserve possible leading whitespace
911
function removeNewFromInspectResult(str: string): string {
10-
return String(str).replace(/^(\s*)(new )/, '$1');
12+
return str.replace(/^(\s*)(new )/, '$1');
13+
}
14+
15+
/** Typed array such as Int8Array have a format like 'Int8Array(3) [1, 2, 3]'
16+
* and we want to remove the prefix and keep just the array contents. */
17+
function removeTypedArrayPrefixFromInspectResult(str: string): string {
18+
return str.replace(/^\s*\S+\s*\(\d+\)\s*/, '');
1119
}
1220

1321
// Create a Node.js-util-inspect() style custom inspect function that
1422
// strips 'new ' from inspect results but otherwise uses the Node.js
1523
// driver's bson library's inspect functions.
16-
function makeClasslessInspect<K extends BSONClassKey>(className: K) {
24+
function makeClasslessInspect<K extends BSONClassKey>(
25+
className: K
26+
): CustomInspectFunction {
1727
const originalInspect = BSON[className].prototype.inspect;
1828
return function (
1929
this: (typeof BSON)[typeof className]['prototype'],
20-
...args: any
30+
...args: Parameters<typeof originalInspect>
2131
) {
22-
return removeNewFromInspectResult(originalInspect.apply(this, args as any));
23-
};
32+
return removeNewFromInspectResult(originalInspect.apply(this, args));
33+
} satisfies CustomInspectFunction;
2434
}
2535

2636
const binaryInspect = makeClasslessInspect('Binary');
37+
38+
const binaryVectorInspect = function (
39+
this: typeof BSON.Binary.prototype,
40+
depth: number,
41+
options: InspectOptionsStylized
42+
): string {
43+
switch (this.buffer[0]) {
44+
case BSON.Binary.VECTOR_TYPE.Int8:
45+
return `Binary.fromInt8Array(new Int8Array(${removeTypedArrayPrefixFromInspectResult(
46+
utilInspect(this.toInt8Array(), {
47+
depth,
48+
...options,
49+
// These arrays can be very large, so would prefer to use the default options instead.
50+
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
51+
})
52+
)}))`;
53+
case BSON.Binary.VECTOR_TYPE.Float32:
54+
return `Binary.fromFloat32Array(new Float32Array(${removeTypedArrayPrefixFromInspectResult(
55+
utilInspect(this.toFloat32Array(), {
56+
depth,
57+
...options,
58+
// These arrays can be very large, so would prefer to use the default options instead.
59+
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
60+
})
61+
)}))`;
62+
case BSON.Binary.VECTOR_TYPE.PackedBit:
63+
return `Binary.fromPackedBits(new Uint8Array(${removeTypedArrayPrefixFromInspectResult(
64+
utilInspect(this.toPackedBits(), {
65+
depth,
66+
...options,
67+
// These arrays can be very large, so would prefer to use the default options instead.
68+
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
69+
})
70+
)}))`;
71+
default:
72+
return binaryInspect.call(this, depth, options);
73+
}
74+
} satisfies CustomInspectFunction;
75+
2776
export const bsonStringifiers: Record<
2877
BSONClassKey | 'ObjectID',
29-
(this: any, depth: any, options: any) => string
78+
CustomInspectFunction
3079
> = {
3180
ObjectId: makeClasslessInspect('ObjectId'),
3281
ObjectID: makeClasslessInspect('ObjectId'),
@@ -43,10 +92,13 @@ export const bsonStringifiers: Record<
4392
BSONRegExp: makeClasslessInspect('BSONRegExp'),
4493
Binary: function (
4594
this: typeof BSON.Binary.prototype,
46-
...args: any[]
95+
...args: Parameters<CustomInspectFunction>
4796
): string {
4897
const hexString = this.toString('hex');
98+
4999
switch (this.sub_type) {
100+
case BSON.Binary.SUBTYPE_VECTOR:
101+
return binaryVectorInspect.apply(this, args);
50102
case BSON.Binary.SUBTYPE_MD5:
51103
return `MD5('${hexString}')`;
52104
case BSON.Binary.SUBTYPE_UUID:
@@ -64,7 +116,7 @@ export const bsonStringifiers: Record<
64116
default:
65117
return binaryInspect.apply(this, args);
66118
}
67-
},
119+
} satisfies CustomInspectFunction,
68120
};
69121

70122
/**

0 commit comments

Comments
 (0)