From bd133c1afc740453638e80ec2b040bf3f9d514c0 Mon Sep 17 00:00:00 2001 From: "Michael S. Klishin" Date: Mon, 14 Nov 2011 15:15:43 +0400 Subject: [PATCH] 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)) --- src/monger/query.clj | 89 +++++++++++++++++++ test/monger/test/querying.clj | 155 ++++++++++++++++++---------------- 2 files changed, 172 insertions(+), 72 deletions(-) create mode 100644 src/monger/query.clj diff --git a/src/monger/query.clj b/src/monger/query.clj new file mode 100644 index 0000000..2362e0e --- /dev/null +++ b/src/monger/query.clj @@ -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#)))) diff --git a/test/monger/test/querying.clj b/test/monger/test/querying.clj index 97fe6a2..fbe82ff 100644 --- a/test/monger/test/querying.clj +++ b/test/monger/test/querying.clj @@ -1,7 +1,7 @@ (set! *warn-on-reflection* true) (ns monger.test.querying - (:refer-clojure :exclude [select find]) + (:refer-clojure :exclude [select find sort]) (:import [com.mongodb WriteResult WriteConcern DBCursor DBObject CommandResult$CommandFailure] [org.bson.types ObjectId] [java.util Date]) @@ -10,8 +10,7 @@ [monger.result :as mgres]) (:use [clojure.test] [monger.test.fixtures] - ;; [monger.query] - [monger.conversion])) + [monger conversion query operators])) (defn purge-locations-collection @@ -78,85 +77,97 @@ ;; < ($lt), <= ($lte), > ($gt), >= ($gte) -;; (deftest query-using-dsl-and-$lt-operator -;; (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 (in-collection "docs" -;; (find { :inception_year { "$lt" 2000 } })] -;; (is (= [doc2] lt-result)))) +(deftest query-using-dsl-and-$lt-operator-1 + (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 } }) + (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" - 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 (mgcol/find-maps coll { :inception_year { "$lt" 2000 } }) - lte-result (mgcol/find-maps coll { :inception_year { "$lte" 1992 } }) - gt-result (mgcol/find-maps coll { :inception_year { "$gt" 2005 } }) - gte-result (mgcol/find-maps coll { :inception_year { "$gte" 2006 } })] - (is (= [doc2] lt-result)) - (is (= [doc2] lte-result)) - (is (= [doc1] gt-result)) - (is (= [doc1] gte-result)))) + +(deftest query-with-find-maps-using-$lt-operator + (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 (mgcol/find-maps coll { :inception_year { "$lt" 2000 } }) + lte-result (mgcol/find-maps coll { :inception_year { "$lte" 1992 } }) + gt-result (mgcol/find-maps coll { :inception_year { "$gt" 2005 } }) + gte-result (mgcol/find-maps coll { :inception_year { "$gte" 2006 } })] + (is (= [doc2] lt-result)) + (is (= [doc2] lte-result)) + (is (= [doc1] gt-result)) + (is (= [doc1] gte-result)))) - ;; $all +;; $all - (deftest query-with-find-maps-using-$all - (let [coll "docs" - doc1 { :_id (ObjectId.) :title "Clojure" :tags ["functional" "homoiconic" "syntax-oriented" "dsls" "concurrency features" "jvm"] } - doc2 { :_id (ObjectId.) :title "Java" :tags ["object-oriented" "jvm"] } - doc3 { :_id (ObjectId.) :title "Scala" :tags ["functional" "object-oriented" "dsls" "concurrency features" "jvm"] } - - (mgcol/insert-batch coll [doc1 doc2 doc3]) - result1 (mgcol/find-maps coll { :tags { "$all" ["functional" "jvm" "homoiconic"] } }) - result2 (mgcol/find-maps coll { :tags { "$all" ["functional" "native" "homoiconic"] } }) - result3 (mgcol/find-maps coll { :tags { "$all" ["functional" "jvm" "dsls"] } })] - (is (= [doc1] result1)) - (is (empty? result2)) - (is (= 2 (count result3))))) +(deftest query-with-find-maps-using-$all + (let [coll "docs" + doc1 { :_id (ObjectId.) :title "Clojure" :tags ["functional" "homoiconic" "syntax-oriented" "dsls" "concurrency features" "jvm"] } + doc2 { :_id (ObjectId.) :title "Java" :tags ["object-oriented" "jvm"] } + doc3 { :_id (ObjectId.) :title "Scala" :tags ["functional" "object-oriented" "dsls" "concurrency features" "jvm"] } + - (mgcol/insert-batch coll [doc1 doc2 doc3]) + result1 (mgcol/find-maps coll { :tags { "$all" ["functional" "jvm" "homoiconic"] } }) + result2 (mgcol/find-maps coll { :tags { "$all" ["functional" "native" "homoiconic"] } }) + result3 (mgcol/find-maps coll { :tags { "$all" ["functional" "jvm" "dsls"] } })] + (is (= [doc1] result1)) + (is (empty? result2)) + (is (= 2 (count result3))))) - ;; $exists +;; $exists - (deftest query-with-find-one-as-map-using-$exists - (let [coll "docs" - 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" } - _ (mgcol/insert-batch coll [doc1 doc2]) - result1 (mgcol/find-one-as-map coll { :published-by { "$exists" true } }) - result2 (mgcol/find-one-as-map coll { :published-by { "$exists" false } })] - (is (= doc1 result1)) - (is (= doc2 result2)))) +(deftest query-with-find-one-as-map-using-$exists + (let [coll "docs" + 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" } + _ (mgcol/insert-batch coll [doc1 doc2]) + result1 (mgcol/find-one-as-map coll { :published-by { "$exists" true } }) + result2 (mgcol/find-one-as-map coll { :published-by { "$exists" false } })] + (is (= doc1 result1)) + (is (= doc2 result2)))) - ;; $mod +;; $mod - (deftest query-with-find-one-as-map-using-$mod - (let [coll "docs" - doc1 { :_id (ObjectId.) :counter 25 } - doc2 { :_id (ObjectId.) :counter 32 } - doc3 { :_id (ObjectId.) :counter 63 } - _ (mgcol/insert-batch coll [doc1 doc2 doc3]) - result1 (mgcol/find-one-as-map coll { :counter { "$mod" [10, 5] } }) - result2 (mgcol/find-one-as-map coll { :counter { "$mod" [10, 2] } }) - result3 (mgcol/find-one-as-map coll { :counter { "$mod" [11, 1] } })] - (is (= doc1 result1)) - (is (= doc2 result2)) - (is (empty? result3)))) +(deftest query-with-find-one-as-map-using-$mod + (let [coll "docs" + doc1 { :_id (ObjectId.) :counter 25 } + doc2 { :_id (ObjectId.) :counter 32 } + doc3 { :_id (ObjectId.) :counter 63 } + _ (mgcol/insert-batch coll [doc1 doc2 doc3]) + result1 (mgcol/find-one-as-map coll { :counter { "$mod" [10, 5] } }) + result2 (mgcol/find-one-as-map coll { :counter { "$mod" [10, 2] } }) + result3 (mgcol/find-one-as-map coll { :counter { "$mod" [11, 1] } })] + (is (= doc1 result1)) + (is (= doc2 result2)) + (is (empty? result3)))) - ;; $ne +;; $ne - (deftest query-with-find-one-as-map-using-$ne - (let [coll "docs" - doc1 { :_id (ObjectId.) :counter 25 } - doc2 { :_id (ObjectId.) :counter 32 } - _ (mgcol/insert-batch coll [doc1 doc2]) - result1 (mgcol/find-one-as-map coll { :counter { "$ne" 25 } }) - result2 (mgcol/find-one-as-map coll { :counter { "$ne" 32 } })] - (is (= doc2 result1)) - (is (= doc1 result2)))) +(deftest query-with-find-one-as-map-using-$ne + (let [coll "docs" + doc1 { :_id (ObjectId.) :counter 25 } + doc2 { :_id (ObjectId.) :counter 32 } + _ (mgcol/insert-batch coll [doc1 doc2]) + result1 (mgcol/find-one-as-map coll { :counter { "$ne" 25 } }) + result2 (mgcol/find-one-as-map coll { :counter { "$ne" 32 } })] + (is (= doc2 result1)) + (is (= doc1 result2))))