Skip to content

Commit a1990e1

Browse files
authored
feat(shell-bson): add legacy uuid helpers MONGOSH-2486 (#2605)
1 parent af538cd commit a1990e1

File tree

3 files changed

+316
-0
lines changed

3 files changed

+316
-0
lines changed

packages/e2e-tests/test/e2e-bson.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,13 @@ describe('BSON e2e', function () {
349349
expect(await shell.executeLine(value)).to.include(value);
350350
shell.assertNoErrors();
351351
});
352+
it('BinData type 4 prints as UUID when valid', async function () {
353+
const value = "BinData(4, 'ASNFZ4mrze8BI0VniavN7w==')";
354+
expect(await shell.executeLine(value)).to.include(
355+
"UUID('01234567-89ab-cdef-0123-456789abcdef')"
356+
);
357+
shell.assertNoErrors();
358+
});
352359
it('BinData prints as MD5 when created by user as such', async function () {
353360
const value = "MD5('0123456789abcdef0123456789abcdef')";
354361
expect(await shell.executeLine(value)).to.include(value);
@@ -380,6 +387,45 @@ describe('BSON e2e', function () {
380387
);
381388
shell.assertNoErrors();
382389
});
390+
it('LegacyJavaUUID prints as Binary subtype 3 when created by user', async function () {
391+
const value = 'LegacyJavaUUID()';
392+
const output = await shell.executeLine(value);
393+
expect(output).to.match(/Binary\.createFromBase64\('.+', 3\)/);
394+
shell.assertNoErrors();
395+
});
396+
it('LegacyCSharpUUID prints as Binary subtype 3 when created by user', async function () {
397+
const value = 'LegacyCSharpUUID()';
398+
const output = await shell.executeLine(value);
399+
expect(output).to.match(/Binary\.createFromBase64\('.+', 3\)/);
400+
shell.assertNoErrors();
401+
});
402+
it('LegacyPythonUUID prints as Binary subtype 3 when created by user', async function () {
403+
const value = 'LegacyPythonUUID()';
404+
const output = await shell.executeLine(value);
405+
expect(output).to.match(/Binary\.createFromBase64\('.+', 3\)/);
406+
shell.assertNoErrors();
407+
});
408+
it('BinData created as LegacyJavaUUID prints as Binary subtype 3', async function () {
409+
const value = "LegacyJavaUUID('01234567-89ab-cdef-0123-456789abcdef')";
410+
expect(await shell.executeLine(value)).to.include(
411+
"Binary.createFromBase64('782riWdFIwHvzauJZ0UjAQ==', 3)"
412+
);
413+
shell.assertNoErrors();
414+
});
415+
it('BinData created as LegacyCSharpUUID prints as Binary subtype 3', async function () {
416+
const value = "LegacyCSharpUUID('01234567-89ab-cdef-0123-456789abcdef')";
417+
expect(await shell.executeLine(value)).to.include(
418+
"Binary.createFromBase64('Z0UjAauJ780BI0VniavN7w==', 3)"
419+
);
420+
shell.assertNoErrors();
421+
});
422+
it('BinData created as LegacyPythonUUID prints as Binary subtype 3', async function () {
423+
const value = "LegacyPythonUUID('01234567-89ab-cdef-0123-456789abcdef')";
424+
expect(await shell.executeLine(value)).to.include(
425+
"Binary.createFromBase64('ASNFZ4mrze8BI0VniavN7w==', 3)"
426+
);
427+
shell.assertNoErrors();
428+
});
383429
});
384430
describe('MaxKey/MinKey special handling', function () {
385431
it('inserts and retrieves MaxKey/MinKey regardless of whether they have been called as functions', async function () {

packages/shell-bson/src/shell-bson.spec.ts

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,187 @@ describe('Shell BSON', function () {
560560
expect.fail('Expecting error, nothing thrown');
561561
});
562562
});
563+
describe('LegacyJavaUUID', function () {
564+
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
565+
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
566+
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
567+
});
568+
569+
it('strips dashes from input', function () {
570+
expect(
571+
shellBson.LegacyJavaUUID('01234567-89ab-cdef-0123-456789abcdef').value()
572+
).to.deep.equal(
573+
shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef').value()
574+
);
575+
});
576+
577+
it('generates a random UUID when no arguments are passed', function () {
578+
const uuid = shellBson.LegacyJavaUUID();
579+
expect(uuid.sub_type).to.equal(3);
580+
expect(uuid.value().length).to.equal(16);
581+
});
582+
583+
it('has help and other metadata', function () {
584+
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
585+
expect(uuid?.help?.type).to.equal('Help');
586+
expect(uuid?.help?.().type).to.equal('Help');
587+
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
588+
});
589+
590+
it('performs byte-swapping for Java compatibility', function () {
591+
// Test that the byte order is swapped correctly for Java UUID format
592+
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
593+
// The MSB and LSB should be byte-swapped according to Java's UUID format
594+
expect(uuid.toString('hex')).to.equal('efcdab8967452301efcdab8967452301');
595+
});
596+
597+
it('errors for wrong type of arg 1', function () {
598+
try {
599+
(shellBson.LegacyJavaUUID as any)(1);
600+
} catch (e: any) {
601+
return expect(e.message).to.contain('string, got number');
602+
}
603+
expect.fail('Expecting error, nothing thrown');
604+
});
605+
});
606+
607+
describe('LegacyCSharpUUID', function () {
608+
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
609+
const uuid = shellBson.LegacyCSharpUUID(
610+
'0123456789abcdef0123456789abcdef'
611+
);
612+
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
613+
});
614+
615+
it('strips dashes from input', function () {
616+
expect(
617+
shellBson
618+
.LegacyCSharpUUID('01234567-89ab-cdef-0123-456789abcdef')
619+
.value()
620+
).to.deep.equal(
621+
shellBson.LegacyCSharpUUID('0123456789abcdef0123456789abcdef').value()
622+
);
623+
});
624+
625+
it('generates a random UUID when no arguments are passed', function () {
626+
const uuid = shellBson.LegacyCSharpUUID();
627+
expect(uuid.sub_type).to.equal(3);
628+
expect(uuid.value().length).to.equal(16);
629+
});
630+
631+
it('has help and other metadata', function () {
632+
const uuid = shellBson.LegacyCSharpUUID(
633+
'0123456789abcdef0123456789abcdef'
634+
);
635+
expect(uuid?.help?.type).to.equal('Help');
636+
expect(uuid?.help?.().type).to.equal('Help');
637+
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
638+
});
639+
640+
it('performs byte-swapping for C# compatibility', function () {
641+
// Test that the byte order is swapped correctly for C# UUID format
642+
const uuid = shellBson.LegacyCSharpUUID(
643+
'0123456789abcdef0123456789abcdef'
644+
);
645+
// The first three groups should be byte-swapped according to C#'s GUID format
646+
expect(uuid.toString('hex')).to.equal('67452301ab89efcd0123456789abcdef');
647+
});
648+
649+
it('errors for wrong type of arg 1', function () {
650+
try {
651+
(shellBson.LegacyCSharpUUID as any)(1);
652+
} catch (e: any) {
653+
return expect(e.message).to.contain('string, got number');
654+
}
655+
expect.fail('Expecting error, nothing thrown');
656+
});
657+
});
658+
659+
describe('LegacyPythonUUID', function () {
660+
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
661+
const uuid = shellBson.LegacyPythonUUID(
662+
'0123456789abcdef0123456789abcdef'
663+
);
664+
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
665+
});
666+
667+
it('strips dashes from input', function () {
668+
expect(
669+
shellBson
670+
.LegacyPythonUUID('01234567-89ab-cdef-0123-456789abcdef')
671+
.value()
672+
).to.deep.equal(
673+
shellBson.LegacyPythonUUID('0123456789abcdef0123456789abcdef').value()
674+
);
675+
});
676+
677+
it('generates a random UUID when no arguments are passed', function () {
678+
const uuid = shellBson.LegacyPythonUUID();
679+
expect(uuid.sub_type).to.equal(3);
680+
expect(uuid.value().length).to.equal(16);
681+
});
682+
683+
it('has help and other metadata', function () {
684+
const uuid = shellBson.LegacyPythonUUID(
685+
'0123456789abcdef0123456789abcdef'
686+
);
687+
expect(uuid?.help?.type).to.equal('Help');
688+
expect(uuid?.help?.().type).to.equal('Help');
689+
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
690+
});
691+
692+
it('uses standard byte order (no swapping)', function () {
693+
// Python UUID uses standard network byte order (big-endian, no swapping)
694+
const uuid = shellBson.LegacyPythonUUID(
695+
'0123456789abcdef0123456789abcdef'
696+
);
697+
expect(uuid.toString('hex')).to.equal('0123456789abcdef0123456789abcdef');
698+
});
699+
700+
it('errors for wrong type of arg 1', function () {
701+
try {
702+
(shellBson.LegacyPythonUUID as any)(1);
703+
} catch (e: any) {
704+
return expect(e.message).to.contain('string, got number');
705+
}
706+
expect.fail('Expecting error, nothing thrown');
707+
});
708+
});
709+
710+
describe('Legacy UUID comparison', function () {
711+
const testUuid = '0123456789abcdef0123456789abcdef';
712+
713+
it('each legacy UUID type produces different byte representations', function () {
714+
const javaUuid = shellBson.LegacyJavaUUID(testUuid);
715+
const csharpUuid = shellBson.LegacyCSharpUUID(testUuid);
716+
const pythonUuid = shellBson.LegacyPythonUUID(testUuid);
717+
718+
const javaBytes = javaUuid.toString('hex');
719+
const csharpBytes = csharpUuid.toString('hex');
720+
const pythonBytes = pythonUuid.toString('hex');
721+
722+
// They should all be different due to different byte-swapping
723+
expect(javaBytes).to.not.equal(csharpBytes);
724+
expect(javaBytes).to.not.equal(pythonBytes);
725+
expect(csharpBytes).to.not.equal(pythonBytes);
726+
727+
// Python should match the original (no swapping)
728+
expect(pythonBytes).to.equal(testUuid);
729+
});
730+
731+
it('all legacy UUID types use SUBTYPE_UUID_OLD', function () {
732+
const javaUuid = shellBson.LegacyJavaUUID(testUuid);
733+
const csharpUuid = shellBson.LegacyCSharpUUID(testUuid);
734+
const pythonUuid = shellBson.LegacyPythonUUID(testUuid);
735+
const uuid = shellBson.UUID(testUuid);
736+
737+
expect(javaUuid.sub_type).to.equal(3);
738+
expect(csharpUuid.sub_type).to.equal(3);
739+
expect(pythonUuid.sub_type).to.equal(3);
740+
expect(uuid.sub_type).to.equal(4);
741+
});
742+
});
743+
563744
describe('MD5', function () {
564745
let b: any;
565746
let h: any;

packages/shell-bson/src/shell-bson.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export interface ShellBsonBase<BSONLib extends BSON = BSON> {
5252
) => BSONLib['Binary']['prototype'];
5353
HexData: (subtype: number, hexstr: string) => BSONLib['Binary']['prototype'];
5454
UUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
55+
LegacyJavaUUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
56+
LegacyCSharpUUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
57+
LegacyPythonUUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
5558
MD5: (hexstr: string) => BSONLib['Binary']['prototype'];
5659
Decimal128: BSONLib['Decimal128'];
5760
BSONSymbol: BSONLib['BSONSymbol'];
@@ -340,6 +343,92 @@ export function constructShellBson<
340343
},
341344
{ prototype: bson.Binary.prototype }
342345
),
346+
347+
LegacyJavaUUID: assignAll(
348+
function LegacyJavaUUID(hexstr?: string): BinaryType {
349+
if (hexstr === undefined) {
350+
// Generate a new UUID and format it.
351+
hexstr = new bson.UUID().toHexString();
352+
}
353+
assertArgsDefinedType([hexstr], ['string'], 'LegacyJavaUUID');
354+
355+
let hex: string = String.prototype.replace.call(
356+
hexstr,
357+
/[{}-]/g,
358+
() => ''
359+
);
360+
let msb = String.prototype.substring.call(hex, 0, 16);
361+
let lsb = String.prototype.substring.call(hex, 16, 32);
362+
msb =
363+
String.prototype.substring.call(msb, 14, 16) +
364+
String.prototype.substring.call(msb, 12, 14) +
365+
String.prototype.substring.call(msb, 10, 12) +
366+
String.prototype.substring.call(msb, 8, 10) +
367+
String.prototype.substring.call(msb, 6, 8) +
368+
String.prototype.substring.call(msb, 4, 6) +
369+
String.prototype.substring.call(msb, 2, 4) +
370+
String.prototype.substring.call(msb, 0, 2);
371+
lsb =
372+
String.prototype.substring.call(lsb, 14, 16) +
373+
String.prototype.substring.call(lsb, 12, 14) +
374+
String.prototype.substring.call(lsb, 10, 12) +
375+
String.prototype.substring.call(lsb, 8, 10) +
376+
String.prototype.substring.call(lsb, 6, 8) +
377+
String.prototype.substring.call(lsb, 4, 6) +
378+
String.prototype.substring.call(lsb, 2, 4) +
379+
String.prototype.substring.call(lsb, 0, 2);
380+
hex = msb + lsb;
381+
382+
const hexBuffer = Buffer.from(hex, 'hex');
383+
return new bson.Binary(hexBuffer, bson.Binary.SUBTYPE_UUID_OLD);
384+
},
385+
{ prototype: bson.Binary.prototype }
386+
),
387+
LegacyCSharpUUID: assignAll(
388+
function LegacyCSharpUUID(hexstr?: string): BinaryType {
389+
if (hexstr === undefined) {
390+
// Generate a new UUID and format it.
391+
hexstr = new bson.UUID().toHexString();
392+
}
393+
assertArgsDefinedType([hexstr], ['string'], 'LegacyCSharpUUID');
394+
395+
let hex: string = String.prototype.replace.call(
396+
hexstr,
397+
/[{}-]/g,
398+
() => ''
399+
);
400+
const a =
401+
String.prototype.substring.call(hex, 6, 8) +
402+
String.prototype.substring.call(hex, 4, 6) +
403+
String.prototype.substring.call(hex, 2, 4) +
404+
String.prototype.substring.call(hex, 0, 2);
405+
const b =
406+
String.prototype.substring.call(hex, 10, 12) +
407+
String.prototype.substring.call(hex, 8, 10);
408+
const c =
409+
String.prototype.substring.call(hex, 14, 16) +
410+
String.prototype.substring.call(hex, 12, 14);
411+
const d = String.prototype.substring.call(hex, 16, 32);
412+
hex = a + b + c + d;
413+
414+
const hexBuffer = Buffer.from(hex, 'hex');
415+
return new bson.Binary(hexBuffer, bson.Binary.SUBTYPE_UUID_OLD);
416+
},
417+
{ prototype: bson.Binary.prototype }
418+
),
419+
LegacyPythonUUID: assignAll(
420+
function LegacyPythonUUID(hexstr?: string): BinaryType {
421+
if (hexstr === undefined) {
422+
hexstr = new bson.UUID().toString();
423+
}
424+
assertArgsDefinedType([hexstr], ['string'], 'LegacyPythonUUID');
425+
// Strip any dashes, as they occur in the standard UUID formatting
426+
// (e.g. 01234567-89ab-cdef-0123-456789abcdef).
427+
const buffer = Buffer.from(hexstr.replace(/-/g, ''), 'hex');
428+
return new bson.Binary(buffer, bson.Binary.SUBTYPE_UUID_OLD);
429+
},
430+
{ prototype: bson.Binary.prototype }
431+
),
343432
MD5: assignAll(
344433
function MD5(hexstr: string): BinaryType {
345434
assertArgsDefinedType([hexstr], ['string'], 'MD5');

0 commit comments

Comments
 (0)