Skip to content
Browse files

Removed extending Memcache protocol on spymemcached client, in favor …

…of the

clojure.core.cache 'defcache' type. All unit tests updated and passing.
  • Loading branch information...
1 parent 251d78f commit 67e5c30372c6790c938d63507dff7180e398c482 @davidhmartin committed Feb 26, 2012
Showing with 125 additions and 199 deletions.
  1. +62 −129 src/mcache/cache.clj
  2. +63 −70 test/mcache/test/cache.clj
View
191 src/mcache/cache.clj
@@ -6,22 +6,28 @@
(:import [clojure.core.cache CacheProtocol])
)
+(def DEFAULT-EXP (* 60 60 24 30))
+
+
(defprotocol Memcache
- "This is a protocol for memcached and similar cache clients.
+ "This is a protocol for memcached clients
The functions which insert values into the cache may optionally
- accept an expiration, the semantics of which depend on the
- protocol implementation."
+ accept an expiration, the semantics of which are assumed to
+ follow the memcached protocol, wherein the time unit is seconds,
+ and a value between 1 and 30 days is treated as time-to-live,
+ and any higher value is treated as a unix-style timestamp (seconds
+ since Jan 1 1970).
+ "
(default-exp [mc]
"Return the default expiration")
(put-if-absent
[mc key value]
[mc key value exp]
- "Add a key/value to the cache, if the key is not already there."
- )
+ "Add a key/value to the cache, if the key is not already there.")
(put-all-if-absent
[mc key-val-map]
@@ -82,28 +88,32 @@
"Gets values for keys. Returns a map of keys to values, omitting
keys that are not in the cache."))
-
(defmacro with-cache
"key is a string, value-fn is a function. Returns keyed value from cache;
if not found, uses value-fn to obtain the value and adds it to cache
before returning."
- ([mc key value-fn]
- `(if-let [cached-val# (fetch ~mc ~key)]
+ ([cache key value-fn]
+ `(if-let [cached-val# (.lookup ~cache ~key)]
cached-val#
- (let [val# ~value-fn]
- (put-if-absent ~mc ~key val#)
- val#)))
- ([mc key value-fn exp]
- `(if-let [cached-val# (fetch ~mc ~key)]
+ (let [val# (~value-fn ~key)]
+ (if (nil? val#)
+ nil
+ (if (.get (.put-if-absent ~cache ~key val#))
+ val#
+ (.lookup ~cache ~key))))))
+ ([cache key value-fn exp]
+ `(if-let [cached-val# (.lookup ~cache ~key)]
cached-val#
- (let [val# ~value-fn]
- (put-if-absent ~mc ~key val# ~exp)
- val#)))
- )
+ (let [val# (~value-fn ~key)]
+ (if (nil? val#)
+ nil
+ (if (.get (.put-if-absent ~cache ~key val# ~exp))
+ val#
+ (.lookup ~cache ~key)))))))
(defn cache-fetch
- ([mc id query-fn key-fn] (cache-fetch mc id query-fn key-fn (default-exp mc)))
- ([mc id query-fn key-fn exp]
+ ([cache id query-fn key-fn] (cache-fetch cache id query-fn key-fn (.default-exp cache)))
+ ([cache id query-fn key-fn exp]
"To be used in conjunction with a persistent storage api. 'id' is
a unique identifier (e.g. a primary key) for a persisted object.
'query-fn' is a function accepting an id as argument, and
@@ -112,42 +122,36 @@
will first attempt to locate the object in cache; if not found, it
uses query-fn to get the object and caches it before returning.
Returns nil if object is not found at all."
- (if-let [cached-obj (fetch mc (key-fn id))]
+ (if-let [cached-obj (.lookup cache (key-fn id))]
cached-obj
(when-let [obj (query-fn id)]
- (put-if-absent mc (key-fn id) obj exp)
+ (.put-if-absent cache (key-fn id) obj exp)
obj))))
(defn- remove-nil-vals [map]
(reduce #(if (nil? (second %2)) %1 (assoc %1 (first %2) (second %2))) {} map))
(defn cache-fetch-all
- ([mc ids query-fn key-fn] (cache-fetch-all mc ids query-fn key-fn (default-exp mc)))
- ([mc ids query-fn key-fn exp]
- "This is similar to cached-fetch, except it handles a sequence of
+ [cache ids query-fn key-fn]
+ "This is similar to cache-fetch, except it handles a sequence of
ids. Returns a sequence containing the resolved objects, or nil for
not-found objects, in the same order as the original sequence of
ids."
- (letfn [(add-to-cache
- [mc id2val key-fn exp]
- (doseq [[id val] id2val]
- (put-if-absent mc (key-fn id) val exp)))
- (from-cache
- [mc ids key-fn]
- (let [keys (map key-fn ids)]
- (clojure.set/rename-keys (fetch-all mc keys) (zipmap keys ids))))]
- (let [fromcache (from-cache mc ids key-fn)
- ids-to-query (remove #(contains? fromcache %) ids)
- fromquery (if (empty? ids-to-query) {} (remove-nil-vals (query-fn ids-to-query)))]
- (add-to-cache mc fromquery key-fn exp)
- (merge fromcache fromquery)))))
-
+ (letfn [(add-to-cache
+ [cache id2val key-fn]
+ (doseq [[id val] id2val]
+ (.put-if-absent cache (key-fn id) val)))
+ (from-cache
+ [cache ids key-fn]
+ (let [keys (map key-fn ids)]
+ (clojure.set/rename-keys (fetch-all cache keys) (zipmap keys ids))))]
+ (let [fromcache (from-cache cache ids key-fn)
+ ids-to-query (remove #(contains? fromcache %) ids)
+ fromquery (if (empty? ids-to-query) {} (remove-nil-vals (query-fn ids-to-query)))]
+ (add-to-cache cache fromquery key-fn)
+ (merge fromcache fromquery))))
-
-
-(def EXP (* 60 60 24 30))
-
(defn- cache-update-multi
"Used by add, set, and replace functions which operate on map of key/value pairs.
Calls the updating function iteratively over each key/val pair, and returns a
@@ -157,90 +161,18 @@
(reduce #(assoc %1 (first %2) (second %2)) {}
(map (fn [k_v] [(first k_v) (cache-updating-fctn mc (first k_v) (second k_v) exp)]) key-val-map)))
-(extend-type net.spy.memcached.MemcachedClient
- Memcache
-
- (default-exp [mc] EXP)
-
- (put-if-absent
- ([mc key value]
- (put-if-absent mc key value EXP))
- ([mc key value exp]
- (.. mc (add key exp value))))
-
- (put-all-if-absent
- ([mc key-val-map]
- (put-all-if-absent mc key-val-map EXP))
- ([mc key-val-map exp]
- (cache-update-multi put-if-absent mc key-val-map exp)))
-
- (put
- ([mc key value]
- (.. mc (set key EXP value)))
- ([mc key value exp]
- (.. mc (set key exp value))))
-
- (put-all
- ([mc key-val-map] (put-all mc key-val-map EXP))
- ([mc key-val-map exp]
- (cache-update-multi put mc key-val-map exp)))
-
- (put-if-present
- ([mc key value] (put-if-present mc key value EXP))
- ([mc key value exp]
- (.. mc (replace key exp value))))
-
- (put-all-if-present
- ([mc key-val-map] (put-all-if-present mc key-val-map EXP))
- ([mc key-val-map exp]
- (cache-update-multi put-if-present mc key-val-map exp)))
-
- (delete [mc key]
- (.. mc (delete key)))
-
- (delete-all [mc keys]
- (doall (map #(delete mc %) keys)))
-
- (incr
- ([mc key]
- (.. mc (incr key 1)))
- ([mc key by]
- (.. mc (incr key by)))
- ([mc key by default]
- (.. mc (incr key by default)))
- ([mc key by default exp]
- (.. mc (incr key by default exp))))
-
- (decr
- ([mc key]
- (.. mc (decr key 1)))
- ([mc key by]
- (.. mc (decr key by)))
- ([mc key by default]
- (.. mc (decr key by default)))
- ([mc key by default exp]
- (.. mc (decr key by default exp))))
-
- (fetch [mc key]
- (.. mc (get key)))
-
- (fetch-all [mc keys]
- (into {} (.. mc (getBulk keys))))
-
- )
-
(defcache MemcachedCache [mc exp]
CacheProtocol
(lookup
[this e]
- (fetch mc e))
+ (.. mc (get e)))
(has? [this e]
"Memcached does provide ability to test existence of a key without
fetching the entire value, so this function has no performance
advantage over lookup."
- (not (nil? (fetch mc e))))
+ (not (nil? (.. mc (get e)))))
(hit [this e]
"Is meant to be called if the cache is determined to contain a value
@@ -250,7 +182,7 @@
(miss [this e ret]
"Is meant to be called if the cache is determined to **not** contain a
value associated with `e`"
- (put mc e ret)
+ (when-not (nil? ret) (put mc e ret))
this)
(evict [this e]
@@ -263,24 +195,25 @@
The contract is that said cache should return an instance of its
own type."
(put-all mc base)
- this
- )
+ this)
+ Object
+ (toString [this] (str "MemcachedCache: " mc))
Memcache
- (default-exp [mc] EXP)
+ (default-exp [this] DEFAULT-EXP)
(put-if-absent
[this key value exp]
(.. mc (add key exp value)))
(put-if-absent
[this key value]
- (.. mc (add key EXP value)))
+ (.. mc (add key DEFAULT-EXP value)))
(put-all-if-absent
[this key-val-map]
- (put-all-if-absent this key-val-map EXP))
+ (put-all-if-absent this key-val-map DEFAULT-EXP))
(put-all-if-absent
[this key-val-map exp]
@@ -289,42 +222,42 @@
(put
[this key value]
(println "put value: " value " of type: " (class value))
- (.. mc (set key EXP value)))
+ (.. mc (set key DEFAULT-EXP value)))
(put
[this key value exp]
(.. mc (set key exp value)))
(put-all
[this key-val-map]
- (put-all mc key-val-map EXP))
+ (put-all this key-val-map DEFAULT-EXP))
(put-all
[this key-val-map exp]
- (cache-update-multi put mc key-val-map exp))
+ (cache-update-multi put this key-val-map exp))
(put-if-present
[this key value]
- (put-if-present mc key value EXP))
+ (put-if-present this key value DEFAULT-EXP))
(put-if-present
[this key value exp]
(.. mc (replace key exp value)))
(put-all-if-present
[this key-val-map]
- (put-all-if-present mc key-val-map EXP))
+ (put-all-if-present this key-val-map DEFAULT-EXP))
(put-all-if-present
[this key-val-map exp]
- (cache-update-multi put-if-present mc key-val-map exp))
+ (cache-update-multi put-if-present this key-val-map exp))
(delete
[this key]
(.. mc (delete key)))
(delete-all
[this keys]
- (doall (map #(delete mc %) keys)))
+ (doall (map #(delete this %) keys)))
(incr [this key]
(.. mc (incr key 1)))
View
133 test/mcache/test/cache.clj
@@ -6,21 +6,28 @@
[mcache.cache MemcachedCache])
)
-(def mc (net.spy.memcached.MemcachedClient. (list (java.net.InetSocketAddress. "127.0.0.1" 11211))))
-(def mcache (make-memcached-cache mc))
+(def spy (net.spy.memcached.MemcachedClient. (list (java.net.InetSocketAddress. "127.0.0.1" 11211))))
+(def mc (make-memcached-cache spy))
(defn clear-cache-fixture
"Flushes the cache before and after. NOTE: Flush is asynchronous, so
it can't really be relied on to bring the cache to a clean state
between tests. Therefore, it is best to avoid using the same keys in
different tests."
[f]
- (.. mc (flush))
+ (.. (.mc mc) (flush))
(f)
- (.. mc (flush)))
+ (.. (.mc mc) (flush)))
(use-fixtures :each clear-cache-fixture)
+(deftest test-lookup
+ (testing "lookup"
+ (.get (put mc "a" 1))
+ (.get (put mc "b" 2))
+ (is (= 1 (.lookup mc "a")))))
+
+
(deftest test-put-if-absent
(testing "put-if-absent"
@@ -36,12 +43,12 @@
(deftest test-put-all-if-absent
(testing "Add multiple items to cache"
(let [input {"one" 1 "two" 2 "three" "the number three"}
- results (put-all-if-absent mc input)]
+ results (.put-all-if-absent mc input)]
(is (= (set (keys input)) (set (keys results))))
(doseq [[k v] results]
(is (.get v))))
(let [input {"one" "a" "two" "b" "three" 3 "four" 4}
- results (put-all-if-absent mc input)]
+ results (.put-all-if-absent mc input)]
(is (= (set (keys input)) (set (keys results))))
(is (false? (.get (get results "one"))))
(is (false? (.get (get results "two"))))
@@ -54,15 +61,15 @@
(deftest test-put
(testing "Put into cache"
- (is (.get (put mc "setkey1" "val1")))
- (is (= "val1" (fetch mc "setkey1")))
- (is (.get (put mc "setkey1" "val2")))
- (is (= "val2" (fetch mc "setkey1")))))
+ (is (.get (.put mc "setkey1" "val1")))
+ (is (= "val1" (.lookup mc "setkey1")))
+ (is (.get (.put mc "setkey1" "val2")))
+ (is (= "val2" (.lookup mc "setkey1")))))
(deftest test-put-all
(testing "Put multiple into cache"
(let [input {"one" 1 "two" 2 "three" "the number three"}
- results (put-all mc input)]
+ results (.put-all mc input)]
(is (= (set (keys input)) (set (keys results))))
(doseq [[k v] results]
(is (.get v))))
@@ -73,80 +80,91 @@
(is (true? (.get (get results "two"))))
(is (true? (.get (get results "three"))))
(is (true? (.get (get results "four"))))
- (is (= "a" (fetch mc "one")))
- (is (= "b" (fetch mc "two")))
- (is (= 3 (fetch mc "three")))
- (is (= 4 (fetch mc "four"))))))
+ (is (= "a" (.lookup mc "one")))
+ (is (= "b" (.lookup mc "two")))
+ (is (= 3 (.lookup mc "three")))
+ (is (= 4 (.lookup mc "four"))))))
(deftest test-put-if-present
(testing "Replace in cache"
(is (false? (.get (put-if-present mc "repkey1" "foo"))))
- (is (nil? (fetch mc "repkey1")))
+ (is (nil? (.lookup mc "repkey1")))
(.get (put mc "repkey1" "bar"))
(is (true? (.get (put-if-present mc "repkey1" "foo"))))
- (is (= "foo" (fetch mc "repkey1")))))
+ (is (= "foo" (.lookup mc "repkey1")))))
(deftest test-put-all-if-present ;;;;;
(testing "Replace multiple"
(let [input {"one" 1 "two" 2 "three" "the number three"}
- results (put-all mc input)])
+ results (.put-all mc input)])
(let [input {"one" "a" "two" "b" "three" 3 "four" 4}
- results (put-all-if-present mc input)]
+ results (.put-all-if-present mc input)]
(is (= (set (keys input)) (set (keys results))))
(is (true? (.get (get results "one"))))
(is (true? (.get (get results "two"))))
(is (true? (.get (get results "three"))))
(is (false? (.get (get results "four"))))
- (is (= "a" (fetch mc "one")))
- (is (= "b" (fetch mc "two")))
- (is (= 3 (fetch mc "three")))
- (is (nil? (fetch mc "four"))))))
+ (is (= "a" (.lookup mc "one")))
+ (is (= "b" (.lookup mc "two")))
+ (is (= 3 (.lookup mc "three")))
+ (is (nil? (.lookup mc "four"))))))
(deftest test-delete
(testing "Delete"
(put mc "is-there" "thing")
- (is (false? (.get (delete mc "not-there"))))
- (is (true? (.get (delete mc "is-there"))))
- (is (nil? (fetch mc "is-there")))))
+ (is (false? (.get (.delete mc "not-there"))))
+ (is (true? (.get (.delete mc "is-there"))))
+ (is (nil? (.lookup mc "is-there")))))
(deftest test-delete-all
(testing "Delete all"
- (.get (put mc "a" 1))
- (.get (put mc "b" 2))
- (let [resp (delete-all mc ["a" "b" "x"])]
+ (.get (.put mc "a" 1))
+ (.get (.put mc "b" 2))
+ (let [resp (.delete-all mc ["a" "b" "x"])]
(is (true? (.get (nth resp 0))))
(is (true? (.get (nth resp 1))))
(is (false? (.get (nth resp 2)))))))
(deftest test-incr
(testing "Increment"
- (is (= -1 (incr mc "n")))
- (.get (put mc "n" 0))
- (is (= 1 (incr mc "n")))
- (is (= 11 (incr mc "n" 10)))
- (is (= 0 (incr mc "m" 1 0)))))
+ (is (= -1 (.incr mc "n")))
+ (.get (.put mc "n" 0))
+ (is (= 1 (.incr mc "n")))
+ (is (= 11 (.incr mc "n" 10)))
+ (is (= 0 (.incr mc "m" 1 0)))))
(deftest test-decr
(testing "Decrement"
- (is (= -1 (decr mc "nn")))
- (.get (put mc "nn" 0))
- (is (= 0 (decr mc "nn")))
- (is (= 10 (decr mc "mm" 1 10)))
- (is (= 9 (decr mc "mm")))
- (is (= 6 (decr mc "mm" 3)))))
+ (is (= -1 (.decr mc "nn")))
+ (.get (.put mc "nn" 0))
+ (is (= 0 (.decr mc "nn")))
+ (is (= 10 (.decr mc "mm" 1 10)))
+ (is (= 9 (.decr mc "mm")))
+ (is (= 6 (.decr mc "mm" 3)))))
(deftest test-get-all
(testing "Get list of keys"
- (.get (put mc "a" 1))
- (.get (put mc "b" 2))
- (.get (put mc "c" "three"))
- (let [results (fetch-all mc ["a" "b" "c" "d"])]
+ (.get (.put mc "a" 1))
+ (.get (.put mc "b" 2))
+ (.get (.put mc "c" "three"))
+ (let [results (.fetch-all mc ["a" "b" "c" "d"])]
(is (= 1 (get results "a")))
(is (= 2 (get results "b")))
(is (= "three" (get results "c")))
(is (nil? (get results "d"))))))
+(deftest test-with-cache
+ (testing "with-cache macro"
+ (letfn [(qfcn [id]
+ (if (< (Integer/parseInt id) 100)
+ (str "foo" id)
+ nil))]
+ (is (nil? (with-cache mc "200" qfcn)))
+ (is (= "foo50" (with-cache mc "50" qfcn)))
+ (is (= "foo50" (.lookup mc "50")))
+ (is (= "foo50" (with-cache mc "50" qfcn))))))
+
(deftest test-cache-fetch
(testing "Cached fetch"
@@ -156,7 +174,7 @@
nil))]
(is (nil? (cache-fetch mc 200 qfcn str)))
(is (= "foo50" (cache-fetch mc 50 qfcn str)))
- (is (= "foo50" (fetch mc "50")))
+ (is (= "foo50" (.lookup mc "50")))
(is (= "foo50" (cache-fetch mc 50 qfcn str))))))
(deftest test-cache-fetch-all
@@ -171,29 +189,4 @@
(is (= {1 "foo1" 5 "foo5" 19 "foo19"}
(cache-fetch-all mc [1 120 5 19] qfcn str))))))
-(deftest test-lookup
- (testing "lookup"
- (.get (put mc "a" 1))
- (.get (put mc "b" 2))
- (is (= 1 (CacheProtocol/.lookup mcache "a")))))
-
-
-
-
-;; (deftest test-memcached-cache-ilookup
-;; (testing "that the MemcachedCache can lookup via keywords"
-;; (coretests/do-ilookup-tests (seed (MemcachedCache. mc) coretests/small-map)))
-;; (testing "that the MemcachedCache can .lookup"
-;; (coretests/do-dot-lookup-tests (seed (MemcachedCache. mc) coretests/small-map)))
-;; (testing "assoc and dissoc for MemcachedCache"
-;; (coretests/do-assoc (MemcachedCache. mc))
-;; (coretests/do-dissoc (seed (MemcachedCache. mc) {:a 1 :b 2})))
-;; (testing "that get and cascading gets work for MemcachedCache"
-;; (coretests/do-getting (seed (MemcachedCache. mc) coretests/big-map)))
-;; (testing "that finding works for MemcachedCache"
-;; (coretests/do-finding (seed (MemcachedCache. mc) coretests/small-map)))
-;; (testing "that contains? works for BasicCache"
-;; (coretests/do-contains (BasicCache. coretests/small-map))))
-
-;; (.valAt mcache "x" (assoc mcache "x" 13))

0 comments on commit 67e5c30

Please sign in to comment.
Something went wrong with that request. Please try again.