diff --git a/src/clojure/monger/collection.clj b/src/clojure/monger/collection.clj index 95a198b..0496bd0 100644 --- a/src/clojure/monger/collection.clj +++ b/src/clojure/monger/collection.clj @@ -24,7 +24,8 @@ * http://clojuremongodb.info/articles/deleting.html * http://clojuremongodb.info/articles/aggregation.html" (:refer-clojure :exclude [find remove count drop distinct empty?]) - (:import [com.mongodb Mongo DB DBCollection WriteResult DBObject WriteConcern DBCursor MapReduceCommand MapReduceCommand$OutputType] + (: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) @@ -34,7 +35,6 @@ monger.constraints)) - ;; ;; API ;; diff --git a/src/clojure/monger/cursor.clj b/src/clojure/monger/cursor.clj new file mode 100644 index 0000000..acded82 --- /dev/null +++ b/src/clojure/monger/cursor.clj @@ -0,0 +1,105 @@ +(ns monger.cursor + "Helper-functions for dbCursor object: + * to initialize new cursor, + * for CRUD functionality of options of dbCursor" + (:import [com.mongodb DBCursor Bytes] + [java.util List Map] + [java.lang Integer] + [clojure.lang Keyword]) + (:require [monger.conversion :refer [to-db-object from-db-object as-field-selector]])) + +(defn ^DBCursor make-db-cursor + "initializes new db-cursor." + ([^String collection] (make-db-cursor collection {} {})) + ([^String collection ^Map ref] (make-db-cursor collection ref {})) + ([^String collection ^Map ref fields] + (.find + (.getCollection monger.core/*mongodb-database* (name collection)) + (to-db-object ref) + (as-field-selector fields)))) + +(def cursor-options {:awaitdata Bytes/QUERYOPTION_AWAITDATA + ;;:exhaust Bytes/QUERYOPTION_EXHAUST - not human settable + :notimeout Bytes/QUERYOPTION_NOTIMEOUT + :oplogreplay Bytes/QUERYOPTION_OPLOGREPLAY + :partial Bytes/QUERYOPTION_PARTIAL + :slaveok Bytes/QUERYOPTION_SLAVEOK + :tailable Bytes/QUERYOPTION_TAILABLE}) + +(defn get-options + "Returns map of cursor's options with current state." + [^DBCursor db-cur] + (into {} + (for [[opt option-mask] cursor-options] + [opt (< 0 (bit-and (.getOptions db-cur) option-mask))]))) + +(defn add-option! [^DBCursor db-cur, ^String opt] + (.addOption db-cur (get cursor-options (keyword opt) 0))) + +(defn remove-option! [^DBCursor db-cur, ^String opt] + (.setOptions db-cur (bit-and-not (.getOptions db-cur) + (get cursor-options (keyword opt) 0)))) + +(defmulti add-options (fn [db-cur opts] (class opts))) +(defmethod add-options Map [^DBCursor db-cur options] + "Changes options by using map of settings, which key specifies name of settings + and boolean value specifies new state of the setting. + usage: + (add-options db-cur {:notimeout true, :tailable false}) + returns: + ^DBCursor object." + (doseq [[opt value] (seq options)] + (if (= true value) + (add-option! db-cur opt) + (remove-option! db-cur opt))) + db-cur) + +(defmethod add-options List [^DBCursor db-cur options] + "Takes list of options and activates these options + usage: + (add-options db-cur [:notimeout :tailable]) + returns: + ^DBCursor object" + (doseq [opt (seq options)] + (add-option! db-cur opt)) + db-cur) + +(defmethod add-options Integer [^DBCursor db-cur, option] + "Takes com.mongodb.Byte value and adds it to current settings. + usage: + (add-options db-cur com.mongodb.Bytes/QUERYOPTION_NOTIMEOUT) + returns: + ^DBCursor object" + (.addOption db-cur option) + db-cur) + +(defmethod add-options Keyword [^DBCursor db-cur, option] + "Takes just one keyword as name of settings and applies it to the db-cursor. + usage: + (add-options db-cur :notimeout) + returns: + ^DBCursor object" + (add-option! db-cur option) + db-cur) + +(defmethod add-options :default [^DBCursor db-cur, options] + "Using add-options with not supported type of options just passes unchanged cursor" + db-cur) + +(defn ^DBCursor reset-options + "Resets cursor options to default value and returns cursor" + [^DBCursor db-cur] + (.resetOptions db-cur) + db-cur) + +(defmulti format-as (fn [db-cur as] as)) + +(defmethod format-as :map [db-cur as] + (map #(from-db-object %1 true) db-cur)) + +(defmethod format-as :seq [db-cur as] + (seq db-cur)) + +(defmethod format-as :default [db-cur as] + db-cur) + diff --git a/src/clojure/monger/query.clj b/src/clojure/monger/query.clj index 99233a8..bae17bf 100644 --- a/src/clojure/monger/query.clj +++ b/src/clojure/monger/query.clj @@ -14,7 +14,8 @@ monger.query (:refer-clojure :exclude [select find sort]) (:require [monger.core] - [monger.internal pagination]) + [monger.internal pagination] + [monger.cursor :as cursor :refer [add-options]]) (:import [com.mongodb DB DBCollection DBObject DBCursor ReadPreference] [java.util List]) (:use [monger conversion operators])) @@ -75,7 +76,7 @@ (when read-preference (.setReadPreference cursor read-preference)) (when options - (.setOptions cursor options)) + (add-options cursor options)) (map (fn [x] (from-db-object x keywordize-fields)) cursor))) diff --git a/test/monger/test/cursor_test.clj b/test/monger/test/cursor_test.clj new file mode 100644 index 0000000..eb2cc2a --- /dev/null +++ b/test/monger/test/cursor_test.clj @@ -0,0 +1,110 @@ +(set! *warn-on-reflection* true) + +(ns monger.test.cursor-test + (:import [com.mongodb DBCursor DBObject Bytes] + [java.util List Map]) + (:require [monger.test.helper :as helper]) + (:use clojure.test + monger.cursor + monger.test.fixtures)) + +(helper/connect!) + +(deftest make-db-cursor-for-collection + (is (= DBCursor + (class (make-db-cursor :docs))))) + +(deftest getting-cursor-options-value + (let [db-cur (make-db-cursor :docs) + opts (get-options db-cur)] + (is (= true (isa? (class opts) Map))) + (is (= 0 (.getOptions db-cur))) ;;test default value + (is (= false (:notimeout opts))) + (is (= false (:partial opts))) + (is (= false (:awaitdata opts))) + (is (= false (:oplogreplay opts))) + (is (= false (:slaveok opts))) + (is (= false (:tailable opts))))) + +(deftest adding-option-to-cursor + (let [db-cur (make-db-cursor :docs) + _ (add-option! db-cur :notimeout)] + (is (= (:notimeout cursor-options) + (.getOptions db-cur))) + (add-option! db-cur :tailable) + (is (= (.getOptions db-cur) + (bit-or (:notimeout cursor-options) + (:tailable cursor-options)))))) + +(deftest remove-option-from-cursor + (let [db-cur (make-db-cursor :docs)] + (add-option! db-cur :partial) + (add-option! db-cur :awaitdata) + ;; removing not-set option should not affect result + (remove-option! db-cur :notimeout) + (is (= (.getOptions db-cur) + (bit-or (:partial cursor-options) + (:awaitdata cursor-options)))) + ;; removing active option should remove correct value + (remove-option! db-cur :awaitdata) + (is (= (.getOptions db-cur) + (:partial cursor-options))))) + + +(deftest test-reset-options + (let [db-cur (make-db-cursor :docs)] + (add-option! db-cur :partial) + (is (= (.getOptions db-cur) + (:partial cursor-options))) + (is (= 0 + (int (.getOptions (reset-options db-cur))))))) + +(deftest add-options-with-hashmap + (let [db-cur (make-db-cursor :docs) + _ (add-options db-cur {:notimeout true :slaveok true}) + opts (get-options db-cur)] + (is (= true (:notimeout opts))) + (is (= true (:slaveok opts))) + (is (= false (:tailable opts))) + (is (= false (:oplogreplay opts))))) + +(deftest add-options-with-hashmap-and-remove-option + (let [db-cur (make-db-cursor :docs) + _ (add-options db-cur {:notimeout true :slaveok true}) + opts (get-options db-cur)] + (is (= true (:notimeout opts))) + (is (= true (:slaveok opts))) + ;;remove key and add another option + (add-options db-cur {:partial true :slaveok false}) + (let [opts (get-options db-cur)] + (is (= true (:notimeout opts))) + (is (= true (:partial opts))) + (is (= false (:slaveok opts))) + (is (= false (:tailable opts)))))) + +(deftest add-options-with-list + (let [db-cur (make-db-cursor :docs) + _ (add-options db-cur [:notimeout :slaveok]) + opts (get-options db-cur)] + (is (= true (:notimeout opts))) + (is (= true (:slaveok opts))) + (is (= false (:tailable opts))) + (is (= false (:oplogreplay opts))))) + +(deftest add-options-with-Bytes + (let [db-cur (make-db-cursor :docs) + _ (add-options db-cur Bytes/QUERYOPTION_NOTIMEOUT) + opts (get-options db-cur)] + (is (= true (:notimeout opts))) + (is (= false (:slaveok opts))) + (is (= false (:tailable opts))) + (is (= false (:oplogreplay opts))))) + +(deftest add-options-with-one-keyword + (let [db-cur (make-db-cursor :docs) + _ (add-options db-cur :notimeout) + opts (get-options db-cur)] + (is (= true (:notimeout opts))) + (is (= false (:slaveok opts))) + (is (= false (:tailable opts))) + (is (= false (:oplogreplay opts)))))