diff --git a/src/clojure/monger/collection.clj b/src/clojure/monger/collection.clj index d17e11a..0496bd0 100644 --- a/src/clojure/monger/collection.clj +++ b/src/clojure/monger/collection.clj @@ -25,8 +25,7 @@ * 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 - Bytes] + DBCursor MapReduceCommand MapReduceCommand$OutputType] [java.util List Map] [clojure.lang IPersistentMap ISeq] org.bson.types.ObjectId) @@ -123,91 +122,6 @@ ;; ;; monger.collection/find ;; -;;;;TODO: add query-options -(comment - (import '[com.mongodb Mongo DB DBCollection WriteResult DBObject WriteConcern - DBCursor MapReduceCommand MapReduceCommand$OutputType - Bytes] - '[java.util List Map] - '[clojure.lang IPersistentMap ISeq] - 'org.bson.types.ObjectId) - (require '[monger.core :as mongo] - '[monger.result]) - - (require '[monger.conversion :refer [from-db-object to-db-object as-field-selector]]) - - (mongo/connect!) - (mongo/set-db! (mongo/get-db "veye_dev")) - (def collection :products) - (def coll (.getCollection monger.core/*mongodb-database* (name collection))) -) - -(defn ^DBCursor make-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-map {:awaitdata Bytes/QUERYOPTION_AWAITDATA - :exhaust Bytes/QUERYOPTION_EXHAUST - :notimeout Bytes/QUERYOPTION_NOTIMEOUT - :oplogreplay Bytes/QUERYOPTION_OPLOGREPLAY - :partial Bytes/QUERYOPTION_PARTIAL - :slaveok Bytes/QUERYOPTION_SLAVEOK - :tailable Bytes/QUERYOPTION_TAILABLE}) - -(defn get-cursor-options - "" - [db-cur] - (into {} - (for [[opt option-mask] cursor-options-map] - [opt (< 0 (bit-and (.getOptions db-cur) option-mask))]))) - -(defn ^DBCursor apply-cursor-options [db-cur options] - "Applies cursor options and return cursor." - (doseq [[opt value] (seq options)] - (if (= true value) - (.addOption db-cur (get cursor-options-map opt 0)) - (.setOptions db-cur (bit-and-not (.getOptions db-cur) (get cursor-options-map opt 0))))) - db-cur) - -(defmulti format-cursor (fn [db-cur as] as)) - -(defmethod format-cursor :map [db-cur as] - (map #(from-db-object %1 true) db-cur)) - -(defmethod format-cursor :seq [db-cur as] - (seq db-cur)) - -(defmethod format-cursor :default [db-cur as] - db-cur) - -(defn find-all - "Queries for objects from specified collection and accepts additional parameters as keywords. - Arguments: - collection - required, name of collections as string or keyword value - Keyword arguments: - :criteria - clojure map of matching criterias, for example {:name 'Monger', :language 'Clojure'} - :fields - specify fields, valid formats [:field1 :field2], {:_id -1, :name 1} - :options - clojure map of options for cursors, for example {:notimeout true :partial false} - allows notimeout and cancels shardings for query's cursor. - :as - specifies format of response, ala unified method to replace find, find-maps, find-seq. - Valid values are :map, :seq and other values are ignored. - - Examples: - (.count (find-all :products)) - (.count (find-all :products :criteria {:name 'Monger'} :fields {:_id -1})) - (find-all :product :options {:notimeout true} :as :map) - " - [collection & {:keys [criteria fields options as] - :or {criteria {}, fields {}, options nil, as nil}}] - (let [db-cur (make-db-cursor collection criteria fields)] - (-> db-cur - (apply-cursor-options options) - (format-cursor as)))) (defn ^DBCursor find "Queries for objects in this collection. diff --git a/src/clojure/monger/cursor.clj b/src/clojure/monger/cursor.clj new file mode 100644 index 0000000..35de6df --- /dev/null +++ b/src/clojure/monger/cursor.clj @@ -0,0 +1,93 @@ +(ns monger.cursor + "Helpers function for dbCursor object: creating new cursor, + CRUD functionality for cursor options + Related documentation guides: + * ... + " + (: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-collection." + ([^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] + "Applies cursor options with switch values, where true means switch on + and false removes specified options from current cursor. + example: (add-options db-cur {:notimeout true, :tailable false}) + returns cursor." + (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 to add current key" + (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." + (.addOption db-cur option) + db-cur) + +(defmethod add-options Keyword [^DBCursor db-cur, option] + (add-option! db-cur option) + db-cur) + +(defmethod add-options :default [^DBCursor db-cur, options] + (println "add-options dont support type for options: " (class options)) + 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/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)))))