Initial version of the monger.query DSL

Here is what it looks like:

(with-collection "docs"
  (find { :inception_year { $lt 2000 $gte 2011 } })
  (fields { :inception_year 1 :name 1 })
  (skip 10)
  (limit 20)
  (batch-size 50)
  (hint "my-index-name")
  (snapshot))
This commit is contained in:
Michael S. Klishin 2011-11-14 15:15:43 +04:00
parent c40b0e25c1
commit bd133c1afc
2 changed files with 172 additions and 72 deletions

89
src/monger/query.clj Normal file
View file

@ -0,0 +1,89 @@
(ns monger.query
(:refer-clojure :exclude [select find sort])
(:require monger.core)
(:import [com.mongodb DB DBCollection DBObject DBCursor]
[java.util List])
(:use [monger conversion operators]))
;;
;; Implementation
;;
(def ^{ :dynamic true } *query-collection*)
(defn empty-query
[^DBCollection coll]
{
:collection coll
:query {}
:sort {}
:fields []
:skip 0
:limit 0
:batch-size 256
:hint nil
:snapshot false
})
(defn- fields-to-db-object
[^List fields]
(to-db-object (zipmap fields (repeat 1))))
(defn exec
[{ :keys [collection query fields skip limit sort batch-size hint snapshot] :or { limit 0 batch-size 256 skip 0 } }]
(let [cursor (doto ^DBCursor (.find ^DBCollection collection (to-db-object query) (fields-to-db-object fields))
(.limit limit)
(.skip skip)
(.sort (to-db-object sort))
(.batchSize batch-size)
(.hint ^DBObject (to-db-object hint))
)]
(if snapshot
(.snapshot cursor))
(map (fn [x] (from-db-object x true))
(seq cursor))))
;;
;; API
;;
(defn find
[m query]
(merge m { :query query }))
(defn fields
[m flds]
(merge m { :fields flds }))
(defn sort
[m srt]
(merge m { :sort srt }))
(defn skip
[m ^long n]
(merge m { :skip n }))
(defn limit
[m ^long n]
(merge m { :limit n }))
(defn batch-size
[m ^long n]
(merge m { :batch-size n }))
(defn hint
[m h]
(merge m { :hint h }))
(defn snapshot
[m]
(merge m { :snapshot true }))
(defmacro with-collection
[^String coll & body]
`(binding [*query-collection* (if (string? ~coll)
(.getCollection ^DB monger.core/*mongodb-database* ~coll)
~coll)]
(let [query# (-> (empty-query *query-collection*) ~@body)]
(exec query#))))

View file

@ -1,7 +1,7 @@
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
(ns monger.test.querying (ns monger.test.querying
(:refer-clojure :exclude [select find]) (:refer-clojure :exclude [select find sort])
(:import [com.mongodb WriteResult WriteConcern DBCursor DBObject CommandResult$CommandFailure] (:import [com.mongodb WriteResult WriteConcern DBCursor DBObject CommandResult$CommandFailure]
[org.bson.types ObjectId] [org.bson.types ObjectId]
[java.util Date]) [java.util Date])
@ -10,8 +10,7 @@
[monger.result :as mgres]) [monger.result :as mgres])
(:use [clojure.test] (:use [clojure.test]
[monger.test.fixtures] [monger.test.fixtures]
;; [monger.query] [monger conversion query operators]))
[monger.conversion]))
(defn purge-locations-collection (defn purge-locations-collection
@ -78,85 +77,97 @@
;; < ($lt), <= ($lte), > ($gt), >= ($gte) ;; < ($lt), <= ($lte), > ($gt), >= ($gte)
;; (deftest query-using-dsl-and-$lt-operator (deftest query-using-dsl-and-$lt-operator-1
;; (let [coll "docs" (let [coll "docs"
;; doc1 { :language "Clojure" :_id (ObjectId.) :inception_year 2006 } doc1 { :language "Clojure" :_id (ObjectId.) :inception_year 2006 }
;; doc2 { :language "Java" :_id (ObjectId.) :inception_year 1992 } doc2 { :language "Java" :_id (ObjectId.) :inception_year 1992 }
;; doc3 { :language "Scala" :_id (ObjectId.) :inception_year 2003 } doc3 { :language "Scala" :_id (ObjectId.) :inception_year 2003 }
;; _ (mgcol/insert-batch coll [doc1 doc2]) _ (mgcol/insert-batch coll [doc1 doc2])
;; lt-result (in-collection "docs" lt-result (with-collection "docs"
;; (find { :inception_year { "$lt" 2000 } })] (find { :inception_year { $lt 2000 } })
;; (is (= [doc2] lt-result)))) (limit 2))]
(is (= [doc2] lt-result))))
(deftest query-using-dsl-and-$lt-operator-2
(let [coll "docs"
doc1 { :language "Clojure" :_id (ObjectId.) :inception_year 2006 }
doc2 { :language "Java" :_id (ObjectId.) :inception_year 1992 }
doc3 { :language "Scala" :_id (ObjectId.) :inception_year 2003 }
_ (mgcol/insert-batch coll [doc1 doc2])
lt-result (with-collection "docs"
(find { :inception_year { $lt 2000 } }))]
(is (= [doc2] lt-result))))
(deftest query-with-find-maps-using-$lt-operator
(let [coll "docs" (deftest query-with-find-maps-using-$lt-operator
doc1 { :language "Clojure" :_id (ObjectId.) :inception_year 2006 } (let [coll "docs"
doc2 { :language "Java" :_id (ObjectId.) :inception_year 1992 } doc1 { :language "Clojure" :_id (ObjectId.) :inception_year 2006 }
doc3 { :language "Scala" :_id (ObjectId.) :inception_year 2003 } doc2 { :language "Java" :_id (ObjectId.) :inception_year 1992 }
_ (mgcol/insert-batch coll [doc1 doc2]) doc3 { :language "Scala" :_id (ObjectId.) :inception_year 2003 }
lt-result (mgcol/find-maps coll { :inception_year { "$lt" 2000 } }) _ (mgcol/insert-batch coll [doc1 doc2])
lte-result (mgcol/find-maps coll { :inception_year { "$lte" 1992 } }) lt-result (mgcol/find-maps coll { :inception_year { "$lt" 2000 } })
gt-result (mgcol/find-maps coll { :inception_year { "$gt" 2005 } }) lte-result (mgcol/find-maps coll { :inception_year { "$lte" 1992 } })
gte-result (mgcol/find-maps coll { :inception_year { "$gte" 2006 } })] gt-result (mgcol/find-maps coll { :inception_year { "$gt" 2005 } })
(is (= [doc2] lt-result)) gte-result (mgcol/find-maps coll { :inception_year { "$gte" 2006 } })]
(is (= [doc2] lte-result)) (is (= [doc2] lt-result))
(is (= [doc1] gt-result)) (is (= [doc2] lte-result))
(is (= [doc1] gte-result)))) (is (= [doc1] gt-result))
(is (= [doc1] gte-result))))
;; $all ;; $all
(deftest query-with-find-maps-using-$all (deftest query-with-find-maps-using-$all
(let [coll "docs" (let [coll "docs"
doc1 { :_id (ObjectId.) :title "Clojure" :tags ["functional" "homoiconic" "syntax-oriented" "dsls" "concurrency features" "jvm"] } doc1 { :_id (ObjectId.) :title "Clojure" :tags ["functional" "homoiconic" "syntax-oriented" "dsls" "concurrency features" "jvm"] }
doc2 { :_id (ObjectId.) :title "Java" :tags ["object-oriented" "jvm"] } doc2 { :_id (ObjectId.) :title "Java" :tags ["object-oriented" "jvm"] }
doc3 { :_id (ObjectId.) :title "Scala" :tags ["functional" "object-oriented" "dsls" "concurrency features" "jvm"] } doc3 { :_id (ObjectId.) :title "Scala" :tags ["functional" "object-oriented" "dsls" "concurrency features" "jvm"] }
- (mgcol/insert-batch coll [doc1 doc2 doc3]) - (mgcol/insert-batch coll [doc1 doc2 doc3])
result1 (mgcol/find-maps coll { :tags { "$all" ["functional" "jvm" "homoiconic"] } }) result1 (mgcol/find-maps coll { :tags { "$all" ["functional" "jvm" "homoiconic"] } })
result2 (mgcol/find-maps coll { :tags { "$all" ["functional" "native" "homoiconic"] } }) result2 (mgcol/find-maps coll { :tags { "$all" ["functional" "native" "homoiconic"] } })
result3 (mgcol/find-maps coll { :tags { "$all" ["functional" "jvm" "dsls"] } })] result3 (mgcol/find-maps coll { :tags { "$all" ["functional" "jvm" "dsls"] } })]
(is (= [doc1] result1)) (is (= [doc1] result1))
(is (empty? result2)) (is (empty? result2))
(is (= 2 (count result3))))) (is (= 2 (count result3)))))
;; $exists ;; $exists
(deftest query-with-find-one-as-map-using-$exists (deftest query-with-find-one-as-map-using-$exists
(let [coll "docs" (let [coll "docs"
doc1 { :_id (ObjectId.) :published-by "Jill The Blogger" :draft false :title "X announces another Y" } doc1 { :_id (ObjectId.) :published-by "Jill The Blogger" :draft false :title "X announces another Y" }
doc2 { :_id (ObjectId.) :draft true :title "Z announces a Y competitor" } doc2 { :_id (ObjectId.) :draft true :title "Z announces a Y competitor" }
_ (mgcol/insert-batch coll [doc1 doc2]) _ (mgcol/insert-batch coll [doc1 doc2])
result1 (mgcol/find-one-as-map coll { :published-by { "$exists" true } }) result1 (mgcol/find-one-as-map coll { :published-by { "$exists" true } })
result2 (mgcol/find-one-as-map coll { :published-by { "$exists" false } })] result2 (mgcol/find-one-as-map coll { :published-by { "$exists" false } })]
(is (= doc1 result1)) (is (= doc1 result1))
(is (= doc2 result2)))) (is (= doc2 result2))))
;; $mod ;; $mod
(deftest query-with-find-one-as-map-using-$mod (deftest query-with-find-one-as-map-using-$mod
(let [coll "docs" (let [coll "docs"
doc1 { :_id (ObjectId.) :counter 25 } doc1 { :_id (ObjectId.) :counter 25 }
doc2 { :_id (ObjectId.) :counter 32 } doc2 { :_id (ObjectId.) :counter 32 }
doc3 { :_id (ObjectId.) :counter 63 } doc3 { :_id (ObjectId.) :counter 63 }
_ (mgcol/insert-batch coll [doc1 doc2 doc3]) _ (mgcol/insert-batch coll [doc1 doc2 doc3])
result1 (mgcol/find-one-as-map coll { :counter { "$mod" [10, 5] } }) result1 (mgcol/find-one-as-map coll { :counter { "$mod" [10, 5] } })
result2 (mgcol/find-one-as-map coll { :counter { "$mod" [10, 2] } }) result2 (mgcol/find-one-as-map coll { :counter { "$mod" [10, 2] } })
result3 (mgcol/find-one-as-map coll { :counter { "$mod" [11, 1] } })] result3 (mgcol/find-one-as-map coll { :counter { "$mod" [11, 1] } })]
(is (= doc1 result1)) (is (= doc1 result1))
(is (= doc2 result2)) (is (= doc2 result2))
(is (empty? result3)))) (is (empty? result3))))
;; $ne ;; $ne
(deftest query-with-find-one-as-map-using-$ne (deftest query-with-find-one-as-map-using-$ne
(let [coll "docs" (let [coll "docs"
doc1 { :_id (ObjectId.) :counter 25 } doc1 { :_id (ObjectId.) :counter 25 }
doc2 { :_id (ObjectId.) :counter 32 } doc2 { :_id (ObjectId.) :counter 32 }
_ (mgcol/insert-batch coll [doc1 doc2]) _ (mgcol/insert-batch coll [doc1 doc2])
result1 (mgcol/find-one-as-map coll { :counter { "$ne" 25 } }) result1 (mgcol/find-one-as-map coll { :counter { "$ne" 25 } })
result2 (mgcol/find-one-as-map coll { :counter { "$ne" 32 } })] result2 (mgcol/find-one-as-map coll { :counter { "$ne" 32 } })]
(is (= doc2 result1)) (is (= doc2 result1))
(is (= doc1 result2)))) (is (= doc1 result2))))