Skip to content

Commit f14b7ca

Browse files
committed
feat(shell-bson): add legacy uuid helpers MONGOSH-2486
1 parent 30a42c0 commit f14b7ca

File tree

3 files changed

+309
-0
lines changed

3 files changed

+309
-0
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,45 @@ describe('BSON e2e', function () {
380380
);
381381
shell.assertNoErrors();
382382
});
383+
it('LegacyJavaUUID prints when created by user', async function () {
384+
const value = 'LegacyJavaUUID()';
385+
const output = await shell.executeLine(value);
386+
expect(output).to.match(
387+
/LegacyJavaUUID\('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'\)/
388+
);
389+
shell.assertNoErrors();
390+
});
391+
it('LegacyCSharpUUID prints when created by user', async function () {
392+
const value = 'LegacyCSharpUUID()';
393+
const output = await shell.executeLine(value);
394+
expect(output).to.match(
395+
/LegacyCSharpUUID\('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'\)/
396+
);
397+
shell.assertNoErrors();
398+
});
399+
it('LegacyPythonUUID prints when created by user', async function () {
400+
const value = 'LegacyPythonUUID()';
401+
const output = await shell.executeLine(value);
402+
expect(output).to.match(
403+
/LegacyPythonUUID\('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'\)/
404+
);
405+
shell.assertNoErrors();
406+
});
407+
it('BinData prints as LegacyJavaUUID when created by user as such', async function () {
408+
const value = "LegacyJavaUUID('01234567-89ab-cdef-0123-456789abcdef')";
409+
expect(await shell.executeLine(value)).to.include(value);
410+
shell.assertNoErrors();
411+
});
412+
it('BinData prints as LegacyCSharpUUID when created by user as such', async function () {
413+
const value = "LegacyCSharpUUID('01234567-89ab-cdef-0123-456789abcdef')";
414+
expect(await shell.executeLine(value)).to.include(value);
415+
shell.assertNoErrors();
416+
});
417+
it('BinData prints as LegacyPythonUUID when created by user as such', async function () {
418+
const value = "LegacyPythonUUID('01234567-89ab-cdef-0123-456789abcdef')";
419+
expect(await shell.executeLine(value)).to.include(value);
420+
shell.assertNoErrors();
421+
});
383422
});
384423
describe('MaxKey/MinKey special handling', function () {
385424
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
@@ -559,6 +559,187 @@ describe('Shell BSON', function () {
559559
expect.fail('Expecting error, nothing thrown');
560560
});
561561
});
562+
describe('LegacyJavaUUID', function () {
563+
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
564+
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
565+
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
566+
});
567+
568+
it('strips dashes from input', function () {
569+
expect(
570+
shellBson.LegacyJavaUUID('01234567-89ab-cdef-0123-456789abcdef').value()
571+
).to.deep.equal(
572+
shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef').value()
573+
);
574+
});
575+
576+
it('generates a random UUID when no arguments are passed', function () {
577+
const uuid = shellBson.LegacyJavaUUID();
578+
expect(uuid.sub_type).to.equal(3);
579+
expect(uuid.value().length).to.equal(16);
580+
});
581+
582+
it('has help and other metadata', function () {
583+
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
584+
expect(uuid?.help?.type).to.equal('Help');
585+
expect(uuid?.help?.().type).to.equal('Help');
586+
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
587+
});
588+
589+
it('performs byte-swapping for Java compatibility', function () {
590+
// Test that the byte order is swapped correctly for Java UUID format
591+
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
592+
// The MSB and LSB should be byte-swapped according to Java's UUID format
593+
expect(uuid.toString('hex')).to.equal('efcdab8967452301efcdab8967452301');
594+
});
595+
596+
it('errors for wrong type of arg 1', function () {
597+
try {
598+
(shellBson.LegacyJavaUUID as any)(1);
599+
} catch (e: any) {
600+
return expect(e.message).to.contain('string, got number');
601+
}
602+
expect.fail('Expecting error, nothing thrown');
603+
});
604+
});
605+
606+
describe('LegacyCSharpUUID', function () {
607+
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
608+
const uuid = shellBson.LegacyCSharpUUID(
609+
'0123456789abcdef0123456789abcdef'
610+
);
611+
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
612+
});
613+
614+
it('strips dashes from input', function () {
615+
expect(
616+
shellBson
617+
.LegacyCSharpUUID('01234567-89ab-cdef-0123-456789abcdef')
618+
.value()
619+
).to.deep.equal(
620+
shellBson.LegacyCSharpUUID('0123456789abcdef0123456789abcdef').value()
621+
);
622+
});
623+
624+
it('generates a random UUID when no arguments are passed', function () {
625+
const uuid = shellBson.LegacyCSharpUUID();
626+
expect(uuid.sub_type).to.equal(3);
627+
expect(uuid.value().length).to.equal(16);
628+
});
629+
630+
it('has help and other metadata', function () {
631+
const uuid = shellBson.LegacyCSharpUUID(
632+
'0123456789abcdef0123456789abcdef'
633+
);
634+
expect(uuid?.help?.type).to.equal('Help');
635+
expect(uuid?.help?.().type).to.equal('Help');
636+
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
637+
});
638+
639+
it('performs byte-swapping for C# compatibility', function () {
640+
// Test that the byte order is swapped correctly for C# UUID format
641+
const uuid = shellBson.LegacyCSharpUUID(
642+
'0123456789abcdef0123456789abcdef'
643+
);
644+
// The first three groups should be byte-swapped according to C#'s GUID format
645+
expect(uuid.toString('hex')).to.equal('67452301ab89efcd0123456789abcdef');
646+
});
647+
648+
it('errors for wrong type of arg 1', function () {
649+
try {
650+
(shellBson.LegacyCSharpUUID as any)(1);
651+
} catch (e: any) {
652+
return expect(e.message).to.contain('string, got number');
653+
}
654+
expect.fail('Expecting error, nothing thrown');
655+
});
656+
});
657+
658+
describe('LegacyPythonUUID', function () {
659+
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
660+
const uuid = shellBson.LegacyPythonUUID(
661+
'0123456789abcdef0123456789abcdef'
662+
);
663+
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
664+
});
665+
666+
it('strips dashes from input', function () {
667+
expect(
668+
shellBson
669+
.LegacyPythonUUID('01234567-89ab-cdef-0123-456789abcdef')
670+
.value()
671+
).to.deep.equal(
672+
shellBson.LegacyPythonUUID('0123456789abcdef0123456789abcdef').value()
673+
);
674+
});
675+
676+
it('generates a random UUID when no arguments are passed', function () {
677+
const uuid = shellBson.LegacyPythonUUID();
678+
expect(uuid.sub_type).to.equal(3);
679+
expect(uuid.value().length).to.equal(16);
680+
});
681+
682+
it('has help and other metadata', function () {
683+
const uuid = shellBson.LegacyPythonUUID(
684+
'0123456789abcdef0123456789abcdef'
685+
);
686+
expect(uuid?.help?.type).to.equal('Help');
687+
expect(uuid?.help?.().type).to.equal('Help');
688+
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
689+
});
690+
691+
it('uses standard byte order (no swapping)', function () {
692+
// Python UUID uses standard network byte order (big-endian, no swapping)
693+
const uuid = shellBson.LegacyPythonUUID(
694+
'0123456789abcdef0123456789abcdef'
695+
);
696+
expect(uuid.toString('hex')).to.equal('0123456789abcdef0123456789abcdef');
697+
});
698+
699+
it('errors for wrong type of arg 1', function () {
700+
try {
701+
(shellBson.LegacyPythonUUID as any)(1);
702+
} catch (e: any) {
703+
return expect(e.message).to.contain('string, got number');
704+
}
705+
expect.fail('Expecting error, nothing thrown');
706+
});
707+
});
708+
709+
describe('Legacy UUID comparison', function () {
710+
const testUuid = '0123456789abcdef0123456789abcdef';
711+
712+
it('each legacy UUID type produces different byte representations', function () {
713+
const javaUuid = shellBson.LegacyJavaUUID(testUuid);
714+
const csharpUuid = shellBson.LegacyCSharpUUID(testUuid);
715+
const pythonUuid = shellBson.LegacyPythonUUID(testUuid);
716+
717+
const javaBytes = javaUuid.toString('hex');
718+
const csharpBytes = csharpUuid.toString('hex');
719+
const pythonBytes = pythonUuid.toString('hex');
720+
721+
// They should all be different due to different byte-swapping
722+
expect(javaBytes).to.not.equal(csharpBytes);
723+
expect(javaBytes).to.not.equal(pythonBytes);
724+
expect(csharpBytes).to.not.equal(pythonBytes);
725+
726+
// Python should match the original (no swapping)
727+
expect(pythonBytes).to.equal(testUuid);
728+
});
729+
730+
it('all legacy UUID types use SUBTYPE_UUID_OLD', function () {
731+
const javaUuid = shellBson.LegacyJavaUUID(testUuid);
732+
const csharpUuid = shellBson.LegacyCSharpUUID(testUuid);
733+
const pythonUuid = shellBson.LegacyPythonUUID(testUuid);
734+
const uuid = shellBson.UUID(testUuid);
735+
736+
expect(javaUuid.sub_type).to.equal(3);
737+
expect(csharpUuid.sub_type).to.equal(3);
738+
expect(pythonUuid.sub_type).to.equal(3);
739+
expect(uuid.sub_type).to.equal(4);
740+
});
741+
});
742+
562743
describe('MD5', function () {
563744
let b: any;
564745
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'], 'UUID');
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'], 'UUID');
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'], 'UUID');
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)