A MongoDB Library for Clojure.
Mongoika simplify building queries behaved like lazy sequences, and supports basic operations, MapReduce and GridFS using Mongo Java Driver.
;; Use mongoika namespace.
(use 'mongoika)
;; Connect to a MongoDB server.
(with-mongo [connection {:host your-mongodb-host :port your-mongodb-port}]
;; Bind a database dynamically.
(with-db-binding (database connection :your-database)
;; Index:
(ensure-index! :fruits {:name :asc} :unique true)
(ensure-index! :fruits [:price :asc :name :asc])
;; Insertion:
(insert! :fruits {:name "Banana" :color :yellow :price 100})
(insert! :fruits {:name "Apple" :color :red :price 80})
(insert! :fruits {:name "Lemon" :color :yellow :price 50})
;; Fetch all:
(query :fruits)
; => [{:_id #<ObjectId> :name "Banana" :color "yellow" :price 100}
; {:_id #<ObjectId> :name "Apple" :color "red" :price 80}
; {:_id #<ObjectId> :name "Lemon" :color "yellow" :price 50}]
;; Find:
(restrict :color :yellow :fruits)
; => [banana lemon]
(restrict :price {< 100} :fruits)
; => [apple lemon]
;; Sort:
(order :price :asc :fruits)
; => [lemon apple banana]
;; Find and sort:
(order :price :asc (restrict :color :yellow :fruits))
; => [lemon banana]
;; Fetch first:
(fetch-one (order :price :desc (restrict :color :yellow :fruits)))
; => banana
;; MapReduce:
(map-reduce! :map "function() {
emit(this.color, this.price);
}"
:reduce "function(key, vals) {
var sum = 0;
for (var i = 0; i < vals.length; ++i) sum += vals[i];
return sum;
}"
:out :sum-of-prices
:fruits)
(order :color :asc :sum-of-prices)
; => [{:_id "red" :value 80.0}
; {:_id "yellow" :value 150.0}]
))
mongo
returns a Mongo instance of Mongo Java Driver. It has a connection pool.
(mongo {:host "127.0.0.1" :port 27017})
; Connect with options
(mongo {:host "127.0.0.1" :port 27017} {:safe true :socketTimeout 5})
with-mongo
binds a Mongo instance to the specified symbol. The mongo instance is closed automatically.
(with-mongo [connection {:host "127.0.0.1" :port 27017} {:safe true}]
...)
database
returns a DB instance.
(database connection :your-database)
with-db-binding
binds a specified database to the dynamic var *db*
, and most functions in Mongoika use it.
(with-db-binding (database connection :your-database)
...)
You can use set-default-db!
to set a database to *db*
.
(set-default-db! (database connection :your-database)
bound-db
returns the current bound database.
(bound-db)
insert!
inserts a document to the specified collection, and returns the inserted document as a map that has an _id
field. Each key of a map returned from insert!
is converted to a keyword.
(insert! :foods {:name "Cheese" :quantity 120 :price 300})
; => db.foods.insert({name: "Cheese", quantity: 120, price 300})
insert-multi!
inserts multiple documents, and returns inserted documents.
(insert-multi! :foods
{:name "Cookie" :quantity 70 :price 120}
{:name "Banana" :quantity 40 :price 100}
{:name "Chunky Bacon" :quantity 600 :price 800})
query
makes a query behaved like a lazy sequence that contains all documents in the specified collection.
(query :foods)
; => db.foods.find()
The following code prints names of all foods.
(doseq [food (query :foods)]
(println (:name food)))
(restrict :name "Cheese" :foods)
; => db.foods.find({name: "Cheese"})
(restrict :quantity {:$gt 100} :price {:$lt 300} :foods)
; => db.foods.find({quantity: {$gt: 100}, price: {$lt: 300}})
You can use following functions as operators in conditions.
> => < <= mod type not
(restrict :quantity {> 100} :price {< 300} :foods)
; => db.foods.find({quantity: {$gt: 100}, price: {$lt: 300}})
(project :name :price :foods)
; => db.foods.find({}, {name: 1, price: 1})
(project :name :price (restrict :price {> 100} :foods))
; => db.foods.find({price: {$gt: 100}}, {name: 1, price: 1})
(project {:price false :quantity false} :foods)
; => db.foods.find({}, {price: 0, quantity: 0})
(order :price 1 :foods)
; => db.foods.find().sort({price: 1})
You can use :asc and :desc instead of 1 and -1.
(order :price :desc :name :asc (restrict :quantity {< 100} :foods))
; => db.foods.find({quantity: {$lt: 100}}).sort({price: -1, name: 1})
reverse-order
reverses the order of the specified query.
(reverse-order (order :price :asc :name :desc :foods))
; => db.foods.find().sort({price: -1, name: 1})
(limit 3 :foods)
; => db.foods.find().limit(3)
(limit 2 (order :price :asc (restrict :price {> 50} :foods)))
; => db.foods.find({price: {$gt: 50}}).sort({price: 1}).limit(2)
(skip 2 :foods)
; => db.foods.find().skip(2)
(skip 3 (order :price :asc :foods))
; => db.foods.find().sort({price: 1}).skip(3)
postapply
applies the specified function to documents returned from the specified query, that is, postapply
behaves like apply
, but postapply
returns a new query.
(postapply #(partition-all 2 %) :foods)
(postapply #(filter (fn [document] (odd? (:number document))) (restrict :type 1 :users)))
A function passed to postapply
must return a sequence.
You can pass a query returned from postapply
to other functions that receive a query.
map-after
applies the specified function to each document returned from the specified query, that is, map-after
behaves like map
, but map-after
returns a new query instead of a sequence of objects.
(map-after #(assoc :discounted-price (* (:price %) 0.8))
(restrict :price {> 100} :foods))
You can pass a query returned from `map-after` to other functions that receive a query.
```clojure
(restrict :price {> 100}
(map-after #(assoc :discounted-price (* (:price %) 0.8)) :foods))
(count (restrict :price {> 100} :oods))
MongoDB does not return any documents when count
is called.
query-options
sets query options to the specified query.
(query-option com.mongodb.Bytes/QUERYOPTION_NOTIMEOUT :foods)
You can pass keywords instead of numbers.
(query-option :notimeout :foods)
update!
updates just one document that are returned from the specified query.
(update! :$set {:quantity 80} (restrict :name "Banana" :foods))
; => db.foods.update({name: "Banana"}, {$set: {quantity: 80}}, false, false)
(update! :$set {:quantity 80} :$inc {:price 10} (restrict :name "Banana" :foods))
; => db.foods.update({name: "Banana"}, {$set: {quantity: 80}, $inc: {:price 10}}, false, false)
upsert!
does an "upsert" operation.
(upsert! :$set {:price 100 :quantity 80} (restrict :name "Cheese" :foods))
; => db.foods.update({name: "Cheese"}, {$set: {price: 100, autntity: 80}}, true, false)
update-multi!
updates all documents that are returned from the specified query.
(update-multi! :$inc {:price 10} :foods)
; => db.foods.update({}, {$inc: {price: 10}}, false, true)
upsert-multi!
does an "upsert" operation with true as "multi" parameter.
(upsert-multi! :$inc {:price 10} :foods)
; => db.foods.update({}, {$inc: {price: 10}}, true, true)
delete!
removes all documents that are returned from the specified query.
(delete! (restrict :price {< 100} :foods))
; => db.foods.remove({price: {$lt: 100}})
delete-one!
removes just one document and returns it.
(delete-one! (restrict :price {< 100} :foods))
; => db.foods.findAndModify({query: {price: {$lt: 100}}, remove: true})
ensure-index!
creates an index.
(ensure-index! :foods {:category :asc})
(ensure-index! :foods {:name :asc} :unique true)
(ensure-index! :foods [:price :asc :name :asc])
(ensure-index! :users {:rank :desc} :name :user-rank-desc)
map-reduce!
invoke mapReduce command with query and following options:
- map: map function as a JavaScript code
- reduce: reduce function as a JavaScript code
- finalize: finalize function as a JavaScript code
- out: name of collection to output to
- out-type: replace/merge/reduce
- scope: variables to use in map/reduce/finalize functions
- verbose
The query can contain restriction, limit and sorting.
(map-reduce! :map "function() {
emit(this.color, this.price);
}"
:reduce "function(key, vals) {
var sum = 0;
for (var i = 0; i < vals.length; ++i) sum += vals[i];
return sum;
}"
:out :sum-of-prices
:fruits)
(map-reduce! :map "function() {
emit(this['item-id'], 1);
}"
:reduce "function(key, vals) {
var count = 0;
for (var i = 0; i < vals.length; ++i) count += vals[i];
return count;
}"
:out :sell-count
:out-type :reduce
(restrict :date {>= first-of-month} :date {< first-of-next-month} :fruits))
grid-fs
returns a GridFS instance. You can pass it to functions as a query.
(grid-fs :images)
(query (grid-fs :images))
(restrict :_id image-id (grid-fs :images))
(count (restrict :metadata.width {> 200} (grid-fs :images)))
A map returned from a GridFS query has a :data
field, and it's value is an InputStream of bytes.
You can use insert!
, insert-multi!
and delete!
for GridFS, but update!
, upsert!
and update-multi!
does not support GridFS.
(insert! {:data byte-array-or-iterator
:filename "image1.png"
:contentType "image/png"}
(grid-fs :images))
(delete! (restrict :_id image-id (grid-fs :images)))
collection-names
returns names of collections in the current bound database.
(collection-names)
collection-exists?
returns if the specified collection exists.
(collection-exists? :items)
collection-stats
returns stats of the specified collection.
(collection-stats :items)
Add
[mongoika "0.8.7"]
to your project.clj.
Mongoika is named from a cuttlefish called "Mongou Ika" (紋甲イカ) in Japanese.