Fun Mongo #1: ObjectId
How Primary Key works. Explanation
After working with relational SQL databases for a long time, switching to the document base MongoDB is not as easy as it seems.
You have to rebuild your mentality. It’s like with PHP or Python and Node.js programming languages. Switching to Node.js won’t be easy at first. You’ll have to get used to asynchronous code. A lot of code requires calback functions.
It’s the same with MongoDB. For example, you have to get used to the idea that there’s always a primary key and it’s different from an auto increment as a number like Int or Unsigned Int.
Mongodb has unusual IDs. This is not autoincrement as in other databases. Moreover, Mongodb has no autoincrement of int type at all, and you have to implement it yourself if you need it.
Let’s start with ObjectId. It seems to be a hash and how to do lookups:
SELECT * FROM t WHERE id > 1234
In fact, in MongoDB you can also build queries by _id (_id — autoincrement in Mongo)
db.t.find({ _id: { $gt: ObjectId('5e1b270142dcae0010186f02') } })
And you can also sort by this field.
Not everyone knows, but it’s not just a hash, it’s a function of time, and the hash can be cast to DateTime. And it’s done quite simply (example in JS):
function objectIdToDateTime(objectId) {
return new Date(parseInt(objectId.substring(0,8),16)*1e3)
}objectIdToDateTime('5e1b270142dcae0010186f02')
// Sun Jan 12 2020 17:02:41 GMT+0300
But you don’t need to write your own function, mongodb already has a built-in function
ObjectId("5e1b270142dcae0010186f02").getTimestamp()
// ISODate("2020-01-12T17:02:41.000+03:00")
If you want to generate an ObjectId, you have to do the reverse conversion:
const generateObjectId =>
Math.floor((new Date).getTime()/1e3)
.toString(16) + '0'.repeat(16)
Here we generate the first part of the hash, but the end of the hash consists of zeros. The original ObjectId in the second part contains some random hash. We can use the algorithm to generate the UID:
const generateObjectId = ()=>
Math.floor((new Date).getTime()/1e3).toString(16) +
('x'.repeat(16).replace(
/x/g,
_=>(Math.random()*16|0).toString(16)
))