diff --git a/snippets/mongocompat/mongotypes.js b/snippets/mongocompat/mongotypes.js index 13dfc3d..8a8ec34 100644 --- a/snippets/mongocompat/mongotypes.js +++ b/snippets/mongocompat/mongotypes.js @@ -1,5 +1,59 @@ // Date and time types if (typeof (Timestamp) != "undefined") { + const OriginalTimestamp = Timestamp; + + // Reference: https://github.com/mongodb/mongo/blob/c4d21d3346572e28df2f174df4d87e7618df4a77/src/mongo/scripting/mozjs/timestamp.cpp#L67-L78 + function validateTimestampComponent(component, name) { + const MAX_UINT32 = 4294967295; + + if (typeof component !== 'number') { + throw new TypeError(`${name} must be a number`); + } + + const val = Math.floor(component); + if (val < 0 || val > MAX_UINT32) { + throw new TypeError( + `${name} must be non-negative and not greater than ${MAX_UINT32}, got ${val}` + ); + } + + return val; + } + + Timestamp = function(t, i) { + if (arguments.length === 0) { + return new OriginalTimestamp({ t: 0, i: 0 }); + } + + if (arguments.length === 1) { + const proto = Object.getPrototypeOf(t); + if ((proto === null || proto === Object.prototype) && ('t' in t || 'i' in t)) { + const validatedT = validateTimestampComponent(t.t || 0, "Timestamp time (seconds)"); + const validatedI = validateTimestampComponent(t.i || 0, "Timestamp increment"); + return new OriginalTimestamp({ t: validatedT, i: validatedI }); + } + return new OriginalTimestamp(t); + } + + // Reference: https://github.com/mongodb/mongo/blob/c4d21d3346572e28df2f174df4d87e7618df4a77/src/mongo/scripting/mozjs/timestamp.cpp#L91-L98 + if (arguments.length === 2) { + const validatedT = validateTimestampComponent(t, "Timestamp time (seconds)"); + const validatedI = validateTimestampComponent(i, "Timestamp increment"); + return new OriginalTimestamp({ t: validatedT, i: validatedI }); + } + + throw new Error("Timestamp needs 0 or 2 arguments"); + }; + + Timestamp.prototype = OriginalTimestamp.prototype; + + for (const key of Object.getOwnPropertyNames(OriginalTimestamp)) { + // Skip prototype, length, name(function internals) + if (key !== 'prototype' && key !== 'length' && key !== 'name') { + Timestamp[key] = OriginalTimestamp[key]; + } + } + Timestamp.prototype.tojson = function() { return this.toStringIncomparable(); }; diff --git a/snippets/mongocompat/test.js b/snippets/mongocompat/test.js index f54223f..df4b8db 100644 --- a/snippets/mongocompat/test.js +++ b/snippets/mongocompat/test.js @@ -22,6 +22,51 @@ assert.strictEqual(minLong.bottom, 0); assert.strictEqual(minLong.exactValueString, "-9223372036854775808"); const nl2 = NumberLong("200"); assert.strictEqual(maxLong.compare(nl2), 1); + const decimal = NumberDecimal("1.1"); assert.strictEqual(decimal.toString(), 'NumberDecimal("1.1")'); assert.strictEqual(decimal.tojson(), 'NumberDecimal("1.1")'); + +const ts1 = Timestamp(); +assert.strictEqual(ts1.toString(), 'Timestamp(0, 0)'); +const ts2 = Timestamp(100, 200); +assert.strictEqual(ts2.toString(), 'Timestamp(100, 200)'); +const ts3 = Timestamp(1.9, 2.1); +assert.strictEqual(ts3.toString(), 'Timestamp(1, 2)'); +try { + Timestamp(-1, 0); + assert.fail('Should throw for negative time'); +} catch (e) { + assert(e.message.includes('must be non-negative')); +} +try { + Timestamp(0, 5000000000); + assert.fail('Should throw for i > uint32 max'); +} catch (e) { + assert(e.message.includes('not greater than 4294967295')); +} +const ts4 = Timestamp(123, 456); +assert(ts4 instanceof Timestamp); +assert.strictEqual(ts4.toString(), 'Timestamp(123, 456)'); +assert.strictEqual(ts4.tojson(), 'Timestamp(123, 456)'); +assert.strictEqual(ts4.getTime(), 123); +assert.strictEqual(ts4.getInc(), 456); +assert.strictEqual(ts4._bsontype, 'Timestamp'); +const tsFromBits = Timestamp.fromBits(100, 200); +assert(tsFromBits instanceof Timestamp); +assert.strictEqual(tsFromBits.i, 100); +assert.strictEqual(tsFromBits.t, 200); +assert.strictEqual(tsFromBits.toString(), 'Timestamp(200, 100)'); +const tsFromInt = Timestamp.fromInt(12345); +assert.strictEqual(tsFromInt._bsontype, 'Timestamp'); +assert.strictEqual(tsFromInt.i, 12345); +assert.strictEqual(tsFromInt.t, 0); +const tsFromNum = Timestamp.fromNumber(67890); +assert.strictEqual(tsFromNum._bsontype, 'Timestamp'); +assert.strictEqual(tsFromNum.i, 67890); +assert.strictEqual(tsFromNum.t, 0); +const tsFromStr = Timestamp.fromString('ff', 16); +assert.strictEqual(tsFromStr.i, 255); +assert.strictEqual(tsFromStr.t, 0); +assert.strictEqual(Timestamp.MAX_VALUE._bsontype, 'Long'); +assert.strictEqual(Timestamp.MAX_VALUE, Long.MAX_UNSIGNED_VALUE);