By default MongoDB uses 12-byte BSON id for objects. For some reason I wanted to use an increasing sequence of integers.
The samples in documentation are in JSON and I was not sure how they translate to the Java driver. The JSON sample looks like:
function counter(name) {
var ret = db.counters.findAndModify({query:{_id:name}, update:{$inc : {next:1}}, "new":true, upsert:true});
// ret == { "_id" : "users", "next" : 1 }
return ret.next;
}
db.users.insert({_id:counter("users"), name:"Sarah C."}) // _id : 1
db.users.insert({_id:counter("users"), name:"Bob D."}) // _id : 2
After some googling I found an implementation in Java. Just as I expected, it’s much longer and completely different.
public static String getNextId(DB db, String seq_name) {
String sequence_collection = "seq"; // the name of the sequence collection
String sequence_field = "seq"; // the name of the field which holds the sequence
DBCollection seq = db.getCollection(sequence_collection); // get the collection (this will create it if needed)
// this object represents your "query", its analogous to a WHERE clause in SQL
DBObject query = new BasicDBObject();
query.put("_id", seq_name); // where _id = the input sequence name
// this object represents the "update" or the SET blah=blah in SQL
DBObject change = new BasicDBObject(sequence_field, 1);
DBObject update = new BasicDBObject("$inc", change); // the $inc here is a mongodb command for increment
// Atomically updates the sequence field and returns the value for you
DBObject res = seq.findAndModify(query, new BasicDBObject(), new BasicDBObject(), false, update, true, true);
return res.get(sequence_field).toString();
}
Not much later I checked docs and source code for Congomongo and discovered fetch-and-modify. I rewrote the Java sample above to Clojure and later polished it using code from this commit by Krzysztof Magiera. In the end my sequence generator looks like this:
(defn next-seq [coll]
(with-mongo db
(:seq
(fetch-and-modify :sequences {:_id coll} {:$inc {:seq 1}} :return-new? true :upsert? true))))
(with-mongo db
(insert! :books {:author "Adam Mickiewicz" :title "Dziady" :_id (next-seq :books)}))
The raw call to insert! could be wrapped in a function or macro to save some boilerplate if there are more collections. For instance:
(defn insert-with-id [coll el]
(insert! coll (assoc el :_id (next-seq coll))))
(with-mongo db
(insert-with-id :books {:author "Adam Mickiewicz" :title "Dziady"}))
In some circles this probably is common knowledge, but it took me a while to figure it all out.