├── .deps-versions.clj ├── .gitignore ├── modules ├── celtuce-core │ ├── .gitignore │ ├── src │ │ └── celtuce │ │ │ ├── args │ │ │ ├── set.clj │ │ │ ├── sort.clj │ │ │ ├── migrate.clj │ │ │ ├── scripting.clj │ │ │ ├── zset.clj │ │ │ ├── kill.clj │ │ │ ├── geo.clj │ │ │ └── bitfield.clj │ │ │ ├── impl │ │ │ ├── pubsub.clj │ │ │ ├── cluster.clj │ │ │ └── server.clj │ │ │ ├── codec.clj │ │ │ ├── scan.clj │ │ │ ├── connector.clj │ │ │ └── commands.clj │ ├── README.md │ └── project.clj ├── celtuce-pool │ ├── .gitignore │ ├── project.clj │ ├── README.md │ └── src │ │ └── celtuce │ │ └── pool.clj └── celtuce-manifold │ ├── .gitignore │ ├── src │ └── celtuce │ │ ├── manifold │ │ ├── scan.clj │ │ └── pubsub.clj │ │ └── manifold.clj │ ├── project.clj │ └── README.md ├── project.clj ├── test └── celtuce │ ├── server_dynamic_test.clj │ ├── cluster_dynamic_test.clj │ ├── pool_test.clj │ ├── connector_test.clj │ ├── cluster_manifold_test.clj │ ├── cluster_sync_test.clj │ ├── server_manifold_test.clj │ └── server_sync_test.clj ├── README.md └── LICENSE /.deps-versions.clj: -------------------------------------------------------------------------------- 1 | (def celtuce-version "0.4.2") 2 | (def clj-version "1.10.3") 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | *.iml 11 | .idea -------------------------------------------------------------------------------- /modules/celtuce-core/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /modules/celtuce-pool/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /modules/celtuce-manifold/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/args/set.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.args.set 2 | (:import 3 | (io.lettuce.core SetArgs))) 4 | 5 | (defn set-args [& {ex :ex px :px nx :nx xx :xx}] 6 | (cond-> (SetArgs.) 7 | ex (.ex ^long ex) 8 | px (.px ^long px) 9 | nx (.nx) 10 | xx (.xx))) 11 | -------------------------------------------------------------------------------- /modules/celtuce-manifold/src/celtuce/manifold/scan.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.manifold.scan 2 | (:require 3 | [celtuce.scan :refer [scan-res PScanResult]] 4 | [manifold.deferred]) 5 | (:import 6 | (manifold.deferred Deferred))) 7 | 8 | (extend-protocol PScanResult 9 | Deferred 10 | (scan-res [this] 11 | (scan-res @this))) 12 | 13 | -------------------------------------------------------------------------------- /modules/celtuce-core/README.md: -------------------------------------------------------------------------------- 1 | # celtuce-core 2 | 3 | Main module that defines the Redis commands. 4 | Provides core functionalities and an implementation for synchronous commands. 5 | 6 | ## Usage 7 | 8 | [![Clojars Project](https://img.shields.io/clojars/v/celtuce-core.svg)](https://clojars.org/celtuce-core) 9 | 10 | ## License 11 | 12 | * [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 13 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/args/sort.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.args.sort 2 | (:import 3 | (io.lettuce.core SortArgs))) 4 | 5 | (defn sort-args 6 | [& {by :by [offset count :as limit] :limit get :get 7 | asc :asc desc :desc alpha :alpha}] 8 | {:pre []} 9 | (cond-> (SortArgs.) 10 | by (.by by) 11 | limit (.limit offset count) 12 | get (.get get) 13 | asc (.asc) 14 | desc (.desc) 15 | alpha (.alpha))) 16 | 17 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/args/migrate.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.args.migrate 2 | (:import 3 | (io.lettuce.core MigrateArgs))) 4 | 5 | (defn migrate-args [& {copy :copy replace :replace k :key ks :keys}] 6 | {:pre [(or (and (not= nil k) (nil? ks)) 7 | (and (not= nil ks) (nil? k)))]} 8 | (cond-> (MigrateArgs.) 9 | copy (.copy) 10 | replace (.replace) 11 | k (.key k) 12 | ks (.keys ^objects (into-array Object ks)))) 13 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/args/scripting.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.args.scripting 2 | (:import 3 | (io.lettuce.core ScriptOutputType))) 4 | 5 | (defn ^ScriptOutputType output-type [t] 6 | (case t 7 | :boolean ScriptOutputType/BOOLEAN 8 | :integer ScriptOutputType/INTEGER 9 | :multi ScriptOutputType/MULTI 10 | :status ScriptOutputType/STATUS 11 | :value ScriptOutputType/VALUE 12 | (throw (ex-info "invalid scripting output type" {:type t})))) 13 | -------------------------------------------------------------------------------- /modules/celtuce-manifold/project.clj: -------------------------------------------------------------------------------- 1 | (load-file "../../.deps-versions.clj") 2 | (defproject celtuce-manifold celtuce-version 3 | :url "https://github.com/lerouxrgd/celtuce" 4 | :license {:name "Apache License 2.0" 5 | :url "http://www.apache.org/licenses/LICENSE-2.0"} 6 | :dependencies [[org.clojure/clojure ~clj-version] 7 | [celtuce-core ~celtuce-version] 8 | [manifold "0.1.8"]] 9 | :global-vars {*warn-on-reflection* true}) 10 | -------------------------------------------------------------------------------- /modules/celtuce-pool/project.clj: -------------------------------------------------------------------------------- 1 | (load-file "../../.deps-versions.clj") 2 | (defproject celtuce-pool celtuce-version 3 | :url "https://github.com/lerouxrgd/celtuce" 4 | :license {:name "Apache License 2.0" 5 | :url "http://www.apache.org/licenses/LICENSE-2.0"} 6 | :dependencies [[org.clojure/clojure ~clj-version] 7 | [celtuce-core ~celtuce-version] 8 | [org.apache.commons/commons-pool2 "2.9.0"]] 9 | :global-vars {*warn-on-reflection* true}) 10 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (load-file ".deps-versions.clj") 2 | (defproject celtuce celtuce-version 3 | :description "An idiomatic Clojure Redis client wrapping the Java client Lettuce" 4 | :url "https://github.com/lerouxrgd/celtuce" 5 | :license {:name "Apache License 2.0" 6 | :url "http://www.apache.org/licenses/LICENSE-2.0"} 7 | :dependencies [[org.clojure/clojure ~clj-version] 8 | [celtuce-core ~celtuce-version] 9 | [celtuce-pool ~celtuce-version] 10 | [celtuce-manifold ~celtuce-version]] 11 | :global-vars {*warn-on-reflection* true}) 12 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/args/zset.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.args.zset 2 | (:import 3 | (io.lettuce.core ZAddArgs ZAddArgs$Builder ZStoreArgs))) 4 | 5 | (defn ^ZAddArgs zadd-args [opt] 6 | (case opt 7 | :nx (ZAddArgs$Builder/nx) 8 | :xx (ZAddArgs$Builder/xx) 9 | :ch (ZAddArgs$Builder/ch) 10 | (throw (ex-info "invalid zadd opt" {:opt opt})))) 11 | 12 | (defn zstore-args [agg & weights] 13 | (cond-> (ZStoreArgs.) 14 | (not (empty? weights)) (.weights ^doubles (into-array Double/TYPE weights)) 15 | (= :sum agg) (.sum) 16 | (= :min agg) (.min) 17 | (= :max agg) (.max))) 18 | -------------------------------------------------------------------------------- /modules/celtuce-core/project.clj: -------------------------------------------------------------------------------- 1 | (load-file "../../.deps-versions.clj") 2 | (defproject celtuce-core celtuce-version 3 | :description "An idiomatic Clojure Redis client wrapping the Java client Lettuce" 4 | :url "https://github.com/lerouxrgd/celtuce" 5 | :license {:name "Apache License 2.0" 6 | :url "http://www.apache.org/licenses/LICENSE-2.0"} 7 | :dependencies [[org.clojure/clojure ~clj-version] 8 | [io.lettuce/lettuce-core "6.1.1.RELEASE"] 9 | [potemkin "0.4.5"] 10 | [com.taoensso/nippy "3.1.1"] 11 | [com.twitter/carbonite "1.5.0"]] 12 | :global-vars {*warn-on-reflection* true}) 13 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/args/kill.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.args.kill 2 | (:import 3 | (io.lettuce.core KillArgs KillArgs$Builder))) 4 | 5 | (defn ^KillArgs kill-args [& {skipme :skipme addr :addr id :id type :type}] 6 | {:pre [(or (nil? type) 7 | (#{:pubsub :normal :slave} type))]} 8 | (-> (if type 9 | (cond 10 | (= :pubsub type) (KillArgs$Builder/typePubsub) 11 | (= :normal type) (KillArgs$Builder/typeNormal) 12 | (= :slave type) (KillArgs$Builder/typeSlave)) 13 | (KillArgs.)) 14 | (cond-> 15 | skipme (.skipme skipme) 16 | addr (.addr addr) 17 | id (.id id)))) 18 | 19 | -------------------------------------------------------------------------------- /modules/celtuce-manifold/README.md: -------------------------------------------------------------------------------- 1 | # celtuce-manifold 2 | 3 | Module that provides an implementation for asynchronous commands based on [Manifold][]. 4 | 5 | ## Usage 6 | 7 | [![Clojars Project](https://img.shields.io/clojars/v/celtuce-manifold.svg)](https://clojars.org/celtuce-manifold) 8 | 9 | Commands are wrapped in Manifold's `deferred` which allows for flexible composition of asynchronous results. 10 | 11 | ```clj 12 | (require '[celtuce.manifold :refer [commands-manifold]]) 13 | 14 | (def connector (conn/redis-server "redis://localhost:6379")) 15 | (def cmds (commands-manifold connector)) 16 | 17 | @(redis/set cmds :foo "bar") 18 | @(redis/get cmds :foo) 19 | 20 | (conn/shutdown connector) 21 | ``` 22 | 23 | ## License 24 | 25 | * [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 26 | 27 | [manifold]: https://github.com/ztellman/manifold -------------------------------------------------------------------------------- /modules/celtuce-manifold/src/celtuce/manifold.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.manifold 2 | (:require 3 | [celtuce.connector]) 4 | (:import 5 | (celtuce.connector 6 | RedisServer RedisCluster RedisPubSub) 7 | (io.lettuce.core.api StatefulRedisConnection) 8 | (io.lettuce.core.cluster.api StatefulRedisClusterConnection) 9 | (io.lettuce.core.pubsub StatefulRedisPubSubConnection))) 10 | 11 | (defprotocol CommandsManifold 12 | "Adds support for manifold based asynchronous commands" 13 | (commands-manifold [this])) 14 | 15 | (extend-protocol CommandsManifold 16 | RedisServer 17 | (commands-manifold [this] 18 | (locking clojure.lang.RT/REQUIRE_LOCK 19 | (require '[celtuce.manifold.scan]) 20 | (require '[celtuce.manifold.server])) 21 | (.async ^StatefulRedisConnection (:stateful-conn this))) 22 | RedisCluster 23 | (commands-manifold [this] 24 | (locking clojure.lang.RT/REQUIRE_LOCK 25 | (require '[celtuce.manifold.scan]) 26 | (require '[celtuce.manifold.cluster])) 27 | (.async ^StatefulRedisClusterConnection (:stateful-conn this))) 28 | RedisPubSub 29 | (commands-manifold [this] 30 | (locking clojure.lang.RT/REQUIRE_LOCK 31 | (require '[celtuce.manifold.scan]) 32 | (require '[celtuce.manifold.pubsub])) 33 | (.async ^StatefulRedisPubSubConnection (:stateful-conn this)))) 34 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/args/geo.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.args.geo 2 | (:import 3 | (io.lettuce.core 4 | GeoArgs GeoArgs$Unit GeoArgs$Sort GeoRadiusStoreArgs))) 5 | 6 | (defn ^GeoArgs$Unit ->unit [u] 7 | (case u 8 | :m GeoArgs$Unit/m 9 | :km GeoArgs$Unit/km 10 | :ft GeoArgs$Unit/ft 11 | :mi GeoArgs$Unit/mi 12 | (throw (ex-info "Invalid Unit" {:unit u :valids #{:m :km :ft :mi}})))) 13 | 14 | (defn ^GeoArgs$Sort ->sort [s] 15 | (case s 16 | :asc GeoArgs$Sort/asc 17 | :desc GeoArgs$Sort/desc 18 | :none GeoArgs$Sort/none 19 | (throw (ex-info "Invalid Sort" {:sort s :valids #{:asc :desc :none}})))) 20 | 21 | (defn geo-args [& {with-dist :with-dist with-coord :with-coord with-hash :with-hash 22 | count :count sort :sort}] 23 | (cond-> (GeoArgs.) 24 | with-dist (.withDistance) 25 | with-coord (.withCoordinates) 26 | with-hash (.withHash) 27 | count (.withCount count) 28 | sort (.sort (->sort sort)))) 29 | 30 | (defn georadius-store-args [& {store-key :store-key store-dist-key :store-dist-key 31 | count :count sort :sort}] 32 | (cond-> (GeoRadiusStoreArgs.) 33 | store-key (.withStore store-key) 34 | store-dist-key (.withStoreDist store-dist-key) 35 | count (.withCount count) 36 | sort (.sort (->sort sort)))) 37 | 38 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/impl/pubsub.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.impl.pubsub 2 | (:refer-clojure :exclude [get set keys sort type eval time]) 3 | (:require 4 | [celtuce.commands :refer :all]) 5 | (:import 6 | (io.lettuce.core.pubsub.api.sync RedisPubSubCommands))) 7 | 8 | (extend-type RedisPubSubCommands 9 | PubSubCommands 10 | (publish [this channel message] 11 | (.publish this channel message)) 12 | (subscribe [this channel] 13 | (.subscribe this (into-array Object [channel]))) 14 | (unsubscribe [this channel] 15 | (.unsubscribe this (into-array Object [channel]))) 16 | (msubscribe [this channels] 17 | (.subscribe this (into-array Object channels))) 18 | (munsubscribe [this channels] 19 | (.unsubscribe this (into-array Object channels))) 20 | (psubscribe [this pattern] 21 | (.psubscribe this (into-array Object [pattern]))) 22 | (punsubscribe [this pattern] 23 | (.punsubscribe this (into-array Object [pattern]))) 24 | (mpsubscribe [this patterns] 25 | (.psubscribe this (into-array Object patterns))) 26 | (mpunsubscribe [this patterns] 27 | (.punsubscribe this (into-array Object patterns))) 28 | (pubsub-channels 29 | ([this] 30 | (into [] (.pubsubChannels this))) 31 | ([this channel] 32 | (into [] (.pubsubChannels this channel)))) 33 | (pubsub-numsub [this channel] 34 | (into {} (.pubsubNumsub this ^objects (into-array Object [channel])))) 35 | (pubsub-numpat [this] 36 | (.pubsubNumpat this))) 37 | -------------------------------------------------------------------------------- /modules/celtuce-manifold/src/celtuce/manifold/pubsub.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.manifold.pubsub 2 | (:require 3 | [celtuce.commands :refer :all] 4 | [manifold.deferred :as d]) 5 | (:import 6 | (io.lettuce.core.pubsub.api.async RedisPubSubAsyncCommands))) 7 | 8 | (extend-type RedisPubSubAsyncCommands 9 | PubSubCommands 10 | (publish [this channel message] 11 | (d/->deferred (.publish this channel message))) 12 | (subscribe [this channel] 13 | (d/->deferred (.subscribe this (into-array Object [channel])))) 14 | (unsubscribe [this channel] 15 | (d/->deferred (.unsubscribe this (into-array Object [channel])))) 16 | (msubscribe [this channels] 17 | (d/->deferred (.subscribe this (into-array Object channels)))) 18 | (munsubscribe [this channels] 19 | (d/->deferred (.unsubscribe this (into-array Object channels)))) 20 | (psubscribe [this pattern] 21 | (d/->deferred (.psubscribe this (into-array Object [pattern])))) 22 | (punsubscribe [this pattern] 23 | (d/->deferred (.punsubscribe this (into-array Object [pattern])))) 24 | (mpsubscribe [this patterns] 25 | (d/->deferred (.psubscribe this (into-array Object patterns)))) 26 | (mpunsubscribe [this patterns] 27 | (d/->deferred (.punsubscribe this (into-array Object patterns)))) 28 | (pubsub-channels 29 | ([this] 30 | (d/chain (d/->deferred (.pubsubChannels this)) 31 | #(into [] %))) 32 | ([this channel] 33 | (d/chain (d/->deferred (.pubsubChannels this channel)) 34 | #(into [] %)))) 35 | (pubsub-numsub [this channel] 36 | (d/chain (d/->deferred (.pubsubNumsub this ^objects (into-array Object [channel]))) 37 | #(into {} %))) 38 | (pubsub-numpat [this] 39 | (d/->deferred (.pubsubNumpat this)))) 40 | 41 | -------------------------------------------------------------------------------- /test/celtuce/server_dynamic_test.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.server-dynamic-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [celtuce.connector :as conn])) 5 | 6 | (def redis-url "redis://localhost:6379") 7 | (def ^:dynamic *cmds*) 8 | 9 | (gen-interface 10 | :name io.celtuce.MyCommands 11 | :extends [io.lettuce.core.dynamic.Commands] 12 | :methods 13 | [[^{io.lettuce.core.dynamic.annotation.Command "GET"} 14 | myGetRaw 15 | [String] 16 | bytes] 17 | [^{io.lettuce.core.dynamic.annotation.Command "GET"} 18 | myGet 19 | [String] 20 | String] 21 | [^{io.lettuce.core.dynamic.annotation.Command "SET ?0 ?1"} 22 | mySet 23 | [String String] 24 | String] 25 | [flushall 26 | [] 27 | Object] 28 | ]) 29 | 30 | (defprotocol MyCommands 31 | (my-get-raw [this k]) 32 | (my-get [this k]) 33 | (my-set [this k v]) 34 | (flushall [this])) 35 | 36 | (extend-type io.celtuce.MyCommands 37 | MyCommands 38 | (my-get-raw [this k] 39 | (.myGetRaw this k)) 40 | (my-get [this k] 41 | (.myGet this k)) 42 | (my-set [this k v] 43 | (.mySet this k v)) 44 | (flushall [this] 45 | (.flushall this))) 46 | 47 | (defn cmds-fixture [test-function] 48 | (let [rserv (conn/redis-server redis-url)] 49 | (binding [*cmds* (conn/commands-dynamic rserv io.celtuce.MyCommands)] 50 | (try (test-function) 51 | (finally (conn/shutdown rserv)))))) 52 | 53 | (defn flush-fixture [test-function] 54 | (flushall *cmds*) 55 | (test-function)) 56 | 57 | (use-fixtures :once cmds-fixture) 58 | (use-fixtures :each flush-fixture) 59 | 60 | (deftest my-commands-test 61 | (testing "set and get various keys/values" 62 | (my-set *cmds* "foo" "bar") 63 | (is (= "bar" (my-get *cmds* "foo"))) 64 | (is (= "bar" (String. ^bytes (my-get-raw *cmds* "foo")))))) 65 | -------------------------------------------------------------------------------- /test/celtuce/cluster_dynamic_test.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.cluster-dynamic-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [celtuce.connector :as conn])) 5 | 6 | (def redis-url "redis://localhost:30001") 7 | (def ^:dynamic *cmds*) 8 | 9 | (gen-interface 10 | :name io.celtuce.MyCommands 11 | :extends [io.lettuce.core.dynamic.Commands] 12 | :methods 13 | [[^{io.lettuce.core.dynamic.annotation.Command "GET"} 14 | myGetRaw 15 | [String] 16 | bytes] 17 | [^{io.lettuce.core.dynamic.annotation.Command "GET"} 18 | myGet 19 | [String] 20 | String] 21 | [^{io.lettuce.core.dynamic.annotation.Command "SET ?0 ?1"} 22 | mySet 23 | [String String] 24 | String] 25 | [flushall 26 | [] 27 | Object] 28 | ]) 29 | 30 | (defprotocol MyCommands 31 | (my-get-raw [this k]) 32 | (my-get [this k]) 33 | (my-set [this k v]) 34 | (flushall [this])) 35 | 36 | (extend-type io.celtuce.MyCommands 37 | MyCommands 38 | (my-get-raw [this k] 39 | (.myGetRaw this k)) 40 | (my-get [this k] 41 | (.myGet this k)) 42 | (my-set [this k v] 43 | (.mySet this k v)) 44 | (flushall [this] 45 | (.flushall this))) 46 | 47 | (defn cmds-fixture [test-function] 48 | (let [rclust (conn/redis-cluster redis-url)] 49 | (binding [*cmds* (conn/commands-dynamic rclust io.celtuce.MyCommands)] 50 | (try (test-function) 51 | (finally (conn/shutdown rclust)))))) 52 | 53 | (defn flush-fixture [test-function] 54 | (flushall *cmds*) 55 | (test-function)) 56 | 57 | (use-fixtures :once cmds-fixture) 58 | (use-fixtures :each flush-fixture) 59 | 60 | (deftest my-commands-test 61 | (testing "set and get various keys/values" 62 | (my-set *cmds* "foo" "bar") 63 | (is (= "bar" (my-get *cmds* "foo"))) 64 | (is (= "bar" (String. ^bytes (my-get-raw *cmds* "foo")))) 65 | )) 66 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/args/bitfield.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.args.bitfield 2 | (:import 3 | (io.lettuce.core 4 | BitFieldArgs 5 | BitFieldArgs$BitFieldType 6 | BitFieldArgs$OverflowType))) 7 | 8 | (defn ^BitFieldArgs$BitFieldType bft 9 | "Constructs a BitFieldType from a keyword" 10 | [bft-kw] 11 | (if-let [[_ sign bits] (re-find #"(^[us])(\d+)$" ((fnil name "") bft-kw))] 12 | (case sign 13 | "s" (BitFieldArgs/signed (Integer/parseInt bits)) 14 | "u" (BitFieldArgs/unsigned (Integer/parseInt bits))) 15 | (throw 16 | (ex-info "invalid bitfield type keyword" 17 | {:value bft-kw :valid #"(^[us])(\d+)$"})))) 18 | 19 | (defn bitfield-args 20 | "Constructs a BitFieldArgs from a chain of commands" 21 | [& commands] 22 | (loop [args (BitFieldArgs.) 23 | [sub & tail] commands] 24 | (case sub 25 | :overflow 26 | (let [[behavior & tail] tail] 27 | (case behavior 28 | :wrap (.overflow args BitFieldArgs$OverflowType/WRAP) 29 | :sat (.overflow args BitFieldArgs$OverflowType/SAT) 30 | :fail (.overflow args BitFieldArgs$OverflowType/FAIL) 31 | (throw 32 | (ex-info "invalid :overflow" 33 | {:value behavior :valid #{:wrap :sat :fail}}))) 34 | (if (nil? tail) 35 | (throw 36 | (ex-info (str "no sub-command after :overflow " behavior) 37 | {:value tail :valid #{:get :set :incrby}})) 38 | (recur args tail))) 39 | :get 40 | (let [[bft-kw offset & tail] tail] 41 | (.get args (bft bft-kw) ^int offset) 42 | (if (nil? tail) 43 | args 44 | (recur args tail))) 45 | :set 46 | (let [[bft-kw offset value & tail] tail] 47 | (.set args (bft bft-kw) ^int offset ^long value) 48 | (if (nil? tail) 49 | args 50 | (recur args tail))) 51 | :incrby 52 | (let [[bft-kw offset amount & tail] tail] 53 | (.incrBy args (bft bft-kw) ^int offset ^long amount) 54 | (if (nil? tail) 55 | args 56 | (recur args tail)))))) 57 | -------------------------------------------------------------------------------- /test/celtuce/pool_test.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.pool-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [celtuce.commands :as redis] 5 | [celtuce.connector :as conn] 6 | [celtuce.pool :as pool] 7 | [celtuce.manifold :refer [commands-manifold]] 8 | [manifold.deferred :as d])) 9 | 10 | (def redis-url "redis://localhost:6379") 11 | (def redis-conn (conn/redis-server redis-url)) 12 | 13 | (defn cleanup-fixture [test-function] 14 | (redis/flushall (conn/commands-sync redis-conn)) 15 | (test-function) 16 | (conn/shutdown redis-conn)) 17 | 18 | (use-fixtures :once cleanup-fixture) 19 | 20 | (deftest pool-test 21 | (testing "pooled transaction" 22 | (let [conn-pool (pool/conn-pool redis-conn conn/commands-sync)] 23 | (pool/with-conn-pool conn-pool cmds 24 | (redis/multi cmds) 25 | (redis/set cmds :a 1) 26 | (redis/set cmds :b 2) 27 | (redis/get cmds :a) 28 | (redis/get cmds :b) 29 | (is (= ["OK" "OK" 1 2] (redis/exec cmds)))) 30 | (pool/close conn-pool))) 31 | 32 | (testing "async pooled transaction with future" 33 | (let [conn-pool (pool/conn-pool redis-conn conn/commands-sync) 34 | res (pool/with-conn-pool* conn-pool cmds c 35 | (future ;; simulating async commands 36 | (redis/multi cmds) 37 | (redis/set cmds :c 3) 38 | (redis/set cmds :d 4) 39 | (redis/get cmds :c) 40 | (redis/get cmds :d) 41 | (let [res (redis/exec cmds)] 42 | (pool/return-conn conn-pool c) 43 | res)))] 44 | (is (= ["OK" "OK" 3 4] @res)) 45 | (pool/close conn-pool))) 46 | 47 | (testing "async pooled transaction with manifold" 48 | (let [conn-pool (pool/conn-pool redis-conn commands-manifold) 49 | res (pool/with-conn-pool* conn-pool cmds c 50 | (d/chain (redis/multi cmds) 51 | (redis/set cmds :e 5) 52 | (redis/set cmds :f 6) 53 | (redis/get cmds :e) 54 | (redis/get cmds :f) 55 | (fn [_] (redis/exec cmds)) 56 | (fn [res] 57 | (pool/return-conn conn-pool c) 58 | res)) )] 59 | (is (= ["OK" "OK" 5 6] @res)) 60 | (pool/close conn-pool)))) 61 | -------------------------------------------------------------------------------- /test/celtuce/connector_test.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.connector-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [celtuce.connector :as conn])) 5 | 6 | (def redis-server-url "redis://localhost:6379") 7 | (def redis-cluster-url "redis://localhost:30001") 8 | 9 | (deftest redis-connector-options-test 10 | 11 | (testing "default options for redis-server" 12 | (conn/redis-server 13 | redis-server-url 14 | :conn-options 15 | {:auto-flush true} 16 | :client-options 17 | {:ping-before-activate-connection false 18 | :suspend-reconnect-on-protocol-failure false 19 | :cancel-commands-on-reconnect-failure false 20 | :auto-reconnect true 21 | :request-queue-size Integer/MAX_VALUE 22 | :disconnected-behavior :default 23 | :timeout-options 24 | {:fixed-timeout {:timeout 2 :unit :seconds} :timeout-commands true} 25 | :socket-options 26 | {:timeout 10, :unit :seconds, :keep-alive false, :tcp-no-delay false} 27 | :ssl-options 28 | {:provider :jdk}})) 29 | 30 | (testing "default options for redis-cluster" 31 | (conn/redis-cluster 32 | redis-cluster-url 33 | :conn-options 34 | {:auto-flush true} 35 | :client-options 36 | {;; regular client-options 37 | :ping-before-activate-connection false 38 | :suspend-reconnect-on-protocol-failure false 39 | :cancel-commands-on-reconnect-failure false 40 | :auto-reconnect true 41 | :request-queue-size Integer/MAX_VALUE 42 | :disconnected-behavior :default 43 | :timeout-options 44 | {:fixed-timeout {:timeout 2 :unit :seconds} :timeout-commands true} 45 | :socket-options 46 | {:timeout 10, :unit :seconds, :keep-alive false, :tcp-no-delay false} 47 | :ssl-options 48 | {:provider :jdk} 49 | ;; specific cluster options 50 | :validate-cluster-node-membership true 51 | :max-redirects 5 52 | :topology-refresh-options 53 | {:enable-periodic-refresh false 54 | :refresh-period {:period 60 :unit :seconds} 55 | :close-stale-connections true 56 | :dynamic-refresh-sources true 57 | :enable-adaptive-refresh-trigger #{} 58 | :adaptive-refresh-triggers-timeout {:timeout 30 :unit :seconds} 59 | :refresh-triggers-reconnect-attempts 5}}))) 60 | 61 | -------------------------------------------------------------------------------- /modules/celtuce-pool/README.md: -------------------------------------------------------------------------------- 1 | # celtuce-pool 2 | 3 | Module that provides [connection pooling][conn-pool] for `connector`s. 4 | 5 | ## Usage 6 | 7 | [![Clojars Project](https://img.shields.io/clojars/v/celtuce-pool.svg)](https://clojars.org/celtuce-pool) 8 | 9 | Pooling connections is particularly useful for transactions or long running time commands 10 | (so that other threads can use different connections in the meantime). 11 | 12 | ### Connection Pool for Synchronous Commands 13 | 14 | ```clj 15 | (require '[celtuce.connector :as conn] 16 | '[celtuce.pool :as pool] 17 | '[celtuce.commands :as redis]) 18 | 19 | (def connector (conn/redis-server "redis://localhost:6379")) 20 | 21 | ;; Create a pool of connections from a connector 22 | ;; and specify a command function to use on it 23 | (def sync-conn-pool (pool/conn-pool connector conn/commands-sync)) 24 | 25 | ;; Use a connection from the pool, call command function on it 26 | ;; and binds the resulting commands to cmds 27 | (pool/with-conn-pool sync-conn-pool cmds 28 | (redis/multi cmds) 29 | (redis/set cmds :a 1) 30 | (redis/set cmds :b 2) 31 | (redis/get cmds :a) 32 | (redis/get cmds :b) 33 | (redis/exec cmds)) 34 | 35 | ;; When you are done using the connection pool 36 | (pool/close sync-conn-pool) 37 | (conn/shutdown connector) 38 | ``` 39 | 40 | ### Connection Pool for Asynchronous Commands 41 | 42 | ```clj 43 | (require '[celtuce.connector :as conn] 44 | '[celtuce.pool :as pool] 45 | '[celtuce.manifold :as celtuce-manifold] 46 | '[manifold.deferred :as d] 47 | '[celtuce.commands :as redis]) 48 | 49 | (def connector (conn/redis-server "redis://localhost:6379")) 50 | 51 | ;; Create a pool of connections from a connector 52 | ;; and specify a command function to use on it 53 | (def async-conn-pool (pool/conn-pool connector celtuce-manifold/commands-manifold)) 54 | 55 | ;; Use a connection from the pool, call command function on it 56 | ;; and binds the resulting commands to cmds 57 | (let [result (pool/with-conn-pool* async-conn-pool cmds c 58 | (d/chain (redis/multi cmds) 59 | (redis/set cmds :a 1) 60 | (redis/set cmds :b 2) 61 | (redis/get cmds :a) 62 | (redis/get cmds :b) 63 | (fn [_] (redis/exec cmds)) 64 | (fn [res] (pool/return-conn async-conn-pool c) res)))] 65 | (= ["OK" "OK" 1 2] @result)) 66 | 67 | ;; When you are done using the connection pool 68 | (pool/close async-conn-pool) 69 | (conn/shutdown connector) 70 | ``` 71 | 72 | 73 | ### Connection Pool Configuration 74 | Note that `conn-pool` can take an optional pool configuration: 75 | 76 | ```clj 77 | (pool/conn-pool connector conn/commands-sync 78 | {:max-total 8, :max-idle 8, :min-idle 0}) 79 | ``` 80 | 81 | 82 | ```clj 83 | (pool/conn-pool connector celtuce-manifold/commands-manifold 84 | {:max-total 8, :max-idle 8, :min-idle 0}) 85 | ``` 86 | 87 | ## License 88 | 89 | * [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 90 | 91 | [conn-pool]: https://github.com/lettuce-io/lettuce-core/wiki/Connection-Pooling 92 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/codec.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.codec 2 | (:require 3 | [carbonite.api :refer [default-registry]] 4 | [taoensso.nippy :as nippy]) 5 | (:import 6 | (io.lettuce.core.codec 7 | RedisCodec Utf8StringCodec ByteArrayCodec 8 | CompressionCodec CompressionCodec$CompressionType) 9 | (java.io ByteArrayOutputStream ByteArrayInputStream) 10 | (java.nio ByteBuffer) 11 | (com.esotericsoftware.kryo Kryo) 12 | (com.esotericsoftware.kryo.io Output Input) 13 | (com.esotericsoftware.kryo.pool KryoFactory KryoPool KryoPool$Builder))) 14 | 15 | (defn ^"[B" bb->bytes [^ByteBuffer bb] 16 | (let [bytes (byte-array (.remaining bb))] 17 | (.get bb bytes) 18 | bytes)) 19 | 20 | (defn ^ByteBuffer bytes->bb [^bytes b] 21 | (ByteBuffer/wrap b)) 22 | 23 | ;; Lettuce codecs 24 | 25 | (defn utf8-string-codec [] 26 | (Utf8StringCodec.)) 27 | 28 | (defn byte-array-codec [] 29 | (ByteArrayCodec/INSTANCE)) 30 | 31 | (defn compression-codec [^RedisCodec delegate-codec compression-type] 32 | {:pre [(#{:gzip :deflate} compression-type)]} 33 | (let [type (case compression-type 34 | :gzip CompressionCodec$CompressionType/GZIP 35 | :deflate CompressionCodec$CompressionType/DEFLATE)] 36 | (CompressionCodec/valueCompressor delegate-codec type))) 37 | 38 | ;; Carbonite based codec 39 | 40 | (defn kryo-read 41 | "Deserialize obj from ByteBuffer bb" 42 | [^Kryo kryo bb] 43 | (with-open [in (Input. (ByteArrayInputStream. (bb->bytes bb)))] 44 | (.readClassAndObject kryo in))) 45 | 46 | (defn kryo-write 47 | "Serialize obj to ByteBuffer" 48 | [^Kryo kryo obj] 49 | (let [bos (ByteArrayOutputStream.)] 50 | (with-open [out (Output. bos)] 51 | (.writeClassAndObject kryo out obj)) 52 | (bytes->bb (.toByteArray bos)))) 53 | 54 | (defn kryos-pool 55 | "Kryo objects pool with soft references to allow for GC when running out of memory" 56 | [kryo-factory] 57 | (-> (proxy [KryoFactory] [] 58 | (create [] 59 | (kryo-factory))) 60 | (KryoPool$Builder.) 61 | .softReferences 62 | .build)) 63 | 64 | (defmacro with-kryos-pool 65 | "Inject a Kryo object from kryo-pool as the first parameter of form and run it" 66 | [kryo-pool form] 67 | (let [kryo-pool (vary-meta kryo-pool assoc :tag `KryoPool)] 68 | `(let [kryo# (.borrow ~kryo-pool) 69 | res# (-> kryo# ~form)] 70 | (.release ~kryo-pool kryo#) 71 | res#))) 72 | 73 | (defn carbonite-codec 74 | ([] 75 | (carbonite-codec (fn [] (default-registry)))) 76 | ([kryo-factory] 77 | (let [kryos (kryos-pool kryo-factory)] 78 | (proxy [RedisCodec] [] 79 | (decodeKey [bb] 80 | (with-kryos-pool kryos (kryo-read bb))) 81 | (decodeValue [bb] 82 | (with-kryos-pool kryos (kryo-read bb))) 83 | (encodeKey [k] 84 | (with-kryos-pool kryos (kryo-write k))) 85 | (encodeValue [v] 86 | (with-kryos-pool kryos (kryo-write v))))))) 87 | 88 | ;; Nippy based codec 89 | 90 | (defn nippy-codec 91 | ([] 92 | (nippy-codec nil nil)) 93 | ([freeze-opts thaw-opts] 94 | (proxy [RedisCodec] [] 95 | (decodeKey [bb] 96 | (nippy/thaw (bb->bytes bb) thaw-opts)) 97 | (decodeValue [bb] 98 | (nippy/thaw (bb->bytes bb) thaw-opts)) 99 | (encodeKey [k] 100 | (bytes->bb (nippy/freeze k freeze-opts))) 101 | (encodeValue [v] 102 | (bytes->bb (nippy/freeze v freeze-opts)))))) 103 | 104 | -------------------------------------------------------------------------------- /modules/celtuce-pool/src/celtuce/pool.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.pool 2 | (:import 3 | (java.util.function Supplier) 4 | (io.lettuce.core RedisClient) 5 | (io.lettuce.core.cluster RedisClusterClient) 6 | (io.lettuce.core.codec RedisCodec) 7 | (io.lettuce.core.api StatefulRedisConnection) 8 | (io.lettuce.core.cluster.api StatefulRedisClusterConnection) 9 | (io.lettuce.core.pubsub StatefulRedisPubSubConnection) 10 | (io.lettuce.core.support ConnectionPoolSupport) 11 | (org.apache.commons.pool2.impl GenericObjectPool GenericObjectPoolConfig))) 12 | 13 | (defprotocol ConnectionPool 14 | "Functions for using the connection pool" 15 | (borrow-conn [this]) 16 | (return-conn [this conn]) 17 | (close [this])) 18 | 19 | (defrecord ConnectionPoolImpl [^GenericObjectPool conn-pool connector cmds-fn] 20 | ConnectionPool 21 | (borrow-conn [_] 22 | (.borrowObject conn-pool)) 23 | (return-conn [_ conn] 24 | (.returnObject conn-pool conn)) 25 | (close [_] 26 | (.close conn-pool))) 27 | 28 | (defn ^GenericObjectPoolConfig pool-config 29 | "Internal helper to build GenericObjectPoolConfig from a map" 30 | [{:keys [max-total max-idle min-idle] 31 | :or {max-total GenericObjectPoolConfig/DEFAULT_MAX_TOTAL 32 | max-idle GenericObjectPoolConfig/DEFAULT_MAX_IDLE 33 | min-idle GenericObjectPoolConfig/DEFAULT_MIN_IDLE}}] 34 | (doto (GenericObjectPoolConfig.) 35 | (.setMaxTotal max-total) 36 | (.setMaxIdle max-idle) 37 | (.setMinIdle min-idle))) 38 | 39 | (defn conn-pool 40 | "Create a ConnectionPoolImpl that wraps a ConnectionPoolSupport. 41 | Takes a connector and a command function that will be called on pooled connections. 42 | Last parameter (options) allows to fully customize your pool based on Apache2 GenericObjectPoolConfig; 43 | you have two options: pass a map with keys max-idle, min-idle and max-total or a org.apache.commons.pool2.impl.GenericObjectPoolConfig" 44 | ([connector cmds-fn] 45 | (conn-pool connector cmds-fn {})) 46 | ([{:keys [redis-client stateful-conn codec] :as connector} cmds-fn options] 47 | (->ConnectionPoolImpl 48 | (ConnectionPoolSupport/createGenericObjectPool 49 | (reify Supplier 50 | (get [this] 51 | (condp instance? stateful-conn 52 | StatefulRedisConnection 53 | (.connect ^RedisClient redis-client ^RedisCodec codec) 54 | StatefulRedisClusterConnection 55 | (.connect ^RedisClusterClient redis-client codec) 56 | StatefulRedisPubSubConnection 57 | (condp instance? redis-client 58 | RedisClusterClient 59 | (.connectPubSub ^RedisClusterClient redis-client codec) 60 | RedisClient 61 | (.connectPubSub ^RedisClient redis-client ^RedisCodec codec))))) 62 | (if (map? options) 63 | (pool-config options) 64 | options)) 65 | connector 66 | cmds-fn))) 67 | 68 | (defmacro with-conn-pool 69 | "Takes a ConnectionPool `coon-pool` and a `cmds-name` symbol that will be bound to 70 | the command function of the pool called on a borrowed connection" 71 | [conn-pool cmds-name & body] 72 | `(let [conn# (borrow-conn ~conn-pool) 73 | ~cmds-name ((:cmds-fn ~conn-pool) 74 | (assoc (:connector ~conn-pool) :stateful-conn conn#))] 75 | (try 76 | ~@body 77 | (finally (return-conn ~conn-pool conn#))))) 78 | 79 | (defmacro with-conn-pool* 80 | "Like with-conn-pool but also binds the pooled connection to `conn-name`. 81 | User is responsible for returning it to the pool within `body`" 82 | [conn-pool cmds-name conn-name & body] 83 | `(let [~conn-name (borrow-conn ~conn-pool) 84 | ~cmds-name ((:cmds-fn ~conn-pool) 85 | (assoc (:connector ~conn-pool) :stateful-conn ~conn-name))] 86 | ~@body)) 87 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/scan.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.scan 2 | (:import 3 | (io.lettuce.core 4 | ScanArgs ScanCursor ScanIterator 5 | KeyScanCursor ValueScanCursor MapScanCursor ScoredValueScanCursor 6 | KeyValue ScoredValue))) 7 | 8 | (defn ^ScanArgs scan-args [& {limit :limit match :match}] 9 | (cond-> (ScanArgs.) 10 | limit (.limit (long limit)) 11 | match (.match ^String match))) 12 | 13 | (defprotocol PScanCursor 14 | (get-cursor [this] "Get the String cursor id") 15 | (finished? [this] "True if the scan operation of this cursor is finished")) 16 | 17 | (extend-type ScanCursor 18 | PScanCursor 19 | (get-cursor [this] (.getCursor this)) 20 | (finished? [this] (.isFinished this))) 21 | 22 | (defprotocol PScanResult 23 | (scan-res [this] "Get the data contained in a scan cursor result")) 24 | 25 | (extend-protocol PScanResult 26 | MapScanCursor 27 | (scan-res [this] 28 | (into {} (.getMap this))) 29 | KeyScanCursor 30 | (scan-res [this] 31 | (into [] (.getKeys this))) 32 | ValueScanCursor 33 | (scan-res [this] 34 | (into [] (.getValues this))) 35 | ScoredValueScanCursor 36 | (scan-res [this] 37 | (->> (.getValues this) 38 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 39 | (into [])))) 40 | 41 | (defn ^ScanCursor scan-cursor 42 | ([] 43 | ScanCursor/INITIAL) 44 | ([cursor] 45 | (doto (ScanCursor.) 46 | (.setCursor cursor)))) 47 | 48 | (defn chunked-scan-seq* [scan-fn cursor args] 49 | (let [cursor (if (instance? clojure.lang.IDeref cursor) @cursor cursor)] 50 | (when-not (finished? cursor) 51 | (let [cursor-res (if args (scan-fn cursor args) (scan-fn cursor))] 52 | (lazy-seq (cons (scan-res cursor-res) 53 | (chunked-scan-seq* scan-fn cursor-res args))))))) 54 | 55 | (defmacro chunked-scan-seq 56 | "Takes a scan EXPR composed of: 57 | - a command: \"scan\", \"sscan\", \"hscan\", \"zscan\" 58 | - a key when the command is not \"scan\" 59 | - optionals: ScanCursor c, ScanArgs args 60 | Returns a lazy seq that calls scan-res on each cursor iteration result (chunk)." 61 | [scan-expr] 62 | (let [[scan-cmd this a1 a2 a3] scan-expr] 63 | `(cond 64 | ;; scan-cmd is: SCAN 65 | ;; a1 is ScanCursor, a2 is ScanArgs 66 | (or (every? nil? [~a1 ~a2 ~a3]) (instance? ScanCursor ~a1)) 67 | (chunked-scan-seq* (partial ~scan-cmd ~this) (or ~a1 (scan-cursor)) ~a2) 68 | ;; scan-cmd is: SSCAN, HSCAN, or ZSCAN 69 | ;; a1 is a key, a2 is ScanCursor, a3 is ScanArgs 70 | (not= nil ~a1) 71 | (chunked-scan-seq* (partial ~scan-cmd ~this ~a1) (or ~a2 (scan-cursor)) ~a3) 72 | ;; invalid arguments for the given scan-cmd 73 | :else (throw (ex-info "malformed scan command" {:scan-cmd (name '~scan-cmd) 74 | :args ['~a1 '~a2 '~a3]}))))) 75 | 76 | (defn scan-seq 77 | "Lazy SCAN sequence, takes optional scan-args" 78 | ([cmds] 79 | (iterator-seq (ScanIterator/scan cmds))) 80 | ([cmds args] 81 | (iterator-seq (ScanIterator/scan cmds ^ScanArgs args)))) 82 | 83 | (defn hscan-seq 84 | "Lazy HSCAN sequence, takes optional scan-args" 85 | ([cmds key] 86 | (->> (ScanIterator/hscan cmds key) 87 | (iterator-seq) 88 | (map (fn [^KeyValue kv] [(.getKey kv) (.getValue kv)])))) 89 | ([cmds key args] 90 | (->> (ScanIterator/hscan cmds key ^ScanArgs args) 91 | (iterator-seq) 92 | (map (fn [^KeyValue kv] [(.getKey kv) (.getValue kv)]))))) 93 | 94 | (defn sscan-seq 95 | "Lazy SSCAN sequence, takes optional scan-args" 96 | ([cmds key] 97 | (iterator-seq (ScanIterator/sscan cmds key))) 98 | ([cmds key args] 99 | (iterator-seq (ScanIterator/sscan cmds key ^ScanArgs args)))) 100 | 101 | (defn zscan-seq 102 | "Lazy ZSCAN sequence, takes optional scan-args" 103 | ([cmds key] 104 | (->> (ScanIterator/zscan cmds key) 105 | (iterator-seq) 106 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])))) 107 | ([cmds key args] 108 | (->> (ScanIterator/zscan cmds key) 109 | (iterator-seq) 110 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)]))))) 111 | 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # celtuce 2 | 3 | An idiomatic Clojure Redis client wrapping the Java client [Lettuce][]. 4 | 5 | ## Usage 6 | 7 | [![Clojars Project](https://img.shields.io/clojars/v/celtuce.svg)](https://clojars.org/celtuce) to include all [modules][]. 8 | 9 | Or pick up only the ones you need: 10 | 11 | * [celtuce-core][]: Main module with all the core functionalities (required) 12 | 13 | [![Clojars Project](https://img.shields.io/clojars/v/celtuce-core.svg)](https://clojars.org/celtuce-core) 14 | [![cljdoc badge](https://cljdoc.org/badge/celtuce-core/celtuce-core)](https://cljdoc.org/d/celtuce-core/celtuce-core/CURRENT) 15 | 16 | * [celtuce-pool][]: Provides pooling for connections 17 | 18 | [![Clojars Project](https://img.shields.io/clojars/v/celtuce-pool.svg)](https://clojars.org/celtuce-pool) 19 | [![cljdoc badge](https://cljdoc.org/badge/celtuce-pool/celtuce-pool)](https://cljdoc.org/d/celtuce-pool/celtuce-pool/CURRENT) 20 | 21 | * [celtuce-manifold][]: Implementation of asynchronous commands based on [Manifold][] 22 | 23 | [![Clojars Project](https://img.shields.io/clojars/v/celtuce-manifold.svg)](https://clojars.org/celtuce-manifold) 24 | [![cljdoc badge](https://cljdoc.org/badge/celtuce-manifold/celtuce-manifold)](https://cljdoc.org/d/celtuce-manifold/celtuce-manifold/CURRENT) 25 | 26 | ### Redis Connectors 27 | 28 | Connectors are available for both Redis `Server` and `Cluster`. 29 | They are defined in `celtuce.connector` namespace of `celtuce-core` module. 30 | 31 | ```clj 32 | (require '[celtuce.connector :as conn]) 33 | 34 | (conn/redis-server "redis://localhost:6379") 35 | 36 | (conn/redis-cluster "redis://localhost:30001") 37 | ``` 38 | 39 | Redis URI synthax details can be found in [Lettuce Wiki][wiki-uri]. 40 | 41 | Serialization defaults to [Nippy][], but other serializers are available in `celtuce.codec`. 42 | Especially [Lettuce][] original `String` serializer can be used as follows: 43 | 44 | ```clj 45 | (conn/redis-server 46 | "redis://localhost:6379" 47 | :codec (celtuce.codec/utf8-string-codec)) 48 | ``` 49 | 50 | Other connector options: 51 | 52 | * `:conn-options` a map of connection options 53 | * `:timeout` timeout for executing commands 54 | * `:unit` corresponding `TimeUnit` in keyword (i.e. `:milliseconds`, etc) 55 | * `:auto-flush` automatically flush commands on the underlying Netty connection 56 | 57 | * `:client-options`: a map of client options 58 | * [Client-options][] available in Lettuce, with their names keywordized 59 | 60 | Note that you can find options default values in the [tests][tests-connector]. 61 | 62 | ### Redis Commands 63 | 64 | All Redis [commands][] are implemented using protocols in `celtuce.commands` namespace of `celtuce-core` module. 65 | 66 | ```clj 67 | (require '[celtuce.commands :as redis]) 68 | ``` 69 | 70 | **Sync Commands** 71 | 72 | ```clj 73 | (def connector (conn/redis-server "redis://localhost:6379")) 74 | (def cmds (conn/commands-sync connector)) 75 | 76 | (redis/set cmds :foo "bar") 77 | (redis/get cmds :foo) 78 | 79 | (conn/shutdown connector) 80 | ``` 81 | 82 | **PubSub Commands** 83 | 84 | Redis prevents publishing and subscribing on the same connection. 85 | The following contrive example demonstrates pubsub usage with two connections. 86 | 87 | ```clj 88 | ;; note that conn/as-pubsub also works on cluster connectors 89 | (def conn-pub (conn/as-pubsub (conn/redis-server "redis://localhost:6379"))) 90 | (def conn-sub (conn/as-pubsub (conn/redis-server "redis://localhost:6379"))) 91 | 92 | (def pub (conn/commands-sync conn-pub)) 93 | (def sub (conn/commands-sync conn-sub)) 94 | 95 | (conn/add-listener! 96 | conn-sub 97 | (reify redis/PubSubListener 98 | (message [_ channel message] 99 | (println "received message" message "from channel" channel)) 100 | (message [_ pattern channel message]) 101 | (subscribed [_ channel count] 102 | (println "new subscriber !")) 103 | (unsubscribed [_ channel count] 104 | (println "a subscriber left...")) 105 | (psubscribed [_ pattern count]) 106 | (punsubscribed [_ pattern count]))) 107 | 108 | (redis/subscribe sub "foo-chan") 109 | (redis/publish pub "foo-chan" "bar-msg") 110 | (redis/unsubscribe sub "foo-chan") 111 | 112 | (conn/shutdown conn-pub) 113 | (conn/shutdown conn-sub) 114 | ``` 115 | 116 | **Dynamic Commands** 117 | 118 | Starting from Lettuce 5 it is now possible to define commands [dynamically][dynamic] by extending a `Commands` interface. 119 | Such commands are obtained as follows. 120 | 121 | ```clj 122 | (conn/commands-dynamic connector some.interface.extending.Commands) 123 | ``` 124 | 125 | You can find basic examples in the tests. 126 | 127 | ## Tests 128 | 129 | To run unit tests you need to have both a redis server running on a `localhost:6379`, 130 | and a redis cluster running on `localhost:30001`. 131 | 132 | Then build artifacts and run tests: 133 | 134 | ```sh 135 | (cd modules/celtuce-core/; lein do clean, install) 136 | (cd modules/celtuce-pool/; lein do clean, install) 137 | (cd modules/celtuce-manifold/; lein do clean, install) 138 | 139 | lein test 140 | ``` 141 | 142 | ## License 143 | 144 | * [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 145 | 146 | [lettuce]: https://github.com/lettuce-io/lettuce-core 147 | [wiki-uri]: https://github.com/lettuce-io/lettuce-core/wiki/Redis-URI-and-connection-details#uri-syntax 148 | [client-options]: https://github.com/lettuce-io/lettuce-core/wiki/Client-options 149 | [dynamic]: https://github.com/lettuce-io/lettuce-core/wiki/Redis-Command-Interfaces 150 | 151 | [modules]: https://github.com/lerouxrgd/celtuce/tree/master/modules 152 | [commands]: https://github.com/lerouxrgd/celtuce/blob/master/modules/celtuce-core/src/celtuce/commands.clj 153 | [celtuce-core]: https://github.com/lerouxrgd/celtuce/tree/master/modules/celtuce-core 154 | [celtuce-pool]: https://github.com/lerouxrgd/celtuce/tree/master/modules/celtuce-pool 155 | [celtuce-manifold]: https://github.com/lerouxrgd/celtuce/tree/master/modules/celtuce-manifold 156 | [tests-connector]: https://github.com/lerouxrgd/celtuce/blob/master/test/celtuce/connector_test.clj 157 | 158 | [nippy]: https://github.com/ptaoussanis/nippy 159 | [manifold]: https://github.com/ztellman/manifold 160 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/connector.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.connector 2 | (:require 3 | [clojure.java.io :as io] 4 | [celtuce.codec :refer [nippy-codec]] 5 | [celtuce.commands :as cmds]) 6 | (:import 7 | (java.util.concurrent TimeUnit) 8 | (java.time Duration) 9 | (java.time.temporal ChronoUnit) 10 | (io.lettuce.core 11 | RedisClient 12 | ClientOptions ClientOptions$Builder ClientOptions$DisconnectedBehavior 13 | SocketOptions SslOptions TimeoutOptions) 14 | (io.lettuce.core.cluster 15 | ClusterClientOptions ClusterClientOptions$Builder 16 | ClusterTopologyRefreshOptions ClusterTopologyRefreshOptions$RefreshTrigger) 17 | (io.lettuce.core.codec RedisCodec) 18 | (io.lettuce.core.api StatefulRedisConnection) 19 | (io.lettuce.core.dynamic RedisCommandFactory) 20 | (io.lettuce.core.cluster RedisClusterClient) 21 | (io.lettuce.core.cluster.api StatefulRedisClusterConnection) 22 | (io.lettuce.core.pubsub StatefulRedisPubSubConnection RedisPubSubListener) 23 | (io.lettuce.core.resource ClientResources))) 24 | 25 | (defprotocol RedisConnector 26 | "Manipulate Redis client and stateful connection" 27 | (commands-sync [this]) 28 | (commands-dynamic [this cmd-class]) 29 | (flush-commands [this]) 30 | (set-options [this options]) 31 | (reset [this]) 32 | (shutdown [this])) 33 | 34 | (def kw->tunit 35 | {:nanoseconds TimeUnit/NANOSECONDS 36 | :microseconds TimeUnit/MICROSECONDS 37 | :milliseconds TimeUnit/MILLISECONDS 38 | :seconds TimeUnit/SECONDS 39 | :minutes TimeUnit/MINUTES 40 | :hours TimeUnit/HOURS 41 | :days TimeUnit/DAYS}) 42 | 43 | (def kw->cunit 44 | {:nanoseconds ChronoUnit/NANOS 45 | :nanos ChronoUnit/NANOS 46 | :microseconds ChronoUnit/MICROS 47 | :micros ChronoUnit/MICROS 48 | :milliseconds ChronoUnit/MILLIS 49 | :millis ChronoUnit/MILLIS 50 | :seconds ChronoUnit/SECONDS 51 | :minutes ChronoUnit/MINUTES 52 | :hours ChronoUnit/HOURS 53 | :half-day ChronoUnit/HALF_DAYS 54 | :days ChronoUnit/DAYS 55 | :weeks ChronoUnit/WEEKS 56 | :months ChronoUnit/MONTHS 57 | :years ChronoUnit/YEARS 58 | :decades ChronoUnit/DECADES 59 | :centuries ChronoUnit/CENTURIES 60 | :millennia ChronoUnit/MILLENNIA 61 | :eras ChronoUnit/ERAS 62 | :forever ChronoUnit/FOREVER}) 63 | 64 | (def kw->dbehavior 65 | {:default ClientOptions$DisconnectedBehavior/DEFAULT 66 | :accept-commands ClientOptions$DisconnectedBehavior/ACCEPT_COMMANDS 67 | :reject-commmands ClientOptions$DisconnectedBehavior/REJECT_COMMANDS}) 68 | 69 | (def kw->rtrigger 70 | {:moved-redirect 71 | ClusterTopologyRefreshOptions$RefreshTrigger/MOVED_REDIRECT 72 | :ask-redirect 73 | ClusterTopologyRefreshOptions$RefreshTrigger/ASK_REDIRECT 74 | :persistent-reconnects 75 | ClusterTopologyRefreshOptions$RefreshTrigger/PERSISTENT_RECONNECTS}) 76 | 77 | (defn- ^SocketOptions socket-options 78 | "Internal helper to build SocketOptions, used by b-client-options" 79 | [opts] 80 | (cond-> (SocketOptions/builder) 81 | (and (contains? opts :timeout) 82 | (contains? opts :unit)) 83 | (.connectTimeout (:timeout opts) (kw->tunit (:unit opts))) 84 | (contains? opts :keep-alive) 85 | (.keepAlive ^boolean (:keep-alive opts)) 86 | (contains? opts :tcp-no-delay) 87 | (.tcpNoDelay (:tcp-no-delay opts)) 88 | true (.build))) 89 | 90 | (defn- ^SslOptions ssl-options 91 | "Internal helper to build SslOptions, used by b-client-options" 92 | [opts] 93 | (cond-> (SslOptions/builder) 94 | ;; provider setup 95 | (contains? opts :provider) 96 | (cond-> 97 | (= :open-ssl (:provider opts)) (.openSslProvider) 98 | (= :jdk (:provider opts)) (.jdkSslProvider)) 99 | ;; keystore setup 100 | (contains? opts :keystore) 101 | (cond-> 102 | (and (contains? (:keystore opts) :file) 103 | (contains? (:keystore opts) :password)) 104 | (.keystore (io/as-file (-> opts :keystore :file)) 105 | (chars (-> opts :keystore :password))) 106 | (contains? (:keystore opts) :file) 107 | (.keystore (io/as-file (-> opts :keystore :file))) 108 | (and (contains? (:keystore opts) :url) 109 | (contains? (:keystore opts) :password)) 110 | (.keystore (io/as-url (-> opts :keystore :url)) 111 | (chars (-> opts :keystore :password))) 112 | (contains? (:keystore opts) :url) 113 | (.keystore (io/as-url (-> opts :keystore :url)))) 114 | ;; truststore setup 115 | (contains? opts :truststore) 116 | (cond-> 117 | (and (contains? (:truststore opts) :file) 118 | (contains? (:truststore opts) :password)) 119 | (.truststore (io/as-file (-> opts :truststore :file)) 120 | (-> opts :truststore :password str)) 121 | (contains? (:truststore opts) :file) 122 | (.truststore (io/as-file (-> opts :truststore :file))) 123 | (and (contains? (:truststore opts) :url) 124 | (contains? (:truststore opts) :password)) 125 | (.truststore (io/as-url (-> opts :truststore :url)) 126 | ^String (-> opts :truststore :password str)) 127 | (contains? (:truststore opts) :url) 128 | (.truststore (io/as-url (-> opts :truststore :url)))) 129 | ;; finally, build 130 | true (.build))) 131 | 132 | (defn- ^TimeoutOptions timeout-options 133 | "Internal helper to build TimeoutOptions, used by b-client-options" 134 | [opts] 135 | (cond-> (TimeoutOptions/builder) 136 | (and (contains? (:fixed-timeout opts) :timeout) 137 | (contains? (:fixed-timeout opts) :unit)) 138 | (.fixedTimeout 139 | (Duration/of (-> opts :fixed-timeout :timeout) (kw->cunit (-> opts :fixed-timeout :unit)))) 140 | (contains? opts :timeout-commands) 141 | (.timeoutCommands (:timeout-commands opts)) 142 | true (.build))) 143 | 144 | (defn- ^ClientOptions$Builder b-client-options 145 | "Sets up a ClientOptions builder from a map of options" 146 | ([opts] 147 | (b-client-options (ClientOptions/builder) opts)) 148 | ([^ClientOptions$Builder builder opts] 149 | (cond-> builder 150 | (contains? opts :ping-before-activate-connection) 151 | (.pingBeforeActivateConnection (:ping-before-activate-connection opts)) 152 | (contains? opts :auto-reconnect) 153 | (.autoReconnect (:auto-reconnect opts)) 154 | (contains? opts :suspend-reconnect-on-protocol-failure) 155 | (.suspendReconnectOnProtocolFailure (:suspend-reconnect-on-protocol-failure opts)) 156 | (contains? opts :cancel-commands-on-reconnect-failure) 157 | (.cancelCommandsOnReconnectFailure (:cancel-commands-on-reconnect-failure opts)) 158 | (contains? opts :request-queue-size) 159 | (.requestQueueSize (:request-queue-size opts)) 160 | (contains? opts :disconnected-behavior) 161 | (.disconnectedBehavior (-> opts :disconnected-behavior kw->dbehavior)) 162 | (contains? opts :socket-options) 163 | (.socketOptions (socket-options (:socket-options opts))) 164 | (contains? opts :timeout-options) 165 | (.timeoutOptions (timeout-options (:timeout-options opts))) 166 | (contains? opts :ssl-options) 167 | (.sslOptions (ssl-options (:ssl-options opts)))))) 168 | 169 | (defn- ^ClusterTopologyRefreshOptions cluster-topo-refresh-options 170 | "Internal helper to build ClusterTopologyRefreshOptions, 171 | used by b-cluster-client-options" 172 | [opts] 173 | (cond-> (ClusterTopologyRefreshOptions/builder) 174 | (and (contains? opts :enable-periodic-refresh) 175 | (true? (:enable-periodic-refresh opts)) 176 | (contains? opts :refresh-period)) 177 | (.enablePeriodicRefresh 178 | (Duration/of (-> opts :refresh-period :period) 179 | (-> opts :refresh-period :unit kw->cunit))) 180 | (contains? opts :close-stale-connections) 181 | (.closeStaleConnections (:close-stale-connections opts)) 182 | (contains? opts :dynamic-refresh-sources) 183 | (.dynamicRefreshSources (:dynamic-refresh-sources opts)) 184 | (contains? opts :enable-adaptive-refresh-trigger) 185 | (cond-> 186 | (= :all (:enable-adaptive-refresh-trigger opts)) 187 | (.enableAllAdaptiveRefreshTriggers) 188 | (set? (:enable-adaptive-refresh-trigger opts)) 189 | (.enableAdaptiveRefreshTrigger 190 | (into-array ClusterTopologyRefreshOptions$RefreshTrigger 191 | (->> opts :enable-adaptive-refresh-trigger (map kw->rtrigger))))) 192 | (contains? opts :adaptive-refresh-triggers-timeout) 193 | (.adaptiveRefreshTriggersTimeout 194 | (Duration/of 195 | (-> opts :adaptive-refresh-triggers-timeout :timeout) 196 | (-> opts :adaptive-refresh-triggers-timeout :unit kw->cunit))) 197 | (contains? opts :refresh-triggers-reconnect-attempts) 198 | (.refreshTriggersReconnectAttempts (:refresh-triggers-reconnect-attempts opts)) 199 | true (.build))) 200 | 201 | (defn- ^ClusterClientOptions$Builder b-cluster-client-options 202 | "Sets up a ClusterClientOptions builder from a map of options" 203 | [opts] 204 | (cond-> (ClusterClientOptions/builder) 205 | (contains? opts :validate-cluster-node-membership) 206 | (.validateClusterNodeMembership (:validate-cluster-node-membership opts)) 207 | (contains? opts :max-redirects) 208 | (.maxRedirects (:max-redirects opts)) 209 | (contains? opts :topology-refresh-options) 210 | (.topologyRefreshOptions 211 | (cluster-topo-refresh-options (:topology-refresh-options opts))) 212 | true (b-client-options opts))) 213 | 214 | (defn create-client-resource 215 | "You can create an instance of client resources in a clojuresque way; check out the 216 | class io.lettuce.core.resource.ClientResources for details. 217 | 218 | It is useful to configure \"plumbing\" of client side redis connections such as: Netty 219 | threads, metrics, etc. But also it is good to have it for sharing the same NIO layer 220 | across multiple connections. 221 | 222 | Currently only the number of threads are implemented. Also, you can call it without 223 | any param or with an empty map and it will create a default client resource, but that 224 | can be shared across client connections." 225 | [options-map] 226 | (let [builder (ClientResources/builder)] 227 | (cond-> builder 228 | (contains? options-map :nb-io-threads) 229 | (.ioThreadPoolSize (:nb-io-threads options-map)) 230 | (contains? options-map :nb-worker-threads) 231 | (.computationThreadPoolSize (:nb-worker-threads options-map))) 232 | (.build builder))) 233 | 234 | (defn destroy-client-resource 235 | "If you create a client resource, you must close/dispose it; otherwise you will not 236 | shutdown the Netty threads." 237 | [^ClientResources client-resources] 238 | (.shutdown client-resources 100 1000 TimeUnit/MILLISECONDS)) 239 | 240 | ;; 241 | ;; Redis Server 242 | ;; 243 | 244 | (defrecord RedisServer 245 | [^RedisClient redis-client 246 | client-options 247 | ^StatefulRedisConnection stateful-conn 248 | conn-options 249 | ^RedisCodec codec 250 | ^RedisCommandFactory dynamic-factory] 251 | RedisConnector 252 | (commands-sync [_] 253 | (locking clojure.lang.RT/REQUIRE_LOCK 254 | (require '[celtuce.impl.server])) 255 | (.sync stateful-conn)) 256 | (commands-dynamic [_ cmd-class] 257 | (.getCommands dynamic-factory cmd-class)) 258 | (flush-commands [_] 259 | (.flushCommands stateful-conn)) 260 | (reset [_] 261 | (.reset stateful-conn)) 262 | (shutdown [_] 263 | (.close stateful-conn) 264 | (.shutdown redis-client))) 265 | 266 | (defn redis-server 267 | [^String redis-uri & 268 | {codec :codec 269 | client-options :client-options 270 | {auto-flush :auto-flush 271 | conn-timeout :timeout 272 | conn-unit :unit ^ClientResources 273 | client-resources :client-resources 274 | :or 275 | {auto-flush true} 276 | } :conn-options 277 | :or 278 | {codec (nippy-codec) 279 | client-options {}}}] 280 | (let [redis-client (if (nil? client-resources) 281 | (RedisClient/create redis-uri) 282 | (RedisClient/create client-resources redis-uri)) 283 | _ (.setOptions redis-client (.build (b-client-options client-options))) 284 | stateful-conn (.connect redis-client ^RedisCodec codec)] 285 | (when (and conn-timeout conn-unit) 286 | (.setTimeout stateful-conn (Duration/of conn-timeout (kw->cunit conn-unit)))) 287 | (.setAutoFlushCommands stateful-conn auto-flush) 288 | (map->RedisServer 289 | {:redis-client redis-client 290 | :client-options client-options 291 | :codec codec 292 | :stateful-conn stateful-conn 293 | :conn-options {:auto-flush auto-flush 294 | :timeout conn-timeout 295 | :unit conn-unit} 296 | :dynamic-factory (RedisCommandFactory. stateful-conn)}))) 297 | 298 | ;; 299 | ;; Redis Cluster 300 | ;; 301 | 302 | (defrecord RedisCluster 303 | [^RedisClusterClient redis-client 304 | client-options 305 | ^StatefulRedisClusterConnection stateful-conn 306 | conn-options 307 | ^RedisCodec codec 308 | ^RedisCommandFactory dynamic-factory] 309 | RedisConnector 310 | (commands-sync [_] 311 | (locking clojure.lang.RT/REQUIRE_LOCK 312 | (require '[celtuce.impl.cluster])) 313 | (.sync stateful-conn)) 314 | (commands-dynamic [_ cmd-class] 315 | (.getCommands dynamic-factory cmd-class)) 316 | (flush-commands [_] 317 | (.flushCommands stateful-conn)) 318 | (reset [_] 319 | (.reset stateful-conn)) 320 | (shutdown [_] 321 | (.close stateful-conn) 322 | (.shutdown redis-client))) 323 | 324 | (defn redis-cluster 325 | [^String redis-uri & 326 | {codec :codec 327 | client-options :client-options 328 | {auto-flush :auto-flush 329 | conn-timeout :timeout 330 | conn-unit :unit ^ClientResources 331 | client-resources :client-resources 332 | :or 333 | {auto-flush true} 334 | } :conn-options 335 | :or 336 | {codec (nippy-codec) 337 | client-options {}}}] 338 | (let [redis-client (if (nil? client-resources) 339 | (RedisClusterClient/create redis-uri) 340 | (RedisClusterClient/create client-resources redis-uri)) 341 | _ (.setOptions redis-client 342 | (.build (b-cluster-client-options client-options))) 343 | stateful-conn (.connect redis-client codec)] 344 | (when (and conn-timeout conn-unit) 345 | (.setTimeout stateful-conn (Duration/of conn-timeout (kw->cunit conn-unit)))) 346 | (.setAutoFlushCommands stateful-conn auto-flush) 347 | (map->RedisCluster 348 | {:redis-client redis-client 349 | :client-options client-options 350 | :codec codec 351 | :stateful-conn stateful-conn 352 | :conn-options {:auto-flush auto-flush 353 | :timeout conn-timeout 354 | :unit conn-unit} 355 | :dynamic-factory (RedisCommandFactory. stateful-conn)}))) 356 | 357 | ;; 358 | ;; Redis PubSub 359 | ;; 360 | 361 | (defprotocol Listenable 362 | "Register a celtuce.commands.PubSubListener on a stateful pubsub connection" 363 | (add-listener! [this listener])) 364 | 365 | (defrecord RedisPubSub 366 | [redis-client ^StatefulRedisPubSubConnection stateful-conn codec] 367 | RedisConnector 368 | (commands-sync [_] 369 | (locking clojure.lang.RT/REQUIRE_LOCK 370 | (require '[celtuce.impl.pubsub])) 371 | (.sync stateful-conn)) 372 | (flush-commands [_] 373 | (.flushCommands stateful-conn)) 374 | (reset [_] 375 | (.reset stateful-conn)) 376 | (shutdown [_] 377 | (.close stateful-conn) 378 | (condp instance? redis-client 379 | RedisClusterClient (.shutdown ^RedisClusterClient redis-client) 380 | RedisClient (.shutdown ^RedisClient redis-client))) 381 | Listenable 382 | (add-listener! [_ listener] 383 | (.addListener 384 | stateful-conn 385 | (reify 386 | RedisPubSubListener 387 | (message [_ ch msg] 388 | (cmds/message listener ch msg)) 389 | (message [_ p ch msg] 390 | (cmds/message listener p ch msg)) 391 | (subscribed [_ ch cnt] 392 | (cmds/subscribed listener ch cnt)) 393 | (unsubscribed [_ ch cnt] 394 | (cmds/unsubscribed listener ch cnt)) 395 | (psubscribed [_ p cnt] 396 | (cmds/psubscribed listener p cnt)) 397 | (punsubscribed [_ p cnt] 398 | (cmds/punsubscribed listener p cnt)))))) 399 | 400 | (defn as-pubsub [{:keys [redis-client ^RedisCodec codec]}] 401 | (->RedisPubSub 402 | redis-client 403 | (condp instance? redis-client 404 | RedisClusterClient (.connectPubSub ^RedisClusterClient redis-client codec) 405 | RedisClient (.connectPubSub ^RedisClient redis-client codec)) 406 | codec)) 407 | -------------------------------------------------------------------------------- /test/celtuce/cluster_manifold_test.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.cluster-manifold-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [celtuce.commands :as redis] 5 | [celtuce.connector :as conn] 6 | [celtuce.manifold :refer [commands-manifold]])) 7 | 8 | (def redis-url "redis://localhost:30001") 9 | (def ^:dynamic *cmds*) 10 | 11 | (defmacro with-str-cmds [& body] 12 | `(let [rclust# (conn/redis-cluster redis-url 13 | :codec (celtuce.codec/utf8-string-codec))] 14 | (binding [*cmds* (commands-manifold rclust#)] 15 | (try ~@body 16 | (finally (conn/shutdown rclust#)))))) 17 | 18 | (defmacro with-pubsub-cmds 19 | "Binds local @pub and @sub with different connections, 20 | registers the given listener on @sub" 21 | [listener & body] 22 | `(let [rclust-pub# (conn/as-pubsub (conn/redis-cluster redis-url)) 23 | rclust-sub# (conn/as-pubsub (conn/redis-cluster redis-url))] 24 | (conn/add-listener! rclust-sub# ~listener) 25 | (with-local-vars 26 | [~'pub (conn/commands-sync rclust-pub#) 27 | ~'sub (conn/commands-sync rclust-sub#)] 28 | (try ~@body 29 | (finally (conn/shutdown rclust-pub#) 30 | (conn/shutdown rclust-sub#)))))) 31 | 32 | (defn cmds-fixture [test-function] 33 | (let [rclust (conn/redis-cluster redis-url)] 34 | (binding [*cmds* (commands-manifold rclust)] 35 | (try (test-function) 36 | (finally (conn/shutdown rclust)))))) 37 | 38 | (defn flush-fixture [test-function] 39 | (redis/flushall *cmds*) 40 | (test-function)) 41 | 42 | (use-fixtures :once cmds-fixture) 43 | (use-fixtures :each flush-fixture) 44 | 45 | (deftest connection-commands-test 46 | 47 | (testing "ping and echo" 48 | (is (= "PONG" @(redis/ping *cmds*))) 49 | (is (= "hello" @(redis/echo *cmds* "hello"))))) 50 | 51 | (deftest hash-commands-test 52 | 53 | (testing "set and get multiple hash values" 54 | @(redis/hmset *cmds* "h" {:foo "bar" :a 1 0 nil}) 55 | @(redis/hset *cmds* "h" "b" :b) 56 | (is (= true @(redis/hexists *cmds* "h" :a))) 57 | (is (= false @(redis/hexists *cmds* "h" :dont-exist))) 58 | (is (= nil @(redis/hget *cmds* "h" :dont-exist))) 59 | (is (= false @(redis/hsetnx *cmds* "h" :a :a))) 60 | (is (= "bar" @(redis/hget *cmds* "h" :foo))) 61 | (is (= ["bar" 1 nil :b nil] 62 | @(redis/hmget *cmds* "h" [:foo :a 0 "b" :dont-exist]))) 63 | (is (= {:foo "bar" :a 1 0 nil "b" :b} @(redis/hgetall *cmds* "h"))) 64 | (is (= #{:foo :a 0 "b"} 65 | (into #{} @(redis/hkeys *cmds* "h")))) 66 | (is (= #{"bar" 1 nil :b} 67 | (into #{} @(redis/hvals *cmds* "h"))))) 68 | 69 | (testing "delete and multi delete hash values" 70 | @(redis/hmdel *cmds* "h" [:a :b "b"]) 71 | @(redis/hdel *cmds* "h" 0) 72 | (is (= false @(redis/hexists *cmds* "h" 0))) 73 | (is (= {:foo "bar"} @(redis/hgetall *cmds* "h")))) 74 | 75 | (testing "increments and length" 76 | (is (= 1 @(redis/hlen *cmds* "h"))) 77 | (is (= 9 @(redis/hstrlen *cmds* "h" :foo))) 78 | (is (= 1 @(redis/hincrby *cmds* "h" :x 1))) 79 | (is (= 10 @(redis/hincrby *cmds* "h" :x 9))) 80 | (is (= 1.0 @(redis/hincrbyfloat *cmds* "h" :y 1))) 81 | (is (= 3.0 @(redis/hincrbyfloat *cmds* "h" :y 2.0)))) 82 | 83 | (testing "hscan cursors" 84 | @(redis/hmset *cmds* "hl" (->> (range 10000) (split-at 5000) (apply zipmap))) 85 | (let [cur @(redis/hscan 86 | *cmds* "hl" (redis/scan-cursor) (redis/scan-args :limit 10)) 87 | res (redis/scan-res cur)] 88 | (is (= false (celtuce.scan/finished? cur))) 89 | (is (= true (map? res))) 90 | (is (<= 5 (count res) 15))) ;; about 10 91 | (let [els (->> (redis/scan-args :limit 50) 92 | (redis/hscan *cmds* "hl" (redis/scan-cursor)) 93 | (redis/chunked-scan-seq) 94 | (take 100))] 95 | (is (<= 95 (count els) 105)) ;; about 100 96 | (is (= @(redis/hgetall *cmds* "hl") 97 | (apply merge els)))) 98 | @(redis/hmset 99 | *cmds* "hs" (->> (range 100) (map str) (split-at 50) (apply zipmap))) 100 | (let [cur @(redis/hscan 101 | *cmds* "hs" (redis/scan-cursor) (redis/scan-args :match "*0")) 102 | res (redis/scan-res cur)] 103 | (is (= true (celtuce.scan/finished? cur))) 104 | (is (= (->> (range 0 50 10) (map (fn [x] [(str x) (str (+ x 50))])) (into {})) 105 | res))) 106 | (is (thrown? Exception 107 | (redis/chunked-scan-seq 108 | (redis/hscan *cmds* nil (redis/scan-cursor))))))) 109 | 110 | (deftest key-commands-test 111 | 112 | (testing "basic key checks" 113 | @(redis/hmset *cmds* "h" {:foo "bar" :a 1 "b" :b}) 114 | (is (= 1 @(redis/exists *cmds* "h"))) 115 | (is (= 1 @(redis/mexists *cmds* ["h" :dont-exist]))) 116 | (is (= ["h"] @(redis/keys *cmds* "h"))) 117 | (is (= ["h"] (->> (redis/chunked-scan-seq (redis/scan *cmds*)) 118 | (apply concat) 119 | (into [])))) 120 | (is (= "hash" @(redis/type *cmds* "h")))) 121 | 122 | (testing "ttl related" 123 | (is (= -1 @(redis/ttl *cmds* "h"))) 124 | @(redis/expire *cmds* "h" 1) 125 | (is (> 1001 @(redis/pttl *cmds* "h") 0)) 126 | @(redis/persist *cmds* "h") 127 | (is (= -1 @(redis/pttl *cmds* "h")))) 128 | 129 | (testing "dump/restore and delete" 130 | (let [dump @(redis/dump *cmds* "h")] 131 | @(redis/del *cmds* "h") 132 | (is (= 0 @(redis/exists *cmds* "h"))) 133 | @(redis/restore *cmds* "h" 0 dump) 134 | (is (= 1 @(redis/exists *cmds* "h"))) 135 | (is (= @(redis/hgetall *cmds* "h") 136 | {:foo "bar" :a 1 "b" :b}))))) 137 | 138 | (deftest string-commands-test 139 | 140 | (testing "set and get various keys/values" 141 | @(redis/set *cmds* "foo-int" 1337) 142 | @(redis/set *cmds* 666 "devil") 143 | @(redis/set *cmds* :foo-kw :bar-kw) 144 | @(redis/set *cmds* #{1 2 3} '(1 2 3)) 145 | @(redis/set *cmds* {:a "a"} [:b "b"] (redis/set-args :ex 1)) 146 | (is (= 1337 @(redis/get *cmds* "foo-int"))) 147 | (is (= :bar-kw @(redis/get *cmds* :foo-kw))) 148 | (is (= '(1 2 3) @(redis/get *cmds* #{1 2 3}))) 149 | (is (= [:b "b"] @(redis/get *cmds* {:a "a"}))) 150 | (is (> 1001 @(redis/pttl *cmds* {:a "a"}) 0)) 151 | (is (= "devil" @(redis/getset *cmds* 666 0xdeadbeef))) 152 | (is (= 0xdeadbeef @(redis/get *cmds* 666)))) 153 | 154 | (testing "multiget/set and result type (with underlying (into (empty keys) ...)" 155 | (redis/mset *cmds* {"foo" "bar" "foofoo" "barbar"}) 156 | (is (= ["bar" "barbar"] @(redis/mget *cmds* ["foo" "foofoo"]))) 157 | (is (= '("barbar" "bar") @(redis/mget *cmds* '("foo" "foofoo")))) 158 | (is (= [nil nil] @(redis/mget *cmds* ["dont" "exist"])))) 159 | 160 | (testing "raw string manipulations" 161 | (with-str-cmds 162 | (is (= 5 @(redis/append *cmds* "msg" "Hello"))) 163 | (is (= 11 @(redis/append *cmds* "msg" " World"))) 164 | (is (= "Hello World" @(redis/get *cmds* "msg"))) 165 | @(redis/setrange *cmds* "msg" 6 "Redis") 166 | (is (= "Hello Redis" @(redis/get *cmds* "msg"))) 167 | @(redis/append *cmds* "ts" "0043") 168 | @(redis/append *cmds* "ts" "0035") 169 | (is (= "0043" @(redis/getrange *cmds* "ts" 0 3))) 170 | (is (= "0035" @(redis/getrange *cmds* "ts" 4 7))) 171 | @(redis/set *cmds* "k" "foobar") 172 | (is (= 6 @(redis/strlen *cmds* "k"))) 173 | (is (= 26 @(redis/bitcount *cmds* "k"))) 174 | (is (= 4 @(redis/bitcount *cmds* "k" 0 0))) 175 | (is (= 6 @(redis/bitcount *cmds* "k" 1 1))) 176 | @(redis/set *cmds* "i" "10") 177 | (is (= 11 @(redis/incr *cmds* "i"))) 178 | (is (= 15 @(redis/incrby *cmds* "i" 4))) 179 | (is (= 11 @(redis/decrby *cmds* "i" 4))) 180 | (is (= 10 @(redis/decr *cmds* "i"))) 181 | (is (= 11.11 @(redis/incrbyfloat *cmds* "i" 1.11))) 182 | (is (= 0 @(redis/setbit *cmds* "b" 0 0))) 183 | (is (= 0 @(redis/setbit *cmds* "b" 1 1))) 184 | (is (= 1 @(redis/setbit *cmds* "b" 1 1))) 185 | (is (= 1 @(redis/getbit *cmds* "b" 1))) 186 | (is (= 1 @(redis/bitpos *cmds* "b" true))) 187 | @(redis/setbit *cmds* "b" 0 1) 188 | @(redis/setbit *cmds* "b" 1 1) 189 | @(redis/setbit *cmds* "b" 2 0) 190 | @(redis/setbit *cmds* "b" 3 0) 191 | @(redis/setbit *cmds* "b" 4 0) 192 | @(redis/setbit *cmds* "b" 5 1) 193 | (is (= 2 @(redis/bitpos *cmds* "b" false 0 0))))) 194 | 195 | (testing "bitfield command" 196 | (let [args (redis/bitfield-args 197 | :incrby :u2 100 1 :overflow :sat :incrby :u2 102 1)] 198 | (is (= [1 1] @(redis/bitfield *cmds* "bf" args))) 199 | (is (= [2 2] @(redis/bitfield *cmds* "bf" args))) 200 | (is (= [3 3] @(redis/bitfield *cmds* "bf" args))) 201 | (is (= [0 3] @(redis/bitfield *cmds* "bf" args)))))) 202 | 203 | (deftest list-commands-test 204 | 205 | (testing "basic list manipulations" 206 | (is (= 0 @(redis/rpushx *cmds* "x" :no-op))) 207 | (is (= 5 @(redis/mrpush *cmds* "l" (->> (range 65 70) (map char))))) 208 | (is (= 5 @(redis/llen *cmds* "l"))) 209 | (is (= [\A \B \C \D \E] @(redis/lrange *cmds* "l" 0 5))) 210 | (is (= 6 @(redis/lpush *cmds* "l" \A))) 211 | (is (= 2 @(redis/lrem *cmds* "l" 2 \A))) 212 | (is (= \B @(redis/lindex *cmds* "l" 0))) 213 | (is (= 5 @(redis/linsert *cmds* "l" true \B \A))) 214 | @(redis/lset *cmds* "l" 2 \Z) 215 | @(redis/ltrim *cmds* "l" 0 2) 216 | (is (= [\A \B \Z] @(redis/lrange *cmds* "l" 0 5))) 217 | (is (= \Z @(redis/rpop *cmds* "l"))) 218 | (is (= 2 @(redis/llen *cmds* "l")))) 219 | 220 | (testing "list blocking commands" 221 | @(redis/mrpush *cmds* "bl" [1 2 3]) 222 | (is (= ["bl" 1] @(redis/blpop *cmds* 1 ["bl"]))) 223 | (is (= ["bl" 3] @(redis/brpop *cmds* 1 ["bl"]))) 224 | @(redis/del *cmds* "bl") 225 | (is (nil? @(redis/blpop *cmds* 1 ["bl"]))) 226 | (is (nil? @(redis/brpop *cmds* 1 ["bl"]))))) 227 | 228 | (deftest set-commands-test 229 | 230 | (testing "add and scan set members" 231 | (is (= 1 @(redis/sadd *cmds* "s1" :a))) 232 | (is (= 4 @(redis/msadd *cmds* "s1" [:b :c :d :e]))) 233 | (is (= 5 @(redis/scard *cmds* "s1"))) 234 | (is (= true @(redis/sismember *cmds* "s1" :a))) 235 | (is (= #{:a :b :c :d :e} 236 | @(redis/smembers *cmds* "s1"))) 237 | (is (= #{:a :b :c :d :e} 238 | (->> (redis/sscan *cmds* "s1" (redis/scan-cursor)) 239 | (redis/chunked-scan-seq) 240 | (take 5) 241 | (apply into #{}))))) 242 | 243 | (testing "deleting set members" 244 | (let [m @(redis/spop *cmds* "s1") 245 | ms @(redis/smembers *cmds* "s1")] 246 | (is (= 4 @(redis/scard *cmds* "s1"))) 247 | (is (= 4 (count ms))) 248 | (is (= #{:a :b :c :d :e} 249 | (conj ms m)))) 250 | (let [m @(redis/srandmember *cmds* "s1")] 251 | (is (= 1 @(redis/srem *cmds* "s1" m))) 252 | (is (= 3 @(redis/scard *cmds* "s1"))) 253 | (is (= false @(redis/sismember *cmds* "s1" m)))))) 254 | 255 | (deftest sortedset-commands-test 256 | 257 | (testing "add, incr, count sorted set members" 258 | (is (= 1 @(redis/zadd *cmds* "z1" 0.1 :a))) 259 | (is (= 1 @(redis/zadd *cmds* "z1" :nx 0.2 :b))) 260 | (is (= 3 @(redis/mzadd *cmds* "z1" [[0.3 :c] [0.4 :d] [0.5 :e]]))) 261 | (is (= 5 @(redis/zcard *cmds* "z1"))) 262 | (is (= 0.6 @(redis/zaddincr *cmds* "z1" 0.1 :e))) 263 | (is (= 0.6 @(redis/zscore *cmds* "z1" :e))) 264 | (is (= 0.5 @(redis/zincrby *cmds* "z1" -0.1 :e))) 265 | (is (= 3 @(redis/zcount *cmds* "z1" 0.3 0.5))) 266 | (is (= 0 @(redis/zrank *cmds* "z1" :a))) 267 | (is (= 4 @(redis/zrevrank *cmds* "z1" :a)))) 268 | 269 | (testing "range, revrange, scan sorted set" 270 | (is (= [:a :b :c] 271 | @(redis/zrange *cmds* "z1" 0 2))) 272 | (is (= [[0.1 :a] [0.2 :b] [0.3 :c]] 273 | @(redis/zrange-withscores *cmds* "z1" 0 2))) 274 | (is (= [:c :d :e] 275 | @(redis/zrangebyscore *cmds* "z1" 0.3 0.5))) 276 | (is (= [[0.3 :c] [0.4 :d] [0.5 :e]] 277 | @(redis/zrangebyscore-withscores *cmds* "z1" 0.3 0.5))) 278 | (is (= [:e :d :c] 279 | @(redis/zrevrange *cmds* "z1" 0 2))) 280 | (is (= [[0.5 :e] [0.4 :d] [0.3 :c]] 281 | @(redis/zrevrange-withscores *cmds* "z1" 0 2))) 282 | (is (= [:e :d :c] 283 | @(redis/zrevrangebyscore *cmds* "z1" 0.5 0.3))) 284 | (is (= [[0.5 :e] [0.4 :d] [0.3 :c]] 285 | @(redis/zrevrangebyscore-withscores *cmds* "z1" 0.5 0.3))) 286 | (is (= #{[0.1 :a] [0.2 :b] [0.3 :c] [0.4 :d] [0.5 :e]} 287 | (->> (redis/zscan *cmds* "z1" (redis/scan-cursor)) 288 | (redis/chunked-scan-seq) 289 | (take 5) 290 | (apply into #{}))))) 291 | 292 | (testing "deleting sorted set members" 293 | (is (= 1 @(redis/zrem *cmds* "z1" :a))) 294 | (is (= :b (first @(redis/zrange *cmds* "z1" 0 0)))) 295 | (is (= 2 @(redis/mzrem *cmds* "z1" [:b :c]))) 296 | (is (= :d (first @(redis/zrange *cmds* "z1" 0 0)))) 297 | (is (= 1 @(redis/zremrangebyrank *cmds* "z1" 0 0))) 298 | (is (= :e (first @(redis/zrange *cmds* "z1" 0 0)))) 299 | (is (= 1 @(redis/zremrangebyscore *cmds* "z1" 0.5 0.5))) 300 | (is (= 0 @(redis/zcard *cmds* "z1")))) 301 | 302 | (testing "lexicographical order based commands" 303 | (with-str-cmds 304 | @(redis/mzadd *cmds* "z2" [[0.0 "a"] [0.0 "b"] [0.0 "c"] [0.0 "d"] [0.0 "e"]]) 305 | (is (= ["a" "b" "c"] 306 | @(redis/zrangebylex *cmds* "z2" "-" "[c"))) 307 | (is (= 5 @(redis/zlexcount *cmds* "z2" "-" "+")))))) 308 | 309 | (deftest scripting-commands-test 310 | (let [script "return 10" 311 | sha "080c414e64bca1184bc4f6220a19c4d495ac896d"] 312 | (with-str-cmds 313 | (testing "simple scripting" 314 | (is (= 10 @(redis/eval *cmds* script :integer []))) 315 | (is (= sha (redis/digest *cmds* script))) 316 | (is (= 10 @(redis/evalsha *cmds* sha :integer []))) 317 | @(redis/script-flush *cmds*) 318 | (is (thrown? io.lettuce.core.RedisCommandExecutionException 319 | @(redis/evalsha *cmds* sha :integer []))))))) 320 | 321 | (deftest hll-commands-test 322 | (let [err 0.81 323 | close? (fn [x y] (<= (- x (* x err)) y (+ x (* x err))))] 324 | (testing "basic hll usage" 325 | (is (= 1 @(redis/pfadd *cmds* "pf1" 0))) 326 | (is (= 1 @(redis/mpfadd *cmds* "pf1" (range 1 1000)))) 327 | (is (close? 1000 @(redis/pfcount *cmds* "pf1"))) 328 | (is (= 0 @(redis/mpfadd *cmds* "pf1" (range 1000)))) 329 | (is (close? 1000 @(redis/pfcount *cmds* "pf1"))) 330 | (is (= 1 @(redis/mpfadd *cmds* "pf1" (range 1000 2000)))) 331 | (is (close? 2000 @(redis/pfcount *cmds* "pf1")))))) 332 | 333 | (deftest geo-commands-test 334 | (let [err 0.999999 335 | close? (fn [x y] (<= (- x (* x err)) y (+ x (* x err))))] 336 | 337 | (testing "basic geo usage" 338 | (is (= 1 @(redis/geoadd *cmds* "Sicily" 13.361389 38.115556 "Palermo"))) 339 | (is (= 2 @(redis/geoadd *cmds* "Sicily" [[15.087269 37.502669 "Catania"] 340 | [13.583333 37.316667 "Agrigento"]]))) 341 | (is (= @(redis/geohash *cmds* "Sicily" "Agrigento") "sq9sm1716e0")) 342 | (is (= @(redis/mgeohash *cmds* "Sicily" ["Palermo" "Catania"]) 343 | ["sqc8b49rny0" "sqdtr74hyu0"]))) 344 | 345 | (testing "georadius, by coord and by member" 346 | (is (= @(redis/georadius *cmds* "Sicily" 15 37 200 :km) 347 | #{"Agrigento" "Catania" "Palermo"})) 348 | (let [[palermo agrigento] 349 | @(redis/georadius *cmds* "Sicily" 15 37 200 :km 350 | (redis/geo-args :with-dist true :with-coord true 351 | :count 2 :sort :desc))] 352 | (is (= "Palermo" (-> palermo :member))) 353 | (is (close? 190.4424 (-> palermo :distance))) 354 | (is (close? 13.361389 (-> palermo :coordinates :x))) 355 | (is (close? 38.115556 (-> palermo :coordinates :y))) 356 | (is (= "Agrigento" (-> agrigento :member))) 357 | (is (close? 130.4235 (-> agrigento :distance))) 358 | (is (close? 13.583333 (-> agrigento :coordinates :x))) 359 | (is (close? 37.316667 (-> agrigento :coordinates :y)))) 360 | (is (= @(redis/georadiusbymember *cmds* "Sicily" "Agrigento" 100 :km) 361 | #{"Agrigento" "Palermo"})) 362 | (let [[agrigento palermo] 363 | @(redis/georadiusbymember 364 | *cmds* "Sicily" "Agrigento" 100 :km 365 | (redis/geo-args :with-dist true :with-coord true))] 366 | (is (= "Agrigento" (-> agrigento :member))) 367 | (is (close? 0.0000 (-> agrigento :distance))) 368 | (is (close? 13.583333 (-> agrigento :coordinates :x))) 369 | (is (close? 37.316667 (-> agrigento :coordinates :y))) 370 | (is (= "Palermo" (-> palermo :member))) 371 | (is (close? 90.9778 (-> palermo :distance))) 372 | (is (close? 13.361389 (-> palermo :coordinates :x))) 373 | (is (close? 38.115556 (-> palermo :coordinates :y))))) 374 | 375 | (testing "position and distance" 376 | (let [palermo @(redis/geopos *cmds* "Sicily" "Palermo")] 377 | (is (close? 13.361389 (-> palermo :x))) 378 | (is (close? 38.115556 (-> palermo :y)))) 379 | (let [[catania agrigento dont-exist] 380 | @(redis/mgeopos *cmds* "Sicily" ["Catania" "Agrigento" "DontExist"])] 381 | (is (close? 15.087269 (-> catania :x))) 382 | (is (close? 37.502669(-> catania :y))) 383 | (is (close? 13.583333 (-> agrigento :x))) 384 | (is (close? 37.316667 (-> agrigento :y))) 385 | (is (nil? dont-exist))) 386 | (is (close? 166.2742 @(redis/geodist *cmds* "Sicily" "Palermo" "Catania" :km))) 387 | (is (close? 103.3182 @(redis/geodist *cmds* "Sicily" "Palermo" "Catania" :mi)))))) 388 | 389 | (deftest pubsub-commands-test 390 | (testing "simple pub/sub mechanism" 391 | (let [nb-sub (atom 0) 392 | subscribed? (promise) 393 | res (promise) 394 | unsubscribed? (promise)] 395 | (with-pubsub-cmds 396 | (reify redis/PubSubListener 397 | (message [_ channel message] 398 | (deliver res [channel message])) 399 | (message [_ pattern channel message]) 400 | (subscribed [_ channel count] 401 | (swap! nb-sub inc) 402 | (deliver subscribed? true)) 403 | (unsubscribed [_ channel count] 404 | (swap! nb-sub dec) 405 | (deliver unsubscribed? true)) 406 | (psubscribed [_ pattern count]) 407 | (punsubscribed [_ pattern count])) 408 | (redis/subscribe @sub "c") 409 | (is (= true @subscribed?)) 410 | (is (= 1 @nb-sub)) 411 | (redis/publish @pub "c" "message") 412 | (is (= ["c" "message"] @res)) 413 | (redis/unsubscribe @sub "c") 414 | (is (= true @unsubscribed?)) 415 | (is (= 0 @nb-sub)) )))) 416 | -------------------------------------------------------------------------------- /test/celtuce/cluster_sync_test.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.cluster-sync-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [celtuce.commands :as redis] 5 | [celtuce.connector :as conn])) 6 | 7 | (def redis-url "redis://localhost:30001") 8 | (def ^:dynamic *cmds*) 9 | 10 | (defmacro with-str-cmds [& body] 11 | `(let [rclust# (conn/redis-cluster redis-url 12 | :codec (celtuce.codec/utf8-string-codec))] 13 | (binding [*cmds* (conn/commands-sync rclust#)] 14 | (try ~@body 15 | (finally (conn/shutdown rclust#)))))) 16 | 17 | (defmacro with-pubsub-cmds 18 | "Binds local @pub and @sub with different connections, 19 | registers the given listener on @sub" 20 | [listener & body] 21 | `(let [rclust-pub# (conn/as-pubsub (conn/redis-cluster redis-url)) 22 | rclust-sub# (conn/as-pubsub (conn/redis-cluster redis-url))] 23 | (conn/add-listener! rclust-sub# ~listener) 24 | (with-local-vars 25 | [~'pub (conn/commands-sync rclust-pub#) 26 | ~'sub (conn/commands-sync rclust-sub#)] 27 | (try ~@body 28 | (finally (conn/shutdown rclust-pub#) 29 | (conn/shutdown rclust-sub#)))))) 30 | 31 | (defn cmds-fixture [test-function] 32 | (let [rclust (conn/redis-cluster redis-url)] 33 | (binding [*cmds* (conn/commands-sync rclust)] 34 | (try (test-function) 35 | (finally (conn/shutdown rclust)))))) 36 | 37 | (defn flush-fixture [test-function] 38 | (redis/flushall *cmds*) 39 | (test-function)) 40 | 41 | (use-fixtures :once cmds-fixture) 42 | (use-fixtures :each flush-fixture) 43 | 44 | (deftest connection-commands-test 45 | 46 | (testing "ping and echo" 47 | (is (= "PONG" (redis/ping *cmds*))) 48 | (is (= "hello" (redis/echo *cmds* "hello"))))) 49 | 50 | (deftest hash-commands-test 51 | 52 | (testing "set and get multiple hash values" 53 | (redis/hmset *cmds* "h" {:foo "bar" :a 1 0 nil}) 54 | (redis/hset *cmds* "h" "b" :b) 55 | (is (= true (redis/hexists *cmds* "h" :a))) 56 | (is (= false (redis/hexists *cmds* "h" :dont-exist))) 57 | (is (= nil (redis/hget *cmds* "h" :dont-exist))) 58 | (is (= false (redis/hsetnx *cmds* "h" :a :a))) 59 | (is (= "bar" (redis/hget *cmds* "h" :foo))) 60 | (is (= ["bar" 1 nil :b nil] 61 | (redis/hmget *cmds* "h" [:foo :a 0 "b" :dont-exist]))) 62 | (is (= {:foo "bar" :a 1 0 nil "b" :b} (redis/hgetall *cmds* "h"))) 63 | (is (= #{:foo :a 0 "b"} 64 | (into #{} (redis/hkeys *cmds* "h")))) 65 | (is (= #{"bar" 1 nil :b} 66 | (into #{} (redis/hvals *cmds* "h"))))) 67 | 68 | (testing "delete and multi delete hash values" 69 | (redis/hmdel *cmds* "h" [:a :b "b"]) 70 | (redis/hdel *cmds* "h" 0) 71 | (is (= false (redis/hexists *cmds* "h" 0))) 72 | (is (= {:foo "bar"} (redis/hgetall *cmds* "h")))) 73 | 74 | (testing "increments and length" 75 | (is (= 1 (redis/hlen *cmds* "h"))) 76 | (is (= 9 (redis/hstrlen *cmds* "h" :foo))) 77 | (is (= 1 (redis/hincrby *cmds* "h" :x 1))) 78 | (is (= 10 (redis/hincrby *cmds* "h" :x 9))) 79 | (is (= 1.0 (redis/hincrbyfloat *cmds* "h" :y 1))) 80 | (is (= 3.0 (redis/hincrbyfloat *cmds* "h" :y 2.0)))) 81 | 82 | (testing "hscan cursors" 83 | (redis/hmset *cmds* "hl" (->> (range 10000) (split-at 5000) (apply zipmap))) 84 | (let [cur (redis/hscan 85 | *cmds* "hl" (redis/scan-cursor) (redis/scan-args :limit 10)) 86 | res (redis/scan-res cur)] 87 | (is (= false (celtuce.scan/finished? cur))) 88 | (is (= true (map? res))) 89 | (is (<= 5 (count res) 15))) ;; about 10 90 | (let [els1 (->> (redis/scan-args :limit 50) 91 | (redis/hscan *cmds* "hl" (redis/scan-cursor)) 92 | (redis/chunked-scan-seq) 93 | (take 100)) 94 | els2 (redis/hscan-seq *cmds* "hl")] 95 | (is (<= 95 (count els1) 105)) ;; about 100 96 | (is (= (redis/hgetall *cmds* "hl") 97 | (apply merge els1) 98 | (into {} els2)))) 99 | (redis/hmset 100 | *cmds* "hs" (->> (range 100) (map str) (split-at 50) (apply zipmap))) 101 | (let [cur (redis/hscan 102 | *cmds* "hs" (redis/scan-cursor) (redis/scan-args :match "*0")) 103 | res (redis/scan-res cur)] 104 | (is (= true (celtuce.scan/finished? cur))) 105 | (is (= (->> (range 0 50 10) (map (fn [x] [(str x) (str (+ x 50))])) (into {})) 106 | res))) 107 | (is (thrown? Exception 108 | (redis/chunked-scan-seq 109 | (redis/hscan *cmds* nil (redis/scan-cursor))))))) 110 | 111 | (deftest key-commands-test 112 | 113 | (testing "basic key checks" 114 | (redis/hmset *cmds* "h" {:foo "bar" :a 1 "b" :b}) 115 | (is (= 1 (redis/exists *cmds* "h"))) 116 | (is (= 1 (redis/mexists *cmds* ["h" :dont-exist]))) 117 | (is (= ["h"] (redis/keys *cmds* "h"))) 118 | (is (= ["h"] (->> (redis/chunked-scan-seq (redis/scan *cmds*)) 119 | (apply concat) 120 | (into [])))) 121 | (is (= ["h"] (redis/scan-seq *cmds*))) 122 | (is (= "hash" (redis/type *cmds* "h")))) 123 | 124 | (testing "ttl related" 125 | (is (= -1 (redis/ttl *cmds* "h"))) 126 | (redis/expire *cmds* "h" 1) 127 | (is (> 1001 (redis/pttl *cmds* "h") 0)) 128 | (redis/persist *cmds* "h") 129 | (is (= -1 (redis/pttl *cmds* "h")))) 130 | 131 | (testing "dump/restore and delete" 132 | (let [dump (redis/dump *cmds* "h")] 133 | (redis/del *cmds* "h") 134 | (is (= 0 (redis/exists *cmds* "h"))) 135 | (redis/restore *cmds* "h" 0 dump) 136 | (is (= 1 (redis/exists *cmds* "h"))) 137 | (is (= (redis/hgetall *cmds* "h") 138 | {:foo "bar" :a 1 "b" :b}))))) 139 | 140 | (deftest string-commands-test 141 | 142 | (testing "set and get various keys/values" 143 | (redis/set *cmds* "foo-int" 1337) 144 | (redis/set *cmds* 666 "devil") 145 | (redis/set *cmds* :foo-kw :bar-kw) 146 | (redis/set *cmds* #{1 2 3} '(1 2 3)) 147 | (redis/set *cmds* {:a "a"} [:b "b"] (redis/set-args :ex 1)) 148 | (is (= 1337 (redis/get *cmds* "foo-int"))) 149 | (is (= :bar-kw (redis/get *cmds* :foo-kw))) 150 | (is (= '(1 2 3) (redis/get *cmds* #{1 2 3}))) 151 | (is (= [:b "b"] (redis/get *cmds* {:a "a"}))) 152 | (is (> 1001 (redis/pttl *cmds* {:a "a"}) 0)) 153 | (is (= "devil" (redis/getset *cmds* 666 0xdeadbeef))) 154 | (is (= 0xdeadbeef (redis/get *cmds* 666)))) 155 | 156 | (testing "multiget/set and result type (with underlying (into (empty keys) ...)" 157 | (redis/mset *cmds* {"foo" "bar" "foofoo" "barbar"}) 158 | (is (= ["bar" "barbar"] (redis/mget *cmds* ["foo" "foofoo"]))) 159 | (is (= '("barbar" "bar") (redis/mget *cmds* '("foo" "foofoo")))) 160 | (is (= [nil nil] (redis/mget *cmds* ["dont" "exist"])))) 161 | 162 | (testing "raw string manipulations" 163 | (with-str-cmds 164 | (is (= 5 (redis/append *cmds* "msg" "Hello"))) 165 | (is (= 11 (redis/append *cmds* "msg" " World"))) 166 | (is (= "Hello World" (redis/get *cmds* "msg"))) 167 | (redis/setrange *cmds* "msg" 6 "Redis") 168 | (is (= "Hello Redis" (redis/get *cmds* "msg"))) 169 | (redis/append *cmds* "ts" "0043") 170 | (redis/append *cmds* "ts" "0035") 171 | (is (= "0043" (redis/getrange *cmds* "ts" 0 3))) 172 | (is (= "0035" (redis/getrange *cmds* "ts" 4 7))) 173 | (redis/set *cmds* "k" "foobar") 174 | (is (= 6 (redis/strlen *cmds* "k"))) 175 | (is (= 26 (redis/bitcount *cmds* "k"))) 176 | (is (= 4 (redis/bitcount *cmds* "k" 0 0))) 177 | (is (= 6 (redis/bitcount *cmds* "k" 1 1))) 178 | (redis/set *cmds* "i" "10") 179 | (is (= 11 (redis/incr *cmds* "i"))) 180 | (is (= 15 (redis/incrby *cmds* "i" 4))) 181 | (is (= 11 (redis/decrby *cmds* "i" 4))) 182 | (is (= 10 (redis/decr *cmds* "i"))) 183 | (is (= 11.11 (redis/incrbyfloat *cmds* "i" 1.11))) 184 | (is (= 0 (redis/setbit *cmds* "b" 0 0))) 185 | (is (= 0 (redis/setbit *cmds* "b" 1 1))) 186 | (is (= 1 (redis/setbit *cmds* "b" 1 1))) 187 | (is (= 1 (redis/getbit *cmds* "b" 1))) 188 | (is (= 1 (redis/bitpos *cmds* "b" true))) 189 | (redis/setbit *cmds* "b" 0 1) 190 | (redis/setbit *cmds* "b" 1 1) 191 | (redis/setbit *cmds* "b" 2 0) 192 | (redis/setbit *cmds* "b" 3 0) 193 | (redis/setbit *cmds* "b" 4 0) 194 | (redis/setbit *cmds* "b" 5 1) 195 | (is (= 2 (redis/bitpos *cmds* "b" false 0 0))))) 196 | 197 | (testing "bitfield command" 198 | (let [args (redis/bitfield-args 199 | :incrby :u2 100 1 :overflow :sat :incrby :u2 102 1)] 200 | (is (= [1 1] (redis/bitfield *cmds* "bf" args))) 201 | (is (= [2 2] (redis/bitfield *cmds* "bf" args))) 202 | (is (= [3 3] (redis/bitfield *cmds* "bf" args))) 203 | (is (= [0 3] (redis/bitfield *cmds* "bf" args)))))) 204 | 205 | (deftest list-commands-test 206 | 207 | (testing "basic list manipulations" 208 | (is (= 0 (redis/rpushx *cmds* "x" :no-op))) 209 | (is (= 5 (redis/mrpush *cmds* "l" (->> (range 65 70) (map char))))) 210 | (is (= 5 (redis/llen *cmds* "l"))) 211 | (is (= [\A \B \C \D \E] (redis/lrange *cmds* "l" 0 5))) 212 | (is (= 6 (redis/lpush *cmds* "l" \A))) 213 | (is (= 2 (redis/lrem *cmds* "l" 2 \A))) 214 | (is (= \B (redis/lindex *cmds* "l" 0))) 215 | (is (= 5 (redis/linsert *cmds* "l" true \B \A))) 216 | (redis/lset *cmds* "l" 2 \Z) 217 | (redis/ltrim *cmds* "l" 0 2) 218 | (is (= [\A \B \Z] (redis/lrange *cmds* "l" 0 5))) 219 | (is (= \Z (redis/rpop *cmds* "l"))) 220 | (is (= 2 (redis/llen *cmds* "l")))) 221 | 222 | (testing "list blocking commands" 223 | (redis/mrpush *cmds* "bl" [1 2 3]) 224 | (is (= ["bl" 1] (redis/blpop *cmds* 1 ["bl"]))) 225 | (is (= ["bl" 3] (redis/brpop *cmds* 1 ["bl"]))) 226 | (redis/del *cmds* "bl") 227 | (is (nil? (redis/blpop *cmds* 1 ["bl"]))) 228 | (is (nil? (redis/brpop *cmds* 1 ["bl"]))))) 229 | 230 | (deftest set-commands-test 231 | 232 | (testing "add and scan set members" 233 | (is (= 1 (redis/sadd *cmds* "s1" :a))) 234 | (is (= 4 (redis/msadd *cmds* "s1" [:b :c :d :e]))) 235 | (is (= 5 (redis/scard *cmds* "s1"))) 236 | (is (= true (redis/sismember *cmds* "s1" :a))) 237 | (is (= #{:a :b :c :d :e} 238 | (redis/smembers *cmds* "s1"))) 239 | (is (= #{:a :b :c :d :e} 240 | (->> (redis/sscan *cmds* "s1" (redis/scan-cursor)) 241 | (redis/chunked-scan-seq) 242 | (take 5) 243 | (apply into #{})))) 244 | (is (= #{:a :b :c :d :e} 245 | (into #{} (redis/sscan-seq *cmds* "s1"))))) 246 | 247 | (testing "deleting set members" 248 | (let [m (redis/spop *cmds* "s1") 249 | ms (redis/smembers *cmds* "s1")] 250 | (is (= 4 (redis/scard *cmds* "s1"))) 251 | (is (= 4 (count ms))) 252 | (is (= #{:a :b :c :d :e} 253 | (conj ms m)))) 254 | (let [m (redis/srandmember *cmds* "s1")] 255 | (is (= 1 (redis/srem *cmds* "s1" m))) 256 | (is (= 3 (redis/scard *cmds* "s1"))) 257 | (is (= false (redis/sismember *cmds* "s1" m)))))) 258 | 259 | (deftest sortedset-commands-test 260 | 261 | (testing "add, incr, count sorted set members" 262 | (is (= 1 (redis/zadd *cmds* "z1" 0.1 :a))) 263 | (is (= 1 (redis/zadd *cmds* "z1" :nx 0.2 :b))) 264 | (is (= 3 (redis/mzadd *cmds* "z1" [[0.3 :c] [0.4 :d] [0.5 :e]]))) 265 | (is (= 5 (redis/zcard *cmds* "z1"))) 266 | (is (= 0.6 (redis/zaddincr *cmds* "z1" 0.1 :e))) 267 | (is (= 0.6 (redis/zscore *cmds* "z1" :e))) 268 | (is (= 0.5 (redis/zincrby *cmds* "z1" -0.1 :e))) 269 | (is (= 3 (redis/zcount *cmds* "z1" 0.3 0.5))) 270 | (is (= 0 (redis/zrank *cmds* "z1" :a))) 271 | (is (= 4 (redis/zrevrank *cmds* "z1" :a)))) 272 | 273 | (testing "range, revrange, scan sorted set" 274 | (is (= [:a :b :c] 275 | (redis/zrange *cmds* "z1" 0 2))) 276 | (is (= [[0.1 :a] [0.2 :b] [0.3 :c]] 277 | (redis/zrange-withscores *cmds* "z1" 0 2))) 278 | (is (= [:c :d :e] 279 | (redis/zrangebyscore *cmds* "z1" 0.3 0.5))) 280 | (is (= [[0.3 :c] [0.4 :d] [0.5 :e]] 281 | (redis/zrangebyscore-withscores *cmds* "z1" 0.3 0.5))) 282 | (is (= [:e :d :c] 283 | (redis/zrevrange *cmds* "z1" 0 2))) 284 | (is (= [[0.5 :e] [0.4 :d] [0.3 :c]] 285 | (redis/zrevrange-withscores *cmds* "z1" 0 2))) 286 | (is (= [:e :d :c] 287 | (redis/zrevrangebyscore *cmds* "z1" 0.5 0.3))) 288 | (is (= [[0.5 :e] [0.4 :d] [0.3 :c]] 289 | (redis/zrevrangebyscore-withscores *cmds* "z1" 0.5 0.3))) 290 | (is (= #{[0.1 :a] [0.2 :b] [0.3 :c] [0.4 :d] [0.5 :e]} 291 | (->> (redis/zscan *cmds* "z1" (redis/scan-cursor)) 292 | (redis/chunked-scan-seq) 293 | (take 5) 294 | (apply into #{})))) 295 | (is (= #{[0.1 :a] [0.2 :b] [0.3 :c] [0.4 :d] [0.5 :e]} 296 | (into #{} (redis/zscan-seq *cmds* "z1"))))) 297 | 298 | (testing "deleting sorted set members" 299 | (is (= 1 (redis/zrem *cmds* "z1" :a))) 300 | (is (= :b (first (redis/zrange *cmds* "z1" 0 0)))) 301 | (is (= 2 (redis/mzrem *cmds* "z1" [:b :c]))) 302 | (is (= :d (first (redis/zrange *cmds* "z1" 0 0)))) 303 | (is (= 1 (redis/zremrangebyrank *cmds* "z1" 0 0))) 304 | (is (= :e (first (redis/zrange *cmds* "z1" 0 0)))) 305 | (is (= 1 (redis/zremrangebyscore *cmds* "z1" 0.5 0.5))) 306 | (is (= 0 (redis/zcard *cmds* "z1")))) 307 | 308 | (testing "lexicographical order based commands" 309 | (with-str-cmds 310 | (redis/mzadd *cmds* "z2" [[0.0 "a"] [0.0 "b"] [0.0 "c"] [0.0 "d"] [0.0 "e"]]) 311 | (is (= ["a" "b" "c"] 312 | (redis/zrangebylex *cmds* "z2" "-" "[c"))) 313 | (is (= 5 (redis/zlexcount *cmds* "z2" "-" "+")))))) 314 | 315 | (deftest scripting-commands-test 316 | (let [script "return 10" 317 | sha "080c414e64bca1184bc4f6220a19c4d495ac896d"] 318 | (with-str-cmds 319 | (testing "simple scripting" 320 | (is (= 10 (redis/eval *cmds* script :integer []))) 321 | (is (= sha (redis/digest *cmds* script))) 322 | (is (= 10 (redis/evalsha *cmds* sha :integer []))) 323 | (redis/script-flush *cmds*) 324 | (is (thrown? io.lettuce.core.RedisCommandExecutionException 325 | (redis/evalsha *cmds* sha :integer []))))))) 326 | 327 | (deftest hll-commands-test 328 | (let [err 0.81 329 | close? (fn [x y] (<= (- x (* x err)) y (+ x (* x err))))] 330 | (testing "basic hll usage" 331 | (is (= 1 (redis/pfadd *cmds* "pf1" 0))) 332 | (is (= 1 (redis/mpfadd *cmds* "pf1" (range 1 1000)))) 333 | (is (close? 1000 (redis/pfcount *cmds* "pf1"))) 334 | (is (= 0 (redis/mpfadd *cmds* "pf1" (range 1000)))) 335 | (is (close? 1000 (redis/pfcount *cmds* "pf1"))) 336 | (is (= 1 (redis/mpfadd *cmds* "pf1" (range 1000 2000)))) 337 | (is (close? 2000 (redis/pfcount *cmds* "pf1")))))) 338 | 339 | (deftest geo-commands-test 340 | (let [err 0.999999 341 | close? (fn [x y] (<= (- x (* x err)) y (+ x (* x err))))] 342 | 343 | (testing "basic geo usage" 344 | (is (= 1 (redis/geoadd *cmds* "Sicily" 13.361389 38.115556 "Palermo"))) 345 | (is (= 2 (redis/geoadd *cmds* "Sicily" [[15.087269 37.502669 "Catania"] 346 | [13.583333 37.316667 "Agrigento"]]))) 347 | (is (= (redis/geohash *cmds* "Sicily" "Agrigento") "sq9sm1716e0")) 348 | (is (= (redis/mgeohash *cmds* "Sicily" ["Palermo" "Catania"]) 349 | ["sqc8b49rny0" "sqdtr74hyu0"]))) 350 | 351 | (testing "georadius, by coord and by member" 352 | (is (= (redis/georadius *cmds* "Sicily" 15 37 200 :km) 353 | #{"Agrigento" "Catania" "Palermo"})) 354 | (let [[palermo agrigento] 355 | (redis/georadius *cmds* "Sicily" 15 37 200 :km 356 | (redis/geo-args :with-dist true :with-coord true 357 | :count 2 :sort :desc))] 358 | (is (= "Palermo" (-> palermo :member))) 359 | (is (close? 190.4424 (-> palermo :distance))) 360 | (is (close? 13.361389 (-> palermo :coordinates :x))) 361 | (is (close? 38.115556 (-> palermo :coordinates :y))) 362 | (is (= "Agrigento" (-> agrigento :member))) 363 | (is (close? 130.4235 (-> agrigento :distance))) 364 | (is (close? 13.583333 (-> agrigento :coordinates :x))) 365 | (is (close? 37.316667 (-> agrigento :coordinates :y)))) 366 | (is (= (redis/georadiusbymember *cmds* "Sicily" "Agrigento" 100 :km) 367 | #{"Agrigento" "Palermo"})) 368 | (let [[agrigento palermo] 369 | (redis/georadiusbymember 370 | *cmds* "Sicily" "Agrigento" 100 :km 371 | (redis/geo-args :with-dist true :with-coord true))] 372 | (is (= "Agrigento" (-> agrigento :member))) 373 | (is (close? 0.0000 (-> agrigento :distance))) 374 | (is (close? 13.583333 (-> agrigento :coordinates :x))) 375 | (is (close? 37.316667 (-> agrigento :coordinates :y))) 376 | (is (= "Palermo" (-> palermo :member))) 377 | (is (close? 90.9778 (-> palermo :distance))) 378 | (is (close? 13.361389 (-> palermo :coordinates :x))) 379 | (is (close? 38.115556 (-> palermo :coordinates :y))))) 380 | 381 | (testing "position and distance" 382 | (let [palermo (redis/geopos *cmds* "Sicily" "Palermo")] 383 | (is (close? 13.361389 (-> palermo :x))) 384 | (is (close? 38.115556 (-> palermo :y)))) 385 | (let [[catania agrigento dont-exist] 386 | (redis/mgeopos *cmds* "Sicily" ["Catania" "Agrigento" "DontExist"])] 387 | (is (close? 15.087269 (-> catania :x))) 388 | (is (close? 37.502669 (-> catania :y))) 389 | (is (close? 13.583333 (-> agrigento :x))) 390 | (is (close? 37.316667 (-> agrigento :y))) 391 | (is (nil? dont-exist))) 392 | (is (close? 166.2742 (redis/geodist *cmds* "Sicily" "Palermo" "Catania" :km))) 393 | (is (close? 103.3182 (redis/geodist *cmds* "Sicily" "Palermo" "Catania" :mi)))))) 394 | 395 | (deftest pubsub-commands-test 396 | (testing "simple pub/sub mechanism" 397 | (let [nb-sub (atom 0) 398 | subscribed? (promise) 399 | res (promise) 400 | unsubscribed? (promise)] 401 | (with-pubsub-cmds 402 | (reify redis/PubSubListener 403 | (message [_ channel message] 404 | (deliver res [channel message])) 405 | (message [_ pattern channel message]) 406 | (subscribed [_ channel count] 407 | (swap! nb-sub inc) 408 | (deliver subscribed? true)) 409 | (unsubscribed [_ channel count] 410 | (swap! nb-sub dec) 411 | (deliver unsubscribed? true)) 412 | (psubscribed [_ pattern count]) 413 | (punsubscribed [_ pattern count])) 414 | (redis/subscribe @sub "c") 415 | (is (= true @subscribed?)) 416 | (is (= 1 @nb-sub)) 417 | (redis/publish @pub "c" "message") 418 | (is (= ["c" "message"] @res)) 419 | (redis/unsubscribe @sub "c") 420 | (is (= true @unsubscribed?)) 421 | (is (= 0 @nb-sub)) )))) 422 | -------------------------------------------------------------------------------- /test/celtuce/server_manifold_test.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.server-manifold-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [celtuce.commands :as redis] 5 | [celtuce.connector :as conn] 6 | [celtuce.manifold :refer [commands-manifold]] 7 | [manifold.deferred :as d])) 8 | 9 | (def redis-url "redis://localhost:6379") 10 | (def ^:dynamic *cmds*) 11 | 12 | (defmacro with-str-cmds [& body] 13 | `(let [rserv# (conn/redis-server redis-url 14 | :codec (celtuce.codec/utf8-string-codec))] 15 | (binding [*cmds* (commands-manifold rserv#)] 16 | (try ~@body 17 | (finally (conn/shutdown rserv#)))))) 18 | 19 | (defmacro with-pubsub-cmds 20 | "Binds local @pub and @sub with different connections, 21 | registers the given listener on @sub" 22 | [listener & body] 23 | `(let [rserv-pub# (conn/as-pubsub (conn/redis-server redis-url)) 24 | rserv-sub# (conn/as-pubsub (conn/redis-server redis-url))] 25 | (conn/add-listener! rserv-sub# ~listener) 26 | (with-local-vars 27 | [~'pub (conn/commands-sync rserv-pub#) 28 | ~'sub (conn/commands-sync rserv-sub#)] 29 | (try ~@body 30 | (finally (conn/shutdown rserv-pub#) 31 | (conn/shutdown rserv-sub#)))))) 32 | 33 | (defn cmds-fixture [test-function] 34 | (let [rserv (conn/redis-server redis-url)] 35 | (binding [*cmds* (commands-manifold rserv)] 36 | (try (test-function) 37 | (finally (conn/shutdown rserv)))))) 38 | 39 | (defn flush-fixture [test-function] 40 | (redis/flushall *cmds*) 41 | (test-function)) 42 | 43 | (use-fixtures :once cmds-fixture) 44 | (use-fixtures :each flush-fixture) 45 | 46 | (deftest connection-commands-test 47 | 48 | (testing "ping and echo" 49 | (is (= "PONG" @(redis/ping *cmds*))) 50 | (is (= "hello" @(redis/echo *cmds* "hello"))))) 51 | 52 | (deftest hash-commands-test 53 | 54 | (testing "set and get multiple hash values" 55 | @(redis/hmset *cmds* "h" {:foo "bar" :a 1 0 nil}) 56 | @(redis/hset *cmds* "h" "b" :b) 57 | (is (= true @(redis/hexists *cmds* "h" :a))) 58 | (is (= false @(redis/hexists *cmds* "h" :dont-exist))) 59 | (is (= nil @(redis/hget *cmds* "h" :dont-exist))) 60 | (is (= false @(redis/hsetnx *cmds* "h" :a :a))) 61 | (is (= "bar" @(redis/hget *cmds* "h" :foo))) 62 | (is (= ["bar" 1 nil :b nil] 63 | @(redis/hmget *cmds* "h" [:foo :a 0 "b" :dont-exist]))) 64 | (is (= {:foo "bar" :a 1 0 nil "b" :b} @(redis/hgetall *cmds* "h"))) 65 | (is (= #{:foo :a 0 "b"} 66 | (into #{} @(redis/hkeys *cmds* "h")))) 67 | (is (= #{"bar" 1 nil :b} 68 | (into #{} @(redis/hvals *cmds* "h"))))) 69 | 70 | (testing "delete and multi delete hash values" 71 | @(redis/hmdel *cmds* "h" [:a :b "b"]) 72 | @(redis/hdel *cmds* "h" 0) 73 | (is (= false @(redis/hexists *cmds* "h" 0))) 74 | (is (= {:foo "bar"} @(redis/hgetall *cmds* "h")))) 75 | 76 | (testing "increments and length" 77 | (is (= 1 @(redis/hlen *cmds* "h"))) 78 | (is (= 9 @(redis/hstrlen *cmds* "h" :foo))) 79 | (is (= 1 @(redis/hincrby *cmds* "h" :x 1))) 80 | (is (= 10 @(redis/hincrby *cmds* "h" :x 9))) 81 | (is (= 1.0 @(redis/hincrbyfloat *cmds* "h" :y 1))) 82 | (is (= 3.0 @(redis/hincrbyfloat *cmds* "h" :y 2.0)))) 83 | 84 | (testing "hscan cursors" 85 | @(redis/hmset *cmds* "hl" (->> (range 10000) (split-at 5000) (apply zipmap))) 86 | (let [cur @(redis/hscan 87 | *cmds* "hl" (redis/scan-cursor) (redis/scan-args :limit 10)) 88 | res (redis/scan-res cur)] 89 | (is (= false (celtuce.scan/finished? cur))) 90 | (is (= true (map? res))) 91 | (is (<= 5 (count res) 15))) ;; about 10 92 | (let [els1 (->> (redis/scan-args :limit 50) 93 | (redis/hscan *cmds* "hl" (redis/scan-cursor)) 94 | (redis/chunked-scan-seq) 95 | (take 100))] 96 | (is (<= 95 (count els1) 105)) ;; about 100 97 | (is (= @(redis/hgetall *cmds* "hl") 98 | (apply merge els1)))) 99 | @(redis/hmset 100 | *cmds* "hs" (->> (range 100) (map str) (split-at 50) (apply zipmap))) 101 | (let [cur @(redis/hscan 102 | *cmds* "hs" (redis/scan-cursor) (redis/scan-args :match "*0")) 103 | res (redis/scan-res cur)] 104 | (is (= true (celtuce.scan/finished? cur))) 105 | (is (= (->> (range 0 50 10) (map (fn [x] [(str x) (str (+ x 50))])) (into {})) 106 | res))) 107 | (is (thrown? Exception 108 | (redis/chunked-scan-seq 109 | (redis/hscan *cmds* nil (redis/scan-cursor))))))) 110 | 111 | (deftest key-commands-test 112 | 113 | (testing "basic key checks" 114 | @(redis/hmset *cmds* "h" {:foo "bar" :a 1 "b" :b}) 115 | (is (= 1 @(redis/exists *cmds* "h"))) 116 | (is (= 1 @(redis/mexists *cmds* ["h" :dont-exist]))) 117 | (is (= ["h"] @(redis/keys *cmds* "h"))) 118 | (is (= ["h"] (->> (redis/chunked-scan-seq (redis/scan *cmds*)) 119 | (apply concat) 120 | (into [])))) 121 | (is (= "hash" @(redis/type *cmds* "h")))) 122 | 123 | (testing "ttl related" 124 | (is (= -1 @(redis/ttl *cmds* "h"))) 125 | @(redis/expire *cmds* "h" 1) 126 | (is (> 1001 @(redis/pttl *cmds* "h") 0)) 127 | @(redis/persist *cmds* "h") 128 | (is (= -1 @(redis/pttl *cmds* "h")))) 129 | 130 | (testing "dump/restore and delete" 131 | (let [dump @(redis/dump *cmds* "h")] 132 | @(redis/del *cmds* "h") 133 | (is (= 0 @(redis/exists *cmds* "h"))) 134 | @(redis/restore *cmds* "h" 0 dump) 135 | (is (= 1 @(redis/exists *cmds* "h"))) 136 | (is (= @(redis/hgetall *cmds* "h") 137 | {:foo "bar" :a 1 "b" :b}))))) 138 | 139 | (deftest string-commands-test 140 | 141 | (testing "set and get various keys/values" 142 | @(redis/set *cmds* "foo-int" 1337) 143 | @(redis/set *cmds* 666 "devil") 144 | @(redis/set *cmds* :foo-kw :bar-kw) 145 | @(redis/set *cmds* #{1 2 3} '(1 2 3)) 146 | @(redis/set *cmds* {:a "a"} [:b "b"] (redis/set-args :ex 1)) 147 | (is (= 1337 @(redis/get *cmds* "foo-int"))) 148 | (is (= :bar-kw @(redis/get *cmds* :foo-kw))) 149 | (is (= '(1 2 3) @(redis/get *cmds* #{1 2 3}))) 150 | (is (= [:b "b"] @(redis/get *cmds* {:a "a"}))) 151 | (is (> 1001 @(redis/pttl *cmds* {:a "a"}) 0)) 152 | (is (= "devil" @(redis/getset *cmds* 666 0xdeadbeef))) 153 | (is (= 0xdeadbeef @(redis/get *cmds* 666)))) 154 | 155 | (testing "multiget/set and result type (with underlying (into (empty keys) ...)" 156 | (redis/mset *cmds* {"foo" "bar" "foofoo" "barbar"}) 157 | (is (= ["bar" "barbar"] @(redis/mget *cmds* ["foo" "foofoo"]))) 158 | (is (= '("barbar" "bar") @(redis/mget *cmds* '("foo" "foofoo")))) 159 | (is (= [nil nil] @(redis/mget *cmds* ["dont" "exist"])))) 160 | 161 | (testing "raw string manipulations" 162 | (with-str-cmds 163 | (is (= 5 @(redis/append *cmds* "msg" "Hello"))) 164 | (is (= 11 @(redis/append *cmds* "msg" " World"))) 165 | (is (= "Hello World" @(redis/get *cmds* "msg"))) 166 | @(redis/setrange *cmds* "msg" 6 "Redis") 167 | (is (= "Hello Redis" @(redis/get *cmds* "msg"))) 168 | @(redis/append *cmds* "ts" "0043") 169 | @(redis/append *cmds* "ts" "0035") 170 | (is (= "0043" @(redis/getrange *cmds* "ts" 0 3))) 171 | (is (= "0035" @(redis/getrange *cmds* "ts" 4 7))) 172 | @(redis/set *cmds* "k" "foobar") 173 | (is (= 6 @(redis/strlen *cmds* "k"))) 174 | (is (= 26 @(redis/bitcount *cmds* "k"))) 175 | (is (= 4 @(redis/bitcount *cmds* "k" 0 0))) 176 | (is (= 6 @(redis/bitcount *cmds* "k" 1 1))) 177 | @(redis/set *cmds* "i" "10") 178 | (is (= 11 @(redis/incr *cmds* "i"))) 179 | (is (= 15 @(redis/incrby *cmds* "i" 4))) 180 | (is (= 11 @(redis/decrby *cmds* "i" 4))) 181 | (is (= 10 @(redis/decr *cmds* "i"))) 182 | (is (= 11.11 @(redis/incrbyfloat *cmds* "i" 1.11))) 183 | (is (= 0 @(redis/setbit *cmds* "b" 0 0))) 184 | (is (= 0 @(redis/setbit *cmds* "b" 1 1))) 185 | (is (= 1 @(redis/setbit *cmds* "b" 1 1))) 186 | (is (= 1 @(redis/getbit *cmds* "b" 1))) 187 | (is (= 1 @(redis/bitpos *cmds* "b" true))) 188 | @(redis/setbit *cmds* "b" 0 1) 189 | @(redis/setbit *cmds* "b" 1 1) 190 | @(redis/setbit *cmds* "b" 2 0) 191 | @(redis/setbit *cmds* "b" 3 0) 192 | @(redis/setbit *cmds* "b" 4 0) 193 | @(redis/setbit *cmds* "b" 5 1) 194 | (is (= 2 @(redis/bitpos *cmds* "b" false 0 0))))) 195 | 196 | (testing "bitfield command" 197 | (let [args (redis/bitfield-args 198 | :incrby :u2 100 1 :overflow :sat :incrby :u2 102 1)] 199 | (is (= [1 1] @(redis/bitfield *cmds* "bf" args))) 200 | (is (= [2 2] @(redis/bitfield *cmds* "bf" args))) 201 | (is (= [3 3] @(redis/bitfield *cmds* "bf" args))) 202 | (is (= [0 3] @(redis/bitfield *cmds* "bf" args)))))) 203 | 204 | (deftest list-commands-test 205 | 206 | (testing "basic list manipulations" 207 | (is (= 0 @(redis/rpushx *cmds* "x" :no-op))) 208 | (is (= 5 @(redis/mrpush *cmds* "l" (->> (range 65 70) (map char))))) 209 | (is (= 5 @(redis/llen *cmds* "l"))) 210 | (is (= [\A \B \C \D \E] @(redis/lrange *cmds* "l" 0 5))) 211 | (is (= 6 @(redis/lpush *cmds* "l" \A))) 212 | (is (= 2 @(redis/lrem *cmds* "l" 2 \A))) 213 | (is (= \B @(redis/lindex *cmds* "l" 0))) 214 | (is (= 5 @(redis/linsert *cmds* "l" true \B \A))) 215 | @(redis/lset *cmds* "l" 2 \Z) 216 | @(redis/ltrim *cmds* "l" 0 2) 217 | (is (= [\A \B \Z] @(redis/lrange *cmds* "l" 0 5))) 218 | (is (= \Z @(redis/rpop *cmds* "l"))) 219 | (is (= 2 @(redis/llen *cmds* "l")))) 220 | 221 | (testing "list blocking commands" 222 | @(redis/mrpush *cmds* "bl" [1 2 3]) 223 | (is (= ["bl" 1] @(redis/blpop *cmds* 1 ["bl"]))) 224 | (is (= ["bl" 3] @(redis/brpop *cmds* 1 ["bl"]))) 225 | @(redis/del *cmds* "bl") 226 | (is (nil? @(redis/blpop *cmds* 1 ["bl"]))) 227 | (is (nil? @(redis/brpop *cmds* 1 ["bl"]))))) 228 | 229 | (deftest set-commands-test 230 | 231 | (testing "add and scan set members" 232 | (is (= 1 @(redis/sadd *cmds* "s1" :a))) 233 | (is (= 4 @(redis/msadd *cmds* "s1" [:b :c :d :e]))) 234 | (is (= 5 @(redis/scard *cmds* "s1"))) 235 | (is (= true @(redis/sismember *cmds* "s1" :a))) 236 | (is (= #{:a :b :c :d :e} 237 | @(redis/smembers *cmds* "s1"))) 238 | (is (= #{:a :b :c :d :e} 239 | (->> (redis/sscan *cmds* "s1" (redis/scan-cursor)) 240 | (redis/chunked-scan-seq) 241 | (take 5) 242 | (apply into #{}))))) 243 | 244 | (testing "deleting set members" 245 | (let [m @(redis/spop *cmds* "s1") 246 | ms @(redis/smembers *cmds* "s1")] 247 | (is (= 4 @(redis/scard *cmds* "s1"))) 248 | (is (= 4 (count ms))) 249 | (is (= #{:a :b :c :d :e} 250 | (conj ms m)))) 251 | (let [m @(redis/srandmember *cmds* "s1")] 252 | (is (= 1 @(redis/srem *cmds* "s1" m))) 253 | (is (= 3 @(redis/scard *cmds* "s1"))) 254 | (is (= false @(redis/sismember *cmds* "s1" m)))))) 255 | 256 | (deftest sortedset-commands-test 257 | 258 | (testing "add, incr, count sorted set members" 259 | (is (= 1 @(redis/zadd *cmds* "z1" 0.1 :a))) 260 | (is (= 1 @(redis/zadd *cmds* "z1" :nx 0.2 :b))) 261 | (is (= 3 @(redis/mzadd *cmds* "z1" [[0.3 :c] [0.4 :d] [0.5 :e]]))) 262 | (is (= 5 @(redis/zcard *cmds* "z1"))) 263 | (is (= 0.6 @(redis/zaddincr *cmds* "z1" 0.1 :e))) 264 | (is (= 0.6 @(redis/zscore *cmds* "z1" :e))) 265 | (is (= 0.5 @(redis/zincrby *cmds* "z1" -0.1 :e))) 266 | (is (= 3 @(redis/zcount *cmds* "z1" 0.3 0.5))) 267 | (is (= 0 @(redis/zrank *cmds* "z1" :a))) 268 | (is (= 4 @(redis/zrevrank *cmds* "z1" :a)))) 269 | 270 | (testing "range, revrange, scan sorted set" 271 | (is (= [:a :b :c] 272 | @(redis/zrange *cmds* "z1" 0 2))) 273 | (is (= [[0.1 :a] [0.2 :b] [0.3 :c]] 274 | @(redis/zrange-withscores *cmds* "z1" 0 2))) 275 | (is (= [:c :d :e] 276 | @(redis/zrangebyscore *cmds* "z1" 0.3 0.5))) 277 | (is (= [[0.3 :c] [0.4 :d] [0.5 :e]] 278 | @(redis/zrangebyscore-withscores *cmds* "z1" 0.3 0.5))) 279 | (is (= [:e :d :c] 280 | @(redis/zrevrange *cmds* "z1" 0 2))) 281 | (is (= [[0.5 :e] [0.4 :d] [0.3 :c]] 282 | @(redis/zrevrange-withscores *cmds* "z1" 0 2))) 283 | (is (= [:e :d :c] 284 | @(redis/zrevrangebyscore *cmds* "z1" 0.5 0.3))) 285 | (is (= [[0.5 :e] [0.4 :d] [0.3 :c]] 286 | @(redis/zrevrangebyscore-withscores *cmds* "z1" 0.5 0.3))) 287 | (is (= #{[0.1 :a] [0.2 :b] [0.3 :c] [0.4 :d] [0.5 :e]} 288 | (->> (redis/zscan *cmds* "z1" (redis/scan-cursor)) 289 | (redis/chunked-scan-seq) 290 | (take 5) 291 | (apply into #{}))))) 292 | 293 | (testing "deleting sorted set members" 294 | (is (= 1 @(redis/zrem *cmds* "z1" :a))) 295 | (is (= :b (first @(redis/zrange *cmds* "z1" 0 0)))) 296 | (is (= 2 @(redis/mzrem *cmds* "z1" [:b :c]))) 297 | (is (= :d (first @(redis/zrange *cmds* "z1" 0 0)))) 298 | (is (= 1 @(redis/zremrangebyrank *cmds* "z1" 0 0))) 299 | (is (= :e (first @(redis/zrange *cmds* "z1" 0 0)))) 300 | (is (= 1 @(redis/zremrangebyscore *cmds* "z1" 0.5 0.5))) 301 | (is (= 0 @(redis/zcard *cmds* "z1")))) 302 | 303 | (testing "lexicographical order based commands" 304 | (with-str-cmds 305 | @(redis/mzadd *cmds* "z2" [[0.0 "a"] [0.0 "b"] [0.0 "c"] [0.0 "d"] [0.0 "e"]]) 306 | (is (= ["a" "b" "c"] 307 | @(redis/zrangebylex *cmds* "z2" "-" "[c"))) 308 | (is (= 5 @(redis/zlexcount *cmds* "z2" "-" "+")))))) 309 | 310 | (deftest scripting-commands-test 311 | (let [script "return 10" 312 | sha "080c414e64bca1184bc4f6220a19c4d495ac896d"] 313 | (with-str-cmds 314 | (testing "simple scripting" 315 | (is (= 10 @(redis/eval *cmds* script :integer []))) 316 | (is (= sha (redis/digest *cmds* script))) 317 | (is (= 10 @(redis/evalsha *cmds* sha :integer []))) 318 | @(redis/script-flush *cmds*) 319 | (is (thrown? io.lettuce.core.RedisCommandExecutionException 320 | @(redis/evalsha *cmds* sha :integer []))))))) 321 | 322 | (deftest hll-commands-test 323 | (let [err 0.81 324 | close? (fn [x y] (<= (- x (* x err)) y (+ x (* x err))))] 325 | (testing "basic hll usage" 326 | (is (= 1 @(redis/pfadd *cmds* "pf1" 0))) 327 | (is (= 1 @(redis/mpfadd *cmds* "pf1" (range 1 1000)))) 328 | (is (close? 1000 @(redis/pfcount *cmds* "pf1"))) 329 | (is (= 0 @(redis/mpfadd *cmds* "pf1" (range 1000)))) 330 | (is (close? 1000 @(redis/pfcount *cmds* "pf1"))) 331 | (is (= 1 @(redis/mpfadd *cmds* "pf1" (range 1000 2000)))) 332 | (is (close? 2000 @(redis/pfcount *cmds* "pf1")))))) 333 | 334 | (deftest geo-commands-test 335 | (let [err 0.999999 336 | close? (fn [x y] (<= (- x (* x err)) y (+ x (* x err))))] 337 | 338 | (testing "basic geo usage" 339 | (is (= 1 @(redis/geoadd *cmds* "Sicily" 13.361389 38.115556 "Palermo"))) 340 | (is (= 2 @(redis/geoadd *cmds* "Sicily" [[15.087269 37.502669 "Catania"] 341 | [13.583333 37.316667 "Agrigento"]]))) 342 | (is (= @(redis/geohash *cmds* "Sicily" "Agrigento") "sq9sm1716e0")) 343 | (is (= @(redis/mgeohash *cmds* "Sicily" ["Palermo" "Catania"]) 344 | ["sqc8b49rny0" "sqdtr74hyu0"]))) 345 | 346 | (testing "georadius, by coord and by member" 347 | (is (= @(redis/georadius *cmds* "Sicily" 15 37 200 :km) 348 | #{"Agrigento" "Catania" "Palermo"})) 349 | (let [[palermo agrigento] 350 | @(redis/georadius *cmds* "Sicily" 15 37 200 :km 351 | (redis/geo-args :with-dist true :with-coord true 352 | :count 2 :sort :desc))] 353 | (is (= "Palermo" (-> palermo :member))) 354 | (is (close? 190.4424 (-> palermo :distance))) 355 | (is (close? 13.361389 (-> palermo :coordinates :x))) 356 | (is (close? 38.115556 (-> palermo :coordinates :y))) 357 | (is (= "Agrigento" (-> agrigento :member))) 358 | (is (close? 130.4235 (-> agrigento :distance))) 359 | (is (close? 13.583333 (-> agrigento :coordinates :x))) 360 | (is (close? 37.316667 (-> agrigento :coordinates :y)))) 361 | (is (= @(redis/georadiusbymember *cmds* "Sicily" "Agrigento" 100 :km) 362 | #{"Agrigento" "Palermo"})) 363 | (let [[agrigento palermo] 364 | @(redis/georadiusbymember 365 | *cmds* "Sicily" "Agrigento" 100 :km 366 | (redis/geo-args :with-dist true :with-coord true))] 367 | (is (= "Agrigento" (-> agrigento :member))) 368 | (is (close? 0.0000 (-> agrigento :distance))) 369 | (is (close? 13.583333 (-> agrigento :coordinates :x))) 370 | (is (close? 37.316667 (-> agrigento :coordinates :y))) 371 | (is (= "Palermo" (-> palermo :member))) 372 | (is (close? 90.9778 (-> palermo :distance))) 373 | (is (close? 13.361389 (-> palermo :coordinates :x))) 374 | (is (close? 38.115556 (-> palermo :coordinates :y))))) 375 | 376 | (testing "position and distance" 377 | (let [palermo @(redis/geopos *cmds* "Sicily" "Palermo")] 378 | (is (close? 13.361389 (-> palermo :x))) 379 | (is (close? 38.115556 (-> palermo :y)))) 380 | (let [[catania agrigento dont-exist] 381 | @(redis/mgeopos *cmds* "Sicily" ["Catania" "Agrigento" "DontExist"])] 382 | (is (close? 15.087269 (-> catania :x))) 383 | (is (close? 37.502669(-> catania :y))) 384 | (is (close? 13.583333 (-> agrigento :x))) 385 | (is (close? 37.316667 (-> agrigento :y))) 386 | (is (nil? dont-exist))) 387 | (is (close? 166.2742 @(redis/geodist *cmds* "Sicily" "Palermo" "Catania" :km))) 388 | (is (close? 103.3182 @(redis/geodist *cmds* "Sicily" "Palermo" "Catania" :mi)))))) 389 | 390 | (deftest transactional-commands-test 391 | (testing "basic transaction" 392 | @(d/chain (redis/multi *cmds*) 393 | (redis/set *cmds* :a 1) 394 | (redis/set *cmds* :b 2) 395 | (redis/get *cmds* :a) 396 | (redis/get *cmds* :b)) 397 | (is (= ["OK" "OK" 1 2] @(redis/exec *cmds*))))) 398 | 399 | (deftest pubsub-commands-test 400 | (testing "simple pub/sub mechanism" 401 | (let [nb-sub (atom 0) 402 | subscribed? (promise) 403 | res (promise) 404 | unsubscribed? (promise)] 405 | (with-pubsub-cmds 406 | (reify redis/PubSubListener 407 | (message [_ channel message] 408 | (deliver res [channel message])) 409 | (message [_ pattern channel message]) 410 | (subscribed [_ channel count] 411 | (swap! nb-sub inc) 412 | (deliver subscribed? true)) 413 | (unsubscribed [_ channel count] 414 | (swap! nb-sub dec) 415 | (deliver unsubscribed? true)) 416 | (psubscribed [_ pattern count]) 417 | (punsubscribed [_ pattern count])) 418 | (redis/subscribe @sub "c") 419 | (is (= true @subscribed?)) 420 | (is (= 1 @nb-sub)) 421 | (redis/publish @pub "c" "message") 422 | (is (= ["c" "message"] @res)) 423 | (redis/unsubscribe @sub "c") 424 | (is (= true @unsubscribed?)) 425 | (is (= 0 @nb-sub)) )))) 426 | -------------------------------------------------------------------------------- /test/celtuce/server_sync_test.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.server-sync-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [celtuce.commands :as redis] 5 | [celtuce.connector :as conn])) 6 | 7 | (def redis-url "redis://localhost:6379") 8 | (def ^:dynamic *cmds*) 9 | 10 | (defmacro with-str-cmds [& body] 11 | `(let [rserv# (conn/redis-server redis-url 12 | :codec (celtuce.codec/utf8-string-codec))] 13 | (binding [*cmds* (conn/commands-sync rserv#)] 14 | (try ~@body 15 | (finally (conn/shutdown rserv#)))))) 16 | 17 | (defmacro with-pubsub-cmds 18 | "Binds local @pub and @sub with different connections, 19 | registers the given listener on @sub" 20 | [listener & body] 21 | `(let [rserv-pub# (conn/as-pubsub (conn/redis-server redis-url)) 22 | rserv-sub# (conn/as-pubsub (conn/redis-server redis-url))] 23 | (conn/add-listener! rserv-sub# ~listener) 24 | (with-local-vars 25 | [~'pub (conn/commands-sync rserv-pub#) 26 | ~'sub (conn/commands-sync rserv-sub#)] 27 | (try ~@body 28 | (finally (conn/shutdown rserv-pub#) 29 | (conn/shutdown rserv-sub#)))))) 30 | 31 | (defn cmds-fixture [test-function] 32 | (let [rserv (conn/redis-server redis-url)] 33 | (binding [*cmds* (conn/commands-sync rserv)] 34 | (try (test-function) 35 | (finally (conn/shutdown rserv)))))) 36 | 37 | (defn flush-fixture [test-function] 38 | (redis/flushall *cmds*) 39 | (test-function)) 40 | 41 | (use-fixtures :once cmds-fixture) 42 | (use-fixtures :each flush-fixture) 43 | 44 | (deftest connection-commands-test 45 | 46 | (testing "ping and echo" 47 | (is (= "PONG" (redis/ping *cmds*))) 48 | (is (= "hello" (redis/echo *cmds* "hello"))))) 49 | 50 | (deftest hash-commands-test 51 | 52 | (testing "set and get multiple hash values" 53 | (redis/hmset *cmds* "h" {:foo "bar" :a 1 0 nil}) 54 | (redis/hset *cmds* "h" "b" :b) 55 | (is (= true (redis/hexists *cmds* "h" :a))) 56 | (is (= false (redis/hexists *cmds* "h" :dont-exist))) 57 | (is (= nil (redis/hget *cmds* "h" :dont-exist))) 58 | (is (= false (redis/hsetnx *cmds* "h" :a :a))) 59 | (is (= "bar" (redis/hget *cmds* "h" :foo))) 60 | (is (= ["bar" 1 nil :b nil] 61 | (redis/hmget *cmds* "h" [:foo :a 0 "b" :dont-exist]))) 62 | (is (= {:foo "bar" :a 1 0 nil "b" :b} (redis/hgetall *cmds* "h"))) 63 | (is (= #{:foo :a 0 "b"} 64 | (into #{} (redis/hkeys *cmds* "h")))) 65 | (is (= #{"bar" 1 nil :b} 66 | (into #{} (redis/hvals *cmds* "h"))))) 67 | 68 | (testing "delete and multi delete hash values" 69 | (redis/hmdel *cmds* "h" [:a :b "b"]) 70 | (redis/hdel *cmds* "h" 0) 71 | (is (= false (redis/hexists *cmds* "h" 0))) 72 | (is (= {:foo "bar"} (redis/hgetall *cmds* "h")))) 73 | 74 | (testing "increments and length" 75 | (is (= 1 (redis/hlen *cmds* "h"))) 76 | (is (= 9 (redis/hstrlen *cmds* "h" :foo))) 77 | (is (= 1 (redis/hincrby *cmds* "h" :x 1))) 78 | (is (= 10 (redis/hincrby *cmds* "h" :x 9))) 79 | (is (= 1.0 (redis/hincrbyfloat *cmds* "h" :y 1))) 80 | (is (= 3.0 (redis/hincrbyfloat *cmds* "h" :y 2.0)))) 81 | 82 | (testing "hscan cursors" 83 | (redis/hmset *cmds* "hl" (->> (range 10000) (split-at 5000) (apply zipmap))) 84 | (let [cur (redis/hscan 85 | *cmds* "hl" (redis/scan-cursor) (redis/scan-args :limit 10)) 86 | res (redis/scan-res cur)] 87 | (is (= false (celtuce.scan/finished? cur))) 88 | (is (= true (map? res))) 89 | (is (<= 5 (count res) 15))) ;; about 10 90 | (let [els1 (->> (redis/scan-args :limit 50) 91 | (redis/hscan *cmds* "hl" (redis/scan-cursor)) 92 | (redis/chunked-scan-seq) 93 | (take 100)) 94 | els2 (redis/hscan-seq *cmds* "hl")] 95 | (is (<= 95 (count els1) 105)) ;; about 100 96 | (is (= (redis/hgetall *cmds* "hl") 97 | (apply merge els1) 98 | (into {} els2)))) 99 | (redis/hmset 100 | *cmds* "hs" (->> (range 100) (map str) (split-at 50) (apply zipmap))) 101 | (let [cur (redis/hscan 102 | *cmds* "hs" (redis/scan-cursor) (redis/scan-args :match "*0")) 103 | res (redis/scan-res cur)] 104 | (is (= true (celtuce.scan/finished? cur))) 105 | (is (= (->> (range 0 50 10) (map (fn [x] [(str x) (str (+ x 50))])) (into {})) 106 | res))) 107 | (is (thrown? Exception 108 | (redis/chunked-scan-seq 109 | (redis/hscan *cmds* nil (redis/scan-cursor))))))) 110 | 111 | (deftest key-commands-test 112 | 113 | (testing "basic key checks" 114 | (redis/hmset *cmds* "h" {:foo "bar" :a 1 "b" :b}) 115 | (is (= 1 (redis/exists *cmds* "h"))) 116 | (is (= 1 (redis/mexists *cmds* ["h" :dont-exist]))) 117 | (is (= ["h"] (redis/keys *cmds* "h"))) 118 | (is (= ["h"] (->> (redis/scan *cmds*) 119 | (redis/chunked-scan-seq) 120 | (apply concat) 121 | (into [])))) 122 | (is (= ["h"] (redis/scan-seq *cmds*))) 123 | (is (= "hash" (redis/type *cmds* "h")))) 124 | 125 | (testing "ttl related" 126 | (is (= -1 (redis/ttl *cmds* "h"))) 127 | (redis/expire *cmds* "h" 1) 128 | (is (> 1001 (redis/pttl *cmds* "h") 0)) 129 | (redis/persist *cmds* "h") 130 | (is (= -1 (redis/pttl *cmds* "h")))) 131 | 132 | (testing "dump/restore and delete" 133 | (let [dump (redis/dump *cmds* "h")] 134 | (redis/del *cmds* "h") 135 | (is (= 0 (redis/exists *cmds* "h"))) 136 | (redis/restore *cmds* "h" 0 dump) 137 | (is (= 1 (redis/exists *cmds* "h"))) 138 | (is (= (redis/hgetall *cmds* "h") 139 | {:foo "bar" :a 1 "b" :b}))))) 140 | 141 | (deftest string-commands-test 142 | 143 | (testing "set and get various keys/values" 144 | (redis/set *cmds* "foo-int" 1337) 145 | (redis/set *cmds* 666 "devil") 146 | (redis/set *cmds* :foo-kw :bar-kw) 147 | (redis/set *cmds* #{1 2 3} '(1 2 3)) 148 | (redis/set *cmds* {:a "a"} [:b "b"] (redis/set-args :ex 1)) 149 | (is (= 1337 (redis/get *cmds* "foo-int"))) 150 | (is (= :bar-kw (redis/get *cmds* :foo-kw))) 151 | (is (= '(1 2 3) (redis/get *cmds* #{1 2 3}))) 152 | (is (= [:b "b"] (redis/get *cmds* {:a "a"}))) 153 | (is (> 1001 (redis/pttl *cmds* {:a "a"}) 0)) 154 | (is (= "devil" (redis/getset *cmds* 666 0xdeadbeef))) 155 | (is (= 0xdeadbeef (redis/get *cmds* 666)))) 156 | 157 | (testing "multiget/set and result type (with underlying (into (empty keys) ...)" 158 | (redis/mset *cmds* {"foo" "bar" "foofoo" "barbar"}) 159 | (is (= ["bar" "barbar"] (redis/mget *cmds* ["foo" "foofoo"]))) 160 | (is (= '("barbar" "bar") (redis/mget *cmds* '("foo" "foofoo")))) 161 | (is (= [nil nil] (redis/mget *cmds* ["dont" "exist"])))) 162 | 163 | (testing "raw string manipulations" 164 | (with-str-cmds 165 | (is (= 5 (redis/append *cmds* "msg" "Hello"))) 166 | (is (= 11 (redis/append *cmds* "msg" " World"))) 167 | (is (= "Hello World" (redis/get *cmds* "msg"))) 168 | (redis/setrange *cmds* "msg" 6 "Redis") 169 | (is (= "Hello Redis" (redis/get *cmds* "msg"))) 170 | (redis/append *cmds* "ts" "0043") 171 | (redis/append *cmds* "ts" "0035") 172 | (is (= "0043" (redis/getrange *cmds* "ts" 0 3))) 173 | (is (= "0035" (redis/getrange *cmds* "ts" 4 7))) 174 | (redis/set *cmds* "k" "foobar") 175 | (is (= 6 (redis/strlen *cmds* "k"))) 176 | (is (= 26 (redis/bitcount *cmds* "k"))) 177 | (is (= 4 (redis/bitcount *cmds* "k" 0 0))) 178 | (is (= 6 (redis/bitcount *cmds* "k" 1 1))) 179 | (redis/set *cmds* "i" "10") 180 | (is (= 11 (redis/incr *cmds* "i"))) 181 | (is (= 15 (redis/incrby *cmds* "i" 4))) 182 | (is (= 11 (redis/decrby *cmds* "i" 4))) 183 | (is (= 10 (redis/decr *cmds* "i"))) 184 | (is (= 11.11 (redis/incrbyfloat *cmds* "i" 1.11))) 185 | (is (= 0 (redis/setbit *cmds* "b" 0 0))) 186 | (is (= 0 (redis/setbit *cmds* "b" 1 1))) 187 | (is (= 1 (redis/setbit *cmds* "b" 1 1))) 188 | (is (= 1 (redis/getbit *cmds* "b" 1))) 189 | (is (= 1 (redis/bitpos *cmds* "b" true))) 190 | (redis/setbit *cmds* "b" 0 1) 191 | (redis/setbit *cmds* "b" 1 1) 192 | (redis/setbit *cmds* "b" 2 0) 193 | (redis/setbit *cmds* "b" 3 0) 194 | (redis/setbit *cmds* "b" 4 0) 195 | (redis/setbit *cmds* "b" 5 1) 196 | (is (= 2 (redis/bitpos *cmds* "b" false 0 0))))) 197 | 198 | (testing "bitfield command" 199 | (let [args (redis/bitfield-args 200 | :incrby :u2 100 1 :overflow :sat :incrby :u2 102 1)] 201 | (is (= [1 1] (redis/bitfield *cmds* "bf" args))) 202 | (is (= [2 2] (redis/bitfield *cmds* "bf" args))) 203 | (is (= [3 3] (redis/bitfield *cmds* "bf" args))) 204 | (is (= [0 3] (redis/bitfield *cmds* "bf" args)))))) 205 | 206 | (deftest list-commands-test 207 | 208 | (testing "basic list manipulations" 209 | (is (= 0 (redis/rpushx *cmds* "x" :no-op))) 210 | (is (= 5 (redis/mrpush *cmds* "l" (->> (range 65 70) (map char))))) 211 | (is (= 5 (redis/llen *cmds* "l"))) 212 | (is (= [\A \B \C \D \E] (redis/lrange *cmds* "l" 0 5))) 213 | (is (= 6 (redis/lpush *cmds* "l" \A))) 214 | (is (= 2 (redis/lrem *cmds* "l" 2 \A))) 215 | (is (= \B (redis/lindex *cmds* "l" 0))) 216 | (is (= 5 (redis/linsert *cmds* "l" true \B \A))) 217 | (redis/lset *cmds* "l" 2 \Z) 218 | (redis/ltrim *cmds* "l" 0 2) 219 | (is (= [\A \B \Z] (redis/lrange *cmds* "l" 0 5))) 220 | (is (= \Z (redis/rpop *cmds* "l"))) 221 | (is (= 2 (redis/llen *cmds* "l")))) 222 | 223 | (testing "list blocking commands" 224 | (redis/mrpush *cmds* "bl" [1 2 3]) 225 | (is (= ["bl" 1] (redis/blpop *cmds* 1 ["bl"]))) 226 | (is (= ["bl" 3] (redis/brpop *cmds* 1 ["bl"]))) 227 | (redis/del *cmds* "bl") 228 | (is (nil? (redis/blpop *cmds* 1 ["bl"]))) 229 | (is (nil? (redis/brpop *cmds* 1 ["bl"]))))) 230 | 231 | (deftest set-commands-test 232 | 233 | (testing "add and scan set members" 234 | (is (= 1 (redis/sadd *cmds* "s1" :a))) 235 | (is (= 4 (redis/msadd *cmds* "s1" [:b :c :d :e]))) 236 | (is (= 5 (redis/scard *cmds* "s1"))) 237 | (is (= true (redis/sismember *cmds* "s1" :a))) 238 | (is (= #{:a :b :c :d :e} 239 | (redis/smembers *cmds* "s1"))) 240 | (is (= #{:a :b :c :d :e} 241 | (->> (redis/sscan *cmds* "s1" (redis/scan-cursor)) 242 | (redis/chunked-scan-seq) 243 | (take 5) 244 | (apply into #{})))) 245 | (is (= #{:a :b :c :d :e} 246 | (into #{} (redis/sscan-seq *cmds* "s1"))))) 247 | 248 | (testing "deleting set members" 249 | (let [m (redis/spop *cmds* "s1") 250 | ms (redis/smembers *cmds* "s1")] 251 | (is (= 4 (redis/scard *cmds* "s1"))) 252 | (is (= 4 (count ms))) 253 | (is (= #{:a :b :c :d :e} 254 | (conj ms m)))) 255 | (let [m (redis/srandmember *cmds* "s1")] 256 | (is (= 1 (redis/srem *cmds* "s1" m))) 257 | (is (= 3 (redis/scard *cmds* "s1"))) 258 | (is (= false (redis/sismember *cmds* "s1" m)))))) 259 | 260 | (deftest sortedset-commands-test 261 | 262 | (testing "add, incr, count sorted set members" 263 | (is (= 1 (redis/zadd *cmds* "z1" 0.1 :a))) 264 | (is (= 1 (redis/zadd *cmds* "z1" :nx 0.2 :b))) 265 | (is (= 3 (redis/mzadd *cmds* "z1" [[0.3 :c] [0.4 :d] [0.5 :e]]))) 266 | (is (= 5 (redis/zcard *cmds* "z1"))) 267 | (is (= 0.6 (redis/zaddincr *cmds* "z1" 0.1 :e))) 268 | (is (= 0.6 (redis/zscore *cmds* "z1" :e))) 269 | (is (= 0.5 (redis/zincrby *cmds* "z1" -0.1 :e))) 270 | (is (= 3 (redis/zcount *cmds* "z1" 0.3 0.5))) 271 | (is (= 0 (redis/zrank *cmds* "z1" :a))) 272 | (is (= 4 (redis/zrevrank *cmds* "z1" :a)))) 273 | 274 | (testing "range, revrange, scan sorted set" 275 | (is (= [:a :b :c] 276 | (redis/zrange *cmds* "z1" 0 2))) 277 | (is (= [[0.1 :a] [0.2 :b] [0.3 :c]] 278 | (redis/zrange-withscores *cmds* "z1" 0 2))) 279 | (is (= [:c :d :e] 280 | (redis/zrangebyscore *cmds* "z1" 0.3 0.5))) 281 | (is (= [[0.3 :c] [0.4 :d] [0.5 :e]] 282 | (redis/zrangebyscore-withscores *cmds* "z1" 0.3 0.5))) 283 | (is (= [:e :d :c] 284 | (redis/zrevrange *cmds* "z1" 0 2))) 285 | (is (= [[0.5 :e] [0.4 :d] [0.3 :c]] 286 | (redis/zrevrange-withscores *cmds* "z1" 0 2))) 287 | (is (= [:e :d :c] 288 | (redis/zrevrangebyscore *cmds* "z1" 0.5 0.3))) 289 | (is (= [[0.5 :e] [0.4 :d] [0.3 :c]] 290 | (redis/zrevrangebyscore-withscores *cmds* "z1" 0.5 0.3))) 291 | (is (= #{[0.1 :a] [0.2 :b] [0.3 :c] [0.4 :d] [0.5 :e]} 292 | (->> (redis/zscan *cmds* "z1" (redis/scan-cursor)) 293 | (redis/chunked-scan-seq) 294 | (take 5) 295 | (apply into #{})))) 296 | (is (= #{[0.1 :a] [0.2 :b] [0.3 :c] [0.4 :d] [0.5 :e]} 297 | (into #{} (redis/zscan-seq *cmds* "z1"))))) 298 | 299 | (testing "deleting sorted set members" 300 | (is (= 1 (redis/zrem *cmds* "z1" :a))) 301 | (is (= :b (first (redis/zrange *cmds* "z1" 0 0)))) 302 | (is (= 2 (redis/mzrem *cmds* "z1" [:b :c]))) 303 | (is (= :d (first (redis/zrange *cmds* "z1" 0 0)))) 304 | (is (= 1 (redis/zremrangebyrank *cmds* "z1" 0 0))) 305 | (is (= :e (first (redis/zrange *cmds* "z1" 0 0)))) 306 | (is (= 1 (redis/zremrangebyscore *cmds* "z1" 0.5 0.5))) 307 | (is (= 0 (redis/zcard *cmds* "z1")))) 308 | 309 | (testing "lexicographical order based commands" 310 | (with-str-cmds 311 | (redis/mzadd *cmds* "z2" [[0.0 "a"] [0.0 "b"] [0.0 "c"] [0.0 "d"] [0.0 "e"]]) 312 | (is (= ["a" "b" "c"] 313 | (redis/zrangebylex *cmds* "z2" "-" "[c"))) 314 | (is (= 5 (redis/zlexcount *cmds* "z2" "-" "+")))))) 315 | 316 | (deftest scripting-commands-test 317 | (let [script "return 10" 318 | sha "080c414e64bca1184bc4f6220a19c4d495ac896d"] 319 | (with-str-cmds 320 | (testing "simple scripting" 321 | (is (= 10 (redis/eval *cmds* script :integer []))) 322 | (is (= sha (redis/digest *cmds* script))) 323 | (is (= 10 (redis/evalsha *cmds* sha :integer []))) 324 | (redis/script-flush *cmds*) 325 | (is (thrown? io.lettuce.core.RedisCommandExecutionException 326 | (redis/evalsha *cmds* sha :integer []))))))) 327 | 328 | (deftest hll-commands-test 329 | (let [err 0.81 330 | close? (fn [x y] (<= (- x (* x err)) y (+ x (* x err))))] 331 | (testing "basic hll usage" 332 | (is (= 1 (redis/pfadd *cmds* "pf1" 0))) 333 | (is (= 1 (redis/mpfadd *cmds* "pf1" (range 1 1000)))) 334 | (is (close? 1000 (redis/pfcount *cmds* "pf1"))) 335 | (is (= 0 (redis/mpfadd *cmds* "pf1" (range 1000)))) 336 | (is (close? 1000 (redis/pfcount *cmds* "pf1"))) 337 | (is (= 1 (redis/mpfadd *cmds* "pf1" (range 1000 2000)))) 338 | (is (close? 2000 (redis/pfcount *cmds* "pf1")))))) 339 | 340 | (deftest geo-commands-test 341 | (let [err 0.999999 342 | close? (fn [x y] (<= (- x (* x err)) y (+ x (* x err))))] 343 | 344 | (testing "basic geo usage" 345 | (is (= 1 (redis/geoadd *cmds* "Sicily" 13.361389 38.115556 "Palermo"))) 346 | (is (= 2 (redis/geoadd *cmds* "Sicily" [[15.087269 37.502669 "Catania"] 347 | [13.583333 37.316667 "Agrigento"]]))) 348 | (is (= (redis/geohash *cmds* "Sicily" "Agrigento") "sq9sm1716e0")) 349 | (is (= (redis/mgeohash *cmds* "Sicily" ["Palermo" "Catania"]) 350 | ["sqc8b49rny0" "sqdtr74hyu0"]))) 351 | 352 | (testing "georadius, by coord and by member" 353 | (is (= (redis/georadius *cmds* "Sicily" 15 37 200 :km) 354 | #{"Agrigento" "Catania" "Palermo"})) 355 | (let [[palermo agrigento] 356 | (redis/georadius *cmds* "Sicily" 15 37 200 :km 357 | (redis/geo-args :with-dist true :with-coord true 358 | :count 2 :sort :desc))] 359 | (is (= "Palermo" (-> palermo :member))) 360 | (is (close? 190.4424 (-> palermo :distance))) 361 | (is (close? 13.361389 (-> palermo :coordinates :x))) 362 | (is (close? 38.115556 (-> palermo :coordinates :y))) 363 | (is (= "Agrigento" (-> agrigento :member))) 364 | (is (close? 130.4235 (-> agrigento :distance))) 365 | (is (close? 13.583333 (-> agrigento :coordinates :x))) 366 | (is (close? 37.316667 (-> agrigento :coordinates :y)))) 367 | (is (= (redis/georadiusbymember *cmds* "Sicily" "Agrigento" 100 :km) 368 | #{"Agrigento" "Palermo"})) 369 | (let [[agrigento palermo] 370 | (redis/georadiusbymember 371 | *cmds* "Sicily" "Agrigento" 100 :km 372 | (redis/geo-args :with-dist true :with-coord true))] 373 | (is (= "Agrigento" (-> agrigento :member))) 374 | (is (close? 0.0000 (-> agrigento :distance))) 375 | (is (close? 13.583333 (-> agrigento :coordinates :x))) 376 | (is (close? 37.316667 (-> agrigento :coordinates :y))) 377 | (is (= "Palermo" (-> palermo :member))) 378 | (is (close? 90.9778 (-> palermo :distance))) 379 | (is (close? 13.361389 (-> palermo :coordinates :x))) 380 | (is (close? 38.115556 (-> palermo :coordinates :y))))) 381 | 382 | (testing "position and distance" 383 | (let [palermo (redis/geopos *cmds* "Sicily" "Palermo")] 384 | (is (close? 13.361389 (-> palermo :x))) 385 | (is (close? 38.115556 (-> palermo :y)))) 386 | (let [[catania agrigento dont-exist] 387 | (redis/mgeopos *cmds* "Sicily" ["Catania" "Agrigento" "DontExist"])] 388 | (is (close? 15.087269 (-> catania :x))) 389 | (is (close? 37.502669(-> catania :y))) 390 | (is (close? 13.583333 (-> agrigento :x))) 391 | (is (close? 37.316667 (-> agrigento :y))) 392 | (is (nil? dont-exist))) 393 | (is (close? 166.2742 (redis/geodist *cmds* "Sicily" "Palermo" "Catania" :km))) 394 | (is (close? 103.3182 (redis/geodist *cmds* "Sicily" "Palermo" "Catania" :mi)))))) 395 | 396 | (deftest transactional-commands-test 397 | (testing "basic transaction" 398 | (redis/multi *cmds*) 399 | (redis/set *cmds* :a 1) 400 | (redis/set *cmds* :b 2) 401 | (redis/get *cmds* :a) 402 | (redis/get *cmds* :b) 403 | (is (= ["OK" "OK" 1 2] (redis/exec *cmds*))))) 404 | 405 | (deftest pubsub-commands-test 406 | (testing "simple pub/sub mechanism" 407 | (let [nb-sub (atom 0) 408 | subscribed? (promise) 409 | res (promise) 410 | unsubscribed? (promise)] 411 | (with-pubsub-cmds 412 | (reify redis/PubSubListener 413 | (message [_ channel message] 414 | (deliver res [channel message])) 415 | (message [_ pattern channel message]) 416 | (subscribed [_ channel count] 417 | (swap! nb-sub inc) 418 | (deliver subscribed? true)) 419 | (unsubscribed [_ channel count] 420 | (swap! nb-sub dec) 421 | (deliver unsubscribed? true)) 422 | (psubscribed [_ pattern count]) 423 | (punsubscribed [_ pattern count])) 424 | (redis/subscribe @sub "c") 425 | (is (= true @subscribed?)) 426 | (is (= 1 @nb-sub)) 427 | (redis/publish @pub "c" "message") 428 | (is (= ["c" "message"] @res)) 429 | (redis/unsubscribe @sub "c") 430 | (is (= true @unsubscribed?)) 431 | (is (= 0 @nb-sub)) )))) 432 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/impl/cluster.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.impl.cluster 2 | (:refer-clojure :exclude [get set keys sort type eval time]) 3 | (:require 4 | [celtuce.commands :refer :all] 5 | [celtuce.args.zset :refer [zadd-args]] 6 | [celtuce.args.scripting :refer [output-type]] 7 | [celtuce.args.geo :refer [->unit]]) 8 | (:import 9 | (io.lettuce.core.cluster.api.sync RedisAdvancedClusterCommands) 10 | (io.lettuce.core 11 | Value KeyValue ScanCursor 12 | ScanArgs MigrateArgs SortArgs BitFieldArgs SetArgs KillArgs 13 | ZStoreArgs ScoredValue 14 | GeoArgs GeoRadiusStoreArgs GeoWithin GeoCoordinates) 15 | (java.util Map))) 16 | 17 | (extend-type RedisAdvancedClusterCommands 18 | 19 | ConnectionCommands 20 | (ping [this] 21 | (.ping this)) 22 | (echo [this val] 23 | (.echo this val)) 24 | 25 | HashCommands 26 | (hdel [this k f] 27 | (.hdel this k ^objects (into-array Object [f]))) 28 | (hmdel [this k fs] 29 | (.hdel this k ^objects (into-array Object fs))) 30 | (hexists [this k f] 31 | (.hexists this k f)) 32 | (hget [this k f] 33 | (.hget this k f)) 34 | (hincrby [this k f a] 35 | (.hincrby this k f (long a))) 36 | (hincrbyfloat [this k f a] 37 | (.hincrbyfloat this k f (double a))) 38 | (hgetall [this k] 39 | (into {} (.hgetall this k))) 40 | (hkeys [this k] 41 | (into [] (.hkeys this k))) 42 | (hlen [this k] 43 | (.hlen this k)) 44 | (hmget [this k fs] 45 | (->> (.hmget this k ^objects (into-array Object fs)) 46 | (map (fn [^KeyValue kv] (.getValueOrElse kv nil))) 47 | (into (empty fs)))) 48 | (hmset [this k ^Map m] 49 | (.hmset this k m)) 50 | (hscan 51 | ([this k] 52 | (.hscan this k)) 53 | ([this k ^ScanCursor c] 54 | (.hscan this k c)) 55 | ([this k ^ScanCursor c ^ScanArgs args] 56 | (.hscan this k c args))) 57 | (hset [this k f v] 58 | (.hset this k f v)) 59 | (hsetnx [this k f v] 60 | (.hsetnx this k f v)) 61 | (hstrlen [this k f] 62 | (.hstrlen this k f)) 63 | (hvals [this k] 64 | (into [] (.hvals this k))) 65 | 66 | KeyCommands 67 | (del [this k] 68 | (.del this (into-array Object [k]))) 69 | (dump [this k] 70 | (.dump this k)) 71 | (exists [this k] 72 | (.exists this ^objects (into-array Object [k]))) 73 | (expire [this k ^long sec] 74 | (.expire this k sec)) 75 | (expireat [this k ts-sec] 76 | (.expire this k ^long ts-sec)) 77 | (keys [this pattern] 78 | (into [] (.keys this pattern))) 79 | (mdel [this ks] 80 | (.del this (into-array Object ks))) 81 | (mexists [this ks] 82 | (.exists this ^objects (into-array Object ks))) 83 | (migrate [this ^String h ^Integer p ^Integer db ^Long ms ^MigrateArgs args] 84 | (.migrate this h p db ms args)) 85 | (move [this k ^Integer db] 86 | (.move this k db)) 87 | (mtouch [this ks] 88 | (.touch this (into-array Object ks))) 89 | (munlink [this ks] 90 | (.unlink this (into-array Object ks))) 91 | (obj-encoding [this k] 92 | (.objectEncoding this k)) 93 | (obj-idletime [this k] 94 | (.objectIdletime this k)) 95 | (obj-refcount [this k] 96 | (.objectRefcount this k)) 97 | (persist [this k] 98 | (.persist this k)) 99 | (pexpire [this k ^long ms] 100 | (.pexpire this k ms)) 101 | (pexpireat [this k ^long ts-ms] 102 | (.pexpireat this k ts-ms)) 103 | (pttl [this k] 104 | (.pttl this k)) 105 | (randomkey [this] 106 | (.randomkey this)) 107 | (rename [this k1 k2] 108 | (.rename this k1 k2)) 109 | (renamenx [this k1 k2] 110 | (.renamenx this k1 k2)) 111 | (restore [this k ^long ttl ^bytes v] 112 | (.restore this k ttl v)) 113 | (scan 114 | ([this] 115 | (.scan this)) 116 | ([this ^ScanCursor c] 117 | (.scan this c)) 118 | ([this ^ScanCursor c ^ScanArgs args] 119 | (.scan this c args))) 120 | (sort 121 | ([this k] 122 | (.sort this k)) 123 | ([this k ^SortArgs args] 124 | (.sort this k args))) 125 | (sort-store [this k ^SortArgs args d] 126 | (.sortStore this k args d)) 127 | (touch [this k] 128 | (.touch this (into-array Object [k]))) 129 | (ttl [this k] 130 | (.ttl this k)) 131 | (type [this k] 132 | (.type this k)) 133 | (unlink [this k] 134 | (.unlink this (into-array Object [k]))) 135 | 136 | StringsCommands 137 | (append [this k v] 138 | (.append this k v)) 139 | (bitcount 140 | ([this k] 141 | (.bitcount this k)) 142 | ([this k ^long s ^long e] 143 | (.bitcount this k s e))) 144 | (bitfield [this k ^BitFieldArgs args] 145 | (into [] (.bitfield this k args))) 146 | (bitop-and [this d ks] 147 | (.bitopAnd this d ^objects (into-array Object ks))) 148 | (bitop-not [this d k] 149 | (.bitopNot this d k)) 150 | (bitop-or [this d ks] 151 | (.bitopOr this d ^objects (into-array Object ks))) 152 | (bitop-xor [this d ks] 153 | (.bitopXor this d ^objects (into-array Object ks))) 154 | (bitpos 155 | ([this k ^Boolean state] 156 | (.bitpos this k state)) 157 | ([this k ^Boolean state ^Long s ^Long e] 158 | (.bitpos this k state s e))) 159 | (decr [this k] 160 | (.decr this k)) 161 | (decrby [this k ^long a] 162 | (.decrby this k a)) 163 | (get [this k] 164 | (.get this k)) 165 | (getbit [this k ^long o] 166 | (.getbit this k o)) 167 | (getrange [this k ^long s ^long e] 168 | (.getrange this k s e)) 169 | (getset [this k v] 170 | (.getset this k v)) 171 | (incr [this k] 172 | (.incr this k)) 173 | (incrby [this k ^long a] 174 | (.incrby this k a)) 175 | (incrbyfloat [this k ^double a] 176 | (.incrbyfloat this k a)) 177 | (mget [this ks] 178 | (->> (.mget this ^objects (into-array Object ks)) 179 | (map (fn [^KeyValue kv] (.getValueOrElse kv nil))) 180 | (into (empty ks)))) 181 | (mset [this m] 182 | (.mset this m)) 183 | (msetnx [this m] 184 | (.msetnx this m)) 185 | (set 186 | ([this k v] 187 | (.set this k v)) 188 | ([this k v ^SetArgs args] 189 | (.set this k v args))) 190 | (setbit [this k ^Long o ^Integer v] 191 | (.setbit this k o v)) 192 | (setex [this k ^long sec v] 193 | (.setex this k sec v)) 194 | (psetex [this k ^long ms v] 195 | (.psetex this k ms v)) 196 | (setnx [this k v] 197 | (.setnx this k v)) 198 | (setrange [this k ^long o v] 199 | (.setrange this k o v)) 200 | (strlen [this k] 201 | (.strlen this k)) 202 | 203 | ListCommands 204 | (blpop [this ^long sec ks] 205 | (let [res (.blpop this sec ^objects (into-array Object ks))] 206 | (when res 207 | [(.getKey res) (.getValue res)]))) 208 | (brpop [this ^long sec ks] 209 | (let [res (.brpop this sec ^objects (into-array Object ks))] 210 | (when res 211 | [(.getKey res) (.getValue res)]))) 212 | (brpoplpush [this ^long sec s d] 213 | (.brpoplpush this sec s d)) 214 | (lindex [this k ^long idx] 215 | (.lindex this k idx)) 216 | (linsert [this k ^Boolean b? p v] 217 | (.linsert this k b? p v)) 218 | (llen [this k] 219 | (.llen this k)) 220 | (lpop [this k] 221 | (.lpop this k)) 222 | (lpush [this k v] 223 | (.lpush this k ^objects (into-array Object [v]))) 224 | (lpushx [this k v] 225 | (.lpushx this k ^objects (into-array Object [v]))) 226 | (lrange [this k ^long s ^long e] 227 | (into [] (.lrange this k s e))) 228 | (lrem [this k ^long c v] 229 | (.lrem this k c v)) 230 | (lset [this k ^long idx v] 231 | (.lset this k idx v)) 232 | (ltrim [this k ^long s ^long e] 233 | (.ltrim this k s e)) 234 | (mrpush [this k vs] 235 | (.rpush this k ^objects (into-array Object vs))) 236 | (mrpushx [this k vs] 237 | (.rpushx this k ^objects (into-array Object vs))) 238 | (mlpush [this k vs] 239 | (.lpush this k ^objects (into-array Object vs))) 240 | (mlpushx [this k vs] 241 | (.lpushx this k ^objects (into-array Object vs))) 242 | (rpop [this k] 243 | (.rpop this k)) 244 | (rpoplpush [this s d] 245 | (.rpoplpush this s d)) 246 | (rpush [this k v] 247 | (.rpush this k ^objects (into-array Object [v]))) 248 | (rpushx [this k v] 249 | (.rpushx this k ^objects (into-array Object [v]))) 250 | 251 | SetCommands 252 | (msadd [this k ms] 253 | (.sadd this k ^objects (into-array Object ms))) 254 | (msrem [this k ms] 255 | (.srem this k ^objects (into-array Object ms))) 256 | (sadd [this k m] 257 | (.sadd this k ^objects (into-array Object [m]))) 258 | (scard [this k] 259 | (.scard this k)) 260 | (sdiff [this ks] 261 | (into #{} (.sdiff this ^objects (into-array Object ks)))) 262 | (sdiffstore [this d ks] 263 | (.sdiffstore this d ^objects (into-array Object ks))) 264 | (sinter [this ks] 265 | (into #{} (.sinter this ^objects (into-array Object ks)))) 266 | (sinterstore [this d ks] 267 | (.sinterstore this d ^objects (into-array Object ks))) 268 | (sismember [this k v] 269 | (.sismember this k v)) 270 | (smove [this k d m] 271 | (.smove this k d m)) 272 | (smembers [this k] 273 | (into #{} (.smembers this k))) 274 | (spop 275 | ([this k] 276 | (.spop this k)) 277 | ([this k ^long c] 278 | (into #{} (.spop this k c)))) 279 | (srandmember 280 | ([this k] 281 | (.srandmember this k)) 282 | ([this k ^long c] 283 | (into #{} (.srandmember this k c)))) 284 | (srem [this k m] 285 | (.srem this k ^objects (into-array Object [m]))) 286 | (sunion [this ks] 287 | (into #{} (.sunion this ^objects (into-array Object ks)))) 288 | (sunionstore [this d ks] 289 | (.sunionstore this d ^objects (into-array Object ks))) 290 | (sscan 291 | ([this k] 292 | (.sscan this k)) 293 | ([this k ^ScanCursor c] 294 | (.sscan this k c)) 295 | ([this k ^ScanCursor c ^ScanArgs args] 296 | (.sscan this k c args))) 297 | 298 | SortedSetCommands 299 | (zadd 300 | ([this k ^double s m] 301 | (.zadd this k s m)) 302 | ([this k opt ^Double s m] 303 | (.zadd this k (zadd-args opt) s m))) 304 | (mzadd 305 | ([this k sms] 306 | (.zadd this k ^objects (into-array Object (mapcat identity sms)))) 307 | ([this k opt sms] 308 | (.zadd this k (zadd-args opt) ^objects (into-array Object (mapcat identity sms))))) 309 | (zaddincr [this k ^double s m] 310 | (.zaddincr this k s m)) 311 | (zcard [this k] 312 | (.zcard this k)) 313 | (zcount [this k ^double min ^double max] 314 | (.zcount this k min max)) 315 | (zincrby [this k ^double a m] 316 | (.zincrby this k a m)) 317 | (zinterstore 318 | ([this d ^objects ks] 319 | (.zinterstore this d ks)) 320 | ([this d ^ZStoreArgs args ^objects ks] 321 | (.zinterstore this d args ks))) 322 | (zrange [this k ^long s ^long e] 323 | (into [] (.zrange this k s e))) 324 | (zrange-withscores [this k ^long s ^long e] 325 | (->> (.zrangeWithScores this k s e) 326 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 327 | (into []))) 328 | (zrangebyscore 329 | ([this k ^double min ^double max] 330 | (into [] (.zrangebyscore this k min max))) 331 | ([this k ^Double min ^Double max ^Long o ^Long c] 332 | (into [] (.zrangebyscore this k min max o c)))) 333 | (zrangebyscore-withscores 334 | ([this k ^double min ^double max] 335 | (->> (.zrangebyscoreWithScores this k min max) 336 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 337 | (into []))) 338 | ([this k ^Double min ^Double max ^Long o ^Long c] 339 | (->> (.zrangebyscoreWithScores this k min max o c) 340 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 341 | (into [])))) 342 | (zrank [this k m] 343 | (.zrank this k m)) 344 | (zrem [this k m] 345 | (.zrem this k ^objects (into-array Object [m]))) 346 | (mzrem [this k ms] 347 | (.zrem this k ^objects (into-array Object ms))) 348 | (zremrangebyrank [this k ^long s ^long e] 349 | (.zremrangebyrank this k s e)) 350 | (zremrangebyscore [this k ^Double min ^Double max] 351 | (.zremrangebyscore this k min max)) 352 | (zrevrange [this k ^long s ^long e] 353 | (into [] (.zrevrange this k s e))) 354 | (zrevrange-withscores [this k ^long s ^long e] 355 | (->> (.zrevrangeWithScores this k s e) 356 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 357 | (into []))) 358 | (zrevrangebyscore 359 | ([this k ^double min ^double max] 360 | (into [] (.zrevrangebyscore this k min max))) 361 | ([this k ^Double min ^Double max ^Long o ^Long c] 362 | (into [] (.zrevrangebyscore this k min max o c)))) 363 | (zrevrangebyscore-withscores 364 | ([this k ^double min ^double max] 365 | (->> (.zrevrangebyscoreWithScores this k min max) 366 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 367 | (into []))) 368 | ([this k ^Double min ^Double max ^Long o ^Long c] 369 | (->> (.zrevrangebyscoreWithScores this k min max o c) 370 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 371 | (into [])))) 372 | (zrevrank [this k m] 373 | (.zrevrank this k m)) 374 | (zscore [this k m] 375 | (.zscore this k m)) 376 | (zunionstore 377 | ([this d ks] 378 | (.zunionstore this d ^objects (into-array Object ks))) 379 | ([this d ^ZStoreArgs args ks] 380 | (.zunionstore this d args ^objects (into-array Object ks)))) 381 | (zscan 382 | ([this k] 383 | (.zscan this k)) 384 | ([this k ^ScanCursor c] 385 | (.zscan this k c)) 386 | ([this k ^ScanCursor c ^ScanArgs args] 387 | (.zscan this c args))) 388 | (zlexcount [this k ^String min ^String max] 389 | (.zlexcount this k min max)) 390 | (zremrangebylex [this k ^String min ^String max] 391 | (.zremrangebylex this k min max)) 392 | (zrangebylex 393 | ([this k ^String min ^String max] 394 | (into [] (.zrangebylex this k min max))) 395 | ([this k ^String min ^String max ^Long o ^Long c] 396 | (into [] (.zrangebylex this k min max o c)))) 397 | 398 | ScriptingCommands 399 | (eval 400 | ([this ^String script t ks] 401 | (.eval this script (output-type t) ^objects (into-array Object ks))) 402 | ([this ^String script t ks vs] 403 | (.eval this script (output-type t) 404 | ^objects (into-array Object ks) 405 | ^objects (into-array Object vs)))) 406 | (evalsha 407 | ([this ^String digest t ks] 408 | (.evalsha this digest (output-type t) ^objects (into-array Object ks))) 409 | ([this ^String digest t ks vs] 410 | (.evalsha this digest (output-type t) 411 | ^objects (into-array Object ks) 412 | ^objects (into-array Object vs)))) 413 | (script-exists? [this digests] 414 | (.scriptExists this ^"[Ljava.lang.String;" (into-array String digests))) 415 | (script-flush [this] 416 | (.scriptFlush this)) 417 | (script-kill [this] 418 | (.scriptKill this)) 419 | (script-load [this ^String script] 420 | (.scriptLoad this script)) 421 | (digest [this ^String script] 422 | (.digest this script)) 423 | 424 | ServerCommands 425 | (bgrewriteaof [this] 426 | (.bgrewriteaof this)) 427 | (bgsave [this] 428 | (.bgsave this)) 429 | (client-getname [this] 430 | (.clientGetname this)) 431 | (client-setname [this name] 432 | (.clientSetname this name)) 433 | (client-kill [this addr-or-args] 434 | (if (instance? KillArgs addr-or-args) 435 | (.clientKill this ^KillArgs addr-or-args) 436 | (.clientKill this ^String addr-or-args))) 437 | (client-pause [this ^long timeout-ms] 438 | (.clientPause this timeout-ms)) 439 | (client-list [this] 440 | (.clientList this)) 441 | (command [this] 442 | (into [] (.command this))) 443 | (command-info [this commands] 444 | (into (empty commands) 445 | (.commandInfo this ^"[Ljava.lang.String;" (into-array String commands)))) 446 | (command-count [this] 447 | (.commandCount this)) 448 | (config-get [this ^String param] 449 | (into [] (.configGet this param))) 450 | (config-resetstat [this] 451 | (.configResetstat this)) 452 | (config-rewrite [this] 453 | (.configRewrite this)) 454 | (config-set [this ^String param ^String val] 455 | (.configSet this param val)) 456 | (dbsize [this] 457 | (.dbsize this)) 458 | (debug-crash-recov [this ^long delay-ms] 459 | (.debugCrashAndRecover this delay-ms)) 460 | (debug-htstats [this ^Integer db] 461 | (.debugHtstats this db)) 462 | (debug-object [this key] 463 | (.debugObject this key)) 464 | (debug-oom [this] 465 | (.debugOom this)) 466 | (debug-segfault [this] 467 | (.debugSegfault this)) 468 | (debug-reload [this] 469 | (.debugReload this)) 470 | (debug-restart [this ^long delay-ms] 471 | (.debugRestart this delay-ms)) 472 | (debug-sds-len [this key] 473 | (.debugSdslen this key)) 474 | (flushall [this] 475 | (.flushall this)) 476 | (flushall-async [this] 477 | (.flushallAsync this)) 478 | (flushdb [this] 479 | (.flushdb this)) 480 | (flushdb-async [this] 481 | (.flushdbAsync this)) 482 | (info 483 | ([this] 484 | (.info this)) 485 | ([this ^String section] 486 | (.info this section))) 487 | (lastsave [this] 488 | (.lastsave this)) 489 | (save [this] 490 | (.save this)) 491 | (shutdown [this save?] 492 | (.shutdown this save?)) 493 | (slaveof [this ^String host ^Integer port] 494 | (.slaveof this host port)) 495 | (slaveof-no-one [this] 496 | (.slaveofNoOne this)) 497 | (slowlog-get 498 | ([this] 499 | (into [] (.slowlogGet this))) 500 | ([this ^Integer count] 501 | (into [] (.slowlogGet this count)))) 502 | (slowlog-len [this] 503 | (.slowlogLen this)) 504 | (slowlog-reset [this] 505 | (.slowlogReset this)) 506 | (time [this] 507 | (into [] (.time this))) 508 | 509 | HLLCommands 510 | (pfadd [this key val] 511 | (.pfadd this key ^objects (into-array Object [val]))) 512 | (mpfadd [this key vals] 513 | (.pfadd this key ^objects (into-array Object vals))) 514 | (pfmerge [this dest keys] 515 | (.pfmerge this dest ^objects (into-array Object keys))) 516 | (pfcount [this key] 517 | (.pfcount this ^objects (into-array Object [key]))) 518 | (mpfcount [this keys] 519 | (.pfcount this ^objects (into-array Object keys))) 520 | 521 | GeoCommands 522 | (geoadd 523 | ([this key ^Double long ^Double lat member] 524 | (.geoadd this key long lat member)) 525 | ([this key lng-lat-members] 526 | (.geoadd this key ^objects (into-array Object (mapcat identity lng-lat-members))))) 527 | (geohash [this key member] 528 | (->> ^objects (into-array Object [member]) 529 | (.geohash this key) 530 | ^Value (first) 531 | (.getValue))) 532 | (mgeohash [this key members] 533 | (->> ^objects (into-array Object members) 534 | (.geohash this key) 535 | (map (fn [^Value v] (.getValue v))) 536 | (into []))) 537 | (georadius 538 | ([this key ^Double long ^Double lat ^Double dist unit] 539 | (into #{} (.georadius this key long lat dist (->unit unit)))) 540 | ([this key ^Double long ^Double lat ^Double dist unit args] 541 | (condp instance? args 542 | GeoArgs 543 | (->> (.georadius this key long lat dist (->unit unit) ^GeoArgs args) 544 | (map (fn [^GeoWithin g] 545 | (if-not g 546 | nil 547 | (cond-> {:member (.getMember g)} 548 | (.getDistance g) (assoc :distance (.getDistance g)) 549 | (.getGeohash g) (assoc :geohash (.getGeohash g)) 550 | (.getCoordinates g) 551 | (assoc :coordinates 552 | {:x (.getX ^GeoCoordinates (.getCoordinates g)) 553 | :y (.getY ^GeoCoordinates (.getCoordinates g))}))))) 554 | (into [])) 555 | GeoRadiusStoreArgs 556 | (.georadius this key long lat dist (->unit unit) ^GeoRadiusStoreArgs args) 557 | (throw (ex-info "Invalid Args" {:args (class args) 558 | :valids #{GeoArgs GeoRadiusStoreArgs}}))))) 559 | (georadiusbymember 560 | ([this key member ^Double dist unit] 561 | (into #{} (.georadiusbymember this key member dist (->unit unit)))) 562 | ([this key member ^Double dist unit args] 563 | (condp instance? args 564 | GeoArgs 565 | (->> (.georadiusbymember this key member dist (->unit unit) ^GeoArgs args) 566 | (map (fn [^GeoWithin g] 567 | (if-not g 568 | nil 569 | (cond-> {:member (.getMember g)} 570 | (.getDistance g) (assoc :distance (.getDistance g)) 571 | (.getGeohash g) (assoc :geohash (.getGeohash g)) 572 | (.getCoordinates g) 573 | (assoc :coordinates 574 | {:x (.getX ^GeoCoordinates (.getCoordinates g)) 575 | :y (.getY ^GeoCoordinates (.getCoordinates g))}))))) 576 | (into [])) 577 | GeoRadiusStoreArgs 578 | (.georadiusbymember this key member dist (->unit unit) ^GeoRadiusStoreArgs args) 579 | (throw (ex-info "Invalid Args" {:args (class args) 580 | :valids #{GeoArgs GeoRadiusStoreArgs}}))))) 581 | (geopos [this key member] 582 | (->> (.geopos this key ^objects (into-array Object [member])) 583 | (map (fn [^GeoCoordinates c] 584 | (if-not c nil {:x (.getX c) :y (.getY c)}))) 585 | (first))) 586 | (mgeopos [this key members] 587 | (->> (.geopos this key ^objects (into-array Object members)) 588 | (map (fn [^GeoCoordinates c] 589 | (if-not c nil {:x (.getX c) :y (.getY c)}))) 590 | (into []))) 591 | (geodist [this key from to unit] 592 | (.geodist this key from to (->unit unit)))) 593 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/impl/server.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.impl.server 2 | (:refer-clojure :exclude [get set keys sort type eval time]) 3 | (:require 4 | [celtuce.commands :refer :all] 5 | [celtuce.args.zset :refer [zadd-args]] 6 | [celtuce.args.scripting :refer [output-type]] 7 | [celtuce.args.geo :refer [->unit]]) 8 | (:import 9 | (io.lettuce.core.api.sync RedisCommands) 10 | (io.lettuce.core 11 | Value KeyValue ScanCursor 12 | ScanArgs MigrateArgs SortArgs BitFieldArgs SetArgs KillArgs 13 | ZStoreArgs ScoredValue 14 | GeoArgs GeoRadiusStoreArgs GeoWithin GeoCoordinates) 15 | (java.util Map))) 16 | 17 | (extend-type RedisCommands 18 | 19 | ConnectionCommands 20 | (ping [this] 21 | (.ping this)) 22 | (echo [this val] 23 | (.echo this val)) 24 | 25 | HashCommands 26 | (hdel [this k f] 27 | (.hdel this k ^objects (into-array Object [f]))) 28 | (hmdel [this k fs] 29 | (.hdel this k ^objects (into-array Object fs))) 30 | (hexists [this k f] 31 | (.hexists this k f)) 32 | (hget [this k f] 33 | (.hget this k f)) 34 | (hincrby [this k f a] 35 | (.hincrby this k f (long a))) 36 | (hincrbyfloat [this k f a] 37 | (.hincrbyfloat this k f (double a))) 38 | (hgetall [this k] 39 | (into {} (.hgetall this k))) 40 | (hkeys [this k] 41 | (into [] (.hkeys this k))) 42 | (hlen [this k] 43 | (.hlen this k)) 44 | (hmget [this k fs] 45 | (->> (.hmget this k ^objects (into-array Object fs)) 46 | (map (fn [^KeyValue kv] (.getValueOrElse kv nil))) 47 | (into (empty fs)))) 48 | (hmset [this k ^Map m] 49 | (.hmset this k m)) 50 | (hscan 51 | ([this k] 52 | (.hscan this k)) 53 | ([this k ^ScanCursor c] 54 | (.hscan this k c)) 55 | ([this k ^ScanCursor c ^ScanArgs args] 56 | (.hscan this k c args))) 57 | (hset [this k f v] 58 | (.hset this k f v)) 59 | (hsetnx [this k f v] 60 | (.hsetnx this k f v)) 61 | (hstrlen [this k f] 62 | (.hstrlen this k f)) 63 | (hvals [this k] 64 | (into [] (.hvals this k))) 65 | 66 | KeyCommands 67 | (del [this k] 68 | (.del this ^objects (into-array Object [k]))) 69 | (dump [this k] 70 | (.dump this k)) 71 | (exists [this k] 72 | (.exists this ^objects (into-array Object [k]))) 73 | (expire [this k ^long sec] 74 | (.expire this k sec)) 75 | (expireat [this k ts-sec] 76 | (.expire this k ^long ts-sec)) 77 | (keys [this pattern] 78 | (into [] (.keys this pattern))) 79 | (mdel [this ks] 80 | (.del this ^objects (into-array Object ks))) 81 | (mexists [this ks] 82 | (.exists this ^objects (into-array Object ks))) 83 | (migrate [this ^String h ^Integer p ^Integer db ^Long ms ^MigrateArgs args] 84 | (.migrate this h p db ms args)) 85 | (move [this k ^Integer db] 86 | (.move this k db)) 87 | (mtouch [this ks] 88 | (.touch this ^objects (into-array Object ks))) 89 | (munlink [this ks] 90 | (.unlink this ^objects (into-array Object ks))) 91 | (obj-encoding [this k] 92 | (.objectEncoding this k)) 93 | (obj-idletime [this k] 94 | (.objectIdletime this k)) 95 | (obj-refcount [this k] 96 | (.objectRefcount this k)) 97 | (persist [this k] 98 | (.persist this k)) 99 | (pexpire [this k ^long ms] 100 | (.pexpire this k ms)) 101 | (pexpireat [this k ^long ts-ms] 102 | (.pexpireat this k ts-ms)) 103 | (pttl [this k] 104 | (.pttl this k)) 105 | (randomkey [this] 106 | (.randomkey this)) 107 | (rename [this k1 k2] 108 | (.rename this k1 k2)) 109 | (renamenx [this k1 k2] 110 | (.renamenx this k1 k2)) 111 | (restore [this k ^long ttl ^bytes v] 112 | (.restore this k ttl v)) 113 | (scan 114 | ([this] 115 | (.scan this)) 116 | ([this ^ScanCursor c] 117 | (.scan this c)) 118 | ([this ^ScanCursor c ^ScanArgs args] 119 | (.scan this c args))) 120 | (sort 121 | ([this k] 122 | (.sort this k)) 123 | ([this k ^SortArgs args] 124 | (.sort this k args))) 125 | (sort-store [this k ^SortArgs args d] 126 | (.sortStore this k args d)) 127 | (touch [this k] 128 | (.touch this ^objects (into-array Object [k]))) 129 | (ttl [this k] 130 | (.ttl this k)) 131 | (type [this k] 132 | (.type this k)) 133 | (unlink [this k] 134 | (.unlink this ^objects (into-array Object [k]))) 135 | 136 | StringsCommands 137 | (append [this k v] 138 | (.append this k v)) 139 | (bitcount 140 | ([this k] 141 | (.bitcount this k)) 142 | ([this k ^long s ^long e] 143 | (.bitcount this k s e))) 144 | (bitfield [this k ^BitFieldArgs args] 145 | (into [] (.bitfield this k args))) 146 | (bitop-and [this d ks] 147 | (.bitopAnd this d ^objects (into-array Object ks))) 148 | (bitop-not [this d k] 149 | (.bitopNot this d k)) 150 | (bitop-or [this d ks] 151 | (.bitopOr this d ^objects (into-array Object ks))) 152 | (bitop-xor [this d ks] 153 | (.bitopXor this d ^objects (into-array Object ks))) 154 | (bitpos 155 | ([this k ^Boolean state] 156 | (.bitpos this k state)) 157 | ([this k ^Boolean state ^Long s ^Long e] 158 | (.bitpos this k state s e))) 159 | (decr [this k] 160 | (.decr this k)) 161 | (decrby [this k ^long a] 162 | (.decrby this k a)) 163 | (get [this k] 164 | (.get this k)) 165 | (getbit [this k ^long o] 166 | (.getbit this k o)) 167 | (getrange [this k ^long s ^long e] 168 | (.getrange this k s e)) 169 | (getset [this k v] 170 | (.getset this k v)) 171 | (incr [this k] 172 | (.incr this k)) 173 | (incrby [this k ^long a] 174 | (.incrby this k a)) 175 | (incrbyfloat [this k ^double a] 176 | (.incrbyfloat this k a)) 177 | (mget [this ks] 178 | (->> (.mget this ^objects (into-array Object ks)) 179 | (map (fn [^KeyValue kv] (.getValueOrElse kv nil))) 180 | (into (empty ks)))) 181 | (mset [this ^Map m] 182 | (.mset this m)) 183 | (msetnx [this ^Map m] 184 | (.msetnx this m)) 185 | (set 186 | ([this k v] 187 | (.set this k v)) 188 | ([this k v ^SetArgs args] 189 | (.set this k v args))) 190 | (setbit [this k ^Long o ^Integer v] 191 | (.setbit this k o v)) 192 | (setex [this k ^long sec v] 193 | (.setex this k sec v)) 194 | (psetex [this k ^long ms v] 195 | (.psetex this k ms v)) 196 | (setnx [this k v] 197 | (.setnx this k v)) 198 | (setrange [this k ^long o v] 199 | (.setrange this k o v)) 200 | (strlen [this k] 201 | (.strlen this k)) 202 | 203 | ListCommands 204 | (blpop [this ^long sec ks] 205 | (let [res (.blpop this sec ^objects (into-array Object ks))] 206 | (when res 207 | [(.getKey res) (.getValue res)]))) 208 | (brpop [this ^long sec ks] 209 | (let [res (.brpop this sec ^objects (into-array Object ks))] 210 | (when res 211 | [(.getKey res) (.getValue res)]))) 212 | (brpoplpush [this ^long sec s d] 213 | (.brpoplpush this sec s d)) 214 | (lindex [this k ^long idx] 215 | (.lindex this k idx)) 216 | (linsert [this k ^Boolean b? p v] 217 | (.linsert this k b? p v)) 218 | (llen [this k] 219 | (.llen this k)) 220 | (lpop [this k] 221 | (.lpop this k)) 222 | (lpush [this k v] 223 | (.lpush this k ^objects (into-array Object [v]))) 224 | (lpushx [this k v] 225 | (.lpushx this k ^objects (into-array Object [v]))) 226 | (lrange [this k ^long s ^long e] 227 | (into [] (.lrange this k s e))) 228 | (lrem [this k ^long c v] 229 | (.lrem this k c v)) 230 | (lset [this k ^long idx v] 231 | (.lset this k idx v)) 232 | (ltrim [this k ^long s ^long e] 233 | (.ltrim this k s e)) 234 | (mrpush [this k vs] 235 | (.rpush this k ^objects (into-array Object vs))) 236 | (mrpushx [this k vs] 237 | (.rpushx this k ^objects (into-array Object vs))) 238 | (mlpush [this k vs] 239 | (.lpush this k ^objects (into-array Object vs))) 240 | (mlpushx [this k vs] 241 | (.lpushx this k ^objects (into-array Object vs))) 242 | (rpop [this k] 243 | (.rpop this k)) 244 | (rpoplpush [this s d] 245 | (.rpoplpush this s d)) 246 | (rpush [this k v] 247 | (.rpush this k ^objects (into-array Object [v]))) 248 | (rpushx [this k v] 249 | (.rpushx this k ^objects (into-array Object [v]))) 250 | 251 | SetCommands 252 | (msadd [this k ms] 253 | (.sadd this k ^objects (into-array Object ms))) 254 | (msrem [this k ms] 255 | (.srem this k ^objects (into-array Object ms))) 256 | (sadd [this k m] 257 | (.sadd this k ^objects (into-array Object [m]))) 258 | (scard [this k] 259 | (.scard this k)) 260 | (sdiff [this ks] 261 | (into #{} (.sdiff this ^objects (into-array Object ks)))) 262 | (sdiffstore [this d ks] 263 | (.sdiffstore this d ^objects (into-array Object ks))) 264 | (sinter [this ks] 265 | (into #{} (.sinter this ^objects (into-array Object ks)))) 266 | (sinterstore [this d ks] 267 | (.sinterstore this d ^objects (into-array Object ks))) 268 | (sismember [this k v] 269 | (.sismember this k v)) 270 | (smove [this k d m] 271 | (.smove this k d m)) 272 | (smembers [this k] 273 | (into #{} (.smembers this k))) 274 | (spop 275 | ([this k] 276 | (.spop this k)) 277 | ([this k ^long c] 278 | (into #{} (.spop this k c)))) 279 | (srandmember 280 | ([this k] 281 | (.srandmember this k)) 282 | ([this k ^long c] 283 | (into #{} (.srandmember this k c)))) 284 | (srem [this k m] 285 | (.srem this k ^objects (into-array Object [m]))) 286 | (sunion [this ks] 287 | (into #{} (.sunion this ^objects (into-array Object ks)))) 288 | (sunionstore [this d ks] 289 | (.sunionstore this d ^objects (into-array Object ks))) 290 | (sscan 291 | ([this k] 292 | (.sscan this k)) 293 | ([this k ^ScanCursor c] 294 | (.sscan this k c)) 295 | ([this k ^ScanCursor c ^ScanArgs args] 296 | (.sscan this k c args))) 297 | 298 | SortedSetCommands 299 | (zadd 300 | ([this k ^double s m] 301 | (.zadd this k s m)) 302 | ([this k opt ^Double s m] 303 | (.zadd this k (zadd-args opt) s m))) 304 | (mzadd 305 | ([this k sms] 306 | (.zadd this k ^objects (into-array Object (mapcat identity sms)))) 307 | ([this k opt sms] 308 | (.zadd this k (zadd-args opt) ^objects (into-array Object (mapcat identity sms))))) 309 | (zaddincr [this k ^double s m] 310 | (.zaddincr this k s m)) 311 | (zcard [this k] 312 | (.zcard this k)) 313 | (zcount [this k ^double min ^double max] 314 | (.zcount this k min max)) 315 | (zincrby [this k ^double a m] 316 | (.zincrby this k a m)) 317 | (zinterstore 318 | ([this d ^objects ks] 319 | (.zinterstore this d ks)) 320 | ([this d ^ZStoreArgs args ^objects ks] 321 | (.zinterstore this d args ks))) 322 | (zrange [this k ^long s ^long e] 323 | (into [] (.zrange this k s e))) 324 | (zrange-withscores [this k ^long s ^long e] 325 | (->> (.zrangeWithScores this k s e) 326 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 327 | (into []))) 328 | (zrangebyscore 329 | ([this k ^double min ^double max] 330 | (into [] (.zrangebyscore this k min max))) 331 | ([this k ^Double min ^Double max ^Long o ^Long c] 332 | (into [] (.zrangebyscore this k min max o c)))) 333 | (zrangebyscore-withscores 334 | ([this k ^double min ^double max] 335 | (->> (.zrangebyscoreWithScores this k min max) 336 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 337 | (into []))) 338 | ([this k ^Double min ^Double max ^Long o ^Long c] 339 | (->> (.zrangebyscoreWithScores this k min max o c) 340 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 341 | (into [])))) 342 | (zrank [this k m] 343 | (.zrank this k m)) 344 | (zrem [this k m] 345 | (.zrem this k ^objects (into-array Object [m]))) 346 | (mzrem [this k ms] 347 | (.zrem this k ^objects (into-array Object ms))) 348 | (zremrangebyrank [this k ^long s ^long e] 349 | (.zremrangebyrank this k s e)) 350 | (zremrangebyscore [this k ^Double min ^Double max] 351 | (.zremrangebyscore this k min max)) 352 | (zrevrange [this k ^long s ^long e] 353 | (into [] (.zrevrange this k s e))) 354 | (zrevrange-withscores [this k ^long s ^long e] 355 | (->> (.zrevrangeWithScores this k s e) 356 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 357 | (into []))) 358 | (zrevrangebyscore 359 | ([this k ^double min ^double max] 360 | (into [] (.zrevrangebyscore this k min max))) 361 | ([this k ^Double min ^Double max ^Long o ^Long c] 362 | (into [] (.zrevrangebyscore this k min max o c)))) 363 | (zrevrangebyscore-withscores 364 | ([this k ^double min ^double max] 365 | (->> (.zrevrangebyscoreWithScores this k min max) 366 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 367 | (into []))) 368 | ([this k ^Double min ^Double max ^Long o ^Long c] 369 | (->> (.zrevrangebyscoreWithScores this k min max o c) 370 | (map (fn [^ScoredValue sv] [(.getScore sv) (.getValue sv)])) 371 | (into [])))) 372 | (zrevrank [this k m] 373 | (.zrevrank this k m)) 374 | (zscore [this k m] 375 | (.zscore this k m)) 376 | (zunionstore 377 | ([this d ks] 378 | (.zunionstore this d ^objects (into-array Object ks))) 379 | ([this d ^ZStoreArgs args ks] 380 | (.zunionstore this d args ^objects (into-array Object ks)))) 381 | (zscan 382 | ([this k] 383 | (.zscan this k)) 384 | ([this k ^ScanCursor c] 385 | (.zscan this k c)) 386 | ([this k ^ScanCursor c ^ScanArgs args] 387 | (.zscan this c args))) 388 | (zlexcount [this k ^String min ^String max] 389 | (.zlexcount this k min max)) 390 | (zremrangebylex [this k ^String min ^String max] 391 | (.zremrangebylex this k min max)) 392 | (zrangebylex 393 | ([this k ^String min ^String max] 394 | (into [] (.zrangebylex this k min max))) 395 | ([this k ^String min ^String max ^Long o ^Long c] 396 | (into [] (.zrangebylex this k min max o c)))) 397 | 398 | ScriptingCommands 399 | (eval 400 | ([this ^String script t ks] 401 | (.eval this script (output-type t) ^objects (into-array Object ks))) 402 | ([this ^String script t ks vs] 403 | (.eval this script (output-type t) 404 | ^objects (into-array Object ks) 405 | ^objects (into-array Object vs)))) 406 | (evalsha 407 | ([this ^String digest t ks] 408 | (.evalsha this digest (output-type t) ^objects (into-array Object ks))) 409 | ([this ^String digest t ks vs] 410 | (.evalsha this digest (output-type t) 411 | ^objects (into-array Object ks) 412 | ^objects (into-array Object vs)))) 413 | (script-exists? [this digests] 414 | (.scriptExists this ^"[Ljava.lang.String;" (into-array String digests))) 415 | (script-flush [this] 416 | (.scriptFlush this)) 417 | (script-kill [this] 418 | (.scriptKill this)) 419 | (script-load [this ^String script] 420 | (.scriptLoad this script)) 421 | (digest [this ^String script] 422 | (.digest this script)) 423 | 424 | ServerCommands 425 | (bgrewriteaof [this] 426 | (.bgrewriteaof this)) 427 | (bgsave [this] 428 | (.bgsave this)) 429 | (client-getname [this] 430 | (.clientGetname this)) 431 | (client-setname [this name] 432 | (.clientSetname this name)) 433 | (client-kill [this addr-or-args] 434 | (if (instance? KillArgs addr-or-args) 435 | (.clientKill this ^KillArgs addr-or-args) 436 | (.clientKill this ^String addr-or-args))) 437 | (client-pause [this ^long timeout-ms] 438 | (.clientPause this timeout-ms)) 439 | (client-list [this] 440 | (.clientList this)) 441 | (command [this] 442 | (into [] (.command this))) 443 | (command-info [this commands] 444 | (into (empty commands) 445 | (.commandInfo this ^"[Ljava.lang.String;" (into-array String commands)))) 446 | (command-count [this] 447 | (.commandCount this)) 448 | (config-get [this ^String param] 449 | (into [] (.configGet this param))) 450 | (config-resetstat [this] 451 | (.configResetstat this)) 452 | (config-rewrite [this] 453 | (.configRewrite this)) 454 | (config-set [this ^String param ^String val] 455 | (.configSet this param val)) 456 | (dbsize [this] 457 | (.dbsize this)) 458 | (debug-crash-recov [this ^long delay-ms] 459 | (.debugCrashAndRecover this delay-ms)) 460 | (debug-htstats [this ^Integer db] 461 | (.debugHtstats this db)) 462 | (debug-object [this key] 463 | (.debugObject this key)) 464 | (debug-oom [this] 465 | (.debugOom this)) 466 | (debug-segfault [this] 467 | (.debugSegfault this)) 468 | (debug-reload [this] 469 | (.debugReload this)) 470 | (debug-restart [this ^long delay-ms] 471 | (.debugRestart this delay-ms)) 472 | (debug-sds-len [this key] 473 | (.debugSdslen this key)) 474 | (flushall [this] 475 | (.flushall this)) 476 | (flushall-async [this] 477 | (.flushallAsync this)) 478 | (flushdb [this] 479 | (.flushdb this)) 480 | (flushdb-async [this] 481 | (.flushdbAsync this)) 482 | (info 483 | ([this] 484 | (.info this)) 485 | ([this ^String section] 486 | (.info this section))) 487 | (lastsave [this] 488 | (.lastsave this)) 489 | (save [this] 490 | (.save this)) 491 | (shutdown [this ^Boolean save?] 492 | (.shutdown this save?)) 493 | (slaveof [this ^String host ^Integer port] 494 | (.slaveof this host port)) 495 | (slaveof-no-one [this] 496 | (.slaveofNoOne this)) 497 | (slowlog-get 498 | ([this] 499 | (into [] (.slowlogGet this))) 500 | ([this ^Integer count] 501 | (into [] (.slowlogGet this count)))) 502 | (slowlog-len [this] 503 | (.slowlogLen this)) 504 | (slowlog-reset [this] 505 | (.slowlogReset this)) 506 | (time [this] 507 | (into [] (.time this))) 508 | 509 | HLLCommands 510 | (pfadd [this key val] 511 | (.pfadd this key ^objects (into-array Object [val]))) 512 | (mpfadd [this key vals] 513 | (.pfadd this key ^objects (into-array Object vals))) 514 | (pfmerge [this dest keys] 515 | (.pfmerge this dest ^objects (into-array Object keys))) 516 | (pfcount [this key] 517 | (.pfcount this ^objects (into-array Object [key]))) 518 | (mpfcount [this keys] 519 | (.pfcount this ^objects (into-array Object keys))) 520 | 521 | GeoCommands 522 | (geoadd 523 | ([this key ^Double long ^Double lat member] 524 | (.geoadd this key long lat member)) 525 | ([this key lng-lat-members] 526 | (.geoadd this key ^objects (into-array Object (mapcat identity lng-lat-members))))) 527 | (geohash [this key member] 528 | (->> ^objects (into-array Object [member]) 529 | (.geohash this key) 530 | ^Value (first) 531 | (.getValue))) 532 | (mgeohash [this key members] 533 | (->> ^objects (into-array Object members) 534 | (.geohash this key) 535 | (map (fn [^Value v] (.getValue v))) 536 | (into []))) 537 | (georadius 538 | ([this key ^Double long ^Double lat ^Double dist unit] 539 | (into #{} (.georadius this key long lat dist (->unit unit)))) 540 | ([this key ^Double long ^Double lat ^Double dist unit args] 541 | (condp instance? args 542 | GeoArgs 543 | (->> (.georadius this key long lat dist (->unit unit) ^GeoArgs args) 544 | (map (fn [^GeoWithin g] 545 | (if-not g 546 | nil 547 | (cond-> {:member (.getMember g)} 548 | (.getDistance g) (assoc :distance (.getDistance g)) 549 | (.getGeohash g) (assoc :geohash (.getGeohash g)) 550 | (.getCoordinates g) 551 | (assoc :coordinates 552 | {:x (.getX ^GeoCoordinates (.getCoordinates g)) 553 | :y (.getY ^GeoCoordinates (.getCoordinates g))}))))) 554 | (into [])) 555 | GeoRadiusStoreArgs 556 | (.georadius this key long lat dist (->unit unit) ^GeoRadiusStoreArgs args) 557 | (throw (ex-info "Invalid Args" {:args (class args) 558 | :valids #{GeoArgs GeoRadiusStoreArgs}}))))) 559 | (georadiusbymember 560 | ([this key member ^Double dist unit] 561 | (into #{} (.georadiusbymember this key member dist (->unit unit)))) 562 | ([this key member ^Double dist unit args] 563 | (condp instance? args 564 | GeoArgs 565 | (->> (.georadiusbymember this key member dist (->unit unit) ^GeoArgs args) 566 | (map (fn [^GeoWithin g] 567 | (if-not g 568 | nil 569 | (cond-> {:member (.getMember g)} 570 | (.getDistance g) (assoc :distance (.getDistance g)) 571 | (.getGeohash g) (assoc :geohash (.getGeohash g)) 572 | (.getCoordinates g) 573 | (assoc :coordinates 574 | {:x (.getX ^GeoCoordinates (.getCoordinates g)) 575 | :y (.getY ^GeoCoordinates (.getCoordinates g))}))))) 576 | (into [])) 577 | GeoRadiusStoreArgs 578 | (.georadiusbymember this key member dist (->unit unit) ^GeoRadiusStoreArgs args) 579 | (throw (ex-info "Invalid Args" {:args (class args) 580 | :valids #{GeoArgs GeoRadiusStoreArgs}}))))) 581 | (geopos [this key member] 582 | (->> (.geopos this key ^objects (into-array Object [member])) 583 | (map (fn [^GeoCoordinates c] 584 | (if-not c nil {:x (.getX c) :y (.getY c)}))) 585 | (first))) 586 | (mgeopos [this key members] 587 | (->> (.geopos this key ^objects (into-array Object members)) 588 | (map (fn [^GeoCoordinates c] 589 | (if-not c nil {:x (.getX c) :y (.getY c)}))) 590 | (into []))) 591 | (geodist [this key from to unit] 592 | (.geodist this key from to (->unit unit))) 593 | 594 | TransactionalCommands 595 | (discard [this] 596 | (.discard this)) 597 | (exec [this] 598 | (into [] (.exec this))) 599 | (multi [this] 600 | (.multi this)) 601 | (watch [this key] 602 | (.watch this ^objects (into-array Object [key]))) 603 | (mwatch [this keys] 604 | (.watch this ^objects (into-array Object keys))) 605 | (unwatch [this] 606 | (.unwatch this))) 607 | -------------------------------------------------------------------------------- /modules/celtuce-core/src/celtuce/commands.clj: -------------------------------------------------------------------------------- 1 | (ns celtuce.commands 2 | (:refer-clojure :exclude [get set keys sort type eval time]) 3 | (:require 4 | [potemkin :refer [import-vars]] 5 | [celtuce.args.migrate] 6 | [celtuce.args.sort] 7 | [celtuce.args.bitfield] 8 | [celtuce.args.set] 9 | [celtuce.args.zset] 10 | [celtuce.args.kill] 11 | [celtuce.args.geo] 12 | [celtuce.scan])) 13 | 14 | (import-vars [celtuce.args.migrate migrate-args]) 15 | (import-vars [celtuce.args.sort sort-args]) 16 | (import-vars [celtuce.args.bitfield bitfield-args]) 17 | (import-vars [celtuce.args.set set-args]) 18 | (import-vars [celtuce.args.zset zstore-args]) 19 | (import-vars [celtuce.args.kill kill-args]) 20 | (import-vars [celtuce.args.geo geo-args georadius-store-args]) 21 | (import-vars [celtuce.scan scan-cursor scan-args scan-res chunked-scan-seq 22 | scan-seq hscan-seq zscan-seq sscan-seq]) 23 | 24 | (defprotocol ConnectionCommands 25 | "Redis connection Commands" 26 | (ping [this] 27 | "Ping the server") 28 | (echo [this val] 29 | "Echo the given string")) 30 | 31 | (defprotocol HashCommands 32 | "Redis Hash Commands" 33 | (hdel [this key field] 34 | "Delete one hash field") 35 | (hexists [this key field] 36 | "Determine if a hash field exists") 37 | (hget [this key field] 38 | "Get the value of a hash field") 39 | (hincrby [this key field amount] 40 | "Increment the value of a hash field by long") 41 | (hincrbyfloat [this key field amount] 42 | "Increment the value of a hash field by double") 43 | (hgetall [this key] 44 | "Get all the fields and values in a hash") 45 | (hkeys [this key] 46 | "Get all the fields in a hash") 47 | (hlen [this key] 48 | "Get the number of fields in a hash") 49 | (hmdel [this key fields] 50 | "Delete multiple hash fields") 51 | (hmget [this key fields] 52 | "Get the values of all the given hash fields") 53 | (hmset [this key map] 54 | "Set multiple hash fields to multiple values (map)") 55 | (hscan [this key] [this key cursor] [this key cursor args] 56 | "Incrementally iterate hash fields and associated values") 57 | (hset [this key field val] 58 | "Set the string value of a hash field") 59 | (hsetnx [this key field val] 60 | "Set the value of a hash field, only if it doesn't exist") 61 | (hstrlen [this key field] 62 | "Get the string length of the field value in a hash") 63 | (hvals [this key] 64 | "Get all the values in a hash")) 65 | 66 | (defprotocol KeyCommands 67 | "Redis Key Commands" 68 | (del [this key] 69 | "Delete one key") 70 | (mdel [this keys] 71 | "Delete multiple keys") 72 | (unlink [this key] 73 | "Unlink one key (non blocking DEL)") 74 | (munlink [this keys] 75 | "Unlink multiple keys (non blocking DEL)") 76 | (dump [this key] 77 | "Serialized version of the value stored at the key") 78 | (exists [this key] 79 | "Determine whether key exists") 80 | (expire [this key sec] 81 | "Set a key's time to live in seconds") 82 | (expireat [this key ts-sec] 83 | "Set the expiration for a key as a UNIX timestamp") 84 | (keys [this pattern] 85 | "Find all keys matching the given pattern") 86 | (mexists [this keys] 87 | "Determine how many keys exist") 88 | (migrate [this host port db timeout-ms args] 89 | "Transfer a key from a Redis instance to another one") 90 | (move [this key db] 91 | "Move a key to another database") 92 | (obj-encoding [this key] 93 | "Internal representation used to store the key's value") 94 | (obj-idletime [this key] 95 | "Number of sec the key's value is idle (no read/write)") 96 | (obj-refcount [this key] 97 | "Number of references of the key's value") 98 | (persist [this key] 99 | "Remove the expiration from a key") 100 | (pexpire [this key ttl-ms] 101 | "Set a key's time to live in milliseconds") 102 | (pexpireat [this key ts-ms] 103 | "Set the expiration for a key as a UNIX timestamp in ms") 104 | (pttl [this key] 105 | "Get the time to live for a key in milliseconds") 106 | (randomkey [this] 107 | "Return a random key from the keyspace") 108 | (rename [this key1 key2] 109 | "Rename a key") 110 | (renamenx [this key1 key2] 111 | "Rename a key, only if the new key does not exist") 112 | (restore [this key ttl val] 113 | "Create a key using a serialized value obtained by DUMP") 114 | (sort [this key] [this key args] 115 | "Sort the elements in a list, set or sorted set") 116 | (sort-store [this key args dest] 117 | "Sort and store the result in destination key") 118 | (touch [this key] 119 | "Touch one key. Sets the key last accessed time") 120 | (mtouch [this keys] 121 | "Touch multiple keys. Sets the keys last accessed time") 122 | (ttl [this key] 123 | "Get the time to live for a key") 124 | (type [this key] 125 | "Determine the type stored at key") 126 | (scan [this] [this cursor] [this cursor args] 127 | "Incrementally iterate the keys space")) 128 | 129 | (defprotocol StringsCommands 130 | "Redis Strings Commands" 131 | (append [this key val] 132 | "Append a value to a key") 133 | (bitcount [this key] [this key start end] 134 | "Count the bits set to 1 in a string") 135 | (bitfield [this key args] 136 | "Execute BITFIELD with its subcommands") 137 | (bitop-and [this dest keys] 138 | "Perform bitwise AND between strings") 139 | (bitop-not [this dest key] 140 | "Perform bitwise NOT between strings") 141 | (bitop-or [this dest keys] 142 | "Perform bitwise OR between strings") 143 | (bitop-xor [this dest keys] 144 | "Perform bitwise XOR between strings") 145 | (bitpos [this key state] [this key state start end] 146 | "Find first bit set or clear in a string") 147 | (decr [this key] 148 | "Decrement the integer value of a key by one") 149 | (decrby [this key amount] 150 | "Decrement the integer value of a key by the given number") 151 | (get [this key] 152 | "Get the value of a key") 153 | (mget [this keys] 154 | "Get the values of all the given keys") 155 | (getbit [this key offset] 156 | "Get the bit value at offset in the key's string value") 157 | (getrange [this key start end] 158 | "Get a substring of the string stored at a key") 159 | (getset [this key val] 160 | "Set the string value of a key and return its old value") 161 | (incr [this key] 162 | "Increment the integer value of a key by one") 163 | (incrby [this key amount] 164 | "Increment the integer value of a key by the given amount") 165 | (incrbyfloat [this key amount] 166 | "Increment the float value of a key by the given amount") 167 | (set [this key val] [this key val args] 168 | "Set the string value of a key") 169 | (mset [this map] 170 | "Set multiple keys to multiple values (map)") 171 | (setbit [this key offset val] 172 | "Sets or clears the bit at offset in the key's string value") 173 | (setex [this key ttl-sec val] 174 | "Set the value and expiration of a key") 175 | (psetex [this key ttl-ms val] 176 | "Set the value and expiration in milliseconds of a key") 177 | (setnx [this key val] 178 | "Set the value of a key, only if the key does not exist") 179 | (msetnx [this map] 180 | "Like mset, but only if none of the keys exist") 181 | (setrange [this key offset val] 182 | "Overwrite part of a string at key starting at the offset") 183 | (strlen [this key] 184 | "Get the length of the value stored in a key")) 185 | 186 | (defprotocol ListCommands 187 | "Redis List Commands" 188 | (blpop [this timeout-sec keys] 189 | "Remove and get the first elem (block until there's one)") 190 | (brpop [this timeout-sec keys] 191 | "Remove and get the last elem (block until there's one)") 192 | (brpoplpush [this timeout-sec src dest] 193 | "Pop and push to another list, return the elem (blocking)") 194 | (lindex [this key idx] 195 | "Get an element from a list by its index") 196 | (linsert [this key before? pivot val] 197 | "Insert an elem before or after another elem in a list") 198 | (llen [this key] 199 | "Get the length of a list") 200 | (lpop [this key] 201 | "Remove and get the first element in a list") 202 | (lpush [this key val] 203 | "Prepend one value to a list") 204 | (mlpush [this key vals] 205 | "Prepend multiple values to a list") 206 | (lpushx [this key val] 207 | "Prepend a value to a list, only if the list exists") 208 | (mlpushx [this key vals] 209 | "Prepend multiple values, only if the list exists") 210 | (lrange [this key start end] 211 | "Get a range of elements from a list") 212 | (lrem [this key count val] 213 | "Remove elements from a list") 214 | (lset [this key idx v] 215 | "Set the value of an element in a list by its index") 216 | (ltrim [this key start end] 217 | "Trim a list to the specified range") 218 | (rpop [this key] 219 | "Remove and get the last element in a list") 220 | (rpoplpush [this src dest] 221 | "Pop and push to another list, return the elem") 222 | (rpush [this key val] 223 | "Append one value to a list") 224 | (mrpush [this key vals] 225 | "Append multiple values to a list") 226 | (rpushx [this key val] 227 | "Append a value to a list, only if the list exists") 228 | (mrpushx [this key vals] 229 | "Append multiple values to a list, only if the list exists")) 230 | 231 | (defprotocol SetCommands 232 | "Redis Set Commands" 233 | (sadd [this key member] 234 | "Add one member to a set") 235 | (msadd [this key members] 236 | "Add multiple members to a set") 237 | (scard [this key] 238 | "Get the number of members in a set") 239 | (sdiff [this keys] 240 | "Subtract multiple sets") 241 | (sdiffstore [this dest keys] 242 | "Subtract multiple sets and store the resulting set in a key") 243 | (sinter [this keys] 244 | "Intersect multiple sets") 245 | (sinterstore [this dest keys] 246 | "Intersect multiple sets and store the resulting set in a key") 247 | (sismember [this key val] 248 | "Determine if a given value is a member of a set") 249 | (smove [this key dest member] 250 | "Move a member from one set to another") 251 | (smembers [this key] 252 | "Get all the members in a set") 253 | (spop [this key] [this key count] 254 | "Remove and return one or multiple random members from a set") 255 | (srandmember [this key] [this key count] 256 | "Get one or multiple random members from a set") 257 | (srem [this key member] 258 | "Remove one member from a set") 259 | (msrem [this key members] 260 | "Remove multiple members from a set") 261 | (sunion [this keys] 262 | "Add multiple sets") 263 | (sunionstore [this dest keys] 264 | "Add multiple sets and store the resulting set in a key") 265 | (sscan [this key] [this key cursor] [this key cursor args] 266 | "Incrementally iterate set elements")) 267 | 268 | (defprotocol SortedSetCommands 269 | "Redis Sorted Set Commands" 270 | (zadd [this key score member] [this key args score member] 271 | "Add one member to a sorted set (update score if exists)") 272 | (mzadd [this key scored-members] [this key args scored-members] 273 | "Add multiple members to a sorted set (update scores if exist)") 274 | (zaddincr [this key score member] 275 | "Increment the score of a member in a sorted set") 276 | (zcard [this key] 277 | "Get the number of members in a sorted set") 278 | (zcount [this key min max] 279 | "Count the members in a sorted set with scores within [min max]") 280 | (zincrby [this key amount member] 281 | "Increment the score of a member in a sorted set") 282 | (zinterstore [this dest keys] [this dest args keys] 283 | "Intersect multiple sorted sets ks, store the result in a new key d") 284 | (zrange [this key start end] 285 | "Return a range of members in a sorted set, by index (scores asc)") 286 | (zrangebyscore [this key min max] [this key min max offset count] 287 | "Return a range of members in a sorted set, by score (scores asc)") 288 | (zrank [this key member] 289 | "Determine the index of a member in a sorted set (score asc)") 290 | (zrem [this key member] 291 | "Remove one member from a sorted set") 292 | (mzrem [this key members] 293 | "Remove multiple members from a sorted set") 294 | (zremrangebyrank [this key start end] 295 | "Remove all members in a sorted set within the given indexes") 296 | (zremrangebyscore [this key min max] 297 | "Remove all members in a sorted set within the given scores") 298 | (zrevrange [this key start end] 299 | "Return a range of members in a sorted set, by index (scores desc)") 300 | (zrevrangebyscore [this key min max] [this key min max offset count] 301 | "Return a range of members in a sorted set, by score (scores desc)") 302 | (zrevrank [this key member] 303 | "Determine the index of a member in a sorted set (score desc)") 304 | (zscan [this key] [this key cursor] [this key cursor args] 305 | "Incrementally iterate sorted sets elements and associated scores") 306 | (zscore [this key member] 307 | "Get the score associated with the given member in a sorted set.") 308 | (zunionstore [this dest keys] [this dest args keys] 309 | "Add multiple sorted sets ks, store the result in a new key d") 310 | (zlexcount [this key min max] 311 | "Count the members in a sorted set in a given lexicographical range") 312 | (zrangebylex [this key min max] [this key min max offset count] 313 | "Return a range of members in a sorted set, by lexicographical range") 314 | (zremrangebylex [this key min max] 315 | "Remove all members in a sorted set in a given lexicographical range") 316 | ;; with scores range commands 317 | (zrange-withscores [this key start end]) 318 | (zrangebyscore-withscores [this key min max] [this key min max offset count]) 319 | (zrevrange-withscores [this key start end]) 320 | (zrevrangebyscore-withscores [this key min max] [this key min max offset count])) 321 | 322 | (defprotocol ScriptingCommands 323 | "Redis Scripting Commands (Lua 5.1)" 324 | (eval [this script type keys] [this script type keys vals] 325 | "Execute a Lua script server side") 326 | (evalsha [this digest type keys] [this digest type keys vals] 327 | "Evaluates a script cached on the server side by its SHA1 digest") 328 | (script-exists? [this digests] 329 | "Check existence of scripts in the script cache") 330 | (script-flush [this] 331 | "Remove all the scripts from the script cache") 332 | (script-kill [this] 333 | "Kill the script currently in execution") 334 | (script-load [this script] 335 | "Load the specified Lua script into the script cache") 336 | (digest [this script] 337 | "Create a SHA1 digest from a Lua script")) 338 | 339 | (defprotocol ServerCommands 340 | "Redis Server Commands" 341 | (bgrewriteaof [this] 342 | "Asynchronously rewrite the append-only file") 343 | (bgsave [this] 344 | "Asynchronously save the dataset to disk") 345 | (client-getname [this] 346 | "Get the current connection name") 347 | (client-setname [this name] 348 | "Set the current connection name") 349 | (client-kill [this addr-or-args] 350 | "Kill the connection of a client identified by ip:port, or args") 351 | (client-pause [this timeout-ms] 352 | "Stop processing commands from clients for some time") 353 | (client-list [this] 354 | "Get the list of client connections") 355 | (command [this] 356 | "Return an array reply of details about all Redis commands") 357 | (command-info [this commands] 358 | "Return an array reply of details about the requested commands") 359 | (command-count [this] 360 | "Get total number of Redis commands") 361 | (config-get [this param] 362 | "Get the value of a configuration parameter") 363 | (config-resetstat [this] 364 | "Reset the stats returned by INFO") 365 | (config-rewrite [this] 366 | "Rewrite the configuration file with the in memory configuration") 367 | (config-set [this param val] 368 | "Set a configuration parameter to the given value") 369 | (dbsize [this] 370 | "Return the number of keys in the selected database") 371 | (debug-crash-recov [this delay-ms] 372 | "Crash and recover") 373 | (debug-htstats [this db] 374 | "Get debugging information about the internal hash-table state") 375 | (debug-object [this key] 376 | "Get debugging information about a key") 377 | (debug-oom [this] 378 | "Make the server crash: Out of memory") 379 | (debug-segfault [this] 380 | "Make the server crash: Invalid pointer access") 381 | (debug-reload [this] 382 | "Save RDB, clear the database and reload RDB") 383 | (debug-restart [this delay-ms] 384 | "Restart the server gracefully") 385 | (debug-sds-len [this key] 386 | "Get debugging information about the internal SDS length") 387 | (flushall [this] 388 | "Remove all keys from all databases") 389 | (flushall-async [this] 390 | "Remove all keys asynchronously from all databases") 391 | (flushdb [this] 392 | "Remove all keys from the current database") 393 | (flushdb-async [this] 394 | "Remove all keys asynchronously from the current database") 395 | (info [this] [this section] 396 | "Get information and statistics about the server") 397 | (lastsave [this] 398 | "Get the UNIX time stamp of the last successful save to disk") 399 | (save [this] 400 | "Synchronously save the dataset to disk") 401 | (shutdown [this save?] 402 | "Synchronously save the dataset to disk and shutdown the server") 403 | (slaveof [this host port] 404 | "Make the server a slave of another server, or promote it as master") 405 | (slaveof-no-one [this] 406 | "Promote server as master") 407 | (slowlog-get [this] [this count] 408 | "Read the slow log") 409 | (slowlog-len [this] 410 | "Obtaining the current length of the slow log") 411 | (slowlog-reset [this] 412 | "Resetting the slow log") 413 | (time [this] 414 | "Return the current server time")) 415 | 416 | (defprotocol HLLCommands 417 | "Redis HLL Commands" 418 | (pfadd [this key val] 419 | "Add the specified element to the specified HyperLogLog") 420 | (mpfadd [this key vals] 421 | "Add the specified elements to the specified HyperLogLog") 422 | (pfmerge [this dest keys] 423 | "Merge N different HyperLogLogs into a single one") 424 | (pfcount [this key] 425 | "Return the approximated cardinality of the set (HyperLogLog) at key") 426 | (mpfcount [this keys] 427 | "Return the approximated cardinality of the sets (HyperLogLog) at keys")) 428 | 429 | (defprotocol GeoCommands 430 | "Redis Geo Commands" 431 | (geoadd [this key long lat member] [this key lng-lat-members] 432 | "Single or multiple geo add") 433 | (geohash [this key member] 434 | "Retrieve Geohash of a member of a geospatial index") 435 | (mgeohash [this key members] 436 | "Retrieve Geohash of multiple members of a geospatial index") 437 | (georadius [this key long lat dist unit] [this key long lat dist unit args] 438 | "Retrieve members selected by dist with the center of long last, 439 | Perform a georadius query and store the results in a zset") 440 | (georadiusbymember [this key member dist unit] [this key member dist unit args] 441 | "Retrieve members selected by dist with the center of member, 442 | Perform a georadiusbymember query and store the results in a zset") 443 | (geopos [this key member] 444 | "Get geo coordinates for the member") 445 | (mgeopos [this key members] 446 | "Get geo coordinates for the members") 447 | (geodist [this key from to unit] 448 | "Retrieve distance between points from and to")) 449 | 450 | (defprotocol TransactionalCommands 451 | "Redis Transactional Commands" 452 | (discard [this] 453 | "Discard all commands issued after MULTI") 454 | (exec [this] 455 | "Execute all commands issued after MULTI") 456 | (multi [this] 457 | "Mark the start of a transaction block") 458 | (watch [this key] 459 | "Watch key to determine execution of a transaction") 460 | (mwatch [this keys] 461 | "Watch keys to determine execution of a transaction") 462 | (unwatch [this] 463 | "Forget about all watched keys")) 464 | 465 | (defprotocol PubSubCommands 466 | "Redis PubSub Commands" 467 | (publish [this channel message] 468 | "Post a message to a channel") 469 | (subscribe [this channel] 470 | "Listen for messages published to channel") 471 | (unsubscribe [this channel] 472 | "Stop listening for messages posted to channel") 473 | (msubscribe [this channels] 474 | "Listen for messages published to channels") 475 | (munsubscribe [this channels] 476 | "Stop listening for messages posted to channels") 477 | (psubscribe [this pattern] 478 | "Listen for messages published to channels matching pattern") 479 | (punsubscribe [this pattern] 480 | "Stop listening for messages posted to channels matching pattern") 481 | (mpsubscribe [this pattern] 482 | "Listen for messages published to channels matching patterns") 483 | (mpunsubscribe [this pattern] 484 | "Stop listening for messages posted to channels matching patterns") 485 | (pubsub-channels [this] [this channel] 486 | "Lists the currently *active channels*") 487 | (pubsub-numsub [this channel] 488 | "Returns the number of subscribers for the specified channel") 489 | (pubsub-numpat [this] 490 | "Returns the number of subscriptions to patterns")) 491 | 492 | (defprotocol PubSubListener 493 | "Protocol for redis pub/sub listeners" 494 | (message [this channel message] [this pattern channel message] 495 | "Message received from a channel (or pattern) subscription") 496 | (subscribed [this channel count] 497 | "Subscribed to a channel") 498 | (unsubscribed [this channel count] 499 | "Unsubscribed from a channel") 500 | (psubscribed [this pattern count] 501 | "Subscribed to a pattern") 502 | (punsubscribed [this pattern count] 503 | "Unsubscribed from a pattern")) 504 | --------------------------------------------------------------------------------