monger/src/clojure/monger/collection.clj
2015-05-11 01:36:24 +03:00

517 lines
18 KiB
Clojure

;; Copyright (c) 2011-2014 Michael S. Klishin
;; Copyright (c) 2012 Toby Hede
;; Copyright (c) 2012 Baishampayan Ghose
;;
;; The use and distribution terms for this software are covered by the
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be bound by
;; the terms of this license.
;; You must not remove this notice, or any other, from this software.
(ns monger.collection
"Provides key functionality for interaction with MongoDB: inserting, querying, updating and deleting documents, performing Aggregation Framework
queries, creating and dropping indexes, creating collections and more.
For more advanced read queries, see monger.query.
Related documentation guides:
* http://clojuremongodb.info/articles/getting_started.html
* http://clojuremongodb.info/articles/inserting.html
* http://clojuremongodb.info/articles/querying.html
* http://clojuremongodb.info/articles/updating.html
* http://clojuremongodb.info/articles/deleting.html
* http://clojuremongodb.info/articles/aggregation.html"
(:refer-clojure :exclude [find remove count drop distinct empty? update])
(:import [com.mongodb Mongo DB DBCollection WriteResult DBObject WriteConcern
DBCursor MapReduceCommand MapReduceCommand$OutputType]
[java.util List Map]
[clojure.lang IPersistentMap ISeq]
org.bson.types.ObjectId)
(:require [monger.core :as mc]
[monger.result :as mres]
[monger.conversion :refer :all]
[monger.constraints :refer :all]))
;;
;; API
;;
;;
;; monger.collection/insert
;;
(defn ^WriteResult insert
"Saves document to collection and returns a write result monger.result/acknowledged?
and related functions operate on. You can optionally specify a WriteConcern.
In case you need the exact inserted document returned, with the :_id key generated,
use monger.collection/insert-and-return instead."
([^DB db ^String coll document]
(.insert (.getCollection db (name coll))
(to-db-object document)
^WriteConcern mc/*mongodb-write-concern*))
([^DB db ^String coll document ^WriteConcern concern]
(.insert (.getCollection db (name coll))
(to-db-object document)
concern)))
(defn ^clojure.lang.IPersistentMap insert-and-return
"Like monger.collection/insert but returns the inserted document as a persistent Clojure map.
If the :_id key wasn't set on the document, it will be generated and merged into the returned
map."
([^DB db ^String coll document]
(insert-and-return db coll document ^WriteConcern mc/*mongodb-write-concern*))
([^DB db ^String coll document ^WriteConcern concern]
;; MongoDB Java driver will generate the _id and set it but it
;; tries to mutate the inserted DBObject and it does not work
;; very well in our case, because that DBObject is short lived
;; and produced from the Clojure map we are passing in. Plus,
;; this approach is very awkward with immutable data structures
;; being the default. MK.
(let [doc (merge {:_id (ObjectId.)} document)]
(insert db coll doc concern)
doc)))
(defn ^WriteResult insert-batch
"Saves documents do collection. You can optionally specify WriteConcern as a third argument."
([^DB db ^String coll ^List documents]
(.insert (.getCollection db (name coll))
^List (to-db-object documents)
^WriteConcern mc/*mongodb-write-concern*))
([^DB db ^String coll ^List documents ^WriteConcern concern]
(.insert (.getCollection db (name coll))
^List (to-db-object documents)
concern)))
;;
;; monger.collection/find
;;
(defn ^DBCursor find
"Queries for objects in this collection.
This function returns DBCursor, which allows you to iterate over DBObjects.
If you want to manipulate clojure sequences maps, use find-maps."
([^DB db ^String coll]
(.find (.getCollection db (name coll))))
([^DB db ^String coll ^Map ref]
(.find (.getCollection db (name coll))
(to-db-object ref)))
([^DB db ^String coll ^Map ref fields]
(.find (.getCollection db (name coll))
(to-db-object ref)
(as-field-selector fields))))
(defn find-maps
"Queries for objects in this collection.
This function returns clojure Seq of Maps.
If you want to work directly with DBObject, use find."
([^DB db ^String coll]
(with-open [dbc (find db coll)]
(map (fn [x] (from-db-object x true)) dbc)))
([^DB db ^String coll ^Map ref]
(with-open [dbc (find db coll ref)]
(map (fn [x] (from-db-object x true)) dbc)))
([^DB db ^String coll ^Map ref fields]
(with-open [dbc (find db coll ref fields)]
(map (fn [x] (from-db-object x true)) dbc))))
(defn find-seq
"Queries for objects in this collection, returns ISeq of DBObjects."
([^DB db ^String coll]
(with-open [dbc (find db coll)]
(seq dbc)))
([^DB db ^String coll ^Map ref]
(with-open [dbc (find db coll ref)]
(seq dbc)))
([^DB db ^String coll ^Map ref fields]
(with-open [dbc (find db coll ref fields)]
(seq dbc))))
;;
;; monger.collection/find-one
;;
(defn ^DBObject find-one
"Returns a single DBObject from this collection matching the query."
([^DB db ^String coll ^Map ref]
(.findOne (.getCollection db (name coll))
(to-db-object ref)))
([^DB db ^String coll ^Map ref fields]
(.findOne (.getCollection db (name coll))
(to-db-object ref)
^DBObject (as-field-selector fields))))
(defn ^IPersistentMap find-one-as-map
"Returns a single object converted to Map from this collection matching the query."
([^DB db ^String coll ^Map ref]
(from-db-object ^DBObject (find-one db coll ref) true))
([^DB db ^String coll ^Map ref fields]
(from-db-object ^DBObject (find-one db coll ref fields) true))
([^DB db ^String coll ^Map ref fields keywordize]
(from-db-object ^DBObject (find-one db coll ref fields) keywordize)))
;;
;; monger.collection/find-and-modify
;;
(defn ^IPersistentMap find-and-modify
"Atomically modify a document (at most one) and return it."
([^DB db ^String coll ^Map conditions ^Map document {:keys [fields sort remove return-new upsert keywordize] :or
{fields nil
sort nil
remove false
return-new false
upsert false
keywordize true}}]
(let [coll (.getCollection db (name coll))
maybe-fields (when fields (as-field-selector fields))
maybe-sort (when sort (to-db-object sort))]
(from-db-object
^DBObject (.findAndModify ^DBCollection coll (to-db-object conditions) maybe-fields maybe-sort remove
(to-db-object document) return-new upsert) keywordize))))
;;
;; monger.collection/find-by-id
;;
(defn ^DBObject find-by-id
"Returns a single object with matching _id field."
([^DB db ^String coll id]
(check-not-nil! id "id must not be nil")
(find-one db coll {:_id id}))
([^DB db ^String coll id fields]
(check-not-nil! id "id must not be nil")
(find-one db coll {:_id id} fields)))
(defn ^IPersistentMap find-map-by-id
"Returns a single object, converted to map with matching _id field."
([^DB db ^String coll id]
(check-not-nil! id "id must not be nil")
(from-db-object ^DBObject (find-one-as-map db coll {:_id id}) true))
([^DB db ^String coll id fields]
(check-not-nil! id "id must not be nil")
(from-db-object ^DBObject (find-one-as-map db coll {:_id id} fields) true))
([^DB db ^String coll id fields keywordize]
(check-not-nil! id "id must not be nil")
(from-db-object ^DBObject (find-one-as-map db coll {:_id id} fields) keywordize)))
;;
;; monger.collection/count
;;
(defn count
"Returns the number of documents in this collection.
Takes optional conditions as an argument."
(^long [^DB db ^String coll]
(.count (.getCollection db (name coll))))
(^long [^DB db ^String coll ^Map conditions]
(.count (.getCollection db (name coll)) (to-db-object conditions))))
(defn any?
"Whether the collection has any items at all, or items matching query."
([^DB db ^String coll]
(> (count db coll) 0))
([^DB db ^String coll ^Map conditions]
(> (count db coll conditions) 0)))
(defn empty?
"Whether the collection is empty."
[^DB db ^String coll]
(= (count db coll {}) 0))
;; monger.collection/update
(defn ^WriteResult update
"Performs an update operation.
Please note that update is potentially destructive operation. It updates document with the given set
emptying the fields not mentioned in the new document. In order to only change certain fields, use
\"$set\".
You can use all the MongoDB modifier operations ($inc, $set, $unset, $push, $pushAll, $addToSet, $pop, $pull
$pullAll, $rename, $bit) here as well.
It also takes options, such as :upsert and :multi.
By default :upsert and :multi are false."
([^DB db ^String coll ^Map conditions ^Map document]
(update db coll conditions document {}))
([^DB db ^String coll ^Map conditions ^Map document {:keys [upsert multi write-concern]
:or {upsert false
multi false
write-concern mc/*mongodb-write-concern*}}]
(.update (.getCollection db (name coll))
(to-db-object conditions)
(to-db-object document)
upsert
multi
write-concern)))
(defn ^WriteResult upsert
"Performs an upsert.
This is a convenience function that delegates to monger.collection/update and
sets :upsert to true.
See monger.collection/update documentation"
([^DB db ^String coll ^Map conditions ^Map document]
(upsert db coll conditions document {}))
([^DB db ^String coll ^Map conditions ^Map document {:keys [multi write-concern]
:or {multi false
write-concern mc/*mongodb-write-concern*}}]
(update db coll conditions document {:multi multi :write-concern write-concern :upsert true})))
(defn ^WriteResult update-by-id
"Update a document with given id"
([^DB db ^String coll id ^Map document]
(update-by-id db coll id document {}))
([^DB db ^String coll id ^Map document {:keys [upsert write-concern]
:or {upsert false
write-concern mc/*mongodb-write-concern*}}]
(check-not-nil! id "id must not be nil")
(.update (.getCollection db (name coll))
(to-db-object {:_id id})
(to-db-object document)
upsert
false
write-concern)))
(defn ^WriteResult update-by-ids
"Update documents by given ids"
([^DB db ^String coll ids ^Map document]
(update-by-ids db coll ids document {}))
([^DB db ^String coll ids ^Map document {:keys [upsert write-concern]
:or {upsert false
write-concern mc/*mongodb-write-concern*}}]
(check-not-nil! (seq ids) "ids must not be nil or empty")
(.update (.getCollection db (name coll))
(to-db-object {:_id {"$in" ids}})
(to-db-object document)
upsert
true
write-concern)))
;; monger.collection/save
(defn ^WriteResult save
"Saves an object to the given collection (does insert or update based on the object _id).
If the object is not present in the database, insert operation will be performed.
If the object is already in the database, it will be updated.
This function returns write result. If you want to get the exact persisted document back,
use `save-and-return`."
([^DB db ^String coll ^Map document]
(.save (.getCollection db (name coll))
(to-db-object document)
mc/*mongodb-write-concern*))
([^DB db ^String coll ^Map document ^WriteConcern write-concern]
(.save (.getCollection db (name coll))
(to-db-object document)
write-concern)))
(defn ^clojure.lang.IPersistentMap save-and-return
"Saves an object to the given collection (does insert or update based on the object _id).
If the object is not present in the database, insert operation will be performed.
If the object is already in the database, it will be updated.
This function returns the exact persisted document back, including the `:_id` key in
case of an insert.
If you want to get write result back, use `save`."
([^DB db ^String coll ^Map document]
(save-and-return db coll document ^WriteConcern mc/*mongodb-write-concern*))
([^DB db ^String coll ^Map document ^WriteConcern write-concern]
;; see the comment in insert-and-return. Here we additionally need to make sure to not scrap the :_id key if
;; it is already present. MK.
(let [doc (merge {:_id (ObjectId.)} document)]
(save db coll doc write-concern)
doc)))
;; monger.collection/remove
(defn ^WriteResult remove
"Removes objects from the database."
([^DB db ^String coll]
(.remove (.getCollection db (name coll)) (to-db-object {})))
([^DB db ^String coll ^Map conditions]
(.remove (.getCollection db (name coll)) (to-db-object conditions))))
(defn ^WriteResult remove-by-id
"Removes a single document with given id"
[^DB db ^String coll id]
(check-not-nil! id "id must not be nil")
(let [coll (.getCollection db (name coll))]
(.remove coll (to-db-object {:_id id}))))
(defn purge-many
"Purges (removes all documents from) multiple collections. Intended
to be used in test environments."
[^DB db xs]
(doseq [coll xs]
(remove db coll)))
;;
;; monger.collection/create-index
;;
(defn create-index
"Forces creation of index on a set of fields, if one does not already exists."
([^DB db ^String coll ^Map keys]
(.createIndex (.getCollection db (name coll)) (as-field-selector keys)))
([^DB db ^String coll ^Map keys ^Map options]
(.createIndex (.getCollection db (name coll))
(as-field-selector keys)
(to-db-object options))))
;;
;; monger.collection/ensure-index
;;
(defn ensure-index
"Creates an index on a set of fields, if one does not already exist.
This operation is inexpensive in the case when an index already exists.
Options are:
:unique (boolean) to create a unique index
:name (string) to specify a custom index name and not rely on the generated one"
([^DB db ^String coll ^Map keys]
(.createIndex (.getCollection db (name coll)) (as-field-selector keys)))
([^DB db ^String coll ^Map keys ^Map options]
(.createIndex (.getCollection db (name coll))
(as-field-selector keys)
(to-db-object options)))
([^DB db ^String coll ^Map keys ^String name unique?]
(.createIndex (.getCollection db (name coll))
(as-field-selector keys)
name
unique?)))
;;
;; monger.collection/indexes-on
;;
(defn indexes-on
"Return a list of the indexes for this collection."
[^DB db ^String coll]
(from-db-object (.getIndexInfo (.getCollection db (name coll))) true))
;;
;; monger.collection/drop-index
;;
(defn drop-index
"Drops an index from this collection."
[^DB db ^String coll idx]
(.dropIndex (.getCollection db (name coll)) (if (string? idx)
idx
(to-db-object idx))))
(defn drop-indexes
"Drops all indixes from this collection."
[^DB db ^String coll]
(.dropIndexes (.getCollection db (name coll))))
;;
;; monger.collection/exists?, /create, /drop, /rename
;;
(defn exists?
"Checks weather collection with certain name exists."
([^DB db ^String coll]
(.collectionExists db coll)))
(defn create
"Creates a collection with a given name and options.
Options are:
:capped (pass true to create a capped collection)
:max (number of documents)
:size (max allowed size of the collection, in bytes)"
[^DB db ^String coll ^Map options]
(.createCollection db coll (to-db-object options)))
(defn drop
"Deletes collection from database."
[^DB db ^String coll]
(.drop (.getCollection db (name coll))))
(defn rename
"Renames collection."
([^DB db ^String from, ^String to]
(.rename (.getCollection db from) to))
([^DB db ^String from ^String to drop-target?]
(.rename (.getCollection db from) to drop-target?)))
;;
;; Map/Reduce
;;
(defn map-reduce
"Performs a map reduce operation"
([^DB db ^String coll ^String js-mapper ^String js-reducer ^String output ^Map query]
(let [coll (.getCollection db (name coll))]
(.mapReduce coll js-mapper js-reducer output (to-db-object query))))
([^DB db ^String coll ^String js-mapper ^String js-reducer ^String output ^MapReduceCommand$OutputType output-type ^Map query]
(let [coll (.getCollection db (name coll))]
(.mapReduce coll js-mapper js-reducer output output-type (to-db-object query)))))
;;
;; monger.collection/distinct
;;
(defn distinct
"Finds distinct values for a key"
([^DB db ^String coll ^String key]
(.distinct (.getCollection db (name coll)) ^String (to-db-object key)))
([^DB db ^String coll ^String key ^Map query]
(.distinct (.getCollection db (name coll)) ^String (to-db-object key) (to-db-object query))))
;;
;; Aggregation
;;
(defn aggregate
"Executes an aggregation query. MongoDB 2.2+ only.
See http://docs.mongodb.org/manual/applications/aggregation/ to learn more."
[^DB db ^String coll stages]
(let [res (mc/command db {:aggregate coll :pipeline stages})]
;; this is what DBCollection#distinct does. Turning a blind's eye!
(.throwOnError res)
(map #(from-db-object % true) (.get res "result"))))
;;
;; Misc
;;
(def ^{:const true}
system-collection-pattern #"^(system|fs)")
(defn system-collection?
"Evaluates to true if the given collection name refers to a system collection. System collections
are prefixed with system. or fs. (default GridFS collection prefix)"
[^String coll]
(re-find system-collection-pattern coll))