I have a mongodb document with an array that contains both normal numbers (32-bit) and big ones (64-bits), and I query this document with the node javascript driver for mongodb.
When querying the document in a mongo shell, I can see 64-bit numbers printed as NumberLong. For example: NumberLong("1099511627776")
Basically I have two questions:
(1) Is there a clean way to check if a value is a 64-bit number? Today I use the following expression which is by elimination
if (typeof counters[slot] !== "number") {
// I assume counters[slot] is a 64-bit object
}
(2) How do I cast this value to an instance of mongo.Long
? Today I cast to mongo.Long in a convoluted way. Is there a cleaner way to perform the cast?
var mongoLong = require('mongodb').Long;
if (typeof counters[slot] !== "number") {
var bigNumber = mongoLong(counters[slot].low_, counters[slot].high_);
// do things with bigNumber
}
Thanks!
Note: One may wonder why I need to cast a Long to a Long. The reason is because the "Long" value which was returned by the MongoDB javascript driver query is not really an instance of the Long class. i.e. it wasn't created with a Long() constructor, i.e. you cannot invoke the member functions on it, like getHighBits(), or isNegative().
Okay. There are a couple of ways you can get numbers stored as different BSON types in MongoDB. For starters let's consider this information in the shell first:
db.numbers.insert({ "n": NumberLong(1) }) // 64bit
db.numbers.insert({ "n": NumberInt(1) }) // 32bit
db.numbers.insert({ "n": 1 }) // Actually a Double!
All have difference BSON types as shown, so the best way to query for each is using the $type
operator which tests for that field "type":
db.numbers.find({ "n": { "$type": 18 } }) // 64bit
db.numbers.find({ "n": { "$type": 16 } }) // 32bit
db.numbers.find({ "n": { "$type": 1 } }) // Double
If you tried to do this with JavaScript evaluation, then only the last two respond to your current test, and it's not the 64bit result since the "typeof" would just return object:
db.numbers.find(function() { return typeof(this.n) == "number" })
db.numbers.find(function() { return typeof(this.n) == "object" })
So only the last test matches the 64Bit integer since that is how this evaluates in JavaScript. But really the above $type
test is better since it can be used directly in the query and use native code. Native code is better than JavaScript evaluation either inside or outside the query, so it is best to use it. And it's faster.
If you have a whole bunch to convert then you are going to need to loop the collection, covert to Long and write back. And best way to construct is using Long.fromInt
, as well as of course handling the loop with Bulk Write Operations:
var async = require('async'),
MongoClient = require('mongodb').MongoClient,
Long = require('mongodb').Long;
MongoClient.connect('mongodb://localhost/test',function(err,db) {
db.collection('numbers',function(err,collection) {
var bulk = collection.initializeOrderedBulkOp();
var count = 0;
var stream = collection.find(
{
"$or": [
{ "n": { "$type": 16 } },
{ "n": { "$type": 1 } }
]
}
).stream();
stream.on('data',function(data) {
bulk.find({ "_id": data._id }).updateOne({
"$set": { "n": Long.fromInt( data.n ) }
});
if ( count % 1000 == 0 ) {
stream.pause();
bulk.execute(function(err,result) {
if (err) throw err;
bulk = collection.initializeOrderedBulkOp();
stream.resume();
});
}
});
stream.on('end',function() {
if ( count % 1000 != 0 )
bulk.execute(function(err,result) {
if (err) throw err;
bulk = collection.initializeOrderedBulkOp();
});
console.log("done");
process.exit();
});
});
});
That's just a basic premise working example, but it's the best way to go through your whole collection and covert the types as wanted.