Skip to content

Commit

Permalink
Added support for buffers to objectId
Browse files Browse the repository at this point in the history
  • Loading branch information
christkv committed May 17, 2016
1 parent 6504dda commit aa0b545
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 41 deletions.
97 changes: 74 additions & 23 deletions lib/bson/objectid.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ ObjectID.prototype.toHexString = function() {

var hexString = '';

if(this.id instanceof _Buffer) {
hexString = convertToHex(this.id);
if(ObjectID.cacheHexString) this.__id = hexString;
return hexString;
}

for (var i = 0; i < this.id.length; i++) {
hexString += hexTable[this.id.charCodeAt(i)];
}
Expand Down Expand Up @@ -113,7 +119,7 @@ ObjectID.prototype.getInc = function() {
*/
ObjectID.prototype.generate = function(time) {
if ('number' != typeof time) {
time = parseInt(Date.now()/1000,10);
time = ~~(Date.now()/1000);
}

var time4Bytes = BinaryParser.encodeInt(time, 32, true, true);
Expand Down Expand Up @@ -160,18 +166,22 @@ ObjectID.prototype.toJSON = function() {
* @param {object} otherID ObjectID instance to compare against.
* @return {boolean} the result of comparing two ObjectID's
*/
ObjectID.prototype.equals = function equals (otherID) {
ObjectID.prototype.equals = function equals (otherId) {
var id;

if(otherID != null && (otherID instanceof ObjectID || otherID.toHexString)) {
id = otherID.id;
} else if(typeof otherID == 'string' && ObjectID.isValid(otherID)) {
id = ObjectID.createFromHexString(otherID).id;
if(otherId instanceof ObjectID) {
return this.toString() == otherId.toString();
} else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 12 && this.id instanceof _Buffer) {
return otherId === this.id.toString('binary');
} else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 24) {
return otherId === this.toHexString();
} else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 12) {
return otherId === this.id;
} else if(otherId != null && (otherId instanceof ObjectID || otherId.toHexString)) {
return otherId.toHexString() === this.toHexString();
} else {
return false;
}

return this.id === id;
}

/**
Expand All @@ -189,7 +199,7 @@ ObjectID.prototype.getTimestamp = function() {
/**
* @ignore
*/
ObjectID.index = parseInt(Math.random() * 0xFFFFFF, 10);
ObjectID.index = ~~(Math.random() * 0xFFFFFF);

/**
* @ignore
Expand All @@ -211,35 +221,63 @@ ObjectID.createFromTime = function createFromTime (time) {
return new ObjectID(id);
};

// Lookup tables
var encodeLookup = '0123456789abcdef'.split('')
var decodeLookup = []
var i = 0
while (i < 10) decodeLookup[0x30 + i] = i++
while (i < 16) decodeLookup[0x61 - 10 + i] = i++

var _Buffer = Buffer;
var convertToHex = function(bytes) {
return bytes.toString('hex');
}

/**
* Creates an ObjectID from a hex string representation of an ObjectID.
*
* @method
* @param {string} hexString create a ObjectID from a passed in 24 byte hexstring.
* @return {ObjectID} return the created ObjectID
*/
ObjectID.createFromHexString = function createFromHexString (hexString) {
ObjectID.createFromHexString = function createFromHexString (string) {
// Throw an error if it's not a valid setup
if(typeof hexString === 'undefined' || hexString != null && hexString.length != 24)
if(typeof string === 'undefined' || string != null && string.length != 24)
throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters");

var len = hexString.length;
var length = string.length;

if(len > 12*2) {
if(length > 12*2) {
throw new Error('Id cannot be longer than 12 bytes');
}

var result = ''
, string
, number;
// Calculate lengths
var sizeof = length >> 1;
var array = new _Buffer(sizeof);
var n = 0;
var i = 0;

for (var index = 0; index < len; index += 2) {
string = hexString.substr(index, 2);
number = parseInt(string, 16);
result += BinaryParser.fromByte(number);
while (i < length) {
array[n++] = decodeLookup[string.charCodeAt(i++)] << 4 | decodeLookup[string.charCodeAt(i++)]
}

return new ObjectID(result, hexString);
// var result = array.toString('binary');
// console.log("!!!!!!!!!!!!!!!!!!!")
// console.log(array.toString('binary'))


// var result = ''
// , string
// , number;
//
// for (var index = 0; index < len; index += 2) {
// string = hexString.substr(index, 2);
// // number = 0 + parseInt(string, 16);
// number = 0 + string;
// result += BinaryParser.fromByte(number);
// }ObjectID

return new ObjectID(array);
};

/**
Expand All @@ -251,18 +289,27 @@ ObjectID.createFromHexString = function createFromHexString (hexString) {
ObjectID.isValid = function isValid(id) {
if(id == null) return false;

if(typeof id == 'number')
if(typeof id == 'number') {
return true;
}

if(typeof id == 'string') {
return id.length == 12 || (id.length == 24 && checkForHexRegExp.test(id));
}

if(id instanceof ObjectID) {
return true;
}

if(id instanceof _Buffer) {
return true;
}

// Duck-Typing detection of ObjectId like objects
if(id.toHexString) {
return id.id.length == 12 || (id.id.length == 24 && checkForHexRegExp.test(id.id));
}

return false;
};

Expand All @@ -272,7 +319,11 @@ ObjectID.isValid = function isValid(id) {
Object.defineProperty(ObjectID.prototype, "generationTime", {
enumerable: true
, get: function () {
return Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true));
if(this.id instanceof _Buffer) {
return this.id[0] | this.id[1] << 8 | this.id[2] << 16 | this.id[3] << 24;
} else {
return Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true));
}
}
, set: function (value) {
var value = BinaryParser.encodeInt(value, 32, true, true);
Expand Down
12 changes: 11 additions & 1 deletion lib/bson/parser/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ var writeIEEE754 = require('../float_parser').writeIEEE754,
DBRef = require('../db_ref').DBRef,
Binary = require('../binary').Binary;

try {
var _Buffer = Uint8Array;
} catch(e) {
var _Buffer = Buffer;
}

var regexp = /\x00/

// To ensure that 0.4 of node works correctly
Expand Down Expand Up @@ -239,7 +245,11 @@ var serializeObjectId = function(buffer, key, value, index) {
buffer[index++] = 0;

// Write the objectId into the shared buffer
buffer.write(value.id, index, 'binary')
if(typeof value.id == 'string') {
buffer.write(value.id, index, 'binary')
} else {
value.id.copy(buffer, index, 0, 12);
}

// Ajust index
return index + 12;
Expand Down
2 changes: 1 addition & 1 deletion test/node/bson_compliance_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ exports['Pass all valid BSON serialization scenarios ./compliance/valid.json'] =
// Attempt to deserialize
var object = bson.deserialize(buffer, {promoteLongs:false});
// // Validate the object
test.deepEqual(expectedDocument, object);
test.deepEqual(JSON.stringify(expectedDocument), JSON.stringify(object));
});

test.done();
Expand Down
8 changes: 6 additions & 2 deletions test/node/bson_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,7 @@ exports['Should deserialize correctly'] = function(test) {
assertBuffersEqual(test, serialized_data, serialized_data2, 0);
var doc2 = new BSON.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data);

test.deepEqual(doc, doc2)
test.deepEqual(JSON.stringify(doc), JSON.stringify(doc2))
test.done();
}

Expand All @@ -1296,7 +1296,10 @@ exports['Should correctly serialize and deserialize MinKey and MaxKey values'] =
assertBuffersEqual(test, serialized_data, serialized_data2, 0);
var doc2 = new BSON.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data);

test.deepEqual(doc, doc2)
// Peform equality checks
test.equal(JSON.stringify(doc), JSON.stringify(doc2));
test.ok(doc._id.equals(doc2._id))
// process.exit(0)
test.ok(doc2.minKey instanceof MinKey);
test.ok(doc2.maxKey instanceof MaxKey);
test.done();
Expand Down Expand Up @@ -1744,6 +1747,7 @@ exports['Should fail to create ObjectID due to illegal hex code'] = function(tes
return tmp.toHexString();
}
};

test.equal(true, tmp.equals(objectIdLike));
test.equal(true, tmp.equals(new ObjectId(objectIdLike)));
test.equal(true, ObjectID.isValid(objectIdLike));
Expand Down
14 changes: 0 additions & 14 deletions test/node/map_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,3 @@ exports['should serialize a map'] = function(test) {
var data = bson.serialize(m, false, true);
test.equal('13000000103100010000001030000200000000', data.toString('hex'));
}














3 comments on commit aa0b545

@vkarpov15
Copy link
Contributor

Choose a reason for hiding this comment

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

Not quite sure why yet, but it looks like this commit breaks lodash and underscore's deep equality check on node 0.10:

var bson = require('bson');
var assert = require('assert');
var _ = require('lodash');

var x1 = new bson.ObjectId();
var x2 = new bson.ObjectId(x1.toHexString());

assert.ok(_.isEqual(x1, x2));

Works fine without this commit on 0.10, but after this commit bugs out.

@christkv
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm not really sure why it breaks while assert.deepEquals works fine for node 0.10. I think there might be an issue in how they do their comparison.

@vkarpov15
Copy link
Contributor

@vkarpov15 vkarpov15 commented on aa0b545 Aug 31, 2016

Choose a reason for hiding this comment

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

Yeah it's the same old issue as #84, that damn undocumented enumerable offset property that buffers have in 0.10 breaks deep equality checks that don't treat buffers as special snowflakes. Since this commit switched out strings for buffers, objectids now have the same limitation. I'm not gonna worry about this too much since node 0.10 is EOL in a month, but for posterity's sake this works fine on node 0.10:

var bson = require('bson');
var assert = require('assert');
var _ = require('lodash');

var x1 = new bson.ObjectId();
var x2 = new bson.ObjectId(x1.toHexString());

console.log(x1);
console.log(x2);

assert.ok(_.isEqualWith(x1, x2, function(a, b) {
  if (Buffer.isBuffer(a) && Buffer.isBuffer(b)) {
    return a.toString('hex') === b.toString('hex');
  }
}));

Please sign in to comment.