From 8a4187976b09f12d0e9a961b295e70d1847e45e6 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Wed, 12 Nov 2025 07:36:15 -0800 Subject: [PATCH 1/5] feat(query-parser, shell-bson-parser): add legacy UUID helpers MONGOSH-2486 --- .tool-versions | 1 + packages/mongodb-constants/src/bson-types.ts | 40 ++++++++++ packages/query-parser/src/index.spec.ts | 34 +++++++++ packages/query-parser/src/stringify.ts | 3 + packages/shell-bson-parser/src/index.spec.ts | 78 ++++++++++++++++++++ packages/shell-bson-parser/src/scope.ts | 72 ++++++++++++++++++ 6 files changed, 228 insertions(+) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..269cea0b --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 20.19.2 diff --git a/packages/mongodb-constants/src/bson-types.ts b/packages/mongodb-constants/src/bson-types.ts index 7a310d25..2d54c5b8 100644 --- a/packages/mongodb-constants/src/bson-types.ts +++ b/packages/mongodb-constants/src/bson-types.ts @@ -122,6 +122,46 @@ const BSON_TYPES = [ description: 'BSON Regex type', snippet: "RegExp('${1:source}', '${2:opts}')", }, + { + name: 'LegacyJavaUUID', + value: 'LegacyJavaUUID', + label: 'LegacyJavaUUID', + score: 1, + meta: 'bson-legacy-uuid-support', + version: '0.0.0', + description: 'BSON Binary subtype 3 (Java legacy UUID)', + snippet: "LegacyJavaUUID('${1:uuid}')", + }, + { + name: 'LegacyCSharpUUID', + value: 'LegacyCSharpUUID', + label: 'LegacyCSharpUUID', + score: 1, + meta: 'bson-legacy-uuid-support', + version: '0.0.0', + description: 'BSON Binary subtype 3 (CSharp legacy UUID)', + snippet: "LegacyCSharpUUID('${1:uuid}')", + }, + { + name: 'LegacyPythonUUID', + value: 'LegacyPythonUUID', + label: 'LegacyPythonUUID', + score: 1, + meta: 'bson-legacy-uuid-support', + version: '0.0.0', + description: 'BSON Binary subtype 3 (Python legacy UUID)', + snippet: "LegacyPythonUUID('${1:uuid}')", + }, + { + name: 'UUID', + value: 'UUID', + label: 'UUID', + score: 1, + meta: 'bson', + version: '0.0.0', + description: 'BSON Binary subtype 4', + snippet: "UUID('${1:uuid}')", + }, ] as const; export { BSON_TYPES }; diff --git a/packages/query-parser/src/index.spec.ts b/packages/query-parser/src/index.spec.ts index 87c08313..32cb4a31 100644 --- a/packages/query-parser/src/index.spec.ts +++ b/packages/query-parser/src/index.spec.ts @@ -120,6 +120,38 @@ describe('mongodb-query-parser', function () { ); }); + // TODO: prototype replace test with val for new val. + + it('should support LegacyJavaUUID', function () { + assert.deepEqual( + convert('LegacyJavaUUID("00112233-4455-6677-8899-aabbccddeeff")'), + { + $binary: 'OyQRAeK7QlWMr0E2xWapYg==', + $type: `0${bson.Binary.SUBTYPE_UUID_OLD}`, + }, + ); + }); + + it('should support LegacyCSharpUUID', function () { + assert.deepEqual( + convert('LegacyCSharpUUID("00112233-4455-6677-8899-aabbccddeeff")'), + { + $binary: 'OyQRAeK7QlWMr0E2xWapYg==', + $type: `0${bson.Binary.SUBTYPE_UUID_OLD}`, + }, + ); + }); + + it('should support LegacyPythonUUID', function () { + assert.deepEqual( + convert('LegacyPythonUUID("00112233-4455-6677-8899-aabbccddeeff")'), + { + $binary: 'OyQRAeK7QlWMr0E2xWapYg==', + $type: `0${bson.Binary.SUBTYPE_UUID_OLD}`, + }, + ); + }); + // https://www.mongodb.com/docs/manual/reference/method/Binary.createFromHexString/ it('should support Binary.createFromHexString', function () { assert.deepEqual( @@ -643,6 +675,8 @@ e s`, ); }); + // TODO: New tests for the legacy UUID formats. + // https://www.mongodb.com/docs/manual/reference/method/Binary.createFromHexString/ it('should support Binary.createFromHexString', function () { const res = parseFilter( diff --git a/packages/query-parser/src/stringify.ts b/packages/query-parser/src/stringify.ts index 30a9ee50..2c4dfaa0 100644 --- a/packages/query-parser/src/stringify.ts +++ b/packages/query-parser/src/stringify.ts @@ -60,6 +60,9 @@ const BSON_TO_JS_STRING = { return `ObjectId('${v.toString('hex')}')`; }, Binary: function (v: Binary) { + // TODO: handle UUID subtypes Compass side or with an + // option for subType 3 handling here. + const subType = v.sub_type; if (subType === 4 && v.buffer.length === 16) { let uuidHex = ''; diff --git a/packages/shell-bson-parser/src/index.spec.ts b/packages/shell-bson-parser/src/index.spec.ts index cb1342fb..88899ec5 100644 --- a/packages/shell-bson-parser/src/index.spec.ts +++ b/packages/shell-bson-parser/src/index.spec.ts @@ -51,12 +51,78 @@ describe('@mongodb-js/shell-bson-parser', function () { }); }); + it('should create new UUIDs', function () { + expect(parse('{name: UUID()}')) + .to.have.property('name') + .that.is.instanceOf(bson.Binary); + expect(parse('{name: LegacyCSharpUUID()}')) + .to.have.property('name') + .that.is.instanceOf(bson.Binary); + expect(parse('{name: LegacyJavaUUID()}')) + .to.have.property('name') + .that.is.instanceOf(bson.Binary); + expect(parse('{name: LegacyPythonUUID()}')) + .to.have.property('name') + .that.is.instanceOf(bson.Binary); + }); + + describe('with a set UUID generation', function () { + let sandbox: SinonSandbox; + + beforeEach(function () { + sandbox = createSandbox(); + + sandbox.replace((bson as any).UUID.prototype, 'toHexString', function () { + return '00112233-4455-6677-8899-aabbccddeeff'; + }); + sandbox.replace((bson as any).UUID.prototype, 'toBinary', function () { + return new bson.Binary( + Buffer.from('00112233445566778899aabbccddeeff', 'hex'), + 4, + ); + }); + }); + afterEach(function () { + sandbox.restore(); + }); + + it('should create new UUIDs in the correct formats for legacy', function () { + expect(parse('{name: UUID()}')).to.have.deep.equal({ + name: new bson.Binary( + Buffer.from('00112233445566778899aabbccddeeff', 'hex'), + 4, + ), + }); + expect(parse('{name: LegacyCSharpUUID()}')).to.have.deep.equal({ + name: new bson.Binary( + Buffer.from('33221100554477668899aabbccddeeff', 'hex'), + 3, + ), + }); + expect(parse('{name: LegacyJavaUUID()}')).to.have.deep.equal({ + name: new bson.Binary( + Buffer.from('7766554433221100ffeeddccbbaa9988', 'hex'), + 3, + ), + }); + expect(parse('{name: LegacyPythonUUID()}')).to.have.deep.equal({ + name: new bson.Binary( + Buffer.from('00112233445566778899aabbccddeeff', 'hex'), + 3, + ), + }); + }); + }); + it('should accept a complex query', function () { expect( parse(`{ RegExp: /test/ig, Binary: new Binary(), BinData: BinData(3, 'dGVzdAo='), + LegacyCSharpUUID: LegacyCSharpUUID('00112233-4455-6677-8899-aabbccddeeff'), + LegacyJavaUUID: LegacyJavaUUID('00112233-4455-6677-8899-aabbccddeeff'), + LegacyPythonUUID: LegacyPythonUUID('00112233-4455-6677-8899-aabbccddeeff'), UUID: UUID('3d37923d-ab8e-4931-9e46-93df5fd3599e'), Code: Code('function() {}'), DBRef: new DBRef('tests', new ObjectId("5e159ba7eac34211f2252aaa"), 'test'), @@ -84,6 +150,18 @@ describe('@mongodb-js/shell-bson-parser', function () { RegExp: /test/gi, Binary: new bson.Binary(), BinData: new bson.Binary(Buffer.from('dGVzdAo=', 'base64'), 3), + LegacyCSharpUUID: new bson.Binary( + Buffer.from('33221100554477668899aabbccddeeff', 'hex'), + 3, + ), + LegacyJavaUUID: new bson.Binary( + Buffer.from('7766554433221100ffeeddccbbaa9988', 'hex'), + 3, + ), + LegacyPythonUUID: new bson.Binary( + Buffer.from('00112233445566778899aabbccddeeff', 'hex'), + 3, + ), UUID: new bson.Binary( Buffer.from('3d37923dab8e49319e4693df5fd3599e', 'hex'), 4, diff --git a/packages/shell-bson-parser/src/scope.ts b/packages/shell-bson-parser/src/scope.ts index 1485bba2..d404ac88 100644 --- a/packages/shell-bson-parser/src/scope.ts +++ b/packages/shell-bson-parser/src/scope.ts @@ -41,6 +41,78 @@ const SCOPE_ANY: { [x: string]: Function } = lookupMap({ Binary: function (buffer: any, subType: any) { return new bson.Binary(buffer, subType); }, + + // Legacy UUID functions are from originally taken from + // https://github.com/mongodb/mongo-csharp-driver/blob/ac2b2a61c6b7a193cf0266dfb8c65f86c2bf7572/uuidhelpers.js + LegacyJavaUUID: function (u: any) { + if (u === undefined) { + // Generate a new UUID and format it. + u = new bson.UUID().toHexString(); + } + + let hex: string = String.prototype.replace.call(u, /[{}-]/g, () => ''); + let msb = String.prototype.substring.call(hex, 0, 16); + let lsb = String.prototype.substring.call(hex, 16, 32); + msb = + String.prototype.substring.call(msb, 14, 16) + + String.prototype.substring.call(msb, 12, 14) + + String.prototype.substring.call(msb, 10, 12) + + String.prototype.substring.call(msb, 8, 10) + + String.prototype.substring.call(msb, 6, 8) + + String.prototype.substring.call(msb, 4, 6) + + String.prototype.substring.call(msb, 2, 4) + + String.prototype.substring.call(msb, 0, 2); + lsb = + String.prototype.substring.call(lsb, 14, 16) + + String.prototype.substring.call(lsb, 12, 14) + + String.prototype.substring.call(lsb, 10, 12) + + String.prototype.substring.call(lsb, 8, 10) + + String.prototype.substring.call(lsb, 6, 8) + + String.prototype.substring.call(lsb, 4, 6) + + String.prototype.substring.call(lsb, 2, 4) + + String.prototype.substring.call(lsb, 0, 2); + hex = msb + lsb; + + const hexBuffer = Buffer.from(hex, 'hex'); + return new bson.Binary(hexBuffer, 3); + }, + LegacyCSharpUUID: function (u: any) { + if (u === undefined) { + // Generate a new UUID and format it. + u = new bson.UUID().toHexString(); + } + + let hex: string = String.prototype.replace.call(u, /[{}-]/g, () => ''); + const a = + String.prototype.substring.call(hex, 6, 8) + + String.prototype.substring.call(hex, 4, 6) + + String.prototype.substring.call(hex, 2, 4) + + String.prototype.substring.call(hex, 0, 2); + const b = + String.prototype.substring.call(hex, 10, 12) + + String.prototype.substring.call(hex, 8, 10); + const c = + String.prototype.substring.call(hex, 14, 16) + + String.prototype.substring.call(hex, 12, 14); + const d = String.prototype.substring.call(hex, 16, 32); + hex = a + b + c + d; + + const hexBuffer = Buffer.from(hex, 'hex'); + return new bson.Binary(hexBuffer, 3); + }, + LegacyPythonUUID: function (u: any) { + if (u === undefined) { + return new bson.Binary(new bson.UUID().toBinary().buffer, 3); + } + + return new bson.Binary( + Buffer.from( + String.prototype.replace.call(u, /[{}-]/g, () => ''), + 'hex', + ), + 3, + ); + }, BinData: function (t: any, d: any) { return new bson.Binary(Buffer.from(d, 'base64'), t); }, From e442dcd2efc8d8fec3d014942de688a5cdfce2e3 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Wed, 12 Nov 2025 19:38:23 -0800 Subject: [PATCH 2/5] fixup: add tests --- packages/query-parser/src/index.spec.ts | 41 +++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/query-parser/src/index.spec.ts b/packages/query-parser/src/index.spec.ts index 32cb4a31..a60b45f9 100644 --- a/packages/query-parser/src/index.spec.ts +++ b/packages/query-parser/src/index.spec.ts @@ -120,13 +120,11 @@ describe('mongodb-query-parser', function () { ); }); - // TODO: prototype replace test with val for new val. - it('should support LegacyJavaUUID', function () { assert.deepEqual( convert('LegacyJavaUUID("00112233-4455-6677-8899-aabbccddeeff")'), { - $binary: 'OyQRAeK7QlWMr0E2xWapYg==', + $binary: 'd2ZVRDMiEQD/7t3Mu6qZiA==', $type: `0${bson.Binary.SUBTYPE_UUID_OLD}`, }, ); @@ -136,7 +134,7 @@ describe('mongodb-query-parser', function () { assert.deepEqual( convert('LegacyCSharpUUID("00112233-4455-6677-8899-aabbccddeeff")'), { - $binary: 'OyQRAeK7QlWMr0E2xWapYg==', + $binary: 'MyIRAFVEd2aImaq7zN3u/w==', $type: `0${bson.Binary.SUBTYPE_UUID_OLD}`, }, ); @@ -146,7 +144,7 @@ describe('mongodb-query-parser', function () { assert.deepEqual( convert('LegacyPythonUUID("00112233-4455-6677-8899-aabbccddeeff")'), { - $binary: 'OyQRAeK7QlWMr0E2xWapYg==', + $binary: 'ABEiM0RVZneImaq7zN3u/w==', $type: `0${bson.Binary.SUBTYPE_UUID_OLD}`, }, ); @@ -675,7 +673,38 @@ e s`, ); }); - // TODO: New tests for the legacy UUID formats. + it('does not stringify LegacyJavaUUID', function () { + const res = parseFilter( + '{name: LegacyJavaUUID("00112233-4455-6677-8899-aabbccddeeff")}', + ); + const stringified = stringify(res); + assert.equal( + stringified, + "{name: BinData(3, 'd2ZVRDMiEQD/7t3Mu6qZiA==')}", + ); + }); + + it('does not stringify LegacyCSharpUUID', function () { + const res = parseFilter( + '{name: LegacyCSharpUUID("00112233-4455-6677-8899-aabbccddeeff")}', + ); + const stringified = stringify(res); + assert.equal( + stringified, + "{name: BinData(3, 'MyIRAFVEd2aImaq7zN3u/w==')}", + ); + }); + + it('does not stringify LegacyPythonUUID', function () { + const res = parseFilter( + '{name: LegacyPythonUUID("00112233-4455-6677-8899-aabbccddeeff")}', + ); + const stringified = stringify(res); + assert.equal( + stringified, + "{name: BinData(3, 'ABEiM0RVZneImaq7zN3u/w==')}", + ); + }); // https://www.mongodb.com/docs/manual/reference/method/Binary.createFromHexString/ it('should support Binary.createFromHexString', function () { From d69f6e88c27953b5518ce2b2eea77c7188ab62cc Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Thu, 13 Nov 2025 14:50:12 -0800 Subject: [PATCH 3/5] fixup: update meta field naming for bson autocomplete --- packages/mongodb-constants/src/bson-types.ts | 6 +++--- packages/shell-bson-parser/src/scope.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mongodb-constants/src/bson-types.ts b/packages/mongodb-constants/src/bson-types.ts index 2d54c5b8..f28ebcf6 100644 --- a/packages/mongodb-constants/src/bson-types.ts +++ b/packages/mongodb-constants/src/bson-types.ts @@ -127,7 +127,7 @@ const BSON_TYPES = [ value: 'LegacyJavaUUID', label: 'LegacyJavaUUID', score: 1, - meta: 'bson-legacy-uuid-support', + meta: 'bson-legacy-uuid', version: '0.0.0', description: 'BSON Binary subtype 3 (Java legacy UUID)', snippet: "LegacyJavaUUID('${1:uuid}')", @@ -137,7 +137,7 @@ const BSON_TYPES = [ value: 'LegacyCSharpUUID', label: 'LegacyCSharpUUID', score: 1, - meta: 'bson-legacy-uuid-support', + meta: 'bson-legacy-uuid', version: '0.0.0', description: 'BSON Binary subtype 3 (CSharp legacy UUID)', snippet: "LegacyCSharpUUID('${1:uuid}')", @@ -147,7 +147,7 @@ const BSON_TYPES = [ value: 'LegacyPythonUUID', label: 'LegacyPythonUUID', score: 1, - meta: 'bson-legacy-uuid-support', + meta: 'bson-legacy-uuid', version: '0.0.0', description: 'BSON Binary subtype 3 (Python legacy UUID)', snippet: "LegacyPythonUUID('${1:uuid}')", diff --git a/packages/shell-bson-parser/src/scope.ts b/packages/shell-bson-parser/src/scope.ts index d404ac88..dfb1167c 100644 --- a/packages/shell-bson-parser/src/scope.ts +++ b/packages/shell-bson-parser/src/scope.ts @@ -42,7 +42,7 @@ const SCOPE_ANY: { [x: string]: Function } = lookupMap({ return new bson.Binary(buffer, subType); }, - // Legacy UUID functions are from originally taken from + // Legacy UUID functions from // https://github.com/mongodb/mongo-csharp-driver/blob/ac2b2a61c6b7a193cf0266dfb8c65f86c2bf7572/uuidhelpers.js LegacyJavaUUID: function (u: any) { if (u === undefined) { From f2fc449f58972d74aa54fe13b92ec5ff25b9d8be Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Thu, 13 Nov 2025 15:08:10 -0800 Subject: [PATCH 4/5] fixup: remove Compass comment --- packages/query-parser/src/stringify.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/query-parser/src/stringify.ts b/packages/query-parser/src/stringify.ts index 2c4dfaa0..30a9ee50 100644 --- a/packages/query-parser/src/stringify.ts +++ b/packages/query-parser/src/stringify.ts @@ -60,9 +60,6 @@ const BSON_TO_JS_STRING = { return `ObjectId('${v.toString('hex')}')`; }, Binary: function (v: Binary) { - // TODO: handle UUID subtypes Compass side or with an - // option for subType 3 handling here. - const subType = v.sub_type; if (subType === 4 && v.buffer.length === 16) { let uuidHex = ''; From e4c1e05cdbf0023a7e7164bc983d49680e6cc6f3 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Thu, 13 Nov 2025 15:31:24 -0800 Subject: [PATCH 5/5] fixup: remove .tool-versions --- .tool-versions | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 269cea0b..00000000 --- a/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -nodejs 20.19.2