3

I'm using Monger to store data in MongoDB. I would like to store a Clojure set. Reading and writing the set does work, but it is returned as a list. I suspect MongoDB doesn't actually support the set data type, so the Monger client doesn't either, but hopefully I'm wrong.

Is there some way to have Monger return the set I stored with the correct type?

Minimal Leiningen example is below (which is just the most basic Monger example):

> lein new mongo-test
> cd mongo-test

Edit project.clj to add Monger dependency:

(defproject mongo-test "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [com.novemberain/monger "2.0.0-rc1"]])

Start the REPL:

> lein repl

Insert and read back a set - note the found value is a list, not a set:

user=> (require '[monger.core :as mg])
nil
user=> (require '[monger.collection :as mc])
nil
user=> (def conn (mg/connect))
#'user/conn
user=> (def db (mg/get-db conn "monger-test"))
#'user/db
user=> (mc/remove db "things")
#<WriteResult { "serverUsed" : "127.0.0.1:27017" , "n" : 1 , "connectionId" : 248 , "err" :  null  , "ok" : 1.0}>
user=> (mc/insert db "things" {:set #{"A" 1}})
#<WriteResult { "serverUsed" : "127.0.0.1:27017" , "n" : 0 , "connectionId" : 248 , "err" :  null  , "ok" : 1.0}>
user=> (.next (mc/find db "things"))
{"_id" #<ObjectId 537ce43130045df5b9ff1102>, "set" [1 "A"]}
user=> (get (.next (mc/find db "things")) "set")
[1 "A"]
user=> (type (get (.next (mc/find db "things")) "set"))
com.mongodb.BasicDBList
user=> (set (get (.next (mc/find db "things")) "set"))
#{1 "A"}
user=> (type (set (get (.next (mc/find db "things")) "set")))
clojure.lang.PersistentHashSet

Obviously I can pass the result to set, as in the last two lines, but I don't want to have to call that for each specific key. Is there a way to have this happen transparently?

Martin Dow
  • 5,273
  • 4
  • 30
  • 43

1 Answers1

1

You are unfortunatly not wrong, MongoDB does not have a build in data type for sets. Your last example is a perfetly valid way of handling this, as would calling

(update-in results [..path.here.. "set"] set) 

in the cases where you really need to use sets.

Arthur Ulfeldt
  • 90,827
  • 27
  • 201
  • 284
  • Thanks - that's what I feared. Calling set is valid, but not convenient in our design. My store is abstracted behind a protocol and at the point I'm reading through Monger I don't know which fields should be wrapped as sets. – Martin Dow May 22 '14 at 07:54
  • I note that clj-time objects are deserialised into the correct type transparently (http://clojuremongodb.info/articles/getting_started.html#cljtime_joda_time). I'm surprised the same mechanism isn't used by Monger for sets. Will need to investigate further - probably with the conversion namespace: http://reference.clojuremongodb.info/monger.conversion.html – Martin Dow May 22 '14 at 07:56
  • Time "deserializes" into other implementations of time because it's just time. But to deserialize a collection into a set, you'd need to store that metadata somewhere. I think faking it at the driver level (if possible) is confusing. I'd handle it in your `db.clj`. Your `db.clj` functions can enforce set-like guarantees on insertion and they can convert the right fields to sets without the rest of your app caring about it. – danneu May 22 '14 at 13:44
  • @MartinDow MongoDB have [build-in DateTime support](http://docs.mongodb.org/manual/reference/bson-types/#date), so Monger is capable of deserializing it back to `clj-time` object. – Leonid Beschastny Aug 17 '14 at 00:12