diff --git a/packages/mongodb-constants/src/bson-types.ts b/packages/mongodb-constants/src/bson-types.ts index 7a310d25..f28ebcf6 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', + 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', + 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', + 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..a60b45f9 100644 --- a/packages/query-parser/src/index.spec.ts +++ b/packages/query-parser/src/index.spec.ts @@ -120,6 +120,36 @@ describe('mongodb-query-parser', function () { ); }); + it('should support LegacyJavaUUID', function () { + assert.deepEqual( + convert('LegacyJavaUUID("00112233-4455-6677-8899-aabbccddeeff")'), + { + $binary: 'd2ZVRDMiEQD/7t3Mu6qZiA==', + $type: `0${bson.Binary.SUBTYPE_UUID_OLD}`, + }, + ); + }); + + it('should support LegacyCSharpUUID', function () { + assert.deepEqual( + convert('LegacyCSharpUUID("00112233-4455-6677-8899-aabbccddeeff")'), + { + $binary: 'MyIRAFVEd2aImaq7zN3u/w==', + $type: `0${bson.Binary.SUBTYPE_UUID_OLD}`, + }, + ); + }); + + it('should support LegacyPythonUUID', function () { + assert.deepEqual( + convert('LegacyPythonUUID("00112233-4455-6677-8899-aabbccddeeff")'), + { + $binary: 'ABEiM0RVZneImaq7zN3u/w==', + $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 +673,39 @@ e s`, ); }); + 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 () { const res = parseFilter( 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..dfb1167c 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 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); },