Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 20.19.2
40 changes: 40 additions & 0 deletions packages/mongodb-constants/src/bson-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The meta here will make it into Compass' autocompleter:

Image

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',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously UUID didn't show up in Compass' autocomplete. I'm thinking it was just missing. This will make it start showing up:

Image

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 };
63 changes: 63 additions & 0 deletions packages/query-parser/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
78 changes: 78 additions & 0 deletions packages/shell-bson-parser/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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,
Expand Down
72 changes: 72 additions & 0 deletions packages/shell-bson-parser/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link
Member Author

@Anemy Anemy Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chatting with @nbbeeken I think we were thinking of throwing an error here instead. This is when someone writes new LegacyJavaUUID() without a string in the quotes. Currently UUID creates a new UUID so I had initially written it to also generate a new UUID (of subtype 3).
Any other opinions on if it we should throw an error or allow it? I'll add an error throw otherwise. I'll ask Betsy on it too.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, I think this is meaningful to have, since the "naïve" solution of replacing it with new UUID() won't necessarily generate valid legacy UUIDs

Copy link
Collaborator

@nbbeeken nbbeeken Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new UUID() won't necessarily generate valid legacy UUIDs

A little confused, yes new UUID will never make a legacy UUID. I'm suggesting that if the creation of new random UUIDs is desirable we have that mechanism in the UUID class but not in these helpers. Since this effort is intended to assist with viewing existing legacy uuids and querying them I think most of the time folks are going to have a UUID string on hand to construct one of these legacy forms.

LegacyJavaUUID(UUID());

I don't feel uber strongly about this, totally fine to keep the creation support directly in the ctor. Just sharing the perspective I had on greatly discouraging this use

}

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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't really care a lot, so feel free to just resolve this, but working with the binary buffers might be a bit clearer/easier


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);
},
Expand Down
Loading