From 154470b570cb39f18e518f1a76cdc744894a1313 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 15:03:56 -0700 Subject: [PATCH 01/13] add original hiredis --- controller/EmbeddedNetworkController.cpp | 7 +- controller/EmbeddedNetworkController.hpp | 5 +- controller/PostgreSQL.cpp | 13 +- controller/PostgreSQL.hpp | 6 +- ext/hiredis-0.14.1/.gitignore | 7 + ext/hiredis-0.14.1/.travis.yml | 45 + ext/hiredis-0.14.1/CHANGELOG.md | 190 +++ ext/hiredis-0.14.1/COPYING | 29 + ext/hiredis-0.14.1/Makefile | 214 +++ ext/hiredis-0.14.1/README.md | 410 ++++++ ext/hiredis-0.14.1/adapters/ae.h | 127 ++ ext/hiredis-0.14.1/adapters/glib.h | 153 ++ ext/hiredis-0.14.1/adapters/ivykis.h | 81 ++ ext/hiredis-0.14.1/adapters/libev.h | 147 ++ ext/hiredis-0.14.1/adapters/libevent.h | 108 ++ ext/hiredis-0.14.1/adapters/libuv.h | 122 ++ ext/hiredis-0.14.1/adapters/macosx.h | 114 ++ ext/hiredis-0.14.1/adapters/qt.h | 135 ++ ext/hiredis-0.14.1/alloc.c | 65 + ext/hiredis-0.14.1/alloc.h | 53 + ext/hiredis-0.14.1/appveyor.yml | 23 + ext/hiredis-0.14.1/async.c | 717 ++++++++++ ext/hiredis-0.14.1/async.h | 130 ++ ext/hiredis-0.14.1/dict.c | 339 +++++ ext/hiredis-0.14.1/dict.h | 126 ++ ext/hiredis-0.14.1/examples/example-ae.c | 62 + ext/hiredis-0.14.1/examples/example-glib.c | 73 + ext/hiredis-0.14.1/examples/example-ivykis.c | 58 + ext/hiredis-0.14.1/examples/example-libev.c | 52 + .../examples/example-libevent.c | 53 + ext/hiredis-0.14.1/examples/example-libuv.c | 53 + ext/hiredis-0.14.1/examples/example-macosx.c | 66 + ext/hiredis-0.14.1/examples/example-qt.cpp | 46 + ext/hiredis-0.14.1/examples/example-qt.h | 32 + ext/hiredis-0.14.1/examples/example.c | 78 + ext/hiredis-0.14.1/fmacros.h | 12 + ext/hiredis-0.14.1/hiredis.c | 1006 +++++++++++++ ext/hiredis-0.14.1/hiredis.h | 200 +++ ext/hiredis-0.14.1/net.c | 477 +++++++ ext/hiredis-0.14.1/net.h | 49 + ext/hiredis-0.14.1/read.c | 598 ++++++++ ext/hiredis-0.14.1/read.h | 111 ++ ext/hiredis-0.14.1/sds.c | 1272 +++++++++++++++++ ext/hiredis-0.14.1/sds.h | 273 ++++ ext/hiredis-0.14.1/sdsalloc.h | 42 + ext/hiredis-0.14.1/test.c | 923 ++++++++++++ ext/hiredis-0.14.1/win32.h | 42 + make-mac.mk | 2 +- objects.mk | 1 + service/OneService.cpp | 18 +- 50 files changed, 8952 insertions(+), 13 deletions(-) create mode 100644 ext/hiredis-0.14.1/.gitignore create mode 100644 ext/hiredis-0.14.1/.travis.yml create mode 100644 ext/hiredis-0.14.1/CHANGELOG.md create mode 100644 ext/hiredis-0.14.1/COPYING create mode 100644 ext/hiredis-0.14.1/Makefile create mode 100644 ext/hiredis-0.14.1/README.md create mode 100644 ext/hiredis-0.14.1/adapters/ae.h create mode 100644 ext/hiredis-0.14.1/adapters/glib.h create mode 100644 ext/hiredis-0.14.1/adapters/ivykis.h create mode 100644 ext/hiredis-0.14.1/adapters/libev.h create mode 100644 ext/hiredis-0.14.1/adapters/libevent.h create mode 100644 ext/hiredis-0.14.1/adapters/libuv.h create mode 100644 ext/hiredis-0.14.1/adapters/macosx.h create mode 100644 ext/hiredis-0.14.1/adapters/qt.h create mode 100644 ext/hiredis-0.14.1/alloc.c create mode 100644 ext/hiredis-0.14.1/alloc.h create mode 100644 ext/hiredis-0.14.1/appveyor.yml create mode 100644 ext/hiredis-0.14.1/async.c create mode 100644 ext/hiredis-0.14.1/async.h create mode 100644 ext/hiredis-0.14.1/dict.c create mode 100644 ext/hiredis-0.14.1/dict.h create mode 100644 ext/hiredis-0.14.1/examples/example-ae.c create mode 100644 ext/hiredis-0.14.1/examples/example-glib.c create mode 100644 ext/hiredis-0.14.1/examples/example-ivykis.c create mode 100644 ext/hiredis-0.14.1/examples/example-libev.c create mode 100644 ext/hiredis-0.14.1/examples/example-libevent.c create mode 100644 ext/hiredis-0.14.1/examples/example-libuv.c create mode 100644 ext/hiredis-0.14.1/examples/example-macosx.c create mode 100644 ext/hiredis-0.14.1/examples/example-qt.cpp create mode 100644 ext/hiredis-0.14.1/examples/example-qt.h create mode 100644 ext/hiredis-0.14.1/examples/example.c create mode 100644 ext/hiredis-0.14.1/fmacros.h create mode 100644 ext/hiredis-0.14.1/hiredis.c create mode 100644 ext/hiredis-0.14.1/hiredis.h create mode 100644 ext/hiredis-0.14.1/net.c create mode 100644 ext/hiredis-0.14.1/net.h create mode 100644 ext/hiredis-0.14.1/read.c create mode 100644 ext/hiredis-0.14.1/read.h create mode 100644 ext/hiredis-0.14.1/sds.c create mode 100644 ext/hiredis-0.14.1/sds.h create mode 100644 ext/hiredis-0.14.1/sdsalloc.h create mode 100644 ext/hiredis-0.14.1/test.c create mode 100644 ext/hiredis-0.14.1/win32.h diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index ef2ce2cfb..b2bd7bfb9 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -456,14 +456,15 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) } // anonymous namespace -EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort) : +EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc) : _startTime(OSUtils::now()), _listenPort(listenPort), _node(node), _ztPath(ztPath), _path(dbPath), _sender((NetworkController::Sender *)0), - _db(this) + _db(this), + _rc(rc) { } @@ -484,7 +485,7 @@ void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender) #ifdef ZT_CONTROLLER_USE_LIBPQ if ((_path.length() > 9)&&(_path.substr(0,9) == "postgres:")) { - _db.addDB(std::shared_ptr(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort))); + _db.addDB(std::shared_ptr(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort, _rc))); } else { #endif _db.addDB(std::shared_ptr(new FileDB(_path.c_str()))); diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index d09e08ac6..3fb421074 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -43,6 +43,7 @@ namespace ZeroTier { class Node; +struct RedisConfig; class EmbeddedNetworkController : public NetworkController,public DB::ChangeListener { @@ -51,7 +52,7 @@ public: * @param node Parent node * @param dbPath Database path (file path or database credentials) */ - EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort); + EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc); virtual ~EmbeddedNetworkController(); virtual void init(const Identity &signingId,Sender *sender); @@ -148,6 +149,8 @@ private: std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus; std::mutex _memberStatus_l; + + RedisConfig *_rc; }; } // namespace ZeroTier diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 72acdca9e..91cbdb789 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -18,7 +18,7 @@ #include "../node/Constants.hpp" #include "EmbeddedNetworkController.hpp" #include "../version.h" -#include "hiredis.h" +#include "Redis.hpp" #include #include @@ -68,7 +68,7 @@ std::string join(const std::vector &elements, const char * const se using namespace ZeroTier; -PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort) +PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc) : DB() , _myId(myId) , _myAddress(myId.address()) @@ -77,6 +77,7 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort) , _run(1) , _waitNoticePrinted(false) , _listenPort(listenPort) + , _rc(rc) { char myAddress[64]; _myAddressStr = myId.address().toString(myAddress); @@ -717,10 +718,10 @@ void PostgreSQL::networksDbWatcher() initializeNetworks(conn); - if (false) { - // PQfinish(conn); - // conn = NULL; - // _networksWatcher_RabbitMQ(); + if (_rc) { + PQfinish(conn); + conn = NULL; + _networksWatcher_Redis(); } else { _networksWatcher_Postgres(conn); PQfinish(conn); diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index dcad35e7d..5d14e2ff6 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -26,6 +26,8 @@ typedef struct pg_conn PGconn; namespace ZeroTier { +struct RedisConfig; + /** * A controller database driver that talks to PostgreSQL * @@ -35,7 +37,7 @@ namespace ZeroTier { class PostgreSQL : public DB { public: - PostgreSQL(const Identity &myId, const char *path, int listenPort); + PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc); virtual ~PostgreSQL(); virtual bool waitForReady(); @@ -94,6 +96,8 @@ private: mutable volatile bool _waitNoticePrinted; int _listenPort; + + RedisConfig *_rc; }; } // namespace ZeroTier diff --git a/ext/hiredis-0.14.1/.gitignore b/ext/hiredis-0.14.1/.gitignore new file mode 100644 index 000000000..c44b5c537 --- /dev/null +++ b/ext/hiredis-0.14.1/.gitignore @@ -0,0 +1,7 @@ +/hiredis-test +/examples/hiredis-example* +/*.o +/*.so +/*.dylib +/*.a +/*.pc diff --git a/ext/hiredis-0.14.1/.travis.yml b/ext/hiredis-0.14.1/.travis.yml new file mode 100644 index 000000000..faf2ce684 --- /dev/null +++ b/ext/hiredis-0.14.1/.travis.yml @@ -0,0 +1,45 @@ +language: c +sudo: false +compiler: + - gcc + - clang + +os: + - linux + - osx + +branches: + only: + - staging + - trying + - master + +before_script: + - if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi + +addons: + apt: + packages: + - libc6-dbg + - libc6-dev + - libc6:i386 + - libc6-dev-i386 + - libc6-dbg:i386 + - gcc-multilib + - valgrind + +env: + - CFLAGS="-Werror" + - PRE="valgrind --track-origins=yes --leak-check=full" + - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror" + - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" + +matrix: + exclude: + - os: osx + env: PRE="valgrind --track-origins=yes --leak-check=full" + + - os: osx + env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" + +script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example diff --git a/ext/hiredis-0.14.1/CHANGELOG.md b/ext/hiredis-0.14.1/CHANGELOG.md new file mode 100644 index 000000000..f8e577369 --- /dev/null +++ b/ext/hiredis-0.14.1/CHANGELOG.md @@ -0,0 +1,190 @@ +**NOTE: BREAKING CHANGES upgrading from 0.13.x to 0.14.x **: + +* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now + protocol errors. This is consistent with the RESP specification. On 32-bit + platforms, the upper bound is lowered to `SIZE_MAX`. + +* Change `redisReply.len` to `size_t`, as it denotes the the size of a string + + User code should compare this to `size_t` values as well. If it was used to + compare to other values, casting might be necessary or can be removed, if + casting was applied before. + +### 0.14.1 (2020-03-13) + +* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder) + +### 0.14.0 (2018-09-25) + +* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) +* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) +* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) +* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8]) +* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8]) +* Fix bulk and multi-bulk length truncation (Justin Brewer [109197]) +* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94]) +* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6]) +* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1]) +* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b]) +* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96]) +* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234]) +* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129]) +* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c]) +* Fix libevent leak (zfz [515228]) +* Clean up GCC warning (Ichito Nagata [2ec774]) +* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88]) +* Solaris compilation fix (Donald Whyte [41b07d]) +* Reorder linker arguments when building examples (Tustfarm-heart [06eedd]) +* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999]) +* libuv use after free fix (Paul Scott [cbb956]) +* Properly close socket fd on reconnect attempt (WSL [64d1ec]) +* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78]) +* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5]) +* Update libevent (Chris Xin [386802]) +* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e]) +* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6]) +* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3]) +* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb]) +* Compatibility fix for strerror_r (Tom Lee [bb1747]) +* Properly detect integer parse/overflow errors (Justin Brewer [93421f]) +* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40]) +* Catch a buffer overflow when formatting the error message +* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 +* Fix warnings, when compiled with -Wshadow +* Make hiredis compile in Cygwin on Windows, now CI-tested + +**BREAKING CHANGES**: + +* Remove backwards compatibility macro's + +This removes the following old function aliases, use the new name now: + +| Old | New | +| --------------------------- | ---------------------- | +| redisReplyReaderCreate | redisReaderCreate | +| redisReplyReaderCreate | redisReaderCreate | +| redisReplyReaderFree | redisReaderFree | +| redisReplyReaderFeed | redisReaderFeed | +| redisReplyReaderGetReply | redisReaderGetReply | +| redisReplyReaderSetPrivdata | redisReaderSetPrivdata | +| redisReplyReaderGetObject | redisReaderGetObject | +| redisReplyReaderGetError | redisReaderGetError | + +* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS` + +Previously it broke some builds for people that had `DEBUG` set to some arbitrary value, +due to debugging other software. +By renaming we avoid unintentional name clashes. + +Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again. + +### 0.13.3 (2015-09-16) + +* Revert "Clear `REDIS_CONNECTED` flag when connection is closed". +* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni) + + +If the `REDIS_CONNECTED` flag is cleared, +the async onDisconnect callback function will never be called. +This causes problems as the disconnect is never reported back to the user. + +### 0.13.2 (2015-08-25) + +* Prevent crash on pending replies in async code (Thanks, @switch-st) +* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs) +* Add MacOS X addapter (Thanks, @dizzus) +* Add Qt adapter (Thanks, Pietro Cerutti) +* Add Ivykis adapter (Thanks, Gergely Nagy) + +All adapters are provided as is and are only tested where possible. + +### 0.13.1 (2015-05-03) + +This is a bug fix release. +The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. +Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. +Other non-C99 code can now use hiredis as usual again. +Sorry for the inconvenience. + +* Fix memory leak in async reply handling (Salvatore Sanfilippo) +* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) + +### 0.13.0 (2015-04-16) + +This release adds a minimal Windows compatibility layer. +The parser, standalone since v0.12.0, can now be compiled on Windows +(and thus used in other client libraries as well) + +* Windows compatibility layer for parser code (tzickel) +* Properly escape data printed to PKGCONF file (Dan Skorupski) +* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) +* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) + +### 0.12.1 (2015-01-26) + +* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location +* Fix `make test` as 32 bit build on 64 bit platform + +### 0.12.0 (2015-01-22) + +* Add optional KeepAlive support + +* Try again on EINTR errors + +* Add libuv adapter + +* Add IPv6 support + +* Remove possibility of multiple close on same fd + +* Add ability to bind source address on connect + +* Add redisConnectFd() and redisFreeKeepFd() + +* Fix getaddrinfo() memory leak + +* Free string if it is unused (fixes memory leak) + +* Improve redisAppendCommandArgv performance 2.5x + +* Add support for SO_REUSEADDR + +* Fix redisvFormatCommand format parsing + +* Add GLib 2.0 adapter + +* Refactor reading code into read.c + +* Fix errno error buffers to not clobber errors + +* Generate pkgconf during build + +* Silence _BSD_SOURCE warnings + +* Improve digit counting for multibulk creation + + +### 0.11.0 + +* Increase the maximum multi-bulk reply depth to 7. + +* Increase the read buffer size from 2k to 16k. + +* Use poll(2) instead of select(2) to support large fds (>= 1024). + +### 0.10.1 + +* Makefile overhaul. Important to check out if you override one or more + variables using environment variables or via arguments to the "make" tool. + +* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements + being created by the default reply object functions. + +* Issue #43: Don't crash in an asynchronous context when Redis returns an error + reply after the connection has been made (this happens when the maximum + number of connections is reached). + +### 0.10.0 + +* See commit log. + diff --git a/ext/hiredis-0.14.1/COPYING b/ext/hiredis-0.14.1/COPYING new file mode 100644 index 000000000..a5fc97395 --- /dev/null +++ b/ext/hiredis-0.14.1/COPYING @@ -0,0 +1,29 @@ +Copyright (c) 2009-2011, Salvatore Sanfilippo +Copyright (c) 2010-2011, Pieter Noordhuis + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Redis nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ext/hiredis-0.14.1/Makefile b/ext/hiredis-0.14.1/Makefile new file mode 100644 index 000000000..d1f005af4 --- /dev/null +++ b/ext/hiredis-0.14.1/Makefile @@ -0,0 +1,214 @@ +# Hiredis Makefile +# Copyright (C) 2010-2011 Salvatore Sanfilippo +# Copyright (C) 2010-2011 Pieter Noordhuis +# This file is released under the BSD license, see the COPYING file + +OBJ=net.o hiredis.o sds.o async.o read.o alloc.o +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib +TESTS=hiredis-test +LIBNAME=libhiredis +PKGCONFNAME=hiredis.pc + +HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') +HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') +HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}') +HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}') + +# Installation related variables and target +PREFIX?=/usr/local +INCLUDE_PATH?=include/hiredis +LIBRARY_PATH?=lib +PKGCONF_PATH?=pkgconfig +INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) +INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) +INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) + +# redis-server configuration used for testing +REDIS_PORT=56379 +REDIS_SERVER=redis-server +define REDIS_TEST_CONFIG + daemonize yes + pidfile /tmp/hiredis-test-redis.pid + port $(REDIS_PORT) + bind 127.0.0.1 + unixsocket /tmp/hiredis-test-redis.sock +endef +export REDIS_TEST_CONFIG + +# Fallback to gcc when $CC is not in $PATH. +CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') +CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') +OPTIMIZATION?=-O3 +WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings +DEBUG_FLAGS?= -g -ggdb +REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) +REAL_LDFLAGS=$(LDFLAGS) + +DYLIBSUFFIX=so +STLIBSUFFIX=a +DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) +DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) +DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) +DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) +STLIB_MAKE_CMD=ar rcs $(STLIBNAME) + +# Platform-specific overrides +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +ifeq ($(uname_S),SunOS) + REAL_LDFLAGS+= -ldl -lnsl -lsocket + DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) +endif +ifeq ($(uname_S),Darwin) + DYLIBSUFFIX=dylib + DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) + DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +endif + +all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) + +# Deps (use make dep to generate this) +alloc.o: alloc.c fmacros.h alloc.h +async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h +dict.o: dict.c fmacros.h alloc.h dict.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h +net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h +read.o: read.c fmacros.h read.h sds.h +sds.o: sds.c sds.h sdsalloc.h +test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h + +$(DYLIBNAME): $(OBJ) + $(DYLIB_MAKE_CMD) $(OBJ) + +$(STLIBNAME): $(OBJ) + $(STLIB_MAKE_CMD) $(OBJ) + +dynamic: $(DYLIBNAME) +static: $(STLIBNAME) + +# Binaries: +hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + +hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) + +hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) + +hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME) + +hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) + +ifndef AE_DIR +hiredis-example-ae: + @echo "Please specify AE_DIR (e.g. /src)" + @false +else +hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) +endif + +ifndef LIBUV_DIR +hiredis-example-libuv: + @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" + @false +else +hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) +endif + +ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) +hiredis-example-qt: + @echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR" + @false +else +hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) + $(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ + $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore + $(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ + $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore + $(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore +endif + +hiredis-example: examples/example.c $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + +examples: $(EXAMPLES) + +hiredis-test: test.o $(STLIBNAME) + +hiredis-%: %.o $(STLIBNAME) + $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) + +test: hiredis-test + ./hiredis-test + +check: hiredis-test + @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - + $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \ + ( kill `cat /tmp/hiredis-test-redis.pid` && false ) + kill `cat /tmp/hiredis-test-redis.pid` + +.c.o: + $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< + +clean: + rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov + +dep: + $(CC) -MM *.c + +INSTALL?= cp -pPR + +$(PKGCONFNAME): hiredis.h + @echo "Generating $@ for pkgconfig..." + @echo prefix=$(PREFIX) > $@ + @echo exec_prefix=\$${prefix} >> $@ + @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ + @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ + @echo >> $@ + @echo Name: hiredis >> $@ + @echo Description: Minimalistic C client library for Redis. >> $@ + @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ + @echo Libs: -L\$${libdir} -lhiredis >> $@ + @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ + +install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) + mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) + $(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH) + $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters + $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) + $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) + mkdir -p $(INSTALL_PKGCONF_PATH) + $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) + +32bit: + @echo "" + @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" + @echo "" + $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" + +32bit-vars: + $(eval CFLAGS=-m32) + $(eval LDFLAGS=-m32) + +gprof: + $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" + +gcov: + $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" + +coverage: gcov + make check + mkdir -p tmp/lcov + lcov -d . -c -o tmp/lcov/hiredis.info + genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info + +noopt: + $(MAKE) OPTIMIZATION="" + +.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt diff --git a/ext/hiredis-0.14.1/README.md b/ext/hiredis-0.14.1/README.md new file mode 100644 index 000000000..50e2e6be6 --- /dev/null +++ b/ext/hiredis-0.14.1/README.md @@ -0,0 +1,410 @@ +[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) + +**This Readme reflects the latest changed in the master branch. See [v0.14.1](https://github.com/redis/hiredis/tree/v0.14.1) for the Readme and documentation for the latest release.** + +# HIREDIS + +Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. + +It is minimalistic because it just adds minimal support for the protocol, but +at the same time it uses a high level printf-alike API in order to make it +much higher level than otherwise suggested by its minimal code base and the +lack of explicit bindings for every Redis command. + +Apart from supporting sending commands and receiving replies, it comes with +a reply parser that is decoupled from the I/O layer. It +is a stream parser designed for easy reusability, which can for instance be used +in higher level language bindings for efficient reply parsing. + +Hiredis only supports the binary-safe Redis protocol, so you can use it with any +Redis version >= 1.2.0. + +The library comes with multiple APIs. There is the +*synchronous API*, the *asynchronous API* and the *reply parsing API*. + +## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x + +Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now +protocol errors. This is consistent with the RESP specification. On 32-bit +platforms, the upper bound is lowered to `SIZE_MAX`. + +Change `redisReply.len` to `size_t`, as it denotes the the size of a string + +User code should compare this to `size_t` values as well. If it was used to +compare to other values, casting might be necessary or can be removed, if +casting was applied before. + +For a detailed list of changes please view our [Changelog](CHANGELOG.md). + +## Synchronous API + +To consume the synchronous API, there are only a few function calls that need to be introduced: + +```c +redisContext *redisConnect(const char *ip, int port); +void *redisCommand(redisContext *c, const char *format, ...); +void freeReplyObject(void *reply); +``` + +### Connecting + +The function `redisConnect` is used to create a so-called `redisContext`. The +context is where Hiredis holds state for a connection. The `redisContext` +struct has an integer `err` field that is non-zero when the connection is in +an error state. The field `errstr` will contain a string with a description of +the error. More information on errors can be found in the **Errors** section. +After trying to connect to Redis using `redisConnect` you should +check the `err` field to see if establishing the connection was successful: +```c +redisContext *c = redisConnect("127.0.0.1", 6379); +if (c == NULL || c->err) { + if (c) { + printf("Error: %s\n", c->errstr); + // handle error + } else { + printf("Can't allocate redis context\n"); + } +} +``` + +*Note: A `redisContext` is not thread-safe.* + +### Sending commands + +There are several ways to issue commands to Redis. The first that will be introduced is +`redisCommand`. This function takes a format similar to printf. In the simplest form, +it is used like this: +```c +reply = redisCommand(context, "SET foo bar"); +``` + +The specifier `%s` interpolates a string in the command, and uses `strlen` to +determine the length of the string: +```c +reply = redisCommand(context, "SET foo %s", value); +``` +When you need to pass binary safe strings in a command, the `%b` specifier can be +used. Together with a pointer to the string, it requires a `size_t` length argument +of the string: +```c +reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); +``` +Internally, Hiredis splits the command in different arguments and will +convert it to the protocol used to communicate with Redis. +One or more spaces separates arguments, so you can use the specifiers +anywhere in an argument: +```c +reply = redisCommand(context, "SET key:%s %s", myid, value); +``` + +### Using replies + +The return value of `redisCommand` holds a reply when the command was +successfully executed. When an error occurs, the return value is `NULL` and +the `err` field in the context will be set (see section on **Errors**). +Once an error is returned the context cannot be reused and you should set up +a new connection. + +The standard replies that `redisCommand` are of the type `redisReply`. The +`type` field in the `redisReply` should be used to test what kind of reply +was received: + +* **`REDIS_REPLY_STATUS`**: + * The command replied with a status reply. The status string can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ERROR`**: + * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. + +* **`REDIS_REPLY_INTEGER`**: + * The command replied with an integer. The integer value can be accessed using the + `reply->integer` field of type `long long`. + +* **`REDIS_REPLY_NIL`**: + * The command replied with a **nil** object. There is no data to access. + +* **`REDIS_REPLY_STRING`**: + * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ARRAY`**: + * A multi bulk reply. The number of elements in the multi bulk reply is stored in + `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well + and can be accessed via `reply->element[..index..]`. + Redis may reply with nested arrays but this is fully supported. + +Replies should be freed using the `freeReplyObject()` function. +Note that this function will take care of freeing sub-reply objects +contained in arrays and nested arrays, so there is no need for the user to +free the sub replies (it is actually harmful and will corrupt the memory). + +**Important:** the current version of hiredis (0.10.0) frees replies when the +asynchronous API is used. This means you should not call `freeReplyObject` when +you use this API. The reply is cleaned up by hiredis _after_ the callback +returns. This behavior will probably change in future releases, so make sure to +keep an eye on the changelog when upgrading (see issue #39). + +### Cleaning up + +To disconnect and free the context the following function can be used: +```c +void redisFree(redisContext *c); +``` +This function immediately closes the socket and then frees the allocations done in +creating the context. + +### Sending commands (cont'd) + +Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. +It has the following prototype: +```c +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the +arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will +use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments +need to be binary safe, the entire array of lengths `argvlen` should be provided. + +The return value has the same semantic as `redisCommand`. + +### Pipelining + +To explain how Hiredis supports pipelining in a blocking connection, there needs to be +understanding of the internal execution flow. + +When any of the functions in the `redisCommand` family is called, Hiredis first formats the +command according to the Redis protocol. The formatted command is then put in the output buffer +of the context. This output buffer is dynamic, so it can hold any number of commands. +After the command is put in the output buffer, `redisGetReply` is called. This function has the +following two execution paths: + +1. The input buffer is non-empty: + * Try to parse a single reply from the input buffer and return it + * If no reply could be parsed, continue at *2* +2. The input buffer is empty: + * Write the **entire** output buffer to the socket + * Read from the socket until a single reply could be parsed + +The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply +is expected on the socket. To pipeline commands, the only things that needs to be done is +filling up the output buffer. For this cause, two commands can be used that are identical +to the `redisCommand` family, apart from not returning a reply: +```c +void redisAppendCommand(redisContext *c, const char *format, ...); +void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +After calling either function one or more times, `redisGetReply` can be used to receive the +subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where +the latter means an error occurred while reading a reply. Just as with the other commands, +the `err` field in the context can be used to find out what the cause of this error is. + +The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and +a single call to `read(2)`): +```c +redisReply *reply; +redisAppendCommand(context,"SET foo bar"); +redisAppendCommand(context,"GET foo"); +redisGetReply(context,&reply); // reply for SET +freeReplyObject(reply); +redisGetReply(context,&reply); // reply for GET +freeReplyObject(reply); +``` +This API can also be used to implement a blocking subscriber: +```c +reply = redisCommand(context,"SUBSCRIBE foo"); +freeReplyObject(reply); +while(redisGetReply(context,&reply) == REDIS_OK) { + // consume message + freeReplyObject(reply); +} +``` +### Errors + +When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is +returned. The `err` field inside the context will be non-zero and set to one of the +following constants: + +* **`REDIS_ERR_IO`**: + There was an I/O error while creating the connection, trying to write + to the socket or read from the socket. If you included `errno.h` in your + application, you can use the global `errno` variable to find out what is + wrong. + +* **`REDIS_ERR_EOF`**: + The server closed the connection which resulted in an empty read. + +* **`REDIS_ERR_PROTOCOL`**: + There was an error while parsing the protocol. + +* **`REDIS_ERR_OTHER`**: + Any other error. Currently, it is only used when a specified hostname to connect + to cannot be resolved. + +In every case, the `errstr` field in the context will be set to hold a string representation +of the error. + +## Asynchronous API + +Hiredis comes with an asynchronous API that works easily with any event library. +Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) +and [libevent](http://monkey.org/~provos/libevent/). + +### Connecting + +The function `redisAsyncConnect` can be used to establish a non-blocking connection to +Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field +should be checked after creation to see if there were errors creating the connection. +Because the connection that will be created is non-blocking, the kernel is not able to +instantly return if the specified host and port is able to accept a connection. + +*Note: A `redisAsyncContext` is not thread-safe.* + +```c +redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); +if (c->err) { + printf("Error: %s\n", c->errstr); + // handle error +} +``` + +The asynchronous context can hold a disconnect callback function that is called when the +connection is disconnected (either because of an error or per user request). This function should +have the following prototype: +```c +void(const redisAsyncContext *c, int status); +``` +On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the +user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` +field in the context can be accessed to find out the cause of the error. + +The context object is always freed after the disconnect callback fired. When a reconnect is needed, +the disconnect callback is a good point to do so. + +Setting the disconnect callback can only be done once per context. For subsequent calls it will +return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: +```c +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +``` +### Sending commands and their callbacks + +In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. +Therefore, unlike the synchronous API, there is only a single way to send commands. +Because commands are sent to Redis asynchronously, issuing a command requires a callback function +that is called when the reply is received. Reply callbacks should have the following prototype: +```c +void(redisAsyncContext *c, void *reply, void *privdata); +``` +The `privdata` argument can be used to curry arbitrary data to the callback from the point where +the command is initially queued for execution. + +The functions that can be used to issue commands in an asynchronous context are: +```c +int redisAsyncCommand( + redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, + const char *format, ...); +int redisAsyncCommandArgv( + redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, + int argc, const char **argv, const size_t *argvlen); +``` +Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command +was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection +is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is +returned on calls to the `redisAsyncCommand` family. + +If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback +for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only +valid for the duration of the callback. + +All pending callbacks are called with a `NULL` reply when the context encountered an error. + +### Disconnecting + +An asynchronous connection can be terminated using: +```c +void redisAsyncDisconnect(redisAsyncContext *ac); +``` +When this function is called, the connection is **not** immediately terminated. Instead, new +commands are no longer accepted and the connection is only terminated when all pending commands +have been written to the socket, their respective replies have been read and their respective +callbacks have been executed. After this, the disconnection callback is executed with the +`REDIS_OK` status and the context object is freed. + +### Hooking it up to event library *X* + +There are a few hooks that need to be set on the context object after it is created. +See the `adapters/` directory for bindings to *libev* and *libevent*. + +## Reply parsing API + +Hiredis comes with a reply parsing API that makes it easy for writing higher +level language bindings. + +The reply parsing API consists of the following functions: +```c +redisReader *redisReaderCreate(void); +void redisReaderFree(redisReader *reader); +int redisReaderFeed(redisReader *reader, const char *buf, size_t len); +int redisReaderGetReply(redisReader *reader, void **reply); +``` +The same set of functions are used internally by hiredis when creating a +normal Redis context, the above API just exposes it to the user for a direct +usage. + +### Usage + +The function `redisReaderCreate` creates a `redisReader` structure that holds a +buffer with unparsed data and state for the protocol parser. + +Incoming data -- most likely from a socket -- can be placed in the internal +buffer of the `redisReader` using `redisReaderFeed`. This function will make a +copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed +when `redisReaderGetReply` is called. This function returns an integer status +and a reply object (as described above) via `void **reply`. The returned status +can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went +wrong (either a protocol error, or an out of memory error). + +The parser limits the level of nesting for multi bulk payloads to 7. If the +multi bulk nesting level is higher than this, the parser returns an error. + +### Customizing replies + +The function `redisReaderGetReply` creates `redisReply` and makes the function +argument `reply` point to the created `redisReply` variable. For instance, if +the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` +will hold the status as a vanilla C string. However, the functions that are +responsible for creating instances of the `redisReply` can be customized by +setting the `fn` field on the `redisReader` struct. This should be done +immediately after creating the `redisReader`. + +For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) +uses customized reply object functions to create Ruby objects. + +### Reader max buffer + +Both when using the Reader API directly or when using it indirectly via a +normal Redis context, the redisReader structure uses a buffer in order to +accumulate data from the server. +Usually this buffer is destroyed when it is empty and is larger than 16 +KiB in order to avoid wasting memory in unused buffers + +However when working with very big payloads destroying the buffer may slow +down performances considerably, so it is possible to modify the max size of +an idle buffer changing the value of the `maxbuf` field of the reader structure +to the desired value. The special value of 0 means that there is no maximum +value for an idle buffer, so the buffer will never get freed. + +For instance if you have a normal Redis context you can set the maximum idle +buffer to zero (unlimited) just with: +```c +context->reader->maxbuf = 0; +``` +This should be done only in order to maximize performances when working with +large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again +as soon as possible in order to prevent allocation of useless memory. + +## AUTHORS + +Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and +Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. +Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and +Jan-Erik Rediger (janerik at fnordig dot com) diff --git a/ext/hiredis-0.14.1/adapters/ae.h b/ext/hiredis-0.14.1/adapters/ae.h new file mode 100644 index 000000000..03939928d --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/ae.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HIREDIS_AE_H__ +#define __HIREDIS_AE_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisAeEvents { + redisAsyncContext *context; + aeEventLoop *loop; + int fd; + int reading, writing; +} redisAeEvents; + +static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleRead(e->context); +} + +static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleWrite(e->context); +} + +static void redisAeAddRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->reading) { + e->reading = 1; + aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); + } +} + +static void redisAeDelRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->reading) { + e->reading = 0; + aeDeleteFileEvent(loop,e->fd,AE_READABLE); + } +} + +static void redisAeAddWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->writing) { + e->writing = 1; + aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); + } +} + +static void redisAeDelWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->writing) { + e->writing = 0; + aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); + } +} + +static void redisAeCleanup(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + redisAeDelRead(privdata); + redisAeDelWrite(privdata); + free(e); +} + +static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisAeEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisAeEvents*)hi_malloc(sizeof(*e)); + e->context = ac; + e->loop = loop; + e->fd = c->fd; + e->reading = e->writing = 0; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisAeAddRead; + ac->ev.delRead = redisAeDelRead; + ac->ev.addWrite = redisAeAddWrite; + ac->ev.delWrite = redisAeDelWrite; + ac->ev.cleanup = redisAeCleanup; + ac->ev.data = e; + + return REDIS_OK; +} +#endif diff --git a/ext/hiredis-0.14.1/adapters/glib.h b/ext/hiredis-0.14.1/adapters/glib.h new file mode 100644 index 000000000..e0a6411d3 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/glib.h @@ -0,0 +1,153 @@ +#ifndef __HIREDIS_GLIB_H__ +#define __HIREDIS_GLIB_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct +{ + GSource source; + redisAsyncContext *ac; + GPollFD poll_fd; +} RedisSource; + +static void +redis_source_add_read (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_IN; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_del_read (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_IN; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_add_write (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_OUT; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_del_write (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_OUT; + g_main_context_wakeup(g_source_get_context((GSource *)data)); +} + +static void +redis_source_cleanup (gpointer data) +{ + RedisSource *source = (RedisSource *)data; + + g_return_if_fail(source); + + redis_source_del_read(source); + redis_source_del_write(source); + /* + * It is not our responsibility to remove ourself from the + * current main loop. However, we will remove the GPollFD. + */ + if (source->poll_fd.fd >= 0) { + g_source_remove_poll((GSource *)data, &source->poll_fd); + source->poll_fd.fd = -1; + } +} + +static gboolean +redis_source_prepare (GSource *source, + gint *timeout_) +{ + RedisSource *redis = (RedisSource *)source; + *timeout_ = -1; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_check (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + RedisSource *redis = (RedisSource *)source; + + if ((redis->poll_fd.revents & G_IO_OUT)) { + redisAsyncHandleWrite(redis->ac); + redis->poll_fd.revents &= ~G_IO_OUT; + } + + if ((redis->poll_fd.revents & G_IO_IN)) { + redisAsyncHandleRead(redis->ac); + redis->poll_fd.revents &= ~G_IO_IN; + } + + if (callback) { + return callback(user_data); + } + + return TRUE; +} + +static void +redis_source_finalize (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + + if (redis->poll_fd.fd >= 0) { + g_source_remove_poll(source, &redis->poll_fd); + redis->poll_fd.fd = -1; + } +} + +static GSource * +redis_source_new (redisAsyncContext *ac) +{ + static GSourceFuncs source_funcs = { + .prepare = redis_source_prepare, + .check = redis_source_check, + .dispatch = redis_source_dispatch, + .finalize = redis_source_finalize, + }; + redisContext *c = &ac->c; + RedisSource *source; + + g_return_val_if_fail(ac != NULL, NULL); + + source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); + source->ac = ac; + source->poll_fd.fd = c->fd; + source->poll_fd.events = 0; + source->poll_fd.revents = 0; + g_source_add_poll((GSource *)source, &source->poll_fd); + + ac->ev.addRead = redis_source_add_read; + ac->ev.delRead = redis_source_del_read; + ac->ev.addWrite = redis_source_add_write; + ac->ev.delWrite = redis_source_del_write; + ac->ev.cleanup = redis_source_cleanup; + ac->ev.data = source; + + return (GSource *)source; +} + +#endif /* __HIREDIS_GLIB_H__ */ diff --git a/ext/hiredis-0.14.1/adapters/ivykis.h b/ext/hiredis-0.14.1/adapters/ivykis.h new file mode 100644 index 000000000..75616ee24 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/ivykis.h @@ -0,0 +1,81 @@ +#ifndef __HIREDIS_IVYKIS_H__ +#define __HIREDIS_IVYKIS_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisIvykisEvents { + redisAsyncContext *context; + struct iv_fd fd; +} redisIvykisEvents; + +static void redisIvykisReadEvent(void *arg) { + redisAsyncContext *context = (redisAsyncContext *)arg; + redisAsyncHandleRead(context); +} + +static void redisIvykisWriteEvent(void *arg) { + redisAsyncContext *context = (redisAsyncContext *)arg; + redisAsyncHandleWrite(context); +} + +static void redisIvykisAddRead(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent); +} + +static void redisIvykisDelRead(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_in(&e->fd, NULL); +} + +static void redisIvykisAddWrite(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent); +} + +static void redisIvykisDelWrite(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_out(&e->fd, NULL); +} + +static void redisIvykisCleanup(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + + iv_fd_unregister(&e->fd); + free(e); +} + +static int redisIvykisAttach(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisIvykisEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisIvykisEvents*)hi_malloc(sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisIvykisAddRead; + ac->ev.delRead = redisIvykisDelRead; + ac->ev.addWrite = redisIvykisAddWrite; + ac->ev.delWrite = redisIvykisDelWrite; + ac->ev.cleanup = redisIvykisCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + IV_FD_INIT(&e->fd); + e->fd.fd = c->fd; + e->fd.handler_in = redisIvykisReadEvent; + e->fd.handler_out = redisIvykisWriteEvent; + e->fd.handler_err = NULL; + e->fd.cookie = e->context; + + iv_fd_register(&e->fd); + + return REDIS_OK; +} +#endif diff --git a/ext/hiredis-0.14.1/adapters/libev.h b/ext/hiredis-0.14.1/adapters/libev.h new file mode 100644 index 000000000..abad43634 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/libev.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HIREDIS_LIBEV_H__ +#define __HIREDIS_LIBEV_H__ +#include +#include +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisLibevEvents { + redisAsyncContext *context; + struct ev_loop *loop; + int reading, writing; + ev_io rev, wev; +} redisLibevEvents; + +static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleRead(e->context); +} + +static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleWrite(e->context); +} + +static void redisLibevAddRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->reading) { + e->reading = 1; + ev_io_start(EV_A_ &e->rev); + } +} + +static void redisLibevDelRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->reading) { + e->reading = 0; + ev_io_stop(EV_A_ &e->rev); + } +} + +static void redisLibevAddWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->writing) { + e->writing = 1; + ev_io_start(EV_A_ &e->wev); + } +} + +static void redisLibevDelWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->writing) { + e->writing = 0; + ev_io_stop(EV_A_ &e->wev); + } +} + +static void redisLibevCleanup(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + redisLibevDelRead(privdata); + redisLibevDelWrite(privdata); + free(e); +} + +static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisLibevEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibevEvents*)hi_malloc(sizeof(*e)); + e->context = ac; +#if EV_MULTIPLICITY + e->loop = loop; +#else + e->loop = NULL; +#endif + e->reading = e->writing = 0; + e->rev.data = e; + e->wev.data = e; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibevAddRead; + ac->ev.delRead = redisLibevDelRead; + ac->ev.addWrite = redisLibevAddWrite; + ac->ev.delWrite = redisLibevDelWrite; + ac->ev.cleanup = redisLibevCleanup; + ac->ev.data = e; + + /* Initialize read/write events */ + ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); + ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); + return REDIS_OK; +} + +#endif diff --git a/ext/hiredis-0.14.1/adapters/libevent.h b/ext/hiredis-0.14.1/adapters/libevent.h new file mode 100644 index 000000000..f2330d6f0 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/libevent.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HIREDIS_LIBEVENT_H__ +#define __HIREDIS_LIBEVENT_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisLibeventEvents { + redisAsyncContext *context; + struct event *rev, *wev; +} redisLibeventEvents; + +static void redisLibeventReadEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleRead(e->context); +} + +static void redisLibeventWriteEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleWrite(e->context); +} + +static void redisLibeventAddRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(e->rev,NULL); +} + +static void redisLibeventDelRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(e->rev); +} + +static void redisLibeventAddWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(e->wev,NULL); +} + +static void redisLibeventDelWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(e->wev); +} + +static void redisLibeventCleanup(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_free(e->rev); + event_free(e->wev); + free(e); +} + +static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { + redisContext *c = &(ac->c); + redisLibeventEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e); + e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e); + event_add(e->rev, NULL); + event_add(e->wev, NULL); + return REDIS_OK; +} +#endif diff --git a/ext/hiredis-0.14.1/adapters/libuv.h b/ext/hiredis-0.14.1/adapters/libuv.h new file mode 100644 index 000000000..ff08c25e1 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/libuv.h @@ -0,0 +1,122 @@ +#ifndef __HIREDIS_LIBUV_H__ +#define __HIREDIS_LIBUV_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" +#include + +typedef struct redisLibuvEvents { + redisAsyncContext* context; + uv_poll_t handle; + int events; +} redisLibuvEvents; + + +static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + if (status != 0) { + return; + } + + if (p->context != NULL && (events & UV_READABLE)) { + redisAsyncHandleRead(p->context); + } + if (p->context != NULL && (events & UV_WRITABLE)) { + redisAsyncHandleWrite(p->context); + } +} + + +static void redisLibuvAddRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_READABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_READABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void redisLibuvAddWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_WRITABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_WRITABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void on_close(uv_handle_t* handle) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + free(p); +} + + +static void redisLibuvCleanup(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->context = NULL; // indicate that context might no longer exist + uv_close((uv_handle_t*)&p->handle, on_close); +} + + +static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { + redisContext *c = &(ac->c); + + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + + ac->ev.addRead = redisLibuvAddRead; + ac->ev.delRead = redisLibuvDelRead; + ac->ev.addWrite = redisLibuvAddWrite; + ac->ev.delWrite = redisLibuvDelWrite; + ac->ev.cleanup = redisLibuvCleanup; + + redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); + + if (!p) { + return REDIS_ERR; + } + + memset(p, 0, sizeof(*p)); + + if (uv_poll_init(loop, &p->handle, c->fd) != 0) { + return REDIS_ERR; + } + + ac->ev.data = p; + p->handle.data = p; + p->context = ac; + + return REDIS_OK; +} +#endif diff --git a/ext/hiredis-0.14.1/adapters/macosx.h b/ext/hiredis-0.14.1/adapters/macosx.h new file mode 100644 index 000000000..72121f606 --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/macosx.h @@ -0,0 +1,114 @@ +// +// Created by Дмитрий Бахвалов on 13.07.15. +// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. +// + +#ifndef __HIREDIS_MACOSX_H__ +#define __HIREDIS_MACOSX_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct { + redisAsyncContext *context; + CFSocketRef socketRef; + CFRunLoopSourceRef sourceRef; +} RedisRunLoop; + +static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) { + if( redisRunLoop != NULL ) { + if( redisRunLoop->sourceRef != NULL ) { + CFRunLoopSourceInvalidate(redisRunLoop->sourceRef); + CFRelease(redisRunLoop->sourceRef); + } + if( redisRunLoop->socketRef != NULL ) { + CFSocketInvalidate(redisRunLoop->socketRef); + CFRelease(redisRunLoop->socketRef); + } + free(redisRunLoop); + } + return REDIS_ERR; +} + +static void redisMacOSAddRead(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); +} + +static void redisMacOSDelRead(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); +} + +static void redisMacOSAddWrite(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); +} + +static void redisMacOSDelWrite(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); +} + +static void redisMacOSCleanup(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + freeRedisRunLoop(redisRunLoop); +} + +static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { + redisAsyncContext* context = (redisAsyncContext*) info; + + switch (callbackType) { + case kCFSocketReadCallBack: + redisAsyncHandleRead(context); + break; + + case kCFSocketWriteCallBack: + redisAsyncHandleWrite(context); + break; + + default: + break; + } +} + +static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) { + redisContext *redisCtx = &(redisAsyncCtx->c); + + /* Nothing should be attached when something is already attached */ + if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; + + RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop)); + if( !redisRunLoop ) return REDIS_ERR; + + /* Setup redis stuff */ + redisRunLoop->context = redisAsyncCtx; + + redisAsyncCtx->ev.addRead = redisMacOSAddRead; + redisAsyncCtx->ev.delRead = redisMacOSDelRead; + redisAsyncCtx->ev.addWrite = redisMacOSAddWrite; + redisAsyncCtx->ev.delWrite = redisMacOSDelWrite; + redisAsyncCtx->ev.cleanup = redisMacOSCleanup; + redisAsyncCtx->ev.data = redisRunLoop; + + /* Initialize and install read/write events */ + CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL }; + + redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, + kCFSocketReadCallBack | kCFSocketWriteCallBack, + redisMacOSAsyncCallback, + &socketCtx); + if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop); + + redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0); + if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop); + + CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode); + + return REDIS_OK; +} + +#endif + diff --git a/ext/hiredis-0.14.1/adapters/qt.h b/ext/hiredis-0.14.1/adapters/qt.h new file mode 100644 index 000000000..5cc02e6ce --- /dev/null +++ b/ext/hiredis-0.14.1/adapters/qt.h @@ -0,0 +1,135 @@ +/*- + * Copyright (C) 2014 Pietro Cerutti + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __HIREDIS_QT_H__ +#define __HIREDIS_QT_H__ +#include +#include "../async.h" + +static void RedisQtAddRead(void *); +static void RedisQtDelRead(void *); +static void RedisQtAddWrite(void *); +static void RedisQtDelWrite(void *); +static void RedisQtCleanup(void *); + +class RedisQtAdapter : public QObject { + + Q_OBJECT + + friend + void RedisQtAddRead(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->addRead(); + } + + friend + void RedisQtDelRead(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->delRead(); + } + + friend + void RedisQtAddWrite(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->addWrite(); + } + + friend + void RedisQtDelWrite(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->delWrite(); + } + + friend + void RedisQtCleanup(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->cleanup(); + } + + public: + RedisQtAdapter(QObject * parent = 0) + : QObject(parent), m_ctx(0), m_read(0), m_write(0) { } + + ~RedisQtAdapter() { + if (m_ctx != 0) { + m_ctx->ev.data = NULL; + } + } + + int setContext(redisAsyncContext * ac) { + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + m_ctx = ac; + m_ctx->ev.data = this; + m_ctx->ev.addRead = RedisQtAddRead; + m_ctx->ev.delRead = RedisQtDelRead; + m_ctx->ev.addWrite = RedisQtAddWrite; + m_ctx->ev.delWrite = RedisQtDelWrite; + m_ctx->ev.cleanup = RedisQtCleanup; + return REDIS_OK; + } + + private: + void addRead() { + if (m_read) return; + m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); + connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); + } + + void delRead() { + if (!m_read) return; + delete m_read; + m_read = 0; + } + + void addWrite() { + if (m_write) return; + m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); + connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); + } + + void delWrite() { + if (!m_write) return; + delete m_write; + m_write = 0; + } + + void cleanup() { + delRead(); + delWrite(); + } + + private slots: + void read() { redisAsyncHandleRead(m_ctx); } + void write() { redisAsyncHandleWrite(m_ctx); } + + private: + redisAsyncContext * m_ctx; + QSocketNotifier * m_read; + QSocketNotifier * m_write; +}; + +#endif /* !__HIREDIS_QT_H__ */ diff --git a/ext/hiredis-0.14.1/alloc.c b/ext/hiredis-0.14.1/alloc.c new file mode 100644 index 000000000..55c3020e7 --- /dev/null +++ b/ext/hiredis-0.14.1/alloc.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020, Michael Grunder + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include "alloc.h" +#include + +void *hi_malloc(size_t size) { + void *ptr = malloc(size); + if (ptr == NULL) + HIREDIS_OOM_HANDLER; + + return ptr; +} + +void *hi_calloc(size_t nmemb, size_t size) { + void *ptr = calloc(nmemb, size); + if (ptr == NULL) + HIREDIS_OOM_HANDLER; + + return ptr; +} + +void *hi_realloc(void *ptr, size_t size) { + void *newptr = realloc(ptr, size); + if (newptr == NULL) + HIREDIS_OOM_HANDLER; + + return newptr; +} + +char *hi_strdup(const char *str) { + char *newstr = strdup(str); + if (newstr == NULL) + HIREDIS_OOM_HANDLER; + + return newstr; +} diff --git a/ext/hiredis-0.14.1/alloc.h b/ext/hiredis-0.14.1/alloc.h new file mode 100644 index 000000000..2c9b04e35 --- /dev/null +++ b/ext/hiredis-0.14.1/alloc.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Michael Grunder + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HIREDIS_ALLOC_H +#define HIREDIS_ALLOC_H + +#include /* for size_t */ + +#ifndef HIREDIS_OOM_HANDLER +#define HIREDIS_OOM_HANDLER abort() +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void *hi_malloc(size_t size); +void *hi_calloc(size_t nmemb, size_t size); +void *hi_realloc(void *ptr, size_t size); +char *hi_strdup(const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* HIREDIS_ALLOC_H */ diff --git a/ext/hiredis-0.14.1/appveyor.yml b/ext/hiredis-0.14.1/appveyor.yml new file mode 100644 index 000000000..819efbd58 --- /dev/null +++ b/ext/hiredis-0.14.1/appveyor.yml @@ -0,0 +1,23 @@ +# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) +environment: + matrix: + - CYG_BASH: C:\cygwin64\bin\bash + CC: gcc + - CYG_BASH: C:\cygwin\bin\bash + CC: gcc + TARGET: 32bit + TARGET_VARS: 32bit-vars + +clone_depth: 1 + +# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail +init: + - git config --global core.autocrlf input + +# Install needed build dependencies +install: + - '%CYG_BASH% -lc "cygcheck -dc cygwin"' + +build_script: + - 'echo building...' + - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include "alloc.h" +#include +#include +#include +#include +#include +#include +#include "async.h" +#include "net.h" +#include "dict.c" +#include "sds.h" + +#define _EL_ADD_READ(ctx) do { \ + if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_READ(ctx) do { \ + if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ + } while(0) +#define _EL_ADD_WRITE(ctx) do { \ + if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_WRITE(ctx) do { \ + if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ + } while(0) +#define _EL_CLEANUP(ctx) do { \ + if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ + } while(0); + +/* Forward declaration of function in hiredis.c */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); + +/* Functions managing dictionary of callbacks for pub/sub. */ +static unsigned int callbackHash(const void *key) { + return dictGenHashFunction((const unsigned char *)key, + sdslen((const sds)key)); +} + +static void *callbackValDup(void *privdata, const void *src) { + ((void) privdata); + redisCallback *dup = hi_malloc(sizeof(*dup)); + memcpy(dup,src,sizeof(*dup)); + return dup; +} + +static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { + int l1, l2; + ((void) privdata); + + l1 = sdslen((const sds)key1); + l2 = sdslen((const sds)key2); + if (l1 != l2) return 0; + return memcmp(key1,key2,l1) == 0; +} + +static void callbackKeyDestructor(void *privdata, void *key) { + ((void) privdata); + sdsfree((sds)key); +} + +static void callbackValDestructor(void *privdata, void *val) { + ((void) privdata); + free(val); +} + +static dictType callbackDict = { + callbackHash, + NULL, + callbackValDup, + callbackKeyCompare, + callbackKeyDestructor, + callbackValDestructor +}; + +static redisAsyncContext *redisAsyncInitialize(redisContext *c) { + redisAsyncContext *ac; + + ac = realloc(c,sizeof(redisAsyncContext)); + if (ac == NULL) + return NULL; + + c = &(ac->c); + + /* The regular connect functions will always set the flag REDIS_CONNECTED. + * For the async API, we want to wait until the first write event is + * received up before setting this flag, so reset it here. */ + c->flags &= ~REDIS_CONNECTED; + + ac->err = 0; + ac->errstr = NULL; + ac->data = NULL; + + ac->ev.data = NULL; + ac->ev.addRead = NULL; + ac->ev.delRead = NULL; + ac->ev.addWrite = NULL; + ac->ev.delWrite = NULL; + ac->ev.cleanup = NULL; + + ac->onConnect = NULL; + ac->onDisconnect = NULL; + + ac->replies.head = NULL; + ac->replies.tail = NULL; + ac->sub.invalid.head = NULL; + ac->sub.invalid.tail = NULL; + ac->sub.channels = dictCreate(&callbackDict,NULL); + ac->sub.patterns = dictCreate(&callbackDict,NULL); + return ac; +} + +/* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ +static void __redisAsyncCopyError(redisAsyncContext *ac) { + if (!ac) + return; + + redisContext *c = &(ac->c); + ac->err = c->err; + ac->errstr = c->errstr; +} + +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectNonBlock(ip,port); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectUnix(const char *path) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectUnixNonBlock(path); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { + if (ac->onConnect == NULL) { + ac->onConnect = fn; + + /* The common way to detect an established connection is to wait for + * the first write event to be fired. This assumes the related event + * library functions are already set. */ + _EL_ADD_WRITE(ac); + return REDIS_OK; + } + return REDIS_ERR; +} + +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { + if (ac->onDisconnect == NULL) { + ac->onDisconnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +/* Helper functions to push/shift callbacks */ +static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { + redisCallback *cb; + + /* Copy callback from stack to heap */ + cb = malloc(sizeof(*cb)); + if (cb == NULL) + return REDIS_ERR_OOM; + + if (source != NULL) { + memcpy(cb,source,sizeof(*cb)); + cb->next = NULL; + } + + /* Store callback in list */ + if (list->head == NULL) + list->head = cb; + if (list->tail != NULL) + list->tail->next = cb; + list->tail = cb; + return REDIS_OK; +} + +static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { + redisCallback *cb = list->head; + if (cb != NULL) { + list->head = cb->next; + if (cb == list->tail) + list->tail = NULL; + + /* Copy callback from heap to stack */ + if (target != NULL) + memcpy(target,cb,sizeof(*cb)); + free(cb); + return REDIS_OK; + } + return REDIS_ERR; +} + +static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { + redisContext *c = &(ac->c); + if (cb->fn != NULL) { + c->flags |= REDIS_IN_CALLBACK; + cb->fn(ac,reply,cb->privdata); + c->flags &= ~REDIS_IN_CALLBACK; + } +} + +/* Helper function to free the context. */ +static void __redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + dictIterator *it; + dictEntry *de; + + /* Execute pending callbacks with NULL reply. */ + while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Execute callbacks for invalid commands */ + while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Run subscription callbacks callbacks with NULL reply */ + it = dictGetIterator(ac->sub.channels); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.channels); + + it = dictGetIterator(ac->sub.patterns); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.patterns); + + /* Signal event lib to clean up */ + _EL_CLEANUP(ac); + + /* Execute disconnect callback. When redisAsyncFree() initiated destroying + * this context, the status will always be REDIS_OK. */ + if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { + if (c->flags & REDIS_FREEING) { + ac->onDisconnect(ac,REDIS_OK); + } else { + ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); + } + } + + /* Cleanup self */ + redisFree(c); +} + +/* Free the async context. When this function is called from a callback, + * control needs to be returned to redisProcessCallbacks() before actual + * free'ing. To do so, a flag is set on the context which is picked up by + * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ +void redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_FREEING; + if (!(c->flags & REDIS_IN_CALLBACK)) + __redisAsyncFree(ac); +} + +/* Helper function to make the disconnect happen and clean up. */ +static void __redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + /* Make sure error is accessible if there is any */ + __redisAsyncCopyError(ac); + + if (ac->err == 0) { + /* For clean disconnects, there should be no pending callbacks. */ + int ret = __redisShiftCallback(&ac->replies,NULL); + assert(ret == REDIS_ERR); + } else { + /* Disconnection is caused by an error, make sure that pending + * callbacks cannot call new commands. */ + c->flags |= REDIS_DISCONNECTING; + } + + /* For non-clean disconnects, __redisAsyncFree() will execute pending + * callbacks with a NULL-reply. */ + __redisAsyncFree(ac); +} + +/* Tries to do a clean disconnect from Redis, meaning it stops new commands + * from being issued, but tries to flush the output buffer and execute + * callbacks for all remaining replies. When this function is called from a + * callback, there might be more replies and we can safely defer disconnecting + * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately + * when there are no pending callbacks. */ +void redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_DISCONNECTING; + if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) + __redisAsyncDisconnect(ac); +} + +static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { + redisContext *c = &(ac->c); + dict *callbacks; + redisCallback *cb; + dictEntry *de; + int pvariant; + char *stype; + sds sname; + + /* Custom reply functions are not supported for pub/sub. This will fail + * very hard when they are used... */ + if (reply->type == REDIS_REPLY_ARRAY) { + assert(reply->elements >= 2); + assert(reply->element[0]->type == REDIS_REPLY_STRING); + stype = reply->element[0]->str; + pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; + + if (pvariant) + callbacks = ac->sub.patterns; + else + callbacks = ac->sub.channels; + + /* Locate the right callback */ + assert(reply->element[1]->type == REDIS_REPLY_STRING); + sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); + de = dictFind(callbacks,sname); + if (de != NULL) { + cb = dictGetEntryVal(de); + + /* If this is an subscribe reply decrease pending counter. */ + if (strcasecmp(stype+pvariant,"subscribe") == 0) { + cb->pending_subs -= 1; + } + + memcpy(dstcb,cb,sizeof(*dstcb)); + + /* If this is an unsubscribe message, remove it. */ + if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { + if (cb->pending_subs == 0) + dictDelete(callbacks,sname); + + /* If this was the last unsubscribe message, revert to + * non-subscribe mode. */ + assert(reply->element[2]->type == REDIS_REPLY_INTEGER); + + /* Unset subscribed flag only when no pipelined pending subscribe. */ + if (reply->element[2]->integer == 0 + && dictSize(ac->sub.channels) == 0 + && dictSize(ac->sub.patterns) == 0) + c->flags &= ~REDIS_SUBSCRIBED; + } + } + sdsfree(sname); + } else { + /* Shift callback for invalid commands. */ + __redisShiftCallback(&ac->sub.invalid,dstcb); + } + return REDIS_OK; +} + +void redisProcessCallbacks(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb = {NULL, NULL, 0, NULL}; + void *reply = NULL; + int status; + + while((status = redisGetReply(c,&reply)) == REDIS_OK) { + if (reply == NULL) { + /* When the connection is being disconnected and there are + * no more replies, this is the cue to really disconnect. */ + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0 + && ac->replies.head == NULL) { + __redisAsyncDisconnect(ac); + return; + } + + /* If monitor mode, repush callback */ + if(c->flags & REDIS_MONITORING) { + __redisPushCallback(&ac->replies,&cb); + } + + /* When the connection is not being disconnected, simply stop + * trying to get replies and wait for the next loop tick. */ + break; + } + + /* Even if the context is subscribed, pending regular callbacks will + * get a reply before pub/sub messages arrive. */ + if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { + /* + * A spontaneous reply in a not-subscribed context can be the error + * reply that is sent when a new connection exceeds the maximum + * number of allowed connections on the server side. + * + * This is seen as an error instead of a regular reply because the + * server closes the connection after sending it. + * + * To prevent the error from being overwritten by an EOF error the + * connection is closed here. See issue #43. + * + * Another possibility is that the server is loading its dataset. + * In this case we also want to close the connection, and have the + * user wait until the server is ready to take our request. + */ + if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { + c->err = REDIS_ERR_OTHER; + snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); + c->reader->fn->freeObject(reply); + __redisAsyncDisconnect(ac); + return; + } + /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ + assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); + if(c->flags & REDIS_SUBSCRIBED) + __redisGetSubscribeCallback(ac,reply,&cb); + } + + if (cb.fn != NULL) { + __redisRunCallback(ac,&cb,reply); + c->reader->fn->freeObject(reply); + + /* Proceed with free'ing when redisAsyncFree() was called. */ + if (c->flags & REDIS_FREEING) { + __redisAsyncFree(ac); + return; + } + } else { + /* No callback for this reply. This can either be a NULL callback, + * or there were no callbacks to begin with. Either way, don't + * abort with an error, but simply ignore it because the client + * doesn't know what the server will spit out over the wire. */ + c->reader->fn->freeObject(reply); + } + } + + /* Disconnect when there was an error reading the reply */ + if (status != REDIS_OK) + __redisAsyncDisconnect(ac); +} + +/* Internal helper function to detect socket status the first time a read or + * write event fires. When connecting was not successful, the connect callback + * is called with a REDIS_ERR status and the context is free'd. */ +static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (redisCheckSocketError(c) == REDIS_ERR) { + /* Try again later when connect(2) is still in progress. */ + if (errno == EINPROGRESS) + return REDIS_OK; + + if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); + __redisAsyncDisconnect(ac); + return REDIS_ERR; + } + + /* Mark context as connected. */ + c->flags |= REDIS_CONNECTED; + if (ac->onConnect) ac->onConnect(ac,REDIS_OK); + return REDIS_OK; +} + +/* This function should be called when the socket is readable. + * It processes all replies that can be read and executes their callbacks. + */ +void redisAsyncHandleRead(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferRead(c) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Always re-schedule reads */ + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +void redisAsyncHandleWrite(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + int done = 0; + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferWrite(c,&done) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Continue writing when not done, stop writing otherwise */ + if (!done) + _EL_ADD_WRITE(ac); + else + _EL_DEL_WRITE(ac); + + /* Always schedule reads after writes */ + _EL_ADD_READ(ac); + } +} + +/* Sets a pointer to the first argument and its length starting at p. Returns + * the number of bytes to skip to get to the following argument. */ +static const char *nextArgument(const char *start, const char **str, size_t *len) { + const char *p = start; + if (p[0] != '$') { + p = strchr(p,'$'); + if (p == NULL) return NULL; + } + + *len = (int)strtol(p+1,NULL,10); + p = strchr(p,'\r'); + assert(p); + *str = p+2; + return p+2+(*len)+2; +} + +/* Helper function for the redisAsyncCommand* family of functions. Writes a + * formatted command to the output buffer and registers the provided callback + * function with the context. */ +static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + redisContext *c = &(ac->c); + redisCallback cb; + struct dict *cbdict; + dictEntry *de; + redisCallback *existcb; + int pvariant, hasnext; + const char *cstr, *astr; + size_t clen, alen; + const char *p; + sds sname; + int ret; + + /* Don't accept new commands when the connection is about to be closed. */ + if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; + + /* Setup callback */ + cb.fn = fn; + cb.privdata = privdata; + cb.pending_subs = 1; + + /* Find out which command will be appended. */ + p = nextArgument(cmd,&cstr,&clen); + assert(p != NULL); + hasnext = (p[0] == '$'); + pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; + cstr += pvariant; + clen -= pvariant; + + if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { + c->flags |= REDIS_SUBSCRIBED; + + /* Add every channel/pattern to the list of subscription callbacks. */ + while ((p = nextArgument(p,&astr,&alen)) != NULL) { + sname = sdsnewlen(astr,alen); + if (pvariant) + cbdict = ac->sub.patterns; + else + cbdict = ac->sub.channels; + + de = dictFind(cbdict,sname); + + if (de != NULL) { + existcb = dictGetEntryVal(de); + cb.pending_subs = existcb->pending_subs + 1; + } + + ret = dictReplace(cbdict,sname,&cb); + + if (ret == 0) sdsfree(sname); + } + } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { + /* It is only useful to call (P)UNSUBSCRIBE when the context is + * subscribed to one or more channels or patterns. */ + if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; + + /* (P)UNSUBSCRIBE does not have its own response: every channel or + * pattern that is unsubscribed will receive a message. This means we + * should not append a callback function for this command. */ + } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { + /* Set monitor flag and push callback */ + c->flags |= REDIS_MONITORING; + __redisPushCallback(&ac->replies,&cb); + } else { + if (c->flags & REDIS_SUBSCRIBED) + /* This will likely result in an error reply, but it needs to be + * received and passed to the callback. */ + __redisPushCallback(&ac->sub.invalid,&cb); + else + __redisPushCallback(&ac->replies,&cb); + } + + __redisAppendCommand(c,cmd,len); + + /* Always schedule a write when the write buffer is non-empty */ + _EL_ADD_WRITE(ac); + + return REDIS_OK; +} + +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { + char *cmd; + int len; + int status; + len = redisvFormatCommand(&cmd,format,ap); + + /* We don't want to pass -1 or -2 to future functions as a length. */ + if (len < 0) + return REDIS_ERR; + + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + free(cmd); + return status; +} + +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { + va_list ap; + int status; + va_start(ap,format); + status = redisvAsyncCommand(ac,fn,privdata,format,ap); + va_end(ap); + return status; +} + +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + int status; + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len < 0) + return REDIS_ERR; + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + sdsfree(cmd); + return status; +} + +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + return status; +} diff --git a/ext/hiredis-0.14.1/async.h b/ext/hiredis-0.14.1/async.h new file mode 100644 index 000000000..e69d84090 --- /dev/null +++ b/ext/hiredis-0.14.1/async.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HIREDIS_ASYNC_H +#define __HIREDIS_ASYNC_H +#include "hiredis.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ +struct dict; /* dictionary header is included in async.c */ + +/* Reply callback prototype and container */ +typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); +typedef struct redisCallback { + struct redisCallback *next; /* simple singly linked list */ + redisCallbackFn *fn; + int pending_subs; + void *privdata; +} redisCallback; + +/* List of callbacks for either regular replies or pub/sub */ +typedef struct redisCallbackList { + redisCallback *head, *tail; +} redisCallbackList; + +/* Connection callback prototypes */ +typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); +typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); + +/* Context for an async connection to Redis */ +typedef struct redisAsyncContext { + /* Hold the regular context, so it can be realloc'ed. */ + redisContext c; + + /* Setup error flags so they can be used directly. */ + int err; + char *errstr; + + /* Not used by hiredis */ + void *data; + + /* Event library data and hooks */ + struct { + void *data; + + /* Hooks that are called when the library expects to start + * reading/writing. These functions should be idempotent. */ + void (*addRead)(void *privdata); + void (*delRead)(void *privdata); + void (*addWrite)(void *privdata); + void (*delWrite)(void *privdata); + void (*cleanup)(void *privdata); + } ev; + + /* Called when either the connection is terminated due to an error or per + * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ + redisDisconnectCallback *onDisconnect; + + /* Called when the first write event was received. */ + redisConnectCallback *onConnect; + + /* Regular command callbacks */ + redisCallbackList replies; + + /* Subscription callbacks */ + struct { + redisCallbackList invalid; + struct dict *channels; + struct dict *patterns; + } sub; +} redisAsyncContext; + +/* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnect(const char *ip, int port); +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr); +redisAsyncContext *redisAsyncConnectUnix(const char *path); +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +void redisAsyncDisconnect(redisAsyncContext *ac); +void redisAsyncFree(redisAsyncContext *ac); + +/* Handle read/write events */ +void redisAsyncHandleRead(redisAsyncContext *ac); +void redisAsyncHandleWrite(redisAsyncContext *ac); + +/* Command functions for an async context. Write the command to the + * output buffer and register the provided callback. */ +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-0.14.1/dict.c b/ext/hiredis-0.14.1/dict.c new file mode 100644 index 000000000..29cdc190f --- /dev/null +++ b/ext/hiredis-0.14.1/dict.c @@ -0,0 +1,339 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include "alloc.h" +#include +#include +#include +#include "dict.h" + +/* -------------------------- private prototypes ---------------------------- */ + +static int _dictExpandIfNeeded(dict *ht); +static unsigned long _dictNextPower(unsigned long size); +static int _dictKeyIndex(dict *ht, const void *key); +static int _dictInit(dict *ht, dictType *type, void *privDataPtr); + +/* -------------------------- hash functions -------------------------------- */ + +/* Generic hash function (a popular one from Bernstein). + * I tested a few and this was the best. */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { + unsigned int hash = 5381; + + while (len--) + hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ + return hash; +} + +/* ----------------------------- API implementation ------------------------- */ + +/* Reset an hashtable already initialized with ht_init(). + * NOTE: This function should only called by ht_destroy(). */ +static void _dictReset(dict *ht) { + ht->table = NULL; + ht->size = 0; + ht->sizemask = 0; + ht->used = 0; +} + +/* Create a new hash table */ +static dict *dictCreate(dictType *type, void *privDataPtr) { + dict *ht = hi_malloc(sizeof(*ht)); + _dictInit(ht,type,privDataPtr); + return ht; +} + +/* Initialize the hash table */ +static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { + _dictReset(ht); + ht->type = type; + ht->privdata = privDataPtr; + return DICT_OK; +} + +/* Expand or create the hashtable */ +static int dictExpand(dict *ht, unsigned long size) { + dict n; /* the new hashtable */ + unsigned long realsize = _dictNextPower(size), i; + + /* the size is invalid if it is smaller than the number of + * elements already inside the hashtable */ + if (ht->used > size) + return DICT_ERR; + + _dictInit(&n, ht->type, ht->privdata); + n.size = realsize; + n.sizemask = realsize-1; + n.table = calloc(realsize,sizeof(dictEntry*)); + + /* Copy all the elements from the old to the new table: + * note that if the old hash table is empty ht->size is zero, + * so dictExpand just creates an hash table. */ + n.used = ht->used; + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if (ht->table[i] == NULL) continue; + + /* For each hash entry on this slot... */ + he = ht->table[i]; + while(he) { + unsigned int h; + + nextHe = he->next; + /* Get the new element index */ + h = dictHashKey(ht, he->key) & n.sizemask; + he->next = n.table[h]; + n.table[h] = he; + ht->used--; + /* Pass to the next element */ + he = nextHe; + } + } + assert(ht->used == 0); + free(ht->table); + + /* Remap the new hashtable in the old */ + *ht = n; + return DICT_OK; +} + +/* Add an element to the target hash table */ +static int dictAdd(dict *ht, void *key, void *val) { + int index; + dictEntry *entry; + + /* Get the index of the new element, or -1 if + * the element already exists. */ + if ((index = _dictKeyIndex(ht, key)) == -1) + return DICT_ERR; + + /* Allocates the memory and stores key */ + entry = hi_malloc(sizeof(*entry)); + entry->next = ht->table[index]; + ht->table[index] = entry; + + /* Set the hash entry fields. */ + dictSetHashKey(ht, entry, key); + dictSetHashVal(ht, entry, val); + ht->used++; + return DICT_OK; +} + +/* Add an element, discarding the old if the key already exists. + * Return 1 if the key was added from scratch, 0 if there was already an + * element with such key and dictReplace() just performed a value update + * operation. */ +static int dictReplace(dict *ht, void *key, void *val) { + dictEntry *entry, auxentry; + + /* Try to add the element. If the key + * does not exists dictAdd will succeed. */ + if (dictAdd(ht, key, val) == DICT_OK) + return 1; + /* It already exists, get the entry */ + entry = dictFind(ht, key); + /* Free the old value and set the new one */ + /* Set the new value and free the old one. Note that it is important + * to do that in this order, as the value may just be exactly the same + * as the previous one. In this context, think to reference counting, + * you want to increment (set), and then decrement (free), and not the + * reverse. */ + auxentry = *entry; + dictSetHashVal(ht, entry, val); + dictFreeEntryVal(ht, &auxentry); + return 0; +} + +/* Search and remove an element */ +static int dictDelete(dict *ht, const void *key) { + unsigned int h; + dictEntry *de, *prevde; + + if (ht->size == 0) + return DICT_ERR; + h = dictHashKey(ht, key) & ht->sizemask; + de = ht->table[h]; + + prevde = NULL; + while(de) { + if (dictCompareHashKeys(ht,key,de->key)) { + /* Unlink the element from the list */ + if (prevde) + prevde->next = de->next; + else + ht->table[h] = de->next; + + dictFreeEntryKey(ht,de); + dictFreeEntryVal(ht,de); + free(de); + ht->used--; + return DICT_OK; + } + prevde = de; + de = de->next; + } + return DICT_ERR; /* not found */ +} + +/* Destroy an entire hash table */ +static int _dictClear(dict *ht) { + unsigned long i; + + /* Free all the elements */ + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if ((he = ht->table[i]) == NULL) continue; + while(he) { + nextHe = he->next; + dictFreeEntryKey(ht, he); + dictFreeEntryVal(ht, he); + free(he); + ht->used--; + he = nextHe; + } + } + /* Free the table and the allocated cache structure */ + free(ht->table); + /* Re-initialize the table */ + _dictReset(ht); + return DICT_OK; /* never fails */ +} + +/* Clear & Release the hash table */ +static void dictRelease(dict *ht) { + _dictClear(ht); + free(ht); +} + +static dictEntry *dictFind(dict *ht, const void *key) { + dictEntry *he; + unsigned int h; + + if (ht->size == 0) return NULL; + h = dictHashKey(ht, key) & ht->sizemask; + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return he; + he = he->next; + } + return NULL; +} + +static dictIterator *dictGetIterator(dict *ht) { + dictIterator *iter = hi_malloc(sizeof(*iter)); + + iter->ht = ht; + iter->index = -1; + iter->entry = NULL; + iter->nextEntry = NULL; + return iter; +} + +static dictEntry *dictNext(dictIterator *iter) { + while (1) { + if (iter->entry == NULL) { + iter->index++; + if (iter->index >= + (signed)iter->ht->size) break; + iter->entry = iter->ht->table[iter->index]; + } else { + iter->entry = iter->nextEntry; + } + if (iter->entry) { + /* We need to save the 'next' here, the iterator user + * may delete the entry we are returning. */ + iter->nextEntry = iter->entry->next; + return iter->entry; + } + } + return NULL; +} + +static void dictReleaseIterator(dictIterator *iter) { + free(iter); +} + +/* ------------------------- private functions ------------------------------ */ + +/* Expand the hash table if needed */ +static int _dictExpandIfNeeded(dict *ht) { + /* If the hash table is empty expand it to the initial size, + * if the table is "full" dobule its size. */ + if (ht->size == 0) + return dictExpand(ht, DICT_HT_INITIAL_SIZE); + if (ht->used == ht->size) + return dictExpand(ht, ht->size*2); + return DICT_OK; +} + +/* Our hash table capability is a power of two */ +static unsigned long _dictNextPower(unsigned long size) { + unsigned long i = DICT_HT_INITIAL_SIZE; + + if (size >= LONG_MAX) return LONG_MAX; + while(1) { + if (i >= size) + return i; + i *= 2; + } +} + +/* Returns the index of a free slot that can be populated with + * an hash entry for the given 'key'. + * If the key already exists, -1 is returned. */ +static int _dictKeyIndex(dict *ht, const void *key) { + unsigned int h; + dictEntry *he; + + /* Expand the hashtable if needed */ + if (_dictExpandIfNeeded(ht) == DICT_ERR) + return -1; + /* Compute the key hash value */ + h = dictHashKey(ht, key) & ht->sizemask; + /* Search if this slot does not already contain the given key */ + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return -1; + he = he->next; + } + return h; +} + diff --git a/ext/hiredis-0.14.1/dict.h b/ext/hiredis-0.14.1/dict.h new file mode 100644 index 000000000..95fcd280e --- /dev/null +++ b/ext/hiredis-0.14.1/dict.h @@ -0,0 +1,126 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __DICT_H +#define __DICT_H + +#define DICT_OK 0 +#define DICT_ERR 1 + +/* Unused arguments generate annoying warnings... */ +#define DICT_NOTUSED(V) ((void) V) + +typedef struct dictEntry { + void *key; + void *val; + struct dictEntry *next; +} dictEntry; + +typedef struct dictType { + unsigned int (*hashFunction)(const void *key); + void *(*keyDup)(void *privdata, const void *key); + void *(*valDup)(void *privdata, const void *obj); + int (*keyCompare)(void *privdata, const void *key1, const void *key2); + void (*keyDestructor)(void *privdata, void *key); + void (*valDestructor)(void *privdata, void *obj); +} dictType; + +typedef struct dict { + dictEntry **table; + dictType *type; + unsigned long size; + unsigned long sizemask; + unsigned long used; + void *privdata; +} dict; + +typedef struct dictIterator { + dict *ht; + int index; + dictEntry *entry, *nextEntry; +} dictIterator; + +/* This is the initial size of every hash table */ +#define DICT_HT_INITIAL_SIZE 4 + +/* ------------------------------- Macros ------------------------------------*/ +#define dictFreeEntryVal(ht, entry) \ + if ((ht)->type->valDestructor) \ + (ht)->type->valDestructor((ht)->privdata, (entry)->val) + +#define dictSetHashVal(ht, entry, _val_) do { \ + if ((ht)->type->valDup) \ + entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ + else \ + entry->val = (_val_); \ +} while(0) + +#define dictFreeEntryKey(ht, entry) \ + if ((ht)->type->keyDestructor) \ + (ht)->type->keyDestructor((ht)->privdata, (entry)->key) + +#define dictSetHashKey(ht, entry, _key_) do { \ + if ((ht)->type->keyDup) \ + entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ + else \ + entry->key = (_key_); \ +} while(0) + +#define dictCompareHashKeys(ht, key1, key2) \ + (((ht)->type->keyCompare) ? \ + (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ + (key1) == (key2)) + +#define dictHashKey(ht, key) (ht)->type->hashFunction(key) + +#define dictGetEntryKey(he) ((he)->key) +#define dictGetEntryVal(he) ((he)->val) +#define dictSlots(ht) ((ht)->size) +#define dictSize(ht) ((ht)->used) + +/* API */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len); +static dict *dictCreate(dictType *type, void *privDataPtr); +static int dictExpand(dict *ht, unsigned long size); +static int dictAdd(dict *ht, void *key, void *val); +static int dictReplace(dict *ht, void *key, void *val); +static int dictDelete(dict *ht, const void *key); +static void dictRelease(dict *ht); +static dictEntry * dictFind(dict *ht, const void *key); +static dictIterator *dictGetIterator(dict *ht); +static dictEntry *dictNext(dictIterator *iter); +static void dictReleaseIterator(dictIterator *iter); + +#endif /* __DICT_H */ diff --git a/ext/hiredis-0.14.1/examples/example-ae.c b/ext/hiredis-0.14.1/examples/example-ae.c new file mode 100644 index 000000000..8efa7306a --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-ae.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +#include +#include +#include + +/* Put event loop in the global scope, so it can be explicitly stopped */ +static aeEventLoop *loop; + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Disconnected...\n"); + aeStop(loop); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + loop = aeCreateEventLoop(64); + redisAeAttach(loop, c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + aeMain(loop); + return 0; +} + diff --git a/ext/hiredis-0.14.1/examples/example-glib.c b/ext/hiredis-0.14.1/examples/example-glib.c new file mode 100644 index 000000000..d6e10f8e8 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-glib.c @@ -0,0 +1,73 @@ +#include + +#include +#include +#include + +static GMainLoop *mainloop; + +static void +connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_printerr("Failed to connect: %s\n", ac->errstr); + g_main_loop_quit(mainloop); + } else { + g_printerr("Connected...\n"); + } +} + +static void +disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_error("Failed to disconnect: %s", ac->errstr); + } else { + g_printerr("Disconnected...\n"); + g_main_loop_quit(mainloop); + } +} + +static void +command_cb(redisAsyncContext *ac, + gpointer r, + gpointer user_data G_GNUC_UNUSED) +{ + redisReply *reply = r; + + if (reply) { + g_print("REPLY: %s\n", reply->str); + } + + redisAsyncDisconnect(ac); +} + +gint +main (gint argc G_GNUC_UNUSED, + gchar *argv[] G_GNUC_UNUSED) +{ + redisAsyncContext *ac; + GMainContext *context = NULL; + GSource *source; + + ac = redisAsyncConnect("127.0.0.1", 6379); + if (ac->err) { + g_printerr("%s\n", ac->errstr); + exit(EXIT_FAILURE); + } + + source = redis_source_new(ac); + mainloop = g_main_loop_new(context, FALSE); + g_source_attach(source, context); + + redisAsyncSetConnectCallback(ac, connect_cb); + redisAsyncSetDisconnectCallback(ac, disconnect_cb); + redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); + redisAsyncCommand(ac, command_cb, NULL, "GET key"); + + g_main_loop_run(mainloop); + + return EXIT_SUCCESS; +} diff --git a/ext/hiredis-0.14.1/examples/example-ivykis.c b/ext/hiredis-0.14.1/examples/example-ivykis.c new file mode 100644 index 000000000..67affcef3 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-ivykis.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + iv_init(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisIvykisAttach(c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + + iv_main(); + + iv_deinit(); + + return 0; +} diff --git a/ext/hiredis-0.14.1/examples/example-libev.c b/ext/hiredis-0.14.1/examples/example-libev.c new file mode 100644 index 000000000..cc8b166ec --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-libev.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibevAttach(EV_DEFAULT_ c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + ev_loop(EV_DEFAULT_ 0); + return 0; +} diff --git a/ext/hiredis-0.14.1/examples/example-libevent.c b/ext/hiredis-0.14.1/examples/example-libevent.c new file mode 100644 index 000000000..d333c22b7 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-libevent.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + struct event_base *base = event_base_new(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibeventAttach(c,base); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + event_base_dispatch(base); + return 0; +} diff --git a/ext/hiredis-0.14.1/examples/example-libuv.c b/ext/hiredis-0.14.1/examples/example-libuv.c new file mode 100644 index 000000000..a5462d410 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-libuv.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + uv_loop_t* loop = uv_default_loop(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibuvAttach(c,loop); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + uv_run(loop, UV_RUN_DEFAULT); + return 0; +} diff --git a/ext/hiredis-0.14.1/examples/example-macosx.c b/ext/hiredis-0.14.1/examples/example-macosx.c new file mode 100644 index 000000000..bc84ed5ba --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-macosx.c @@ -0,0 +1,66 @@ +// +// Created by Дмитрий Бахвалов on 13.07.15. +// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. +// + +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + CFRunLoopStop(CFRunLoopGetCurrent()); + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + CFRunLoopRef loop = CFRunLoopGetCurrent(); + if( !loop ) { + printf("Error: Cannot get current run loop\n"); + return 1; + } + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisMacOSAttach(c, loop); + + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + + CFRunLoopRun(); + + return 0; +} + diff --git a/ext/hiredis-0.14.1/examples/example-qt.cpp b/ext/hiredis-0.14.1/examples/example-qt.cpp new file mode 100644 index 000000000..f524c3f3d --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-qt.cpp @@ -0,0 +1,46 @@ +#include +using namespace std; + +#include +#include + +#include "example-qt.h" + +void getCallback(redisAsyncContext *, void * r, void * privdata) { + + redisReply * reply = static_cast(r); + ExampleQt * ex = static_cast(privdata); + if (reply == nullptr || ex == nullptr) return; + + cout << "key: " << reply->str << endl; + + ex->finish(); +} + +void ExampleQt::run() { + + m_ctx = redisAsyncConnect("localhost", 6379); + + if (m_ctx->err) { + cerr << "Error: " << m_ctx->errstr << endl; + redisAsyncFree(m_ctx); + emit finished(); + } + + m_adapter.setContext(m_ctx); + + redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value); + redisAsyncCommand(m_ctx, getCallback, this, "GET key"); +} + +int main (int argc, char **argv) { + + QCoreApplication app(argc, argv); + + ExampleQt example(argv[argc-1]); + + QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit())); + QTimer::singleShot(0, &example, SLOT(run())); + + return app.exec(); +} diff --git a/ext/hiredis-0.14.1/examples/example-qt.h b/ext/hiredis-0.14.1/examples/example-qt.h new file mode 100644 index 000000000..374f47666 --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example-qt.h @@ -0,0 +1,32 @@ +#ifndef __HIREDIS_EXAMPLE_QT_H +#define __HIREDIS_EXAMPLE_QT_H + +#include + +class ExampleQt : public QObject { + + Q_OBJECT + + public: + ExampleQt(const char * value, QObject * parent = 0) + : QObject(parent), m_value(value) {} + + signals: + void finished(); + + public slots: + void run(); + + private: + void finish() { emit finished(); } + + private: + const char * m_value; + redisAsyncContext * m_ctx; + RedisQtAdapter m_adapter; + + friend + void getCallback(redisAsyncContext *, void *, void *); +}; + +#endif /* !__HIREDIS_EXAMPLE_QT_H */ diff --git a/ext/hiredis-0.14.1/examples/example.c b/ext/hiredis-0.14.1/examples/example.c new file mode 100644 index 000000000..4d494c55a --- /dev/null +++ b/ext/hiredis-0.14.1/examples/example.c @@ -0,0 +1,78 @@ +#include +#include +#include + +#include + +int main(int argc, char **argv) { + unsigned int j; + redisContext *c; + redisReply *reply; + const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = (argc > 2) ? atoi(argv[2]) : 6379; + + struct timeval timeout = { 1, 500000 }; // 1.5 seconds + c = redisConnectWithTimeout(hostname, port, timeout); + if (c == NULL || c->err) { + if (c) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + } else { + printf("Connection error: can't allocate redis context\n"); + } + exit(1); + } + + /* PING server */ + reply = redisCommand(c,"PING"); + printf("PING: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key */ + reply = redisCommand(c,"SET %s %s", "foo", "hello world"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key using binary safe API */ + reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); + printf("SET (binary API): %s\n", reply->str); + freeReplyObject(reply); + + /* Try a GET and two INCR */ + reply = redisCommand(c,"GET foo"); + printf("GET foo: %s\n", reply->str); + freeReplyObject(reply); + + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + /* again ... */ + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + + /* Create a list of numbers, from 0 to 9 */ + reply = redisCommand(c,"DEL mylist"); + freeReplyObject(reply); + for (j = 0; j < 10; j++) { + char buf[64]; + + snprintf(buf,64,"%u",j); + reply = redisCommand(c,"LPUSH mylist element-%s", buf); + freeReplyObject(reply); + } + + /* Let's check what we have inside the list */ + reply = redisCommand(c,"LRANGE mylist 0 -1"); + if (reply->type == REDIS_REPLY_ARRAY) { + for (j = 0; j < reply->elements; j++) { + printf("%u) %s\n", j, reply->element[j]->str); + } + } + freeReplyObject(reply); + + /* Disconnects and frees the context */ + redisFree(c); + + return 0; +} diff --git a/ext/hiredis-0.14.1/fmacros.h b/ext/hiredis-0.14.1/fmacros.h new file mode 100644 index 000000000..3227faafd --- /dev/null +++ b/ext/hiredis-0.14.1/fmacros.h @@ -0,0 +1,12 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#define _XOPEN_SOURCE 600 +#define _POSIX_C_SOURCE 200112L + +#if defined(__APPLE__) && defined(__MACH__) +/* Enable TCP_KEEPALIVE */ +#define _DARWIN_C_SOURCE +#endif + +#endif diff --git a/ext/hiredis-0.14.1/hiredis.c b/ext/hiredis-0.14.1/hiredis.c new file mode 100644 index 000000000..98f43c993 --- /dev/null +++ b/ext/hiredis-0.14.1/hiredis.c @@ -0,0 +1,1006 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" +#include "sds.h" + +static redisReply *createReplyObject(int type); +static void *createStringObject(const redisReadTask *task, char *str, size_t len); +static void *createArrayObject(const redisReadTask *task, int elements); +static void *createIntegerObject(const redisReadTask *task, long long value); +static void *createNilObject(const redisReadTask *task); + +/* Default set of functions to build the reply. Keep in mind that such a + * function returning NULL is interpreted as OOM. */ +static redisReplyObjectFunctions defaultFunctions = { + createStringObject, + createArrayObject, + createIntegerObject, + createNilObject, + freeReplyObject +}; + +/* Create a reply object */ +static redisReply *createReplyObject(int type) { + redisReply *r = calloc(1,sizeof(*r)); + + if (r == NULL) + return NULL; + + r->type = type; + return r; +} + +/* Free a reply object */ +void freeReplyObject(void *reply) { + redisReply *r = reply; + size_t j; + + if (r == NULL) + return; + + switch(r->type) { + case REDIS_REPLY_INTEGER: + break; /* Nothing to free */ + case REDIS_REPLY_ARRAY: + if (r->element != NULL) { + for (j = 0; j < r->elements; j++) + freeReplyObject(r->element[j]); + free(r->element); + } + break; + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_STRING: + free(r->str); + break; + } + free(r); +} + +static void *createStringObject(const redisReadTask *task, char *str, size_t len) { + redisReply *r, *parent; + char *buf; + + r = createReplyObject(task->type); + if (r == NULL) + return NULL; + + buf = malloc(len+1); + if (buf == NULL) { + freeReplyObject(r); + return NULL; + } + + assert(task->type == REDIS_REPLY_ERROR || + task->type == REDIS_REPLY_STATUS || + task->type == REDIS_REPLY_STRING); + + /* Copy string value */ + memcpy(buf,str,len); + buf[len] = '\0'; + r->str = buf; + r->len = len; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createArrayObject(const redisReadTask *task, int elements) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_ARRAY); + if (r == NULL) + return NULL; + + if (elements > 0) { + r->element = calloc(elements,sizeof(redisReply*)); + if (r->element == NULL) { + freeReplyObject(r); + return NULL; + } + } + + r->elements = elements; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createIntegerObject(const redisReadTask *task, long long value) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_INTEGER); + if (r == NULL) + return NULL; + + r->integer = value; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createNilObject(const redisReadTask *task) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_NIL); + if (r == NULL) + return NULL; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +/* Return the number of digits of 'v' when converted to string in radix 10. + * Implementation borrowed from link in redis/src/util.c:string2ll(). */ +static uint32_t countDigits(uint64_t v) { + uint32_t result = 1; + for (;;) { + if (v < 10) return result; + if (v < 100) return result + 1; + if (v < 1000) return result + 2; + if (v < 10000) return result + 3; + v /= 10000U; + result += 4; + } +} + +/* Helper that calculates the bulk length given a certain string length. */ +static size_t bulklen(size_t len) { + return 1+countDigits(len)+2+len+2; +} + +int redisvFormatCommand(char **target, const char *format, va_list ap) { + const char *c = format; + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + sds curarg, newarg; /* current argument */ + int touched = 0; /* was the current argument touched? */ + char **curargv = NULL, **newargv = NULL; + int argc = 0; + int totlen = 0; + int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ + int j; + + /* Abort if there is not target to set */ + if (target == NULL) + return -1; + + /* Build the command string accordingly to protocol */ + curarg = sdsempty(); + if (curarg == NULL) + return -1; + + while(*c != '\0') { + if (*c != '%' || c[1] == '\0') { + if (*c == ' ') { + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + + /* curarg is put in argv so it can be overwritten. */ + curarg = sdsempty(); + if (curarg == NULL) goto memory_err; + touched = 0; + } + } else { + newarg = sdscatlen(curarg,c,1); + if (newarg == NULL) goto memory_err; + curarg = newarg; + touched = 1; + } + } else { + char *arg; + size_t size; + + /* Set newarg so it can be checked even if it is not touched. */ + newarg = curarg; + + switch(c[1]) { + case 's': + arg = va_arg(ap,char*); + size = strlen(arg); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case 'b': + arg = va_arg(ap,char*); + size = va_arg(ap,size_t); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case '%': + newarg = sdscat(curarg,"%"); + break; + default: + /* Try to detect printf format */ + { + static const char intfmts[] = "diouxX"; + static const char flags[] = "#0-+ "; + char _format[16]; + const char *_p = c+1; + size_t _l = 0; + va_list _cpy; + + /* Flags */ + while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; + + /* Field width */ + while (*_p != '\0' && isdigit(*_p)) _p++; + + /* Precision */ + if (*_p == '.') { + _p++; + while (*_p != '\0' && isdigit(*_p)) _p++; + } + + /* Copy va_list before consuming with va_arg */ + va_copy(_cpy,ap); + + /* Integer conversion (without modifiers) */ + if (strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); + goto fmt_valid; + } + + /* Double conversion (without modifiers) */ + if (strchr("eEfFgGaA",*_p) != NULL) { + va_arg(ap,double); + goto fmt_valid; + } + + /* Size: char */ + if (_p[0] == 'h' && _p[1] == 'h') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* char gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: short */ + if (_p[0] == 'h') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* short gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long long */ + if (_p[0] == 'l' && _p[1] == 'l') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long long); + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long */ + if (_p[0] == 'l') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long); + goto fmt_valid; + } + goto fmt_invalid; + } + + fmt_invalid: + va_end(_cpy); + goto format_err; + + fmt_valid: + _l = (_p+1)-c; + if (_l < sizeof(_format)-2) { + memcpy(_format,c,_l); + _format[_l] = '\0'; + newarg = sdscatvprintf(curarg,_format,_cpy); + + /* Update current position (note: outer blocks + * increment c twice so compensate here) */ + c = _p-1; + } + + va_end(_cpy); + break; + } + } + + if (newarg == NULL) goto memory_err; + curarg = newarg; + + touched = 1; + c++; + } + c++; + } + + /* Add the last argument if needed */ + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + } else { + sdsfree(curarg); + } + + /* Clear curarg because it was put in curargv or was free'd. */ + curarg = NULL; + + /* Add bytes needed to hold multi bulk count */ + totlen += 1+countDigits(argc)+2; + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) goto memory_err; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); + memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); + pos += sdslen(curargv[j]); + sdsfree(curargv[j]); + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + free(curargv); + *target = cmd; + return totlen; + +format_err: + error_type = -2; + goto cleanup; + +memory_err: + error_type = -1; + goto cleanup; + +cleanup: + if (curargv) { + while(argc--) + sdsfree(curargv[argc]); + free(curargv); + } + + sdsfree(curarg); + free(cmd); + + return error_type; +} + +/* Format a command according to the Redis protocol. This function + * takes a format similar to printf: + * + * %s represents a C null terminated string you want to interpolate + * %b represents a binary safe string + * + * When using %b you need to provide both the pointer to the string + * and the length in bytes as a size_t. Examples: + * + * len = redisFormatCommand(target, "GET %s", mykey); + * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); + */ +int redisFormatCommand(char **target, const char *format, ...) { + va_list ap; + int len; + va_start(ap,format); + len = redisvFormatCommand(target,format,ap); + va_end(ap); + + /* The API says "-1" means bad result, but we now also return "-2" in some + * cases. Force the return value to always be -1. */ + if (len < 0) + len = -1; + + return len; +} + +/* Format a command according to the Redis protocol using an sds string and + * sdscatfmt for the processing of arguments. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, + const size_t *argvlen) +{ + sds cmd; + unsigned long long totlen; + int j; + size_t len; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate our total size */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Use an SDS string for command construction */ + cmd = sdsempty(); + if (cmd == NULL) + return -1; + + /* We already know how much storage we need */ + cmd = sdsMakeRoomFor(cmd, totlen); + if (cmd == NULL) + return -1; + + /* Construct command */ + cmd = sdscatfmt(cmd, "*%i\r\n", argc); + for (j=0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + cmd = sdscatfmt(cmd, "$%u\r\n", len); + cmd = sdscatlen(cmd, argv[j], len); + cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); + } + + assert(sdslen(cmd)==totlen); + + *target = cmd; + return totlen; +} + +void redisFreeSdsCommand(sds cmd) { + sdsfree(cmd); +} + +/* Format a command according to the Redis protocol. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + size_t len; + int totlen, j; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate number of bytes needed for the command */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) + return -1; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",len); + memcpy(cmd+pos,argv[j],len); + pos += len; + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + *target = cmd; + return totlen; +} + +void redisFreeCommand(char *cmd) { + free(cmd); +} + +void __redisSetError(redisContext *c, int type, const char *str) { + size_t len; + + c->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); + memcpy(c->errstr,str,len); + c->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + strerror_r(errno, c->errstr, sizeof(c->errstr)); + } +} + +redisReader *redisReaderCreate(void) { + return redisReaderCreateWithFunctions(&defaultFunctions); +} + +static redisContext *redisContextInit(void) { + redisContext *c; + + c = calloc(1,sizeof(redisContext)); + if (c == NULL) + return NULL; + + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + + if (c->obuf == NULL || c->reader == NULL) { + redisFree(c); + return NULL; + } + + return c; +} + +void redisFree(redisContext *c) { + if (c == NULL) + return; + if (c->fd > 0) + close(c->fd); + sdsfree(c->obuf); + redisReaderFree(c->reader); + free(c->tcp.host); + free(c->tcp.source_addr); + free(c->unix_sock.path); + free(c->timeout); + free(c); +} + +int redisFreeKeepFd(redisContext *c) { + int fd = c->fd; + c->fd = -1; + redisFree(c); + return fd; +} + +int redisReconnect(redisContext *c) { + c->err = 0; + memset(c->errstr, '\0', strlen(c->errstr)); + + if (c->fd > 0) { + close(c->fd); + } + + sdsfree(c->obuf); + redisReaderFree(c->reader); + + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + + if (c->connection_type == REDIS_CONN_TCP) { + return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, + c->timeout, c->tcp.source_addr); + } else if (c->connection_type == REDIS_CONN_UNIX) { + return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); + } else { + /* Something bad happened here and shouldn't have. There isn't + enough information in the context to reconnect. */ + __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); + } + + return REDIS_ERR; +} + +/* Connect to a Redis instance. On error the field error in the returned + * context will be set to the return value of the error function. + * When no set of reply functions is given, the default set will be used. */ +redisContext *redisConnect(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,&tv); + return c; +} + +redisContext *redisConnectNonBlock(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags &= ~REDIS_BLOCK; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags &= ~REDIS_BLOCK; + c->flags |= REDIS_REUSEADDR; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectUnix(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,&tv); + return c; +} + +redisContext *redisConnectUnixNonBlock(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectFd(int fd) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->fd = fd; + c->flags |= REDIS_BLOCK | REDIS_CONNECTED; + return c; +} + +/* Set read/write timeout on a blocking socket. */ +int redisSetTimeout(redisContext *c, const struct timeval tv) { + if (c->flags & REDIS_BLOCK) + return redisContextSetTimeout(c,tv); + return REDIS_ERR; +} + +/* Enable connection KeepAlive. */ +int redisEnableKeepAlive(redisContext *c) { + if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) + return REDIS_ERR; + return REDIS_OK; +} + +/* Use this function to handle a read event on the descriptor. It will try + * and read some bytes from the socket and feed them to the reply parser. + * + * After this function is called, you may use redisContextReadReply to + * see if there is a reply available. */ +int redisBufferRead(redisContext *c) { + char buf[1024*16]; + int nread; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + nread = read(c->fd,buf,sizeof(buf)); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nread == 0) { + __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + return REDIS_ERR; + } else { + if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + } + return REDIS_OK; +} + +/* Write the output buffer to the socket. + * + * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was + * successfully written to the socket. When the buffer is empty after the + * write operation, "done" is set to 1 (if given). + * + * Returns REDIS_ERR if an error occurred trying to write and sets + * c->errstr to hold the appropriate error string. + */ +int redisBufferWrite(redisContext *c, int *done) { + int nwritten; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + if (sdslen(c->obuf) > 0) { + nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); + if (nwritten == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nwritten > 0) { + if (nwritten == (signed)sdslen(c->obuf)) { + sdsfree(c->obuf); + c->obuf = sdsempty(); + } else { + sdsrange(c->obuf,nwritten,-1); + } + } + } + if (done != NULL) *done = (sdslen(c->obuf) == 0); + return REDIS_OK; +} + +/* Internal helper function to try and get a reply from the reader, + * or set an error in the context otherwise. */ +int redisGetReplyFromReader(redisContext *c, void **reply) { + if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisGetReply(redisContext *c, void **reply) { + int wdone = 0; + void *aux = NULL; + + /* Try to read pending replies */ + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + + /* For the blocking context, flush output buffer and read reply */ + if (aux == NULL && c->flags & REDIS_BLOCK) { + /* Write until done */ + do { + if (redisBufferWrite(c,&wdone) == REDIS_ERR) + return REDIS_ERR; + } while (!wdone); + + /* Read until there is a reply */ + do { + if (redisBufferRead(c) == REDIS_ERR) + return REDIS_ERR; + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + } while (aux == NULL); + } + + /* Set reply object */ + if (reply != NULL) *reply = aux; + return REDIS_OK; +} + + +/* Helper function for the redisAppendCommand* family of functions. + * + * Write a formatted command to the output buffer. When this family + * is used, you need to call redisGetReply yourself to retrieve + * the reply (or replies in pub/sub). + */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { + sds newbuf; + + newbuf = sdscatlen(c->obuf,cmd,len); + if (newbuf == NULL) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + c->obuf = newbuf; + return REDIS_OK; +} + +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { + + if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { + char *cmd; + int len; + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + + free(cmd); + return REDIS_OK; +} + +int redisAppendCommand(redisContext *c, const char *format, ...) { + va_list ap; + int ret; + + va_start(ap,format); + ret = redisvAppendCommand(c,format,ap); + va_end(ap); + return ret; +} + +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + sdsfree(cmd); + return REDIS_ERR; + } + + sdsfree(cmd); + return REDIS_OK; +} + +/* Helper function for the redisCommand* family of functions. + * + * Write a formatted command to the output buffer. If the given context is + * blocking, immediately read the reply into the "reply" pointer. When the + * context is non-blocking, the "reply" pointer will not be used and the + * command is simply appended to the write buffer. + * + * Returns the reply when a reply was successfully retrieved. Returns NULL + * otherwise. When NULL is returned in a blocking context, the error field + * in the context will be set. + */ +static void *__redisBlockForReply(redisContext *c) { + void *reply; + + if (c->flags & REDIS_BLOCK) { + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; + } + return NULL; +} + +void *redisvCommand(redisContext *c, const char *format, va_list ap) { + if (redisvAppendCommand(c,format,ap) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} + +void *redisCommand(redisContext *c, const char *format, ...) { + va_list ap; + va_start(ap,format); + void *reply = redisvCommand(c,format,ap); + va_end(ap); + return reply; +} + +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} diff --git a/ext/hiredis-0.14.1/hiredis.h b/ext/hiredis-0.14.1/hiredis.h new file mode 100644 index 000000000..d945bf204 --- /dev/null +++ b/ext/hiredis-0.14.1/hiredis.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HIREDIS_H +#define __HIREDIS_H +#include "read.h" +#include /* for va_list */ +#include /* for struct timeval */ +#include /* uintXX_t, etc */ +#include "sds.h" /* for sds */ +#include "alloc.h" /* for allocation wrappers */ + +#define HIREDIS_MAJOR 0 +#define HIREDIS_MINOR 14 +#define HIREDIS_PATCH 1 +#define HIREDIS_SONAME 0.14 + +/* Connection type can be blocking or non-blocking and is set in the + * least significant bit of the flags field in redisContext. */ +#define REDIS_BLOCK 0x1 + +/* Connection may be disconnected before being free'd. The second bit + * in the flags field is set when the context is connected. */ +#define REDIS_CONNECTED 0x2 + +/* The async API might try to disconnect cleanly and flush the output + * buffer and read all subsequent replies before disconnecting. + * This flag means no new commands can come in and the connection + * should be terminated once all replies have been read. */ +#define REDIS_DISCONNECTING 0x4 + +/* Flag specific to the async API which means that the context should be clean + * up as soon as possible. */ +#define REDIS_FREEING 0x8 + +/* Flag that is set when an async callback is executed. */ +#define REDIS_IN_CALLBACK 0x10 + +/* Flag that is set when the async context has one or more subscriptions. */ +#define REDIS_SUBSCRIBED 0x20 + +/* Flag that is set when monitor mode is active */ +#define REDIS_MONITORING 0x40 + +/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ +#define REDIS_REUSEADDR 0x80 + +#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ + +/* number of times we retry to connect in the case of EADDRNOTAVAIL and + * SO_REUSEADDR is being used. */ +#define REDIS_CONNECT_RETRIES 10 + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reply object returned by redisCommand() */ +typedef struct redisReply { + int type; /* REDIS_REPLY_* */ + long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ + size_t len; /* Length of string */ + char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ + size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ + struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ +} redisReply; + +redisReader *redisReaderCreate(void); + +/* Function to free the reply objects hiredis returns by default. */ +void freeReplyObject(void *reply); + +/* Functions to format a command according to the protocol. */ +int redisvFormatCommand(char **target, const char *format, va_list ap); +int redisFormatCommand(char **target, const char *format, ...); +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); +void redisFreeCommand(char *cmd); +void redisFreeSdsCommand(sds cmd); + +enum redisConnectionType { + REDIS_CONN_TCP, + REDIS_CONN_UNIX +}; + +/* Context for a connection to Redis */ +typedef struct redisContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + int fd; + int flags; + char *obuf; /* Write buffer */ + redisReader *reader; /* Protocol reader */ + + enum redisConnectionType connection_type; + struct timeval *timeout; + + struct { + char *host; + char *source_addr; + int port; + } tcp; + + struct { + char *path; + } unix_sock; + +} redisContext; + +redisContext *redisConnect(const char *ip, int port); +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); +redisContext *redisConnectNonBlock(const char *ip, int port); +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectUnix(const char *path); +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); +redisContext *redisConnectUnixNonBlock(const char *path); +redisContext *redisConnectFd(int fd); + +/** + * Reconnect the given context using the saved information. + * + * This re-uses the exact same connect options as in the initial connection. + * host, ip (or path), timeout and bind address are reused, + * flags are used unmodified from the existing context. + * + * Returns REDIS_OK on successful connect or REDIS_ERR otherwise. + */ +int redisReconnect(redisContext *c); + +int redisSetTimeout(redisContext *c, const struct timeval tv); +int redisEnableKeepAlive(redisContext *c); +void redisFree(redisContext *c); +int redisFreeKeepFd(redisContext *c); +int redisBufferRead(redisContext *c); +int redisBufferWrite(redisContext *c, int *done); + +/* In a blocking context, this function first checks if there are unconsumed + * replies to return and returns one if so. Otherwise, it flushes the output + * buffer to the socket and reads until it has a reply. In a non-blocking + * context, it will return unconsumed replies until there are no more. */ +int redisGetReply(redisContext *c, void **reply); +int redisGetReplyFromReader(redisContext *c, void **reply); + +/* Write a formatted command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); + +/* Write a command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisvAppendCommand(redisContext *c, const char *format, va_list ap); +int redisAppendCommand(redisContext *c, const char *format, ...); +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +/* Issue a command to Redis. In a blocking context, it is identical to calling + * redisAppendCommand, followed by redisGetReply. The function will return + * NULL if there was an error in performing the request, otherwise it will + * return the reply. In a non-blocking context, it is identical to calling + * only redisAppendCommand and will always return NULL. */ +void *redisvCommand(redisContext *c, const char *format, va_list ap); +void *redisCommand(redisContext *c, const char *format, ...); +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-0.14.1/net.c b/ext/hiredis-0.14.1/net.c new file mode 100644 index 000000000..d71bbcd57 --- /dev/null +++ b/ext/hiredis-0.14.1/net.c @@ -0,0 +1,477 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" +#include "sds.h" + +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +static void redisContextCloseFd(redisContext *c) { + if (c && c->fd >= 0) { + close(c->fd); + c->fd = -1; + } +} + +static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { + int errorno = errno; /* snprintf() may change errno */ + char buf[128] = { 0 }; + size_t len = 0; + + if (prefix != NULL) + len = snprintf(buf,sizeof(buf),"%s: ",prefix); + strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); + __redisSetError(c,type,buf); +} + +static int redisSetReuseAddr(redisContext *c) { + int on = 1; + if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int redisCreateSocket(redisContext *c, int type) { + int s; + if ((s = socket(type, SOCK_STREAM, 0)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + c->fd = s; + if (type == AF_INET) { + if (redisSetReuseAddr(c) == REDIS_ERR) { + return REDIS_ERR; + } + } + return REDIS_OK; +} + +static int redisSetBlocking(redisContext *c, int blocking) { + int flags; + + /* Set the socket nonblocking. + * Note that fcntl(2) for F_GETFL and F_SETFL can't be + * interrupted by a signal. */ + if ((flags = fcntl(c->fd, F_GETFL)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (blocking) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(c->fd, F_SETFL, flags) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisKeepAlive(redisContext *c, int interval) { + int val = 1; + int fd = c->fd; + + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval; + +#if defined(__APPLE__) && defined(__MACH__) + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#else +#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval/3; + if (val == 0) val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#endif +#endif + + return REDIS_OK; +} + +static int redisSetTcpNoDelay(redisContext *c) { + int yes = 1; + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) + +static int redisContextTimeoutMsec(redisContext *c, long *result) +{ + const struct timeval *timeout = c->timeout; + long msec = -1; + + /* Only use timeout when not NULL. */ + if (timeout != NULL) { + if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { + *result = msec; + return REDIS_ERR; + } + + msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); + + if (msec < 0 || msec > INT_MAX) { + msec = INT_MAX; + } + } + + *result = msec; + return REDIS_OK; +} + +static int redisContextWaitReady(redisContext *c, long msec) { + struct pollfd wfd[1]; + + wfd[0].fd = c->fd; + wfd[0].events = POLLOUT; + + if (errno == EINPROGRESS) { + int res; + + if ((res = poll(wfd, 1, msec)) == -1) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); + redisContextCloseFd(c); + return REDIS_ERR; + } else if (res == 0) { + errno = ETIMEDOUT; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (redisCheckSocketError(c) != REDIS_OK) + return REDIS_ERR; + + return REDIS_OK; + } + + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; +} + +int redisCheckSocketError(redisContext *c) { + int err = 0; + socklen_t errlen = sizeof(err); + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); + return REDIS_ERR; + } + + if (err) { + errno = err; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisContextSetTimeout(redisContext *c, const struct timeval tv) { + if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); + return REDIS_ERR; + } + if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + int s, rv, n; + char _port[6]; /* strlen("65535"); */ + struct addrinfo hints, *servinfo, *bservinfo, *p, *b; + int blocking = (c->flags & REDIS_BLOCK); + int reuseaddr = (c->flags & REDIS_REUSEADDR); + int reuses = 0; + long timeout_msec = -1; + + servinfo = NULL; + c->connection_type = REDIS_CONN_TCP; + c->tcp.port = port; + + /* We need to take possession of the passed parameters + * to make them reusable for a reconnect. + * We also carefully check we don't free data we already own, + * as in the case of the reconnect method. + * + * This is a bit ugly, but atleast it works and doesn't leak memory. + **/ + if (c->tcp.host != addr) { + free(c->tcp.host); + + c->tcp.host = hi_strdup(addr); + } + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = hi_malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + free(c->timeout); + c->timeout = NULL; + } + + if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { + __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); + goto error; + } + + if (source_addr == NULL) { + free(c->tcp.source_addr); + c->tcp.source_addr = NULL; + } else if (c->tcp.source_addr != source_addr) { + free(c->tcp.source_addr); + c->tcp.source_addr = hi_strdup(source_addr); + } + + snprintf(_port, 6, "%d", port); + memset(&hints,0,sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + /* Try with IPv6 if no IPv4 address was found. We do it in this order since + * in a Redis client you can't afford to test if you have IPv6 connectivity + * as this would add latency to every connect. Otherwise a more sensible + * route could be: Use IPv6 if both addresses are available and there is IPv6 + * connectivity. */ + if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { + hints.ai_family = AF_INET6; + if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); + return REDIS_ERR; + } + } + for (p = servinfo; p != NULL; p = p->ai_next) { +addrretry: + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + continue; + + c->fd = s; + if (redisSetBlocking(c,0) != REDIS_OK) + goto error; + if (c->tcp.source_addr) { + int bound = 0; + /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ + if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + + if (reuseaddr) { + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, + sizeof(n)) < 0) { + freeaddrinfo(bservinfo); + goto error; + } + } + + for (b = bservinfo; b != NULL; b = b->ai_next) { + if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { + bound = 1; + break; + } + } + freeaddrinfo(bservinfo); + if (!bound) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + } + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { + if (errno == EHOSTUNREACH) { + redisContextCloseFd(c); + continue; + } else if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else if (errno == EADDRNOTAVAIL && reuseaddr) { + if (++reuses >= REDIS_CONNECT_RETRIES) { + goto error; + } else { + redisContextCloseFd(c); + goto addrretry; + } + } else { + if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) + goto error; + } + } + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + goto error; + if (redisSetTcpNoDelay(c) != REDIS_OK) + goto error; + + c->flags |= REDIS_CONNECTED; + rv = REDIS_OK; + goto end; + } + if (p == NULL) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + +error: + rv = REDIS_ERR; +end: + if(servinfo) { + freeaddrinfo(servinfo); + } + + return rv; // Need to return REDIS_OK if alright +} + +int redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout) { + return _redisContextConnectTcp(c, addr, port, timeout, NULL); +} + +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + return _redisContextConnectTcp(c, addr, port, timeout, source_addr); +} + +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { + int blocking = (c->flags & REDIS_BLOCK); + struct sockaddr_un sa; + long timeout_msec = -1; + + if (redisCreateSocket(c,AF_UNIX) < 0) + return REDIS_ERR; + if (redisSetBlocking(c,0) != REDIS_OK) + return REDIS_ERR; + + c->connection_type = REDIS_CONN_UNIX; + if (c->unix_sock.path != path) + c->unix_sock.path = hi_strdup(path); + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = hi_malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + free(c->timeout); + c->timeout = NULL; + } + + if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) + return REDIS_ERR; + + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); + if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else { + if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) + return REDIS_ERR; + } + } + + /* Reset socket to be blocking after connect(2). */ + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + return REDIS_ERR; + + c->flags |= REDIS_CONNECTED; + return REDIS_OK; +} diff --git a/ext/hiredis-0.14.1/net.h b/ext/hiredis-0.14.1/net.h new file mode 100644 index 000000000..d9dc36257 --- /dev/null +++ b/ext/hiredis-0.14.1/net.h @@ -0,0 +1,49 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __NET_H +#define __NET_H + +#include "hiredis.h" + +int redisCheckSocketError(redisContext *c); +int redisContextSetTimeout(redisContext *c, const struct timeval tv); +int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr); +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); +int redisKeepAlive(redisContext *c, int interval); + +#endif diff --git a/ext/hiredis-0.14.1/read.c b/ext/hiredis-0.14.1/read.c new file mode 100644 index 000000000..cc2126778 --- /dev/null +++ b/ext/hiredis-0.14.1/read.c @@ -0,0 +1,598 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "fmacros.h" +#include +#include +#ifndef _MSC_VER +#include +#endif +#include +#include +#include +#include + +#include "read.h" +#include "sds.h" + +static void __redisReaderSetError(redisReader *r, int type, const char *str) { + size_t len; + + if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + r->reply = NULL; + } + + /* Clear input buffer on errors. */ + sdsfree(r->buf); + r->buf = NULL; + r->pos = r->len = 0; + + /* Reset task stack. */ + r->ridx = -1; + + /* Set error. */ + r->err = type; + len = strlen(str); + len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); + memcpy(r->errstr,str,len); + r->errstr[len] = '\0'; +} + +static size_t chrtos(char *buf, size_t size, char byte) { + size_t len = 0; + + switch(byte) { + case '\\': + case '"': + len = snprintf(buf,size,"\"\\%c\"",byte); + break; + case '\n': len = snprintf(buf,size,"\"\\n\""); break; + case '\r': len = snprintf(buf,size,"\"\\r\""); break; + case '\t': len = snprintf(buf,size,"\"\\t\""); break; + case '\a': len = snprintf(buf,size,"\"\\a\""); break; + case '\b': len = snprintf(buf,size,"\"\\b\""); break; + default: + if (isprint(byte)) + len = snprintf(buf,size,"\"%c\"",byte); + else + len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); + break; + } + + return len; +} + +static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { + char cbuf[8], sbuf[128]; + + chrtos(cbuf,sizeof(cbuf),byte); + snprintf(sbuf,sizeof(sbuf), + "Protocol error, got %s as reply type byte", cbuf); + __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); +} + +static void __redisReaderSetErrorOOM(redisReader *r) { + __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); +} + +static char *readBytes(redisReader *r, unsigned int bytes) { + char *p; + if (r->len-r->pos >= bytes) { + p = r->buf+r->pos; + r->pos += bytes; + return p; + } + return NULL; +} + +/* Find pointer to \r\n. */ +static char *seekNewline(char *s, size_t len) { + int pos = 0; + int _len = len-1; + + /* Position should be < len-1 because the character at "pos" should be + * followed by a \n. Note that strchr cannot be used because it doesn't + * allow to search a limited length and the buffer that is being searched + * might not have a trailing NULL character. */ + while (pos < _len) { + while(pos < _len && s[pos] != '\r') pos++; + if (pos==_len) { + /* Not found. */ + return NULL; + } else { + if (s[pos+1] == '\n') { + /* Found. */ + return s+pos; + } else { + /* Continue searching. */ + pos++; + } + } + } + return NULL; +} + +/* Convert a string into a long long. Returns REDIS_OK if the string could be + * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value + * will be set to the parsed value when appropriate. + * + * Note that this function demands that the string strictly represents + * a long long: no spaces or other characters before or after the string + * representing the number are accepted, nor zeroes at the start if not + * for the string "0" representing the zero number. + * + * Because of its strictness, it is safe to use this function to check if + * you can convert a string into a long long, and obtain back the string + * from the number without any loss in the string representation. */ +static int string2ll(const char *s, size_t slen, long long *value) { + const char *p = s; + size_t plen = 0; + int negative = 0; + unsigned long long v; + + if (plen == slen) + return REDIS_ERR; + + /* Special case: first and only digit is 0. */ + if (slen == 1 && p[0] == '0') { + if (value != NULL) *value = 0; + return REDIS_OK; + } + + if (p[0] == '-') { + negative = 1; + p++; plen++; + + /* Abort on only a negative sign. */ + if (plen == slen) + return REDIS_ERR; + } + + /* First digit should be 1-9, otherwise the string should just be 0. */ + if (p[0] >= '1' && p[0] <= '9') { + v = p[0]-'0'; + p++; plen++; + } else if (p[0] == '0' && slen == 1) { + *value = 0; + return REDIS_OK; + } else { + return REDIS_ERR; + } + + while (plen < slen && p[0] >= '0' && p[0] <= '9') { + if (v > (ULLONG_MAX / 10)) /* Overflow. */ + return REDIS_ERR; + v *= 10; + + if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ + return REDIS_ERR; + v += p[0]-'0'; + + p++; plen++; + } + + /* Return if not all bytes were used. */ + if (plen < slen) + return REDIS_ERR; + + if (negative) { + if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ + return REDIS_ERR; + if (value != NULL) *value = -v; + } else { + if (v > LLONG_MAX) /* Overflow. */ + return REDIS_ERR; + if (value != NULL) *value = v; + } + return REDIS_OK; +} + +static char *readLine(redisReader *r, int *_len) { + char *p, *s; + int len; + + p = r->buf+r->pos; + s = seekNewline(p,(r->len-r->pos)); + if (s != NULL) { + len = s-(r->buf+r->pos); + r->pos += len+2; /* skip \r\n */ + if (_len) *_len = len; + return p; + } + return NULL; +} + +static void moveToNextTask(redisReader *r) { + redisReadTask *cur, *prv; + while (r->ridx >= 0) { + /* Return a.s.a.p. when the stack is now empty. */ + if (r->ridx == 0) { + r->ridx--; + return; + } + + cur = &(r->rstack[r->ridx]); + prv = &(r->rstack[r->ridx-1]); + assert(prv->type == REDIS_REPLY_ARRAY); + if (cur->idx == prv->elements-1) { + r->ridx--; + } else { + /* Reset the type because the next item can be anything */ + assert(cur->idx < prv->elements); + cur->type = -1; + cur->elements = -1; + cur->idx++; + return; + } + } +} + +static int processLineItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + int len; + + if ((p = readLine(r,&len)) != NULL) { + if (cur->type == REDIS_REPLY_INTEGER) { + if (r->fn && r->fn->createInteger) { + long long v; + if (string2ll(p, len, &v) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad integer value"); + return REDIS_ERR; + } + obj = r->fn->createInteger(cur,v); + } else { + obj = (void*)REDIS_REPLY_INTEGER; + } + } else { + /* Type will be error or status. */ + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,p,len); + else + obj = (void*)(size_t)(cur->type); + } + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj = NULL; + char *p, *s; + long long len; + unsigned long bytelen; + int success = 0; + + p = r->buf+r->pos; + s = seekNewline(p,r->len-r->pos); + if (s != NULL) { + p = r->buf+r->pos; + bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ + + if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad bulk string length"); + return REDIS_ERR; + } + + if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bulk string length out of range"); + return REDIS_ERR; + } + + if (len == -1) { + /* The nil object can always be created. */ + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + success = 1; + } else { + /* Only continue when the buffer contains the entire bulk item. */ + bytelen += len+2; /* include \r\n */ + if (r->pos+bytelen <= r->len) { + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,s+2,len); + else + obj = (void*)REDIS_REPLY_STRING; + success = 1; + } + } + + /* Proceed when obj was created. */ + if (success) { + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->pos += bytelen; + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + } + + return REDIS_ERR; +} + +static int processMultiBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + long long elements; + int root = 0, len; + + /* Set error for nested multi bulks with depth > 7 */ + if (r->ridx == 8) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "No support for nested multi bulk replies with depth > 7"); + return REDIS_ERR; + } + + if ((p = readLine(r,&len)) != NULL) { + if (string2ll(p, len, &elements) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad multi-bulk length"); + return REDIS_ERR; + } + + root = (r->ridx == 0); + + if (elements < -1 || elements > INT_MAX) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Multi-bulk length out of range"); + return REDIS_ERR; + } + + if (elements == -1) { + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + moveToNextTask(r); + } else { + if (r->fn && r->fn->createArray) + obj = r->fn->createArray(cur,elements); + else + obj = (void*)REDIS_REPLY_ARRAY; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Modify task stack when there are more than 0 elements. */ + if (elements > 0) { + cur->elements = elements; + cur->obj = obj; + r->ridx++; + r->rstack[r->ridx].type = -1; + r->rstack[r->ridx].elements = -1; + r->rstack[r->ridx].idx = 0; + r->rstack[r->ridx].obj = NULL; + r->rstack[r->ridx].parent = cur; + r->rstack[r->ridx].privdata = r->privdata; + } else { + moveToNextTask(r); + } + } + + /* Set reply if this is the root object. */ + if (root) r->reply = obj; + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + char *p; + + /* check if we need to read type */ + if (cur->type < 0) { + if ((p = readBytes(r,1)) != NULL) { + switch (p[0]) { + case '-': + cur->type = REDIS_REPLY_ERROR; + break; + case '+': + cur->type = REDIS_REPLY_STATUS; + break; + case ':': + cur->type = REDIS_REPLY_INTEGER; + break; + case '$': + cur->type = REDIS_REPLY_STRING; + break; + case '*': + cur->type = REDIS_REPLY_ARRAY; + break; + default: + __redisReaderSetErrorProtocolByte(r,*p); + return REDIS_ERR; + } + } else { + /* could not consume 1 byte */ + return REDIS_ERR; + } + } + + /* process typed item */ + switch(cur->type) { + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_INTEGER: + return processLineItem(r); + case REDIS_REPLY_STRING: + return processBulkItem(r); + case REDIS_REPLY_ARRAY: + return processMultiBulkItem(r); + default: + assert(NULL); + return REDIS_ERR; /* Avoid warning. */ + } +} + +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { + redisReader *r; + + r = calloc(1,sizeof(redisReader)); + if (r == NULL) + return NULL; + + r->fn = fn; + r->buf = sdsempty(); + r->maxbuf = REDIS_READER_MAX_BUF; + if (r->buf == NULL) { + free(r); + return NULL; + } + + r->ridx = -1; + return r; +} + +void redisReaderFree(redisReader *r) { + if (r == NULL) + return; + if (r->reply != NULL && r->fn && r->fn->freeObject) + r->fn->freeObject(r->reply); + sdsfree(r->buf); + free(r); +} + +int redisReaderFeed(redisReader *r, const char *buf, size_t len) { + sds newbuf; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* Copy the provided buffer. */ + if (buf != NULL && len >= 1) { + /* Destroy internal buffer when it is empty and is quite large. */ + if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { + sdsfree(r->buf); + r->buf = sdsempty(); + r->pos = 0; + + /* r->buf should not be NULL since we just free'd a larger one. */ + assert(r->buf != NULL); + } + + newbuf = sdscatlen(r->buf,buf,len); + if (newbuf == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->buf = newbuf; + r->len = sdslen(r->buf); + } + + return REDIS_OK; +} + +int redisReaderGetReply(redisReader *r, void **reply) { + /* Default target pointer to NULL. */ + if (reply != NULL) + *reply = NULL; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* When the buffer is empty, there will never be a reply. */ + if (r->len == 0) + return REDIS_OK; + + /* Set first item to process when the stack is empty. */ + if (r->ridx == -1) { + r->rstack[0].type = -1; + r->rstack[0].elements = -1; + r->rstack[0].idx = -1; + r->rstack[0].obj = NULL; + r->rstack[0].parent = NULL; + r->rstack[0].privdata = r->privdata; + r->ridx = 0; + } + + /* Process items in reply. */ + while (r->ridx >= 0) + if (processItem(r) != REDIS_OK) + break; + + /* Return ASAP when an error occurred. */ + if (r->err) + return REDIS_ERR; + + /* Discard part of the buffer when we've consumed at least 1k, to avoid + * doing unnecessary calls to memmove() in sds.c. */ + if (r->pos >= 1024) { + sdsrange(r->buf,r->pos,-1); + r->pos = 0; + r->len = sdslen(r->buf); + } + + /* Emit a reply when there is one. */ + if (r->ridx == -1) { + if (reply != NULL) + *reply = r->reply; + r->reply = NULL; + } + return REDIS_OK; +} diff --git a/ext/hiredis-0.14.1/read.h b/ext/hiredis-0.14.1/read.h new file mode 100644 index 000000000..2988aa453 --- /dev/null +++ b/ext/hiredis-0.14.1/read.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef __HIREDIS_READ_H +#define __HIREDIS_READ_H +#include /* for size_t */ + +#define REDIS_ERR -1 +#define REDIS_OK 0 + +/* When an error occurs, the err flag in a context is set to hold the type of + * error that occurred. REDIS_ERR_IO means there was an I/O error and you + * should use the "errno" variable to find out what is wrong. + * For other values, the "errstr" field will hold a description. */ +#define REDIS_ERR_IO 1 /* Error in read or write */ +#define REDIS_ERR_EOF 3 /* End of file */ +#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ +#define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_OTHER 2 /* Everything else... */ + +#define REDIS_REPLY_STRING 1 +#define REDIS_REPLY_ARRAY 2 +#define REDIS_REPLY_INTEGER 3 +#define REDIS_REPLY_NIL 4 +#define REDIS_REPLY_STATUS 5 +#define REDIS_REPLY_ERROR 6 + +#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct redisReadTask { + int type; + int elements; /* number of elements in multibulk container */ + int idx; /* index in parent (array) object */ + void *obj; /* holds user-generated value for a read task */ + struct redisReadTask *parent; /* parent task */ + void *privdata; /* user-settable arbitrary field */ +} redisReadTask; + +typedef struct redisReplyObjectFunctions { + void *(*createString)(const redisReadTask*, char*, size_t); + void *(*createArray)(const redisReadTask*, int); + void *(*createInteger)(const redisReadTask*, long long); + void *(*createNil)(const redisReadTask*); + void (*freeObject)(void*); +} redisReplyObjectFunctions; + +typedef struct redisReader { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + + char *buf; /* Read buffer */ + size_t pos; /* Buffer cursor */ + size_t len; /* Buffer length */ + size_t maxbuf; /* Max length of unused buffer */ + + redisReadTask rstack[9]; + int ridx; /* Index of current read task */ + void *reply; /* Temporary reply pointer */ + + redisReplyObjectFunctions *fn; + void *privdata; +} redisReader; + +/* Public API for the protocol parser. */ +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); +void redisReaderFree(redisReader *r); +int redisReaderFeed(redisReader *r, const char *buf, size_t len); +int redisReaderGetReply(redisReader *r, void **reply); + +#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-0.14.1/sds.c b/ext/hiredis-0.14.1/sds.c new file mode 100644 index 000000000..923ffd82f --- /dev/null +++ b/ext/hiredis-0.14.1/sds.c @@ -0,0 +1,1272 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include "sds.h" +#include "sdsalloc.h" + +static inline int sdsHdrSize(char type) { + switch(type&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return sizeof(struct sdshdr5); + case SDS_TYPE_8: + return sizeof(struct sdshdr8); + case SDS_TYPE_16: + return sizeof(struct sdshdr16); + case SDS_TYPE_32: + return sizeof(struct sdshdr32); + case SDS_TYPE_64: + return sizeof(struct sdshdr64); + } + return 0; +} + +static inline char sdsReqType(size_t string_size) { + if (string_size < 32) + return SDS_TYPE_5; + if (string_size < 0xff) + return SDS_TYPE_8; + if (string_size < 0xffff) + return SDS_TYPE_16; + if (string_size < 0xffffffff) + return SDS_TYPE_32; + return SDS_TYPE_64; +} + +/* Create a new sds string with the content specified by the 'init' pointer + * and 'initlen'. + * If NULL is used for 'init' the string is initialized with zero bytes. + * + * The string is always null-termined (all the sds strings are, always) so + * even if you create an sds string with: + * + * mystring = sdsnewlen("abc",3); + * + * You can print the string with printf() as there is an implicit \0 at the + * end of the string. However the string is binary safe and can contain + * \0 characters in the middle, as the length is stored in the sds header. */ +sds sdsnewlen(const void *init, size_t initlen) { + void *sh; + sds s; + char type = sdsReqType(initlen); + /* Empty strings are usually created in order to append. Use type 8 + * since type 5 is not good at this. */ + if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; + int hdrlen = sdsHdrSize(type); + unsigned char *fp; /* flags pointer. */ + + sh = s_malloc(hdrlen+initlen+1); + if (sh == NULL) return NULL; + if (!init) + memset(sh, 0, hdrlen+initlen+1); + s = (char*)sh+hdrlen; + fp = ((unsigned char*)s)-1; + switch(type) { + case SDS_TYPE_5: { + *fp = type | (initlen << SDS_TYPE_BITS); + break; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + } + if (initlen && init) + memcpy(s, init, initlen); + s[initlen] = '\0'; + return s; +} + +/* Create an empty (zero length) sds string. Even in this case the string + * always has an implicit null term. */ +sds sdsempty(void) { + return sdsnewlen("",0); +} + +/* Create a new sds string starting from a null terminated C string. */ +sds sdsnew(const char *init) { + size_t initlen = (init == NULL) ? 0 : strlen(init); + return sdsnewlen(init, initlen); +} + +/* Duplicate an sds string. */ +sds sdsdup(const sds s) { + return sdsnewlen(s, sdslen(s)); +} + +/* Free an sds string. No operation is performed if 's' is NULL. */ +void sdsfree(sds s) { + if (s == NULL) return; + s_free((char*)s-sdsHdrSize(s[-1])); +} + +/* Set the sds string length to the length as obtained with strlen(), so + * considering as content only up to the first null term character. + * + * This function is useful when the sds string is hacked manually in some + * way, like in the following example: + * + * s = sdsnew("foobar"); + * s[2] = '\0'; + * sdsupdatelen(s); + * printf("%d\n", sdslen(s)); + * + * The output will be "2", but if we comment out the call to sdsupdatelen() + * the output will be "6" as the string was modified but the logical length + * remains 6 bytes. */ +void sdsupdatelen(sds s) { + int reallen = strlen(s); + sdssetlen(s, reallen); +} + +/* Modify an sds string in-place to make it empty (zero length). + * However all the existing buffer is not discarded but set as free space + * so that next append operations will not require allocations up to the + * number of bytes previously available. */ +void sdsclear(sds s) { + sdssetlen(s, 0); + s[0] = '\0'; +} + +/* Enlarge the free space at the end of the sds string so that the caller + * is sure that after calling this function can overwrite up to addlen + * bytes after the end of the string, plus one more byte for nul term. + * + * Note: this does not change the *length* of the sds string as returned + * by sdslen(), but only the free buffer space we have. */ +sds sdsMakeRoomFor(sds s, size_t addlen) { + void *sh, *newsh; + size_t avail = sdsavail(s); + size_t len, newlen; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + + /* Return ASAP if there is enough space left. */ + if (avail >= addlen) return s; + + len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); + newlen = (len+addlen); + if (newlen < SDS_MAX_PREALLOC) + newlen *= 2; + else + newlen += SDS_MAX_PREALLOC; + + type = sdsReqType(newlen); + + /* Don't use type 5: the user is appending to the string and type 5 is + * not able to remember empty space, so sdsMakeRoomFor() must be called + * at every appending operation. */ + if (type == SDS_TYPE_5) type = SDS_TYPE_8; + + hdrlen = sdsHdrSize(type); + if (oldtype==type) { + newsh = s_realloc(sh, hdrlen+newlen+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+hdrlen; + } else { + /* Since the header size changes, need to move the string forward, + * and can't use realloc */ + newsh = s_malloc(hdrlen+newlen+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len+1); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + sdssetlen(s, len); + } + sdssetalloc(s, newlen); + return s; +} + +/* Reallocate the sds string so that it has no free space at the end. The + * contained string remains not altered, but next concatenation operations + * will require a reallocation. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdsRemoveFreeSpace(sds s) { + void *sh, *newsh; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + size_t len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); + + type = sdsReqType(len); + hdrlen = sdsHdrSize(type); + if (oldtype==type) { + newsh = s_realloc(sh, hdrlen+len+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+hdrlen; + } else { + newsh = s_malloc(hdrlen+len+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len+1); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + sdssetlen(s, len); + } + sdssetalloc(s, len); + return s; +} + +/* Return the total size of the allocation of the specifed sds string, + * including: + * 1) The sds header before the pointer. + * 2) The string. + * 3) The free buffer at the end if any. + * 4) The implicit null term. + */ +size_t sdsAllocSize(sds s) { + size_t alloc = sdsalloc(s); + return sdsHdrSize(s[-1])+alloc+1; +} + +/* Return the pointer of the actual SDS allocation (normally SDS strings + * are referenced by the start of the string buffer). */ +void *sdsAllocPtr(sds s) { + return (void*) (s-sdsHdrSize(s[-1])); +} + +/* Increment the sds length and decrements the left free space at the + * end of the string according to 'incr'. Also set the null term + * in the new end of the string. + * + * This function is used in order to fix the string length after the + * user calls sdsMakeRoomFor(), writes something after the end of + * the current string, and finally needs to set the new length. + * + * Note: it is possible to use a negative increment in order to + * right-trim the string. + * + * Usage example: + * + * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the + * following schema, to cat bytes coming from the kernel to the end of an + * sds string without copying into an intermediate buffer: + * + * oldlen = sdslen(s); + * s = sdsMakeRoomFor(s, BUFFER_SIZE); + * nread = read(fd, s+oldlen, BUFFER_SIZE); + * ... check for nread <= 0 and handle it ... + * sdsIncrLen(s, nread); + */ +void sdsIncrLen(sds s, int incr) { + unsigned char flags = s[-1]; + size_t len; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char oldlen = SDS_TYPE_5_LEN(flags); + assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); + *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); + len = oldlen+incr; + break; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); + len = (sh->len += incr); + break; + } + default: len = 0; /* Just to avoid compilation warnings. */ + } + s[len] = '\0'; +} + +/* Grow the sds to have the specified length. Bytes that were not part of + * the original length of the sds will be set to zero. + * + * if the specified length is smaller than the current length, no operation + * is performed. */ +sds sdsgrowzero(sds s, size_t len) { + size_t curlen = sdslen(s); + + if (len <= curlen) return s; + s = sdsMakeRoomFor(s,len-curlen); + if (s == NULL) return NULL; + + /* Make sure added region doesn't contain garbage */ + memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ + sdssetlen(s, len); + return s; +} + +/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the + * end of the specified sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatlen(sds s, const void *t, size_t len) { + size_t curlen = sdslen(s); + + s = sdsMakeRoomFor(s,len); + if (s == NULL) return NULL; + memcpy(s+curlen, t, len); + sdssetlen(s, curlen+len); + s[curlen+len] = '\0'; + return s; +} + +/* Append the specified null termianted C string to the sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscat(sds s, const char *t) { + return sdscatlen(s, t, strlen(t)); +} + +/* Append the specified sds 't' to the existing sds 's'. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); +} + +/* Destructively modify the sds string 's' to hold the specified binary + * safe string pointed by 't' of length 'len' bytes. */ +sds sdscpylen(sds s, const char *t, size_t len) { + if (sdsalloc(s) < len) { + s = sdsMakeRoomFor(s,len-sdslen(s)); + if (s == NULL) return NULL; + } + memcpy(s, t, len); + s[len] = '\0'; + sdssetlen(s, len); + return s; +} + +/* Like sdscpylen() but 't' must be a null-termined string so that the length + * of the string is obtained with strlen(). */ +sds sdscpy(sds s, const char *t) { + return sdscpylen(s, t, strlen(t)); +} + +/* Helper for sdscatlonglong() doing the actual number -> string + * conversion. 's' must point to a string with room for at least + * SDS_LLSTR_SIZE bytes. + * + * The function returns the length of the null-terminated string + * representation stored at 's'. */ +#define SDS_LLSTR_SIZE 21 +int sdsll2str(char *s, long long value) { + char *p, aux; + unsigned long long v; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + v = (value < 0) ? -value : value; + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + if (value < 0) *p++ = '-'; + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Identical sdsll2str(), but for unsigned long long type. */ +int sdsull2str(char *s, unsigned long long v) { + char *p, aux; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Create an sds string from a long long value. It is much faster than: + * + * sdscatprintf(sdsempty(),"%lld\n", value); + */ +sds sdsfromlonglong(long long value) { + char buf[SDS_LLSTR_SIZE]; + int len = sdsll2str(buf,value); + + return sdsnewlen(buf,len); +} + +/* Like sdscatprintf() but gets va_list instead of being variadic. */ +sds sdscatvprintf(sds s, const char *fmt, va_list ap) { + va_list cpy; + char staticbuf[1024], *buf = staticbuf, *t; + size_t buflen = strlen(fmt)*2; + + /* We try to start using a static buffer for speed. + * If not possible we revert to heap allocation. */ + if (buflen > sizeof(staticbuf)) { + buf = s_malloc(buflen); + if (buf == NULL) return NULL; + } else { + buflen = sizeof(staticbuf); + } + + /* Try with buffers two times bigger every time we fail to + * fit the string in the current buffer size. */ + while(1) { + buf[buflen-2] = '\0'; + va_copy(cpy,ap); + vsnprintf(buf, buflen, fmt, cpy); + va_end(cpy); + if (buf[buflen-2] != '\0') { + if (buf != staticbuf) s_free(buf); + buflen *= 2; + buf = s_malloc(buflen); + if (buf == NULL) return NULL; + continue; + } + break; + } + + /* Finally concat the obtained string to the SDS string and return it. */ + t = sdscat(s, buf); + if (buf != staticbuf) s_free(buf); + return t; +} + +/* Append to the sds string 's' a string obtained using printf-alike format + * specifier. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("Sum is: "); + * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). + * + * Often you need to create a string from scratch with the printf-alike + * format. When this is the need, just use sdsempty() as the target string: + * + * s = sdscatprintf(sdsempty(), "... your format ...", args); + */ +sds sdscatprintf(sds s, const char *fmt, ...) { + va_list ap; + char *t; + va_start(ap, fmt); + t = sdscatvprintf(s,fmt,ap); + va_end(ap); + return t; +} + +/* This function is similar to sdscatprintf, but much faster as it does + * not rely on sprintf() family functions implemented by the libc that + * are often very slow. Moreover directly handling the sds string as + * new data is concatenated provides a performance improvement. + * + * However this function only handles an incompatible subset of printf-alike + * format specifiers: + * + * %s - C String + * %S - SDS string + * %i - signed int + * %I - 64 bit signed integer (long long, int64_t) + * %u - unsigned int + * %U - 64 bit unsigned integer (unsigned long long, uint64_t) + * %% - Verbatim "%" character. + */ +sds sdscatfmt(sds s, char const *fmt, ...) { + const char *f = fmt; + int i; + va_list ap; + + va_start(ap,fmt); + i = sdslen(s); /* Position of the next byte to write to dest str. */ + while(*f) { + char next, *str; + size_t l; + long long num; + unsigned long long unum; + + /* Make sure there is always space for at least 1 char. */ + if (sdsavail(s)==0) { + s = sdsMakeRoomFor(s,1); + } + + switch(*f) { + case '%': + next = *(f+1); + f++; + switch(next) { + case 's': + case 'S': + str = va_arg(ap,char*); + l = (next == 's') ? strlen(str) : sdslen(str); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,str,l); + sdsinclen(s,l); + i += l; + break; + case 'i': + case 'I': + if (next == 'i') + num = va_arg(ap,int); + else + num = va_arg(ap,long long); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsll2str(buf,num); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,buf,l); + sdsinclen(s,l); + i += l; + } + break; + case 'u': + case 'U': + if (next == 'u') + unum = va_arg(ap,unsigned int); + else + unum = va_arg(ap,unsigned long long); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsull2str(buf,unum); + if (sdsavail(s) < l) { + s = sdsMakeRoomFor(s,l); + } + memcpy(s+i,buf,l); + sdsinclen(s,l); + i += l; + } + break; + default: /* Handle %% and generally %. */ + s[i++] = next; + sdsinclen(s,1); + break; + } + break; + default: + s[i++] = *f; + sdsinclen(s,1); + break; + } + f++; + } + va_end(ap); + + /* Add null-term */ + s[i] = '\0'; + return s; +} + +/* Remove the part of the string from left and from right composed just of + * contiguous characters found in 'cset', that is a null terminted C string. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); + * s = sdstrim(s,"Aa. :"); + * printf("%s\n", s); + * + * Output will be just "Hello World". + */ +sds sdstrim(sds s, const char *cset) { + char *start, *end, *sp, *ep; + size_t len; + + sp = start = s; + ep = end = s+sdslen(s)-1; + while(sp <= end && strchr(cset, *sp)) sp++; + while(ep > sp && strchr(cset, *ep)) ep--; + len = (sp > ep) ? 0 : ((ep-sp)+1); + if (s != sp) memmove(s, sp, len); + s[len] = '\0'; + sdssetlen(s,len); + return s; +} + +/* Turn the string into a smaller (or equal) string containing only the + * substring specified by the 'start' and 'end' indexes. + * + * start and end can be negative, where -1 means the last character of the + * string, -2 the penultimate character, and so forth. + * + * The interval is inclusive, so the start and end characters will be part + * of the resulting string. + * + * The string is modified in-place. + * + * Example: + * + * s = sdsnew("Hello World"); + * sdsrange(s,1,-1); => "ello World" + */ +void sdsrange(sds s, int start, int end) { + size_t newlen, len = sdslen(s); + + if (len == 0) return; + if (start < 0) { + start = len+start; + if (start < 0) start = 0; + } + if (end < 0) { + end = len+end; + if (end < 0) end = 0; + } + newlen = (start > end) ? 0 : (end-start)+1; + if (newlen != 0) { + if (start >= (signed)len) { + newlen = 0; + } else if (end >= (signed)len) { + end = len-1; + newlen = (start > end) ? 0 : (end-start)+1; + } + } else { + start = 0; + } + if (start && newlen) memmove(s, s+start, newlen); + s[newlen] = 0; + sdssetlen(s,newlen); +} + +/* Apply tolower() to every character of the sds string 's'. */ +void sdstolower(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = tolower(s[j]); +} + +/* Apply toupper() to every character of the sds string 's'. */ +void sdstoupper(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = toupper(s[j]); +} + +/* Compare two sds strings s1 and s2 with memcmp(). + * + * Return value: + * + * positive if s1 > s2. + * negative if s1 < s2. + * 0 if s1 and s2 are exactly the same binary string. + * + * If two strings share exactly the same prefix, but one of the two has + * additional characters, the longer string is considered to be greater than + * the smaller one. */ +int sdscmp(const sds s1, const sds s2) { + size_t l1, l2, minlen; + int cmp; + + l1 = sdslen(s1); + l2 = sdslen(s2); + minlen = (l1 < l2) ? l1 : l2; + cmp = memcmp(s1,s2,minlen); + if (cmp == 0) return l1-l2; + return cmp; +} + +/* Split 's' with separator in 'sep'. An array + * of sds strings is returned. *count will be set + * by reference to the number of tokens returned. + * + * On out of memory, zero length string, zero length + * separator, NULL is returned. + * + * Note that 'sep' is able to split a string using + * a multi-character separator. For example + * sdssplit("foo_-_bar","_-_"); will return two + * elements "foo" and "bar". + * + * This version of the function is binary-safe but + * requires length arguments. sdssplit() is just the + * same function but for zero-terminated strings. + */ +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { + int elements = 0, slots = 5, start = 0, j; + sds *tokens; + + if (seplen < 1 || len < 0) return NULL; + + tokens = s_malloc(sizeof(sds)*slots); + if (tokens == NULL) return NULL; + + if (len == 0) { + *count = 0; + return tokens; + } + for (j = 0; j < (len-(seplen-1)); j++) { + /* make sure there is room for the next element and the final one */ + if (slots < elements+2) { + sds *newtokens; + + slots *= 2; + newtokens = s_realloc(tokens,sizeof(sds)*slots); + if (newtokens == NULL) goto cleanup; + tokens = newtokens; + } + /* search the separator */ + if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { + tokens[elements] = sdsnewlen(s+start,j-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + start = j+seplen; + j = j+seplen-1; /* skip the separator */ + } + } + /* Add the final element. We are sure there is room in the tokens array. */ + tokens[elements] = sdsnewlen(s+start,len-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + *count = elements; + return tokens; + +cleanup: + { + int i; + for (i = 0; i < elements; i++) sdsfree(tokens[i]); + s_free(tokens); + *count = 0; + return NULL; + } +} + +/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ +void sdsfreesplitres(sds *tokens, int count) { + if (!tokens) return; + while(count--) + sdsfree(tokens[count]); + s_free(tokens); +} + +/* Append to the sds string "s" an escaped string representation where + * all the non-printable characters (tested with isprint()) are turned into + * escapes in the form "\n\r\a...." or "\x". + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatrepr(sds s, const char *p, size_t len) { + s = sdscatlen(s,"\"",1); + while(len--) { + switch(*p) { + case '\\': + case '"': + s = sdscatprintf(s,"\\%c",*p); + break; + case '\n': s = sdscatlen(s,"\\n",2); break; + case '\r': s = sdscatlen(s,"\\r",2); break; + case '\t': s = sdscatlen(s,"\\t",2); break; + case '\a': s = sdscatlen(s,"\\a",2); break; + case '\b': s = sdscatlen(s,"\\b",2); break; + default: + if (isprint(*p)) + s = sdscatprintf(s,"%c",*p); + else + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); + break; + } + p++; + } + return sdscatlen(s,"\"",1); +} + +/* Helper function for sdssplitargs() that returns non zero if 'c' + * is a valid hex digit. */ +int is_hex_digit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +/* Helper function for sdssplitargs() that converts a hex digit into an + * integer from 0 to 15 */ +int hex_digit_to_int(char c) { + switch(c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return 0; + } +} + +/* Split a line into arguments, where every argument can be in the + * following programming-language REPL-alike form: + * + * foo bar "newline are supported\n" and "\xff\x00otherstuff" + * + * The number of arguments is stored into *argc, and an array + * of sds is returned. + * + * The caller should free the resulting array of sds strings with + * sdsfreesplitres(). + * + * Note that sdscatrepr() is able to convert back a string into + * a quoted string in the same format sdssplitargs() is able to parse. + * + * The function returns the allocated tokens on success, even when the + * input string is empty, or NULL if the input contains unbalanced + * quotes or closed quotes followed by non space characters + * as in: "foo"bar or "foo' + */ +sds *sdssplitargs(const char *line, int *argc) { + const char *p = line; + char *current = NULL; + char **vector = NULL; + + *argc = 0; + while(1) { + /* skip blanks */ + while(*p && isspace(*p)) p++; + if (*p) { + /* get a token */ + int inq=0; /* set to 1 if we are in "quotes" */ + int insq=0; /* set to 1 if we are in 'single quotes' */ + int done=0; + + if (current == NULL) current = sdsempty(); + while(!done) { + if (inq) { + if (*p == '\\' && *(p+1) == 'x' && + is_hex_digit(*(p+2)) && + is_hex_digit(*(p+3))) + { + unsigned char byte; + + byte = (hex_digit_to_int(*(p+2))*16)+ + hex_digit_to_int(*(p+3)); + current = sdscatlen(current,(char*)&byte,1); + p += 3; + } else if (*p == '\\' && *(p+1)) { + char c; + + p++; + switch(*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'b': c = '\b'; break; + case 'a': c = '\a'; break; + default: c = *p; break; + } + current = sdscatlen(current,&c,1); + } else if (*p == '"') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else if (insq) { + if (*p == '\\' && *(p+1) == '\'') { + p++; + current = sdscatlen(current,"'",1); + } else if (*p == '\'') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else { + switch(*p) { + case ' ': + case '\n': + case '\r': + case '\t': + case '\0': + done=1; + break; + case '"': + inq=1; + break; + case '\'': + insq=1; + break; + default: + current = sdscatlen(current,p,1); + break; + } + } + if (*p) p++; + } + /* add the token to the vector */ + vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); + vector[*argc] = current; + (*argc)++; + current = NULL; + } else { + /* Even on empty input string return something not NULL. */ + if (vector == NULL) vector = s_malloc(sizeof(void*)); + return vector; + } + } + +err: + while((*argc)--) + sdsfree(vector[*argc]); + s_free(vector); + if (current) sdsfree(current); + *argc = 0; + return NULL; +} + +/* Modify the string substituting all the occurrences of the set of + * characters specified in the 'from' string to the corresponding character + * in the 'to' array. + * + * For instance: sdsmapchars(mystring, "ho", "01", 2) + * will have the effect of turning the string "hello" into "0ell1". + * + * The function returns the sds string pointer, that is always the same + * as the input pointer since no resize is needed. */ +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { + size_t j, i, l = sdslen(s); + + for (j = 0; j < l; j++) { + for (i = 0; i < setlen; i++) { + if (s[j] == from[i]) { + s[j] = to[i]; + break; + } + } + } + return s; +} + +/* Join an array of C strings using the specified separator (also a C string). + * Returns the result as an sds string. */ +sds sdsjoin(char **argv, int argc, char *sep) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscat(join, argv[j]); + if (j != argc-1) join = sdscat(join,sep); + } + return join; +} + +/* Like sdsjoin, but joins an array of SDS strings. */ +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscatsds(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + +/* Wrappers to the allocators used by SDS. Note that SDS will actually + * just use the macros defined into sdsalloc.h in order to avoid to pay + * the overhead of function calls. Here we define these wrappers only for + * the programs SDS is linked to, if they want to touch the SDS internals + * even if they use a different allocator. */ +void *sds_malloc(size_t size) { return s_malloc(size); } +void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } +void sds_free(void *ptr) { s_free(ptr); } + +#if defined(SDS_TEST_MAIN) +#include +#include "testhelp.h" +#include "limits.h" + +#define UNUSED(x) (void)(x) +int sdsTest(void) { + { + sds x = sdsnew("foo"), y; + + test_cond("Create a string and obtain the length", + sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) + + sdsfree(x); + x = sdsnewlen("foo",2); + test_cond("Create a string with specified length", + sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) + + x = sdscat(x,"bar"); + test_cond("Strings concatenation", + sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); + + x = sdscpy(x,"a"); + test_cond("sdscpy() against an originally longer string", + sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) + + x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); + test_cond("sdscpy() against an originally shorter string", + sdslen(x) == 33 && + memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) + + sdsfree(x); + x = sdscatprintf(sdsempty(),"%d",123); + test_cond("sdscatprintf() seems working in the base case", + sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) + + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); + test_cond("sdscatfmt() seems working in the base case", + sdslen(x) == 60 && + memcmp(x,"--Hello Hi! World -9223372036854775808," + "9223372036854775807--",60) == 0) + printf("[%s]\n",x); + + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); + test_cond("sdscatfmt() seems working with unsigned numbers", + sdslen(x) == 35 && + memcmp(x,"--4294967295,18446744073709551615--",35) == 0) + + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," x"); + test_cond("sdstrim() works when all chars match", + sdslen(x) == 0) + + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," "); + test_cond("sdstrim() works when a single char remains", + sdslen(x) == 1 && x[0] == 'x') + + sdsfree(x); + x = sdsnew("xxciaoyyy"); + sdstrim(x,"xy"); + test_cond("sdstrim() correctly trims characters", + sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) + + y = sdsdup(x); + sdsrange(y,1,1); + test_cond("sdsrange(...,1,1)", + sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,-1); + test_cond("sdsrange(...,1,-1)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,-2,-1); + test_cond("sdsrange(...,-2,-1)", + sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,2,1); + test_cond("sdsrange(...,2,1)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,100); + test_cond("sdsrange(...,1,100)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,100,100); + test_cond("sdsrange(...,100,100)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("foo"); + y = sdsnew("foa"); + test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("bar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("aar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) + + sdsfree(y); + sdsfree(x); + x = sdsnewlen("\a\n\0foo\r",7); + y = sdscatrepr(sdsempty(),x,sdslen(x)); + test_cond("sdscatrepr(...data...)", + memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) + + { + unsigned int oldfree; + char *p; + int step = 10, j, i; + + sdsfree(x); + sdsfree(y); + x = sdsnew("0"); + test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); + + /* Run the test a few times in order to hit the first two + * SDS header types. */ + for (i = 0; i < 10; i++) { + int oldlen = sdslen(x); + x = sdsMakeRoomFor(x,step); + int type = x[-1]&SDS_TYPE_MASK; + + test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); + if (type != SDS_TYPE_5) { + test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); + oldfree = sdsavail(x); + } + p = x+oldlen; + for (j = 0; j < step; j++) { + p[j] = 'A'+j; + } + sdsIncrLen(x,step); + } + test_cond("sdsMakeRoomFor() content", + memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); + test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); + + sdsfree(x); + } + } + test_report() + return 0; +} +#endif + +#ifdef SDS_TEST_MAIN +int main(void) { + return sdsTest(); +} +#endif diff --git a/ext/hiredis-0.14.1/sds.h b/ext/hiredis-0.14.1/sds.h new file mode 100644 index 000000000..13be75a9f --- /dev/null +++ b/ext/hiredis-0.14.1/sds.h @@ -0,0 +1,273 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SDS_H +#define __SDS_H + +#define SDS_MAX_PREALLOC (1024*1024) + +#include +#include +#include + +typedef char *sds; + +/* Note: sdshdr5 is never used, we just access the flags byte directly. + * However is here to document the layout of type 5 SDS strings. */ +struct __attribute__ ((__packed__)) sdshdr5 { + unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr8 { + uint8_t len; /* used */ + uint8_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr16 { + uint16_t len; /* used */ + uint16_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr32 { + uint32_t len; /* used */ + uint32_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr64 { + uint64_t len; /* used */ + uint64_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; + +#define SDS_TYPE_5 0 +#define SDS_TYPE_8 1 +#define SDS_TYPE_16 2 +#define SDS_TYPE_32 3 +#define SDS_TYPE_64 4 +#define SDS_TYPE_MASK 7 +#define SDS_TYPE_BITS 3 +#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); +#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) +#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) + +static inline size_t sdslen(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->len; + case SDS_TYPE_16: + return SDS_HDR(16,s)->len; + case SDS_TYPE_32: + return SDS_HDR(32,s)->len; + case SDS_TYPE_64: + return SDS_HDR(64,s)->len; + } + return 0; +} + +static inline size_t sdsavail(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + return 0; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + return sh->alloc - sh->len; + } + } + return 0; +} + +static inline void sdssetlen(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len = newlen; + break; + } +} + +static inline void sdsinclen(sds s, size_t inc) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len += inc; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len += inc; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len += inc; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len += inc; + break; + } +} + +/* sdsalloc() = sdsavail() + sdslen() */ +static inline size_t sdsalloc(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->alloc; + case SDS_TYPE_16: + return SDS_HDR(16,s)->alloc; + case SDS_TYPE_32: + return SDS_HDR(32,s)->alloc; + case SDS_TYPE_64: + return SDS_HDR(64,s)->alloc; + } + return 0; +} + +static inline void sdssetalloc(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + /* Nothing to do, this type has no total allocation info. */ + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->alloc = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->alloc = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->alloc = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->alloc = newlen; + break; + } +} + +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +sds sdsdup(const sds s); +void sdsfree(sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); + +sds sdscatvprintf(sds s, const char *fmt, va_list ap); +#ifdef __GNUC__ +sds sdscatprintf(sds s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +sds sdscatprintf(sds s, const char *fmt, ...); +#endif + +sds sdscatfmt(sds s, char const *fmt, ...); +sds sdstrim(sds s, const char *cset); +void sdsrange(sds s, int start, int end); +void sdsupdatelen(sds s); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); + +/* Low level functions exposed to the user API */ +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); +void *sdsAllocPtr(sds s); + +/* Export the allocator used by SDS to the program using SDS. + * Sometimes the program SDS is linked to, may use a different set of + * allocators, but may want to allocate or free things that SDS will + * respectively free or allocate. */ +void *sds_malloc(size_t size); +void *sds_realloc(void *ptr, size_t size); +void sds_free(void *ptr); + +#ifdef REDIS_TEST +int sdsTest(int argc, char *argv[]); +#endif + +#endif diff --git a/ext/hiredis-0.14.1/sdsalloc.h b/ext/hiredis-0.14.1/sdsalloc.h new file mode 100644 index 000000000..f43023c48 --- /dev/null +++ b/ext/hiredis-0.14.1/sdsalloc.h @@ -0,0 +1,42 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* SDS allocator selection. + * + * This file is used in order to change the SDS allocator at compile time. + * Just define the following defines to what you want to use. Also add + * the include of your alternate allocator if needed (not needed in order + * to use the default libc allocator). */ + +#define s_malloc malloc +#define s_realloc realloc +#define s_free free diff --git a/ext/hiredis-0.14.1/test.c b/ext/hiredis-0.14.1/test.c new file mode 100644 index 000000000..0f5bfe572 --- /dev/null +++ b/ext/hiredis-0.14.1/test.c @@ -0,0 +1,923 @@ +#include "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" + +enum connection_type { + CONN_TCP, + CONN_UNIX, + CONN_FD +}; + +struct config { + enum connection_type type; + + struct { + const char *host; + int port; + struct timeval timeout; + } tcp; + + struct { + const char *path; + } unix_sock; +}; + +/* The following lines make up our testing "framework" :) */ +static int tests = 0, fails = 0; +#define test(_s) { printf("#%02d ", ++tests); printf(_s); } +#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} + +static long long usec(void) { + struct timeval tv; + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; +} + +/* The assert() calls below have side effects, so we need assert() + * even if we are compiling without asserts (-DNDEBUG). */ +#ifdef NDEBUG +#undef assert +#define assert(e) (void)(e) +#endif + +static redisContext *select_database(redisContext *c) { + redisReply *reply; + + /* Switch to DB 9 for testing, now that we know we can chat. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Make sure the DB is emtpy */ + reply = redisCommand(c,"DBSIZE"); + assert(reply != NULL); + if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { + /* Awesome, DB 9 is empty and we can continue. */ + freeReplyObject(reply); + } else { + printf("Database #9 is not empty, test can not continue\n"); + exit(1); + } + + return c; +} + +static int disconnect(redisContext *c, int keep_fd) { + redisReply *reply; + + /* Make sure we're on DB 9. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + reply = redisCommand(c,"FLUSHDB"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Free the context as well, but keep the fd if requested. */ + if (keep_fd) + return redisFreeKeepFd(c); + redisFree(c); + return -1; +} + +static redisContext *connect(struct config config) { + redisContext *c = NULL; + + if (config.type == CONN_TCP) { + c = redisConnect(config.tcp.host, config.tcp.port); + } else if (config.type == CONN_UNIX) { + c = redisConnectUnix(config.unix_sock.path); + } else if (config.type == CONN_FD) { + /* Create a dummy connection just to get an fd to inherit */ + redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path); + if (dummy_ctx) { + int fd = disconnect(dummy_ctx, 1); + printf("Connecting to inherited fd %d\n", fd); + c = redisConnectFd(fd); + } + } else { + assert(NULL); + } + + if (c == NULL) { + printf("Connection error: can't allocate redis context\n"); + exit(1); + } else if (c->err) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + exit(1); + } + + return select_database(c); +} + +static void test_format_commands(void) { + char *cmd; + int len; + + test("Format command without interpolation: "); + len = redisFormatCommand(&cmd,"SET foo bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s string interpolation: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s and an empty string: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo",""); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with an empty string in between proper interpolations: "); + len = redisFormatCommand(&cmd,"SET %s %s","","foo"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && + len == 4+4+(3+2)+4+(0+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b string interpolation: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b and an empty string: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with literal %%: "); + len = redisFormatCommand(&cmd,"SET %% %%"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && + len == 4+4+(3+2)+4+(1+2)+4+(1+2)); + free(cmd); + + /* Vararg width depends on the type. These tests make sure that the + * width is correctly determined using the format and subsequent varargs + * can correctly be interpolated. */ +#define INTEGER_WIDTH_TEST(fmt, type) do { \ + type value = 123; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + +#define FLOAT_WIDTH_TEST(type) do { \ + type value = 123.0; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + + INTEGER_WIDTH_TEST("d", int); + INTEGER_WIDTH_TEST("hhd", char); + INTEGER_WIDTH_TEST("hd", short); + INTEGER_WIDTH_TEST("ld", long); + INTEGER_WIDTH_TEST("lld", long long); + INTEGER_WIDTH_TEST("u", unsigned int); + INTEGER_WIDTH_TEST("hhu", unsigned char); + INTEGER_WIDTH_TEST("hu", unsigned short); + INTEGER_WIDTH_TEST("lu", unsigned long); + INTEGER_WIDTH_TEST("llu", unsigned long long); + FLOAT_WIDTH_TEST(float); + FLOAT_WIDTH_TEST(double); + + test("Format command with invalid printf format: "); + len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); + test_cond(len == -1); + + const char *argv[3]; + argv[0] = "SET"; + argv[1] = "foo\0xxx"; + argv[2] = "bar"; + size_t lens[3] = { 3, 7, 3 }; + int argc = 3; + + test("Format command by passing argc/argv without lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,NULL); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command by passing argc/argv with lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,lens); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(7+2)+4+(3+2)); + free(cmd); + + sds sds_cmd; + + sds_cmd = sdsempty(); + test("Format command into sds by passing argc/argv without lengths: "); + len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); + test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + sdsfree(sds_cmd); + + sds_cmd = sdsempty(); + test("Format command into sds by passing argc/argv with lengths: "); + len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); + test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(7+2)+4+(3+2)); + sdsfree(sds_cmd); +} + +static void test_append_formatted_commands(struct config config) { + redisContext *c; + redisReply *reply; + char *cmd; + int len; + + c = connect(config); + + test("Append format command: "); + + len = redisFormatCommand(&cmd, "SET foo bar"); + + test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); + + assert(redisGetReply(c, (void*)&reply) == REDIS_OK); + + free(cmd); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_reply_reader(void) { + redisReader *reader; + void *reply; + int ret; + int i; + + test("Error handling in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + /* when the reply already contains multiple items, they must be free'd + * on an error. valgrind will bark when this doesn't happen. */ + test("Memory cleanup in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*2\r\n",4); + redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + test("Set error on nested multi bulks with depth > 7: "); + reader = redisReaderCreate(); + + for (i = 0; i < 9; i++) { + redisReaderFeed(reader,(char*)"*1\r\n",4); + } + + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strncasecmp(reader->errstr,"No support for",14) == 0); + redisReaderFree(reader); + + test("Correctly parses LLONG_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":9223372036854775807\r\n",22); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->integer == LLONG_MAX); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when > LLONG_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":9223372036854775808\r\n",22); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad integer value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Correctly parses LLONG_MIN: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":-9223372036854775808\r\n",23); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->integer == LLONG_MIN); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when < LLONG_MIN: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":-9223372036854775809\r\n",23); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad integer value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when array < -1: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when bulk < -1: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "$-2\r\nasdf\r\n",11); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bulk string length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when array > INT_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + +#if LLONG_MAX > SIZE_MAX + test("Set error when bulk > SIZE_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bulk string length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); +#endif + + test("Works with NULL functions for reply: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r\n",5); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Works when a single newline (\\r\\n) covers two calls to feed: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r",4); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_OK && reply == NULL); + redisReaderFeed(reader,(char*)"\n",1); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Don't reset state after protocol error: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"x",1); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_ERR); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && reply == NULL); + redisReaderFree(reader); + + /* Regression test for issue #45 on GitHub. */ + test("Don't do empty allocation for empty multi bulk: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*0\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && + ((redisReply*)reply)->elements == 0); + freeReplyObject(reply); + redisReaderFree(reader); +} + +static void test_free_null(void) { + void *redisCtx = NULL; + void *reply = NULL; + + test("Don't fail when redisFree is passed a NULL value: "); + redisFree(redisCtx); + test_cond(redisCtx == NULL); + + test("Don't fail when freeReplyObject is passed a NULL value: "); + freeReplyObject(reply); + test_cond(reply == NULL); +} + +static void test_blocking_connection_errors(void) { + redisContext *c; + + test("Returns error when host cannot be resolved: "); + c = redisConnect((char*)"idontexist.test", 6379); + test_cond(c->err == REDIS_ERR_OTHER && + (strcmp(c->errstr,"Name or service not known") == 0 || + strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || + strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr,"No address associated with hostname") == 0 || + strcmp(c->errstr,"Temporary failure in name resolution") == 0 || + strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 || + strcmp(c->errstr,"no address associated with name") == 0)); + redisFree(c); + + test("Returns error when the port is not open: "); + c = redisConnect((char*)"localhost", 1); + test_cond(c->err == REDIS_ERR_IO && + strcmp(c->errstr,"Connection refused") == 0); + redisFree(c); + + test("Returns error when the unix_sock socket path doesn't accept connections: "); + c = redisConnectUnix((char*)"/tmp/idontexist.sock"); + test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ + redisFree(c); +} + +static void test_blocking_connection(struct config config) { + redisContext *c; + redisReply *reply; + + c = connect(config); + + test("Is able to deliver commands: "); + reply = redisCommand(c,"PING"); + test_cond(reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"pong") == 0) + freeReplyObject(reply); + + test("Is a able to send commands verbatim: "); + reply = redisCommand(c,"SET foo bar"); + test_cond (reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"ok") == 0) + freeReplyObject(reply); + + test("%%s String interpolation works: "); + reply = redisCommand(c,"SET %s %s","foo","hello world"); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + strcmp(reply->str,"hello world") == 0); + freeReplyObject(reply); + + test("%%b String interpolation works: "); + reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + memcmp(reply->str,"hello\x00world",11) == 0) + + test("Binary reply length is correct: "); + test_cond(reply->len == 11) + freeReplyObject(reply); + + test("Can parse nil replies: "); + reply = redisCommand(c,"GET nokey"); + test_cond(reply->type == REDIS_REPLY_NIL) + freeReplyObject(reply); + + /* test 7 */ + test("Can parse integer replies: "); + reply = redisCommand(c,"INCR mycounter"); + test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) + freeReplyObject(reply); + + test("Can parse multi bulk replies: "); + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + freeReplyObject(redisCommand(c,"LPUSH mylist bar")); + reply = redisCommand(c,"LRANGE mylist 0 -1"); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + !memcmp(reply->element[0]->str,"bar",3) && + !memcmp(reply->element[1]->str,"foo",3)) + freeReplyObject(reply); + + /* m/e with multi bulk reply *before* other reply. + * specifically test ordering of reply items to parse. */ + test("Can handle nested multi bulk replies: "); + freeReplyObject(redisCommand(c,"MULTI")); + freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); + freeReplyObject(redisCommand(c,"PING")); + reply = (redisCommand(c,"EXEC")); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + reply->element[0]->type == REDIS_REPLY_ARRAY && + reply->element[0]->elements == 2 && + !memcmp(reply->element[0]->element[0]->str,"bar",3) && + !memcmp(reply->element[0]->element[1]->str,"foo",3) && + reply->element[1]->type == REDIS_REPLY_STATUS && + strcasecmp(reply->element[1]->str,"pong") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_connection_timeouts(struct config config) { + redisContext *c; + redisReply *reply; + ssize_t s; + const char *cmd = "DEBUG SLEEP 3\r\n"; + struct timeval tv; + + c = connect(config); + test("Successfully completes a command when the timeout is not exceeded: "); + reply = redisCommand(c,"SET foo fast"); + freeReplyObject(reply); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); + freeReplyObject(reply); + disconnect(c, 0); + + c = connect(config); + test("Does not return a reply when the command times out: "); + s = write(c->fd, cmd, strlen(cmd)); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); + freeReplyObject(reply); + + test("Reconnect properly reconnects after a timeout: "); + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + test("Reconnect properly uses owned parameters: "); + config.tcp.host = "foo"; + config.unix_sock.path = "foo"; + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_io_errors(struct config config) { + redisContext *c; + redisReply *reply; + void *_reply; + int major, minor; + + /* Connect to target given by config. */ + c = connect(config); + { + /* Find out Redis version to determine the path for the next test */ + const char *field = "redis_version:"; + char *p, *eptr; + + reply = redisCommand(c,"INFO"); + p = strstr(reply->str,field); + major = strtol(p+strlen(field),&eptr,10); + p = eptr+1; /* char next to the first "." */ + minor = strtol(p,&eptr,10); + freeReplyObject(reply); + } + + test("Returns I/O error when the connection is lost: "); + reply = redisCommand(c,"QUIT"); + if (major > 2 || (major == 2 && minor > 0)) { + /* > 2.0 returns OK on QUIT and read() should be issued once more + * to know the descriptor is at EOF. */ + test_cond(strcasecmp(reply->str,"OK") == 0 && + redisGetReply(c,&_reply) == REDIS_ERR); + freeReplyObject(reply); + } else { + test_cond(reply == NULL); + } + + /* On 2.0, QUIT will cause the connection to be closed immediately and + * the read(2) for the reply on QUIT will set the error to EOF. + * On >2.0, QUIT will return with OK and another read(2) needed to be + * issued to find out the socket was closed by the server. In both + * conditions, the error will be set to EOF. */ + assert(c->err == REDIS_ERR_EOF && + strcmp(c->errstr,"Server closed the connection") == 0); + redisFree(c); + + c = connect(config); + test("Returns I/O error on socket timeout: "); + struct timeval tv = { 0, 1000 }; + assert(redisSetTimeout(c,tv) == REDIS_OK); + test_cond(redisGetReply(c,&_reply) == REDIS_ERR && + c->err == REDIS_ERR_IO && errno == EAGAIN); + redisFree(c); +} + +static void test_invalid_timeout_errors(struct config config) { + redisContext *c; + + test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = 0; + config.tcp.timeout.tv_usec = 10000001; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); + redisFree(c); + + test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; + config.tcp.timeout.tv_usec = 0; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); + redisFree(c); +} + +static void test_throughput(struct config config) { + redisContext *c = connect(config); + redisReply **replies; + int i, num; + long long t1, t2; + + test("Throughput:\n"); + for (i = 0; i < 500; i++) + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + + num = 1000; + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"PING"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"LRANGE mylist 0 499"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0); + + num = 10000; + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"PING"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"LRANGE mylist 0 499"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"INCRBY incrkey %d", 1000000); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + disconnect(c, 0); +} + +// static long __test_callback_flags = 0; +// static void __test_callback(redisContext *c, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// } +// +// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// if (reply) freeReplyObject(reply); +// } +// +// static redisContext *__connect_nonblock() { +// /* Reset callback flags */ +// __test_callback_flags = 0; +// return redisConnectNonBlock("127.0.0.1", port, NULL); +// } +// +// static void test_nonblocking_connection() { +// redisContext *c; +// int wdone = 0; +// +// test("Calls command callback when command is issued: "); +// c = __connect_nonblock(); +// redisSetCommandCallback(c,__test_callback,(void*)1); +// redisCommand(c,"PING"); +// test_cond(__test_callback_flags == 1); +// redisFree(c); +// +// test("Calls disconnect callback on redisDisconnect: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 2); +// redisFree(c); +// +// test("Calls disconnect callback and free callback on redisFree: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisSetFreeCallback(c,__test_callback,(void*)4); +// redisFree(c); +// test_cond(__test_callback_flags == ((2 << 8) | 4)); +// +// test("redisBufferWrite against empty write buffer: "); +// c = __connect_nonblock(); +// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); +// redisFree(c); +// +// test("redisBufferWrite against not yet connected fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("redisBufferWrite against closed fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// redisDisconnect(c); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("Process callbacks in the right sequence: "); +// c = __connect_nonblock(); +// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); +// +// /* Write output buffer */ +// wdone = 0; +// while(!wdone) { +// usleep(500); +// redisBufferWrite(c,&wdone); +// } +// +// /* Read until at least one callback is executed (the 3 replies will +// * arrive in a single packet, causing all callbacks to be executed in +// * a single pass). */ +// while(__test_callback_flags == 0) { +// assert(redisBufferRead(c) == REDIS_OK); +// redisProcessCallbacks(c); +// } +// test_cond(__test_callback_flags == 0x010203); +// redisFree(c); +// +// test("redisDisconnect executes pending callbacks with NULL reply: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)1); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 0x0201); +// redisFree(c); +// } + +int main(int argc, char **argv) { + struct config cfg = { + .tcp = { + .host = "127.0.0.1", + .port = 6379 + }, + .unix_sock = { + .path = "/tmp/redis.sock" + } + }; + int throughput = 1; + int test_inherit_fd = 1; + + /* Ignore broken pipe signal (for I/O error tests). */ + signal(SIGPIPE, SIG_IGN); + + /* Parse command line options. */ + argv++; argc--; + while (argc) { + if (argc >= 2 && !strcmp(argv[0],"-h")) { + argv++; argc--; + cfg.tcp.host = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"-p")) { + argv++; argc--; + cfg.tcp.port = atoi(argv[0]); + } else if (argc >= 2 && !strcmp(argv[0],"-s")) { + argv++; argc--; + cfg.unix_sock.path = argv[0]; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { + throughput = 0; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { + test_inherit_fd = 0; + } else { + fprintf(stderr, "Invalid argument: %s\n", argv[0]); + exit(1); + } + argv++; argc--; + } + + test_format_commands(); + test_reply_reader(); + test_blocking_connection_errors(); + test_free_null(); + + printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); + cfg.type = CONN_TCP; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + test_invalid_timeout_errors(cfg); + test_append_formatted_commands(cfg); + if (throughput) test_throughput(cfg); + + printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path); + cfg.type = CONN_UNIX; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + if (throughput) test_throughput(cfg); + + if (test_inherit_fd) { + printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path); + cfg.type = CONN_FD; + test_blocking_connection(cfg); + } + + + if (fails) { + printf("*** %d TESTS FAILED ***\n", fails); + return 1; + } + + printf("ALL TESTS PASSED\n"); + return 0; +} diff --git a/ext/hiredis-0.14.1/win32.h b/ext/hiredis-0.14.1/win32.h new file mode 100644 index 000000000..1a27c18f2 --- /dev/null +++ b/ext/hiredis-0.14.1/win32.h @@ -0,0 +1,42 @@ +#ifndef _WIN32_HELPER_INCLUDE +#define _WIN32_HELPER_INCLUDE +#ifdef _MSC_VER + +#ifndef inline +#define inline __inline +#endif + +#ifndef va_copy +#define va_copy(d,s) ((d) = (s)) +#endif + +#ifndef snprintf +#define snprintf c99_snprintf + +__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +__inline int c99_snprintf(char* str, size_t size, const char* format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(str, size, format, ap); + va_end(ap); + + return count; +} +#endif + +#endif +#endif \ No newline at end of file diff --git a/make-mac.mk b/make-mac.mk index d8b0c4c2a..fccfea7bf 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -29,7 +29,7 @@ ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http ifeq ($(ZT_CONTROLLER),1) LIBS+=-L/usr/local/opt/libpq/lib -lpq - DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER + DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-vip-0.3.0 ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o endif diff --git a/objects.mk b/objects.mk index efa2f3c0f..ed415a508 100644 --- a/objects.mk +++ b/objects.mk @@ -33,6 +33,7 @@ ONE_OBJS=\ controller/FileDB.o \ controller/LFDB.o \ controller/PostgreSQL.o \ + controller/Redis.o \ osdep/EthernetTap.o \ osdep/ManagedRoute.o \ osdep/Http.o \ diff --git a/service/OneService.cpp b/service/OneService.cpp index dcd4adb20..ba8405696 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -86,6 +86,8 @@ extern "C" { using json = nlohmann::json; #include "../controller/EmbeddedNetworkController.hpp" +#include "../controller/PostgreSQL.hpp" +#include "../controller/Redis.hpp" #include "../osdep/EthernetTap.hpp" #ifdef __WINDOWS__ #include "../osdep/WindowsEthernetTap.hpp" @@ -524,6 +526,8 @@ public: volatile bool _run; Mutex _run_m; + RedisConfig *_rc; + // end member variables ---------------------------------------------------- OneServiceImpl(const char *hp,unsigned int port) : @@ -559,6 +563,7 @@ public: ,_vaultPath("cubbyhole/zerotier") #endif ,_run(true) + ,_rc(NULL) { _ports[0] = 0; _ports[1] = 0; @@ -583,6 +588,7 @@ public: delete _portMapper; #endif delete _controller; + delete _rc; } virtual ReasonForTermination run() @@ -722,7 +728,7 @@ public: OSUtils::rmDashRf((_homePath + ZT_PATH_SEPARATOR_S "iddb.d").c_str()); // Network controller is now enabled by default for desktop and server - _controller = new EmbeddedNetworkController(_node,_homePath.c_str(),_controllerDbPath.c_str(),_ports[0]); + _controller = new EmbeddedNetworkController(_node,_homePath.c_str(),_controllerDbPath.c_str(),_ports[0], _rc); _node->setNetconfMaster((void *)_controller); // Join existing networks in networks.d @@ -986,7 +992,17 @@ public: if (cdbp.length() > 0) _controllerDbPath = cdbp; +#ifdef ZT_CONTROLLER_USE_LIBPQ // TODO: Redis config + json &redis = settings["redis"]; + if (redis.is_object() && _rc == NULL) { + _rc = new RedisConfig; + _rc->hostname = OSUtils::jsonString(redis["hostname"],""); + _rc->port = redis["port"]; + _rc->password = OSUtils::jsonString(redis["password"],""); + _rc->clusterMode = OSUtils::jsonBool(redis["clusterMode"], false); + } +#endif // Bind to wildcard instead of to specific interfaces (disables full tunnel capability) json &bind = settings["bind"]; From b5c661c5d590d11b701b54fb157f56950815a224 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 15:06:10 -0700 Subject: [PATCH 02/13] add libhiredis.a for mac --- ext/hiredis-0.14.1/lib/macos/libhiredis.a | Bin 0 -> 260368 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ext/hiredis-0.14.1/lib/macos/libhiredis.a diff --git a/ext/hiredis-0.14.1/lib/macos/libhiredis.a b/ext/hiredis-0.14.1/lib/macos/libhiredis.a new file mode 100644 index 0000000000000000000000000000000000000000..f05e58f354ae2d18e739d42a105aac16d7bc9aae GIT binary patch literal 260368 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

)(vBBD6%xdR(Tm1Y4^@?@^H0 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�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 Date: Mon, 11 May 2020 15:24:13 -0700 Subject: [PATCH 03/13] redis++ binaries for mac --- ext/redis-plus-plus-1.1.1/.gitignore | 32 + ext/redis-plus-plus-1.1.1/CMakeLists.txt | 51 + ext/redis-plus-plus-1.1.1/LICENSE | 201 ++ ext/redis-plus-plus-1.1.1/README.md | 1776 +++++++++++++ .../macos/include/sw/redis++/command.h | 2233 +++++++++++++++++ .../macos/include/sw/redis++/command_args.h | 180 ++ .../include/sw/redis++/command_options.h | 211 ++ .../macos/include/sw/redis++/connection.h | 194 ++ .../include/sw/redis++/connection_pool.h | 115 + .../install/macos/include/sw/redis++/errors.h | 159 ++ .../macos/include/sw/redis++/pipeline.h | 49 + .../macos/include/sw/redis++/queued_redis.h | 1844 ++++++++++++++ .../macos/include/sw/redis++/queued_redis.hpp | 208 ++ .../macos/include/sw/redis++/redis++.h | 25 + .../install/macos/include/sw/redis++/redis.h | 1523 +++++++++++ .../macos/include/sw/redis++/redis.hpp | 1365 ++++++++++ .../macos/include/sw/redis++/redis_cluster.h | 1395 ++++++++++ .../include/sw/redis++/redis_cluster.hpp | 1415 +++++++++++ .../install/macos/include/sw/redis++/reply.h | 363 +++ .../macos/include/sw/redis++/sentinel.h | 138 + .../install/macos/include/sw/redis++/shards.h | 115 + .../macos/include/sw/redis++/shards_pool.h | 137 + .../macos/include/sw/redis++/subscriber.h | 231 ++ .../macos/include/sw/redis++/transaction.h | 77 + .../install/macos/include/sw/redis++/utils.h | 269 ++ .../src/sw/redis++/command.cpp | 376 +++ .../src/sw/redis++/command.h | 2233 +++++++++++++++++ .../src/sw/redis++/command_args.h | 180 ++ .../src/sw/redis++/command_options.cpp | 201 ++ .../src/sw/redis++/command_options.h | 211 ++ .../src/sw/redis++/connection.cpp | 305 +++ .../src/sw/redis++/connection.h | 194 ++ .../src/sw/redis++/connection_pool.cpp | 249 ++ .../src/sw/redis++/connection_pool.h | 115 + .../src/sw/redis++/crc16.cpp | 96 + .../src/sw/redis++/errors.cpp | 136 + .../src/sw/redis++/errors.h | 159 ++ .../src/sw/redis++/pipeline.cpp | 35 + .../src/sw/redis++/pipeline.h | 49 + .../src/sw/redis++/queued_redis.h | 1844 ++++++++++++++ .../src/sw/redis++/queued_redis.hpp | 208 ++ .../src/sw/redis++/redis++.h | 25 + .../src/sw/redis++/redis.cpp | 882 +++++++ .../src/sw/redis++/redis.h | 1523 +++++++++++ .../src/sw/redis++/redis.hpp | 1365 ++++++++++ .../src/sw/redis++/redis_cluster.cpp | 769 ++++++ .../src/sw/redis++/redis_cluster.h | 1395 ++++++++++ .../src/sw/redis++/redis_cluster.hpp | 1415 +++++++++++ .../src/sw/redis++/reply.cpp | 150 ++ .../src/sw/redis++/reply.h | 363 +++ .../src/sw/redis++/sentinel.cpp | 361 +++ .../src/sw/redis++/sentinel.h | 138 + .../src/sw/redis++/shards.cpp | 50 + .../src/sw/redis++/shards.h | 115 + .../src/sw/redis++/shards_pool.cpp | 319 +++ .../src/sw/redis++/shards_pool.h | 137 + .../src/sw/redis++/subscriber.cpp | 222 ++ .../src/sw/redis++/subscriber.h | 231 ++ .../src/sw/redis++/transaction.cpp | 123 + .../src/sw/redis++/transaction.h | 77 + .../src/sw/redis++/utils.h | 269 ++ ext/redis-plus-plus-1.1.1/test/CMakeLists.txt | 33 + .../test/src/sw/redis++/benchmark_test.h | 83 + .../test/src/sw/redis++/benchmark_test.hpp | 178 ++ .../src/sw/redis++/connection_cmds_test.h | 49 + .../src/sw/redis++/connection_cmds_test.hpp | 50 + .../test/src/sw/redis++/geo_cmds_test.h | 47 + .../test/src/sw/redis++/geo_cmds_test.hpp | 149 ++ .../test/src/sw/redis++/hash_cmds_test.h | 55 + .../test/src/sw/redis++/hash_cmds_test.hpp | 177 ++ .../src/sw/redis++/hyperloglog_cmds_test.h | 47 + .../src/sw/redis++/hyperloglog_cmds_test.hpp | 67 + .../test/src/sw/redis++/keys_cmds_test.h | 55 + .../test/src/sw/redis++/keys_cmds_test.hpp | 166 ++ .../test/src/sw/redis++/list_cmds_test.h | 55 + .../test/src/sw/redis++/list_cmds_test.hpp | 154 ++ .../sw/redis++/pipeline_transaction_test.h | 57 + .../sw/redis++/pipeline_transaction_test.hpp | 184 ++ .../test/src/sw/redis++/pubsub_test.h | 53 + .../test/src/sw/redis++/pubsub_test.hpp | 244 ++ .../test/src/sw/redis++/sanity_test.h | 76 + .../test/src/sw/redis++/sanity_test.hpp | 299 +++ .../test/src/sw/redis++/script_cmds_test.h | 49 + .../test/src/sw/redis++/script_cmds_test.hpp | 97 + .../test/src/sw/redis++/set_cmds_test.h | 53 + .../test/src/sw/redis++/set_cmds_test.hpp | 184 ++ .../test/src/sw/redis++/stream_cmds_test.h | 54 + .../test/src/sw/redis++/stream_cmds_test.hpp | 225 ++ .../test/src/sw/redis++/string_cmds_test.h | 57 + .../test/src/sw/redis++/string_cmds_test.hpp | 247 ++ .../test/src/sw/redis++/test_main.cpp | 303 +++ .../test/src/sw/redis++/threads_test.h | 51 + .../test/src/sw/redis++/threads_test.hpp | 147 ++ .../test/src/sw/redis++/utils.h | 96 + .../test/src/sw/redis++/zset_cmds_test.h | 61 + .../test/src/sw/redis++/zset_cmds_test.hpp | 350 +++ 96 files changed, 35078 insertions(+) create mode 100644 ext/redis-plus-plus-1.1.1/.gitignore create mode 100644 ext/redis-plus-plus-1.1.1/CMakeLists.txt create mode 100644 ext/redis-plus-plus-1.1.1/LICENSE create mode 100644 ext/redis-plus-plus-1.1.1/README.md create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_args.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_options.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/errors.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/pipeline.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis++.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/reply.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/sentinel.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/subscriber.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/transaction.h create mode 100644 ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/utils.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command_args.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/crc16.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis++.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.hpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.cpp create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.h create mode 100644 ext/redis-plus-plus-1.1.1/src/sw/redis++/utils.h create mode 100644 ext/redis-plus-plus-1.1.1/test/CMakeLists.txt create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/test_main.cpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.hpp create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/utils.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.h create mode 100644 ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.hpp diff --git a/ext/redis-plus-plus-1.1.1/.gitignore b/ext/redis-plus-plus-1.1.1/.gitignore new file mode 100644 index 000000000..259148fa1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/.gitignore @@ -0,0 +1,32 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/ext/redis-plus-plus-1.1.1/CMakeLists.txt b/ext/redis-plus-plus-1.1.1/CMakeLists.txt new file mode 100644 index 000000000..ba069a62a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/CMakeLists.txt @@ -0,0 +1,51 @@ +project(redis++) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + cmake_minimum_required(VERSION 3.0.0) +else() + cmake_minimum_required(VERSION 2.8.0) +endif() + +set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -W -Werror -fPIC") + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src/sw/redis++) + +file(GLOB PROJECT_SOURCE_FILES "${PROJECT_SOURCE_DIR}/*.cpp") + +set(STATIC_LIB static) +#set(SHARED_LIB shared) + +add_library(${STATIC_LIB} STATIC ${PROJECT_SOURCE_FILES}) +# add_library(${SHARED_LIB} SHARED ${PROJECT_SOURCE_FILES}) + +# hiredis dependency +find_path(HIREDIS_HEADER hiredis) +target_include_directories(${STATIC_LIB} PUBLIC ${HIREDIS_HEADER}) +# target_include_directories(${SHARED_LIB} PUBLIC ${HIREDIS_HEADER}) + +#find_library(HIREDIS_LIB hiredis) +#target_link_libraries(${SHARED_LIB} ${HIREDIS_LIB}) + +set_target_properties(${STATIC_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) +#set_target_properties(${SHARED_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) + +set_target_properties(${STATIC_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1) +#set_target_properties(${SHARED_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1) + +# add_subdirectory(test) + + +# Install static lib. +install(TARGETS ${STATIC_LIB} + ARCHIVE DESTINATION lib) + +# Install shared lib. +#install(TARGETS ${SHARED_LIB} +# LIBRARY DESTINATION lib) + +#Install headers. +set(HEADER_PATH "sw/redis++") +file(GLOB HEADERS "${PROJECT_SOURCE_DIR}/*.h*") +install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${HEADER_PATH}) diff --git a/ext/redis-plus-plus-1.1.1/LICENSE b/ext/redis-plus-plus-1.1.1/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ext/redis-plus-plus-1.1.1/README.md b/ext/redis-plus-plus-1.1.1/README.md new file mode 100644 index 000000000..8d2fd7d28 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/README.md @@ -0,0 +1,1776 @@ +# redis-plus-plus + +- [Overview](#overview) + - [Features](#features) +- [Installation](#installation) + - [Install hiredis](#install-hiredis) + - [Install redis-plus-plus](#install-redis-plus-plus) + - [Run Tests (Optional)](#run-tests-optional) + - [Use redis-plus-plus In Your Project](#use-redis-plus-plus-in-your-project) +- [Getting Started](#getting-started) +- [API Reference](#api-reference) + - [Connection](#connection) + - [Send Command to Redis Server](#send-command-to-redis-server) + - [Generic Command Interface](#generic-command-interface) + - [Publish/Subscribe](#publishsubscribe) + - [Pipeline](#pipeline) + - [Transaction](#transaction) + - [Redis Cluster](#redis-cluster) + - [Redis Sentinel](#redis-sentinel) + - [Redis Stream](#redis-stream) +- [Author](#author) + +## Overview + +This is a C++ client for Redis. It's based on [hiredis](https://github.com/redis/hiredis), and written in C++ 11. + +**NOTE**: I'm not a native speaker. So if the documentation is unclear, please feel free to open an issue or pull request. I'll response ASAP. + +### Features +- Most commands for Redis. +- Connection pool. +- Redis scripting. +- Thread safe unless otherwise stated. +- Redis publish/subscribe. +- Redis pipeline. +- Redis transaction. +- Redis Cluster. +- Redis Sentinel. +- STL-like interfaces. +- Generic command interface. + +## Installation + +### Install hiredis + +Since *redis-plus-plus* is based on *hiredis*, you should install *hiredis* first. The minimum version requirement for *hiredis* is **v0.12.1**, and you'd better use the latest release of *hiredis*. + +``` +git clone https://github.com/redis/hiredis.git + +cd hiredis + +make + +make install +``` + +By default, *hiredis* is installed at */usr/local*. If you want to install *hiredis* at non-default location, use the following commands to specify the installation path. + +``` +make PREFIX=/non/default/path + +make PREFIX=/non/default/path install +``` + +### Install redis-plus-plus + +*redis-plus-plus* is built with [CMAKE](https://cmake.org). + +``` +git clone https://github.com/sewenew/redis-plus-plus.git + +cd redis-plus-plus + +mkdir compile + +cd compile + +cmake -DCMAKE_BUILD_TYPE=Release .. + +make + +make install + +cd .. +``` + +If *hiredis* is installed at non-default location, you should use `CMAKE_PREFIX_PATH` to specify the installation path of *hiredis*. By default, *redis-plus-plus* is installed at */usr/local*. However, you can use `CMAKE_INSTALL_PREFIX` to install *redis-plus-plus* at non-default location. + +``` +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus .. +``` + +### Run Tests (Optional) + +*redis-plus-plus* has been fully tested with the following compilers: + +``` +gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) +gcc version 5.5.0 20171010 (Ubuntu 5.5.0-12ubuntu1) +gcc version 6.5.0 20181026 (Ubuntu 6.5.0-2ubuntu1~18.04) +gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1) +gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1~18.04.1) +clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2) +clang version 4.0.1-10 (tags/RELEASE_401/final) +clang version 5.0.1-4 (tags/RELEASE_501/final) +clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final) +clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final) +Apple clang version 11.0.0 (clang-1100.0.33.8) +``` + +After compiling with cmake, you'll get a test program in *compile/test* directory: *compile/test/test_redis++*. + +In order to run the tests, you need to set up a Redis instance, and a Redis Cluster. Since the test program will send most of Redis commands to the server and cluster, you need to set up Redis of the latest version (by now, it's 5.0). Otherwise, the tests might fail. For example, if you set up Redis 4.0 for testing, the test program will fail when it tries to send the `ZPOPMAX` command (a Redis 5.0 command) to the server. If you want to run the tests with other Redis versions, you have to comment out commands that haven't been supported by your Redis, from test source files in *redis-plus-plus/test/src/sw/redis++/* directory. Sorry for the inconvenience, and I'll fix this problem to make the test program work with any version of Redis in the future. + +**NOTE**: The latest version of Redis is only a requirement for running the tests. In fact, you can use *redis-plus-plus* with Redis of any version, e.g. Redis 2.0, Redis 3.0, Redis 4.0, Redis 5.0. + +**NEVER** run the test program in production envronment, since the keys, which the test program reads or writes, might conflict with your application. + +In order to run tests with both Redis and Redis Cluster, you can run the test program with the following command: + +``` +./compile/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port +``` + +- *host* and *port* are the host and port number of the Redis instance. +- *cluster_node* and *cluster_port* are the host and port number of Redis Cluster. You only need to set the host and port number of a single node in the cluster, *redis-plus-plus* will find other nodes automatically. +- *auth* is the password of the Redis instance and Redis Cluster. The Redis instance and Redis Cluster must be configured with the same password. If there's no password configured, don't set this option. + +If you only want to run tests with Redis, you only need to specify *host*, *port* and *auth* options: + +``` +./compile/test/test_redis++ -h host -p port -a auth +``` + +Similarly, if you only want to run tests with Redis Cluster, just specify *cluster_node*, *cluster_port* and *auth* options: + +``` +./compile/test/test_redis++ -a auth -n cluster_node -c cluster_port +``` + +The test program will test running *redis-plus-plus* in multi-threads environment, and this test will cost a long time. If you want to skip it (not recommended), just comment out the following lines in *test/src/sw/redis++/test_main.cpp* file. + +```C++ +sw::redis::test::ThreadsTest threads_test(opts, cluster_node_opts); +threads_test.run(); +``` + +If all tests have been passed, the test program will print the following message: + +``` +Pass all tests +``` + +Otherwise, it prints the error message. + +#### Performance + +*redis-plus-plus* runs as fast as *hiredis*, since it's a wrapper of *hiredis*. You can run *test_redis++* in benchmark mode to check the performance in your environment. + +``` +./compile/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port -b -t thread_num -s connection_pool_size -r request_num -k key_len -v val_len +``` + +- *-b* option turns the test program into benchmark mode. +- *thread_num* specifies the number of worker threads. `10` by default. +- *connection_pool_size* specifies the size of the connection pool. `5` by default. +- *request_num* specifies the total number of requests sent to server for each test. `100000` by default. +- *key_len* specifies the length of the key for each operation. `10` by default. +- *val_len* specifies the length of the value. `10` by default. + +The bechmark will generate `100` random binary keys for testing, and the size of these keys is specified by *key_len*. When the benchmark runs, it will read/write with these keys. So **NEVER** run the test program in your production environment, otherwise, it might inaccidently delete your data. + +### Use redis-plus-plus In Your Project + +After compiling the code, you'll get both shared library and static library. Since *redis-plus-plus* depends on *hiredis*, you need to link both libraries to your Application. Also don't forget to specify the `-std=c++11` and thread-related option. + +#### Use Static Libraries + +Take gcc as an example. + +``` +g++ -std=c++11 -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread +``` + +If *hiredis* and *redis-plus-plus* are installed at non-default location, you should use `-I` option to specify the header path. + +``` +g++ -std=c++11 -I/non-default/install/include/path -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread +``` + +#### Use Shared Libraries + +``` +g++ -std=c++11 -o app app.cpp -lredis++ -lhiredis -pthread +``` + +If *hiredis* and *redis-plus-plus* are installed at non-default location, you should use `-I` and `-L` options to specify the header and library paths. + +``` +g++ -std=c++11 -I/non-default/install/include/path -L/non-default/install/lib/path -o app app.cpp -lredis++ -lhiredis -pthread +``` + +When linking with shared libraries, and running your application, you might get the following error message: + +``` +error while loading shared libraries: xxx: cannot open shared object file: No such file or directory. +``` + +That's because the linker cannot find the shared libraries. In order to solve the problem, you can add the path where you installed *hiredis* and *redis-plus-plus* libraries, to `LD_LIBRARY_PATH` environment variable. For example: + +``` +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib +``` + +Check [this StackOverflow question](https://stackoverflow.com/questions/480764) for details on how to solve the problem. + +#### Build With Cmake + +If you're using cmake to build your application, you need to add *hiredis* and *redis-plus-plus* dependencies in your *CMakeLists.txt*: + +```CMake +# <------------ add hiredis dependency ---------------> +find_path(HIREDIS_HEADER hiredis) +target_include_directories(target PUBLIC ${HIREDIS_HEADER}) + +find_library(HIREDIS_LIB hiredis) +target_link_libraries(target ${HIREDIS_LIB}) + +# <------------ add redis-plus-plus dependency --------------> +# NOTE: this should be *sw* NOT *redis++* +find_path(REDIS_PLUS_PLUS_HEADER sw) +target_include_directories(target PUBLIC ${REDIS_PLUS_PLUS_HEADER}) + +find_library(REDIS_PLUS_PLUS_LIB redis++) +target_link_libraries(target ${REDIS_PLUS_PLUS_LIB}) +``` + +See [this issue](https://github.com/sewenew/redis-plus-plus/issues/5) for a complete example of *CMakeLists.txt*. + +Also, if you installed *hiredis* and *redis-plus-plus* at non-default location, you need to run cmake with `CMAKE_PREFIX_PATH` option to specify the installation path of these two libraries. + +``` +cmake -DCMAKE_PREFIX_PATH=/installation/path/to/the/two/libs .. +``` + +## Getting Started + +```C++ +#include + +using namespace sw::redis; + +try { + // Create an Redis object, which is movable but NOT copyable. + auto redis = Redis("tcp://127.0.0.1:6379"); + + // ***** STRING commands ***** + + redis.set("key", "val"); + auto val = redis.get("key"); // val is of type OptionalString. See 'API Reference' section for details. + if (val) { + // Dereference val to get the returned value of std::string type. + std::cout << *val << std::endl; + } // else key doesn't exist. + + // ***** LIST commands ***** + + // std::vector to Redis LIST. + std::vector vec = {"a", "b", "c"}; + redis.rpush("list", vec.begin(), vec.end()); + + // std::initializer_list to Redis LIST. + redis.rpush("list", {"a", "b", "c"}); + + // Redis LIST to std::vector. + vec.clear(); + redis.lrange("list", 0, -1, std::back_inserter(vec)); + + // ***** HASH commands ***** + + redis.hset("hash", "field", "val"); + + // Another way to do the same job. + redis.hset("hash", std::make_pair("field", "val")); + + // std::unordered_map to Redis HASH. + std::unordered_map m = { + {"field1", "val1"}, + {"field2", "val2"} + }; + redis.hmset("hash", m.begin(), m.end()); + + // Redis HASH to std::unordered_map. + m.clear(); + redis.hgetall("hash", std::inserter(m, m.begin())); + + // Get value only. + // NOTE: since field might NOT exist, so we need to parse it to OptionalString. + std::vector vals; + redis.hmget("hash", {"field1", "field2"}, std::back_inserter(vals)); + + // ***** SET commands ***** + + redis.sadd("set", "m1"); + + // std::unordered_set to Redis SET. + std::unordered_set set = {"m2", "m3"}; + redis.sadd("set", set.begin(), set.end()); + + // std::initializer_list to Redis SET. + redis.sadd("set", {"m2", "m3"}); + + // Redis SET to std::unordered_set. + set.clear(); + redis.smembers("set", std::inserter(set, set.begin())); + + if (redis.sismember("set", "m1")) { + std::cout << "m1 exists" << std::endl; + } // else NOT exist. + + // ***** SORTED SET commands ***** + + redis.zadd("sorted_set", "m1", 1.3); + + // std::unordered_map to Redis SORTED SET. + std::unordered_map scores = { + {"m2", 2.3}, + {"m3", 4.5} + }; + redis.zadd("sorted_set", scores.begin(), scores.end()); + + // Redis SORTED SET to std::unordered_map. + scores.clear(); + redis.zrangebyscore("sorted_set", + UnboundedInterval{}, // (-inf, +inf) + std::inserter(scores, scores.begin())); + + // Only get member names: + // pass an inserter of std::vector type as output parameter. + std::vector without_score; + redis.zrangebyscore("sorted_set", + BoundedInterval(1.5, 3.4, BoundType::CLOSED), // [1.5, 3.4] + std::back_inserter(without_score)); + + // Get both member names and scores: + // pass an inserter of std::unordered_map as output parameter. + std::unordered_map with_score; + redis.zrangebyscore("sorted_set", + BoundedInterval(1.5, 3.4, BoundType::LEFT_OPEN), // (1.5, 3.4] + std::inserter(with_score, with_score.end())); + + // ***** SCRIPTING commands ***** + + // Script returns a single element. + auto num = redis.eval("return 1", {}, {}); + + // Script returns an array of elements. + std::vector nums; + redis.eval("return {ARGV[1], ARGV[2]}", {}, {"1", "2"}, std::back_inserter(nums)); + + // ***** Pipeline ***** + + // Create a pipeline. + auto pipe = redis.pipeline(); + + // Send mulitple commands and get all replies. + auto pipe_replies = pipe.set("key", "value") + .get("key") + .rename("key", "new-key") + .rpush("list", {"a", "b", "c"}) + .lrange("list", 0, -1) + .exec(); + + // Parse reply with reply type and index. + auto set_cmd_result = pipe_replies.get(0); + + auto get_cmd_result = pipe_replies.get(1); + + // rename command result + pipe_replies.get(2); + + auto rpush_cmd_result = pipe_replies.get(3); + + std::vector lrange_cmd_result; + pipe_replies.get(4, back_inserter(lrange_cmd_result)); + + // ***** Transaction ***** + + // Create a transaction. + auto tx = redis.transaction(); + + // Run multiple commands in a transaction, and get all replies. + auto tx_replies = tx.incr("num0") + .incr("num1") + .mget({"num0", "num1"}) + .exec(); + + // Parse reply with reply type and index. + auto incr_result0 = tx_replies.get(0); + + auto incr_result1 = tx_replies.get(1); + + std::vector mget_cmd_result; + tx_replies.get(2, back_inserter(mget_cmd_result)); + + // ***** Generic Command Interface ***** + + // There's no *Redis::client_getname* interface. + // But you can use *Redis::command* to get the client name. + val = redis.command("client", "getname"); + if (val) { + std::cout << *val << std::endl; + } + + // Same as above. + auto getname_cmd_str = {"client", "getname"}; + val = redis.command(getname_cmd_str.begin(), getname_cmd_str.end()); + + // There's no *Redis::sort* interface. + // But you can use *Redis::command* to send sort the list. + std::vector sorted_list; + redis.command("sort", "list", "ALPHA", std::back_inserter(sorted_list)); + + // Another *Redis::command* to do the same work. + auto sort_cmd_str = {"sort", "list", "ALPHA"}; + redis.command(sort_cmd_str.begin(), sort_cmd_str.end(), std::back_inserter(sorted_list)); + + // ***** Redis Cluster ***** + + // Create a RedisCluster object, which is movable but NOT copyable. + auto redis_cluster = RedisCluster("tcp://127.0.0.1:7000"); + + // RedisCluster has similar interfaces as Redis. + redis_cluster.set("key", "value"); + val = redis_cluster.get("key"); + if (val) { + std::cout << *val << std::endl; + } // else key doesn't exist. + + // Keys with hash-tag. + redis_cluster.set("key{tag}1", "val1"); + redis_cluster.set("key{tag}2", "val2"); + redis_cluster.set("key{tag}3", "val3"); + + std::vector hash_tag_res; + redis_cluster.mget({"key{tag}1", "key{tag}2", "key{tag}3"}, + std::back_inserter(hash_tag_res)); + +} catch (const Error &e) { + // Error handling. +} +``` + +## API Reference + +### Connection + +`Redis` class maintains a connection pool to Redis server. If the connection is broken, `Redis` reconnects to Redis server automatically. + +You can initialize a `Redis` instance with `ConnectionOptions` and `ConnectionPoolOptions`. `ConnectionOptions` specifies options for connection to Redis server, and `ConnectionPoolOptions` specifies options for conneciton pool. `ConnectionPoolOptions` is optional. If not specified, `Redis` maintains a single connection to Redis server. + +```C++ +ConnectionOptions connection_options; +connection_options.host = "127.0.0.1"; // Required. +connection_options.port = 6666; // Optional. The default port is 6379. +connection_options.password = "auth"; // Optional. No password by default. +connection_options.db = 1; // Optional. Use the 0th database by default. + +// Optional. Timeout before we successfully send request to or receive response from redis. +// By default, the timeout is 0ms, i.e. never timeout and block until we send or receive successfuly. +// NOTE: if any command is timed out, we throw a TimeoutError exception. +connection_options.socket_timeout = std::chrono::milliseconds(200); + +// Connect to Redis server with a single connection. +Redis redis1(connection_options); + +ConnectionPoolOptions pool_options; +pool_options.size = 3; // Pool size, i.e. max number of connections. + +// Connect to Redis server with a connection pool. +Redis redis2(connection_options, pool_options); +``` + +See [ConnectionOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/connection.h#L40) and [ConnectionPoolOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/connection_pool.h#L30) for more options. + +**NOTE**: `Redis` class is movable but NOT copyable. + +```C++ +// auto redis3 = redis1; // this won't compile. + +// But it's movable. +auto redis3 = std::move(redis1); +``` + +*redis-plus-plus* also supports connecting to Redis server with Unix Domain Socket. + +```C++ +ConnectionOptions options; +options.type = ConnectionType::UNIX; +options.path = "/path/to/socket"; +Redis redis(options); +``` + +You can also connect to Redis server with a URI. However, in this case, you can only specify *host* and *port*, or *Unix Domain Socket path*. In order to specify other options, you need to use `ConnectionOptions` and `ConnectionPoolOptions`. + +```C++ +// Single connection to the given host and port. +Redis redis1("tcp://127.0.0.1:6666"); + +// Use default port, i.e. 6379. +Redis redis2("tcp://127.0.0.1"); + +// Connect to Unix Domain Socket. +Redis redis3("unix://path/to/socket"); +``` + +#### Lazily Create Connection + +Connections in the pool are lazily created. When the connection pool is initialized, i.e. the constructor of `Redis`, `Redis` does NOT connect to the server. Instead, it connects to the server only when you try to send command. In this way, we can avoid unnecessary connections. So if the pool size is 5, but the number of max concurrent connections is 3, there will be only 3 connections in the pool. + +#### Connection Failure + +You don't need to check whether `Redis` object connects to server successfully. If `Redis` fails to create a connection to Redis server, or the connection is broken at some time, it throws an exception of type `Error` when you try to send command with `Redis`. Even when you get an exception, i.e. the connection is broken, you don't need to create a new `Redis` object. You can reuse the `Redis` object to send commands, and the `Redis` object will try to reconnect to server automatically. If it reconnects successfully, it sends command to server. Otherwise, it throws an exception again. + +See the [Exception section](#exception) for details on exceptions. + +#### Reuse Redis object As Much As Possible + +It's NOT cheap to create a `Redis` object, since it will create new connections to Redis server. So you'd better reuse `Redis` object as much as possible. Also, it's safe to call `Redis`' member functions in multi-thread environment, and you can share `Redis` object in multiple threads. + +```C++ +// This is GOOD practice. +auto redis = Redis("tcp://127.0.0.1"); +for (auto idx = 0; idx < 100; ++idx) { + // Reuse the Redis object in the loop. + redis.set("key", "val"); +} + +// This is VERY BAD! It's very inefficient. +// NEVER DO IT!!! +for (auto idx = 0; idx < 100; ++idx) { + // Create a new Redis object for each iteration. + auto redis = Redis("tcp://127.0.0.1"); + redis.set("key", "val"); +} +``` + +### Send Command to Redis Server + +You can send [Redis commands](https://redis.io/commands) through `Redis` object. `Redis` has one or more (overloaded) methods for each Redis command. The method has the same (lowercased) name as the corresponding command. For example, we have 3 overload methods for the `DEL key [key ...]` command: + +```C++ +// Delete a single key. +long long Redis::del(const StringView &key); + +// Delete a batch of keys: [first, last). +template +long long Redis::del(Input first, Input last); + +// Delete keys in the initializer_list. +template +long long Redis::del(std::initializer_list il); +``` + +With input parameters, these methods build a Redis command based on [Redis protocol](https://redis.io/topics/protocol), and send the command to Redis server. Then synchronously receive the reply, parse it, and return to the caller. + +Let's take a closer look at these methods' parameters and return values. + +#### Parameter Type + +Most of these methods have the same parameters as the corresponding commands. The following is a list of parameter types: + +| Parameter Type | Explaination | Example | Note | +| :------------: | ------------ | ------- | ---- | +| **StringView** | Parameters of string type. Normally used for key, value, member name, field name and so on | ***bool Redis::hset(const StringView &key, const StringView &field, const StringView &val)*** | See the [StringView section](#stringview) for details on `StringView` | +| **long long** | Parameters of integer type. Normally used for index (e.g. list commands) or integer | ***void ltrim(const StringView &key, long long start, long long stop)***
***long long decrby(const StringView &key, long long decrement)*** | | +| **double** | Parameters of floating-point type. Normally used for score (e.g. sorted set commands) or number of floating-point type | ***double incrbyfloat(const StringView &key, double increment)*** | | +| **std::chrono::duration**
**std::chrono::time_point** | Time-related parameters | ***bool expire(const StringView &key, const std::chrono::seconds &timeout)***
***bool expireat(const StringView &key, const std::chrono::time_point &tp)*** | | +| **std::pair** | Used for Redis hash's (field, value) pair | ***bool hset(const StringView &key, const std::pair &item)*** | | +| **std::pair** | Used for Redis geo's (longitude, latitude) pair | ***OptionalLongLong georadius(const StringView &key, const std::pair &location, double radius, GeoUnit unit, const StringView &destination, bool store_dist, long long count)*** | | +| **pair of iterators** | Use a pair of iterators to specify a range of input, so that we can pass the data in a STL container to these methods | ***template < typename Input >***
***long long del(Input first, Input last)*** | Throw an exception, if it's an empty range, i.e. *first == last* | +| **std::initializer_list< T >** | Use an initializer list to specify a batch of input | ***template < typename T >***
***long long del(std::initializer_list< T > il)*** | | +| **some options** | Options for some commands | ***UpdateType***, ***template < typename T > class BoundedInterval*** | See [command_options.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/command_options.h) for details | + +##### StringView + +[std::string_view](http://en.cppreference.com/w/cpp/string/basic_string_view) is a good option for the type of string parameters. However, by now, not all compilers support `std::string_view`. So we wrote a [simple version](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/utils.h#L48), i.e. `StringView`. Since there are conversions from `std::string` and c-style string to `StringView`, you can just pass `std::string` or c-style string to methods that need a `StringView` parameter. + +```C++ +// bool Redis::hset(const StringView &key, const StringView &field, const StringView &val) + +// Pass c-style string to StringView. +redis.hset("key", "field", "value"); + +// Pass std::string to StringView. +std::string key = "key"; +std::string field = "field"; +std::string val = "val"; +redis.hset(key, field, val); + +// Mix std::string and c-style string. +redis.hset(key, field, "value"); +``` + +#### Return Type + +[Redis protocol](https://redis.io/topics/protocol) defines 5 kinds of replies: +- *Status Reply*: Also known as *Simple String Reply*. It's a non-binary string reply. +- *Bulk String Reply*: Binary safe string reply. +- *Integer Reply*: Signed integer reply. Large enough to hold `long long`. +- *Array Reply*: (Nested) Array reply. +- *Error Reply*: Non-binary string reply that gives error info. + +Also these replies might be *NULL*. For instance, when you try to `GET` the value of a nonexistent key, Redis returns a *NULL Bulk String Reply*. + +As we mentioned above, replies are parsed into return values of these methods. The following is a list of return types: + +| Return Type | Explaination | Example | Note | +| :---------: | ------------ | ------- | ---- | +| **void** | *Status Reply* that should always return a string of "OK" | *RENAME*, *SETEX* | | +| **std::string** | *Status Reply* that NOT always return "OK", and *Bulk String Reply* | *PING*, *INFO* | | +| **bool** | *Integer Reply* that always returns 0 or 1 | *EXPIRE*, *HSET* | See the [Boolean Return Value section](#boolean-return-value) for the meaning of a boolean return value | +| **long long** | *Integer Reply* that not always return 0 or 1 | *DEL*, *APPEND* | | +| **double** | *Bulk String Reply* that represents a double | *INCRBYFLOAT*, *ZINCRBY* | | +| **std::pair** | *Array Reply* with exactly 2 elements. Since the return value is always an array of 2 elements, we return the 2 elements as a `std::pair`'s first and second elements | *BLPOP* | | +| **std::tuple** | *Array Reply* with fixed length and has more than 2 elements. Since length of the returned array is fixed, we return the array as a `std::tuple` | *BZPOPMAX* | | +| **output iterator** | General *Array Reply* with non-fixed/dynamic length. We use STL-like interface to return this kind of array replies, so that you can insert the return value into a STL container easily | *MGET*, *LRANGE* | Also, sometimes the type of output iterator decides which options to send with the command. See the [Examples section](#command-overloads) for details | +| **Optional< T >** | For any reply of type `T` that might be *NULL* | *GET*, *LPOP*, *BLPOP*, *BZPOPMAX* | See the [Optional section](#optional) for details on `Optional` | + +##### Boolean Return Value + +The return type of some methods, e.g. `EXPIRE`, `HSET`, is `bool`. If the method returns `false`, it DOES NOT mean that `Redis` failed to send the command to Redis server. Instead, it means that Redis server returns an *Integer Reply*, and the value of the reply is `0`. Accordingly, if the method returns `true`, it means that Redis server returns an *Integer Reply*, and the value of the reply is `1`. You can +check [Redis commands manual](http://redis.io/commands) for what do `0` and `1` stand for. + +For example, when we send `EXPIRE` command to Redis server, it returns `1` if the timeout was set, and it returns `0` if the key doesn't exist. Accordingly, if the timeout was set, `Redis::expire` returns `true`, and if the key doesn't exist, `Redis::expire` returns `false`. + +So, never use the return value to check if the command has been successfully sent to Redis server. Instead, if `Redis` failed to send command to server, it throws an exception of type `Error`. See the [Exception section](#exception) for details on exceptions. + +##### Optional + +[std::optional](http://en.cppreference.com/w/cpp/utility/optional) is a good option for return type, if Redis might return *NULL REPLY*. Again, since not all compilers support `std::optional` so far, we implement our own [simple version](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/utils.h#L85), i.e. `Optional`. + +Take the [GET](https://redis.io/commands/get) and [MGET](https://redis.io/commands/mget) commands for example: + +```C++ +// Or just: auto val = redis.get("key"); +Optional val = redis.get("key"); + +// Optional has a conversion to bool. +// If it's NOT a null Optional object, it's converted to true. +// Otherwise, it's converted to false. +if (val) { + // Key exists. Dereference val to get the string result. + std::cout << *val << std::endl; +} else { + // Redis server returns a NULL Bulk String Reply. + // It's invalid to dereference a null Optional object. + std::cout << "key doesn't exist." << std::endl; +} + +std::vector> values; +redis.mget({"key1", "key2", "key3"}, std::back_inserter(values)); +for (const auto &val : values) { + if (val) { + // Key exist, process the value. + } +} +``` + +We also have some typedefs for some commonly used `Optional`: + +```C++ +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; +``` + +#### Exception + +`Redis` throws exceptions if it receives an *Error Reply* or something bad happens, e.g. failed to create a connection to server, or connection to server is broken. All exceptions derived from `Error` class. See [errors.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/errors.h) for details. + +- `Error`: Generic error. It's also the base class of other exceptions. +- `IoError`: There's some IO error with the connection. +- `TimeoutError`: Read or write operation was timed out. It's a derived class of `IoError`. +- `ClosedError`: Redis server closed the connection. +- `ProtoError`: The command or reply is invalid, and we cannot process it with Redis protocol. +- `OomError`: *hiredis* library got an out-of-memory error. +- `ReplyError`: Redis server returned an error reply, e.g. we try to call `redis::lrange` on a Redis hash. +- `WatchError`: Watched key has been modified. See [Watch section](#watch) for details. + +**NOTE**: *NULL REPLY*` is not taken as an exception. For example, if we try to `GET` a non-existent key, we'll get a *NULL Bulk String Reply*. Instead of throwing an exception, we return the *NULL REPLY* as a null `Optional` object. Also see [Optional section](#optional). + +#### Examples + +Let's see some examples on how to send commands to Redis server. + +##### Various Parameter Types + +```C++ +// ***** Parameters of StringView type ***** + +// Implicitly construct StringView with c-style string. +redis.set("key", "value"); + +// Implicitly construct StringView with std::string. +std::string key("key"); +std::string val("value"); +redis.set(key, val); + +// Explicitly pass StringView as parameter. +std::vector large_data; +// Avoid copying. +redis.set("key", StringView(large_data.data(), large_data.size())); + +// ***** Parameters of long long type ***** + +// For index. +redis.bitcount(key, 1, 3); + +// For number. +redis.incrby("num", 100); + +// ***** Parameters of double type ***** + +// For score. +redis.zadd("zset", "m1", 2.5); +redis.zadd("zset", "m2", 3.5); +redis.zadd("zset", "m3", 5); + +// For (longitude, latitude). +redis.geoadd("geo", std::make_tuple("member", 13.5, 15.6)); + +// ***** Time-related parameters ***** + +using namespace std::chrono; + +redis.expire(key, seconds(1000)); + +auto tp = time_point_cast(system_clock::now() + seconds(100)); +redis.expireat(key, tp); + +// ***** Some options for commands ***** + +if (redis.set(key, "value", milliseconds(100), UpdateType::NOT_EXIST)) { + std::cout << "set OK" << std::endl; +} + +redis.linsert("list", InsertPosition::BEFORE, "pivot", "val"); + +std::vector res; + +// (-inf, inf) +redis.zrangebyscore("zset", UnboundedInterval{}, std::back_inserter(res)); + +// [3, 6] +redis.zrangebyscore("zset", + BoundedInterval(3, 6, BoundType::CLOSED), + std::back_inserter(res)); + +// (3, 6] +redis.zrangebyscore("zset", + BoundedInterval(3, 6, BoundType::LEFT_OPEN), + std::back_inserter(res)); + +// (3, 6) +redis.zrangebyscore("zset", + BoundedInterval(3, 6, BoundType::OPEN), + std::back_inserter(res)); + +// [3, 6) +redis.zrangebyscore("zset", + BoundedInterval(3, 6, BoundType::RIGHT_OPEN), + std::back_inserter(res)); + +// [3, +inf) +redis.zrangebyscore("zset", + LeftBoundedInterval(3, BoundType::RIGHT_OPEN), + std::back_inserter(res)); + +// (3, +inf) +redis.zrangebyscore("zset", + LeftBoundedInterval(3, BoundType::OPEN), + std::back_inserter(res)); + +// (-inf, 6] +redis.zrangebyscore("zset", + RightBoundedInterval(6, BoundType::LEFT_OPEN), + std::back_inserter(res)); + +// (-inf, 6) +redis.zrangebyscore("zset", + RightBoundedInterval(6, BoundType::OPEN), + std::back_inserter(res)); + +// ***** Pair of iterators ***** + +std::vector> kvs = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}; +redis.mset(kvs.begin(), kvs.end()); + +std::unordered_map kv_map = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}; +redis.mset(kv_map.begin(), kv_map.end()); + +std::unordered_map str_map = {{"f1", "v1"}, {"f2", "v2"}, {"f3", "v3"}}; +redis.hmset("hash", str_map.begin(), str_map.end()); + +std::unordered_map score_map = {{"m1", 20}, {"m2", 12.5}, {"m3", 3.14}}; +redis.zadd("zset", score_map.begin(), score_map.end()); + +std::vector keys = {"k1", "k2", "k3"}; +redis.del(keys.begin(), keys.end()); + +// ***** Parameters of initializer_list type ***** + +redis.mset({ + std::make_pair("k1", "v1"), + std::make_pair("k2", "v2"), + std::make_pair("k3", "v3") +}); + +redis.hmset("hash", + { + std::make_pair("f1", "v1"), + std::make_pair("f2", "v2"), + std::make_pair("f3", "v3") + }); + +redis.zadd("zset", + { + std::make_pair("m1", 20.0), + std::make_pair("m2", 34.5), + std::make_pair("m3", 23.4) + }); + +redis.del({"k1", "k2", "k3"}); +``` + +##### Various Return Types + +```C++ +// ***** Return void ***** + +redis.save(); + +// ***** Return std::string ***** + +auto info = redis.info(); + +// ***** Return bool ***** + +if (!redis.expire("nonexistent", std::chrono::seconds(100))) { + std::cerr << "key doesn't exist" << std::endl; +} + +if (redis.setnx("key", "val")) { + std::cout << "set OK" << std::endl; +} + +// ***** Return long long ***** + +auto len = redis.strlen("key"); +auto num = redis.del({"a", "b", "c"}); +num = redis.incr("a"); + +// ***** Return double ***** + +auto real = redis.incrbyfloat("b", 23.4); +real = redis.hincrbyfloat("c", "f", 34.5); + +// ***** Return Optional, i.e. OptionalString ***** + +auto os = redis.get("kk"); +if (os) { + std::cout << *os << std::endl; +} else { + std::cerr << "key doesn't exist" << std::endl; +} + +os = redis.spop("set"); +if (os) { + std::cout << *os << std::endl; +} else { + std::cerr << "set is empty" << std::endl; +} + +// ***** Return Optional, i.e. OptionalLongLong ***** + +auto oll = redis.zrank("zset", "mem"); +if (oll) { + std::cout << "rank is " << *oll << std::endl; +} else { + std::cerr << "member doesn't exist" << std::endl; +} + +// ***** Return Optional, i.e. OptionalDouble ***** + +auto ob = redis.zscore("zset", "m1"); +if (ob) { + std::cout << "score is " << *ob << std::endl; +} else { + std::cerr << "member doesn't exist" << std::endl; +} + +// ***** Return Optional> ***** + +auto op = redis.blpop({"list1", "list2"}, std::chrono::seconds(2)); +if (op) { + std::cout << "key is " << op->first << ", value is " << op->second << std::endl; +} else { + std::cerr << "timeout" << std::endl; +} + +// ***** Output iterators ***** + +std::vector os_vec; +redis.mget({"k1", "k2", "k3"}, std::back_inserter(os_vec)); + +std::vector s_vec; +redis.lrange("list", 0, -1, std::back_inserter(s_vec)); + +std::unordered_map hash; +redis.hgetall("hash", std::inserter(hash, hash.end())); +// You can also save the result in a vecotr of string pair. +std::vector> hash_vec; +redis.hgetall("hash", std::back_inserter(hash_vec)); + +std::unordered_set str_set; +redis.smembers("s1", std::inserter(str_set, str_set.end())); +// You can also save the result in a vecotr of string. +s_vec.clear(); +redis.smembers("s1", std::back_inserter(s_vec)); +``` + +##### SCAN Commands + +```C++ +auto cursor = 0LL; +auto pattern = "*pattern*"; +auto count = 5; +std::vector scan_vec; +while (true) { + cursor = redis.scan(cursor, pattern, count, std::back_inserter(scan_vec)); + // Default pattern is "*", and default count is 10 + // cursor = redis.scan(cursor, std::back_inserter(scan_vec)); + + if (cursor == 0) { + break; + } +} +``` + +##### Command Overloads + +Sometimes the type of output iterator decides which options to send with the command. + +```C++ +// If the output iterator is an iterator of a container of string, +// we send *ZRANGE* command without the *WITHSCORES* option. +std::vector members; +redis.zrange("list", 0, -1, std::back_inserter(members)); + +// If it's an iterator of a container of a pair, +// we send *ZRANGE* command with *WITHSCORES* option. +std::unordered_map res_with_score; +redis.zrange("list", 0, -1, std::inserter(res_with_score, res_with_score.end())); + +// The above examples also apply to other command with the *WITHSCORES* options, +// e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + +// Another example is the *GEORADIUS* command. + +// Only get members. +members.clear(); +redis.georadius("geo", + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(members)); + +// If the iterator is an iterator of a container of tuple, +// we send the *GEORADIUS* command with *WITHDIST* option. +std::vector> mem_with_dist; +redis.georadius("geo", + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mem_with_dist)); + +// If the iterator is an iterator of a container of tuple, +// we send the *GEORADIUS* command with *WITHDIST* and *WITHHASH* options. +std::vector> mem_with_dist_hash; +redis.georadius("geo", + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mem_with_dist_hash)); + +// If the iterator is an iterator of a container of +// tuple, double>, +// we send the *GEORADIUS* command with *WITHHASH*, *WITHCOORD* and *WITHDIST* options. +std::vector> mem_with_hash_coord_dist; +redis.georadius("geo", + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mem_with_hash_coord_dist)); +``` + +Please see [redis.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/redis.h) for more API references, and see the [tests](https://github.com/sewenew/redis-plus-plus/tree/master/test/src/sw/redis%2B%2B) for more examples. + +### Generic Command Interface + +There're too many Redis commands, we haven't implemented all of them. However, you can use the generic `Redis::command` methods to send any commands to Redis. Unlike other client libraries, `Redis::command` doesn't use format string to combine command arguments into a command string. Instead, you can directly pass command arguments of `StringView` type or arithmetic type as parameters of `Redis::command`. For the reason why we don't use format string, please see [this discussion](https://github.com/sewenew/redis-plus-plus/pull/2). + +```C++ +auto redis = Redis("tcp://127.0.0.1"); + +// Redis class doesn't have built-in *CLIENT SETNAME* method. +// However, you can use Redis::command to send the command manually. +redis.command("client", "setname", "name"); +auto val = redis.command("client", "getname"); +if (val) { + std::cout << *val << std::endl; +} + +// NOTE: the following code is for example only. In fact, Redis has built-in +// methods for the following commands. + +// Arguments of the command can be strings. +// NOTE: for SET command, the return value is NOT always void, I'll explain latter. +redis.command("set", "key", "100"); + +// Arguments of the command can be a combination of strings and integers. +auto num = redis.command("incrby", "key", 1); + +// Argument can also be double. +auto real = redis.command("incrbyfloat", "key", 2.3); + +// Even the key of the command can be of arithmetic type. +redis.command("set", 100, "value"); + +val = redis.command("get", 100); + +// If the command returns an array of elements. +std::vector result; +redis.command("mget", "k1", "k2", "k3", std::back_inserter(result)); + +// Or just parse it into a vector. +result = redis.command>("mget", "k1", "k2", "k3"); + +// Arguments of the command can be a range of strings. +auto set_cmd_strs = {"set", "key", "value"}; +redis.command(set_cmd_strs.begin(), set_cmd_strs.end()); + +auto get_cmd_strs = {"get", "key"}; +val = redis.command(get_cmd_strs.begin(), get_cmd_strs.end()); + +// If it returns an array of elements. +result.clear(); +auto mget_cmd_strs = {"mget", "key1", "key2"}; +redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end(), std::back_inserter(result)); +``` + +**NOTE**: The name of some Redis commands is composed with two strings, e.g. *CLIENT SETNAME*. In this case, you need to pass these two strings as two arguments for `Redis::command`. + +```C++ +// This is GOOD. +redis.command("client", "setname", "name"); + +// This is BAD, and will fail to send command to Redis server. +// redis.command("client setname", "name"); +``` + +As I mentioned in the comments, the `SET` command not always returns `void`. Because if you try to set a (key, value) pair with *NX* or *XX* option, you might fail, and Redis will return a *NULL REPLY*. Besides the `SET` command, there're other commands whose return value is NOT a fixed type, you need to parse it by yourself. For example, `Redis::set` method rewrite the reply of `SET` command, and make it return `bool` type, i.e. if no *NX* or *XX* option specified, Redis server will always return an "OK" string, and `Redis::set` returns `true`; if *NX* or *XX* specified, and Redis server returns a *NULL REPLY*, `Redis::set` returns `false`. + +So `Redis` class also has other overloaded `command` methods, these methods return a `ReplyUPtr`, i.e. `std::unique_ptr`, object. Normally you don't need to parse it manually. Instead, you only need to pass the reply to `template T reply::parse(redisReply &)` to get a value of type `T`. Check the [Return Type section](#return-type) for valid `T` types. If the command returns an array of elements, besides calling `reply::parse` to parse the reply to an STL container, you can also call `template reply::to_array(redisReply &reply, Output output)` to parse the result into an array or STL container with an output iterator. + +Let's rewrite the above examples: + +```C++ +auto redis = Redis("tcp://127.0.0.1"); + +redis.command("client", "setname", "name"); +auto r = redis.command("client", "getname"); +assert(r); + +// If the command returns a single element, +// use `reply::parse(redisReply&)` to parse it. +auto val = reply::parse(*r); +if (val) { + std::cout << *val << std::endl; +} + +// Arguments of the command can be strings. +redis.command("set", "key", "100"); + +// Arguments of the command can be a combination of strings and integers. +r = redis.command("incrby", "key", 1); +auto num = reply::parse(*r); + +// Argument can also be double. +r = redis.command("incrbyfloat", "key", 2.3); +auto real = reply::parse(*r); + +// Even the key of the command can be of arithmetic type. +redis.command("set", 100, "value"); + +r = redis.command("get", 100); +val = reply::parse(*r); + +// If the command returns an array of elements. +r = redis.command("mget", "k1", "k2", "k3"); +// Use `reply::to_array(redisReply&, OutputIterator)` to parse the result into an STL container. +std::vector result; +reply::to_array(*r, std::back_inserter(result)); + +// Or just call `reply::parse` to parse it into vector. +result = reply::parse>(*r); + +// Arguments of the command can be a range of strings. +auto get_cmd_strs = {"get", "key"}; +r = redis.command(get_cmd_strs.begin(), get_cmd_strs.end()); +val = reply::parse(*r); + +// If it returns an array of elements. +result.clear(); +auto mget_cmd_strs = {"mget", "key1", "key2"}; +r = redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end()); +reply::to_array(*r, std::back_inserter(result)); +``` + +In fact, there's one more `Redis::command` method: + +```C++ +template +auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; +``` + +However, this method exposes some implementation details, and is only for internal use. You should NOT use this method. + +### Publish/Subscribe + +You can use `Redis::publish` to publish messages to channels. `Redis` randomly picks a connection from the underlying connection pool, and publishes message with that connection. So you might publish two messages with two different connections. + +When you subscribe to a channel with a connection, all messages published to the channel are sent back to that connection. So there's NO `Redis::subscribe` method. Instead, you can call `Redis::subscriber` to create a `Subscriber` and the `Subscriber` maintains a connection to Redis. The underlying connection is a new connection, NOT picked from the connection pool. This new connection has the same `ConnectionOptions` as the `Redis` object. + +With `Subscriber`, you can call `Subscriber::subscribe`, `Subscriber::unsubscribe`, `Subscriber::psubscribe` and `Subscriber::punsubscribe` to send *SUBSCRIBE*, *UNSUBSCRIBE*, *PSUBSCRIBE* and *PUNSUBSCRIBE* commands to Redis. + +#### Thread Safety + +`Subscriber` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually. + +#### Subscriber Callbacks + +There are 6 kinds of messages: +- *MESSAGE*: message sent to a channel. +- *PMESSAGE*: message sent to channels of a given pattern. +- *SUBSCRIBE*: message sent when we successfully subscribe to a channel. +- *UNSUBSCRIBE*: message sent when we successfully unsubscribe to a channel. +- *PSUBSCRIBE*: message sent when we successfully subscribe to a channel pattern. +- *PUNSUBSCRIBE*: message sent when we successfully unsubscribe to a channel pattern. + +We call messages of *SUBSCRIBE*, *UNSUBSCRIBE*, *PSUBSCRIBE* and *PUNSUBSCRIBE* types as *META MESSAGE*s. + +In order to process these messages, you can set callback functions on `Subscriber`: +- `Subscriber::on_message(MsgCallback)`: set callback function for messages of *MESSAGE* type, and the callback interface is: `void (std::string channel, std::string msg)`. +- `Subscriber::on_pmessage(PatternMsgCallback)`: set the callback function for messages of *PMESSAGE* type, and the callback interface is: `void (std::string pattern, std::string channel, std::string msg)`. +- `Subscriber::on_meta(MetaCallback)`: set callback function for messages of *META MESSAGE* type, and the callback interface is: `void (Subscriber::MsgType type, OptionalString channel, long long num)`. `type` is an enum, it can be one of the following enum: `Subscriber::MsgType::SUBSCRIBE`, `Subscriber::MsgType::UNSUBSCRIBE`, `Subscriber::MsgType::PSUBSCRIBE`, `Subscriber::MsgType::PUNSUBSCRIBE`, `Subscriber::MsgType::MESSAGE`, and `Subscriber::MsgType::PMESSAGE`. If you haven't subscribe/psubscribe to any channel/pattern, and try to unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all channels/patterns, *channel* will be null. So the second parameter of meta callback is of type `OptionalString`. + +All these callback interfaces pass `std::string` by value, and you can take their ownership (i.e. `std::move`) safely. + +#### Consume Messages + +You can call `Subscriber::consume` to consume messages published to channels/patterns that the `Subscriber` has been subscribed. + +`Subscriber::consume` waits for message from the underlying connection. If the `ConnectionOptions::socket_timeout` is reached, and there's no message sent to this connection, `Subscriber::consume` throws a `TimeoutError` exception. If `ConnectionOptions::socket_timeout` is `0ms`, `Subscriber::consume` blocks until it receives a message. + +After receiving the message, `Subscriber::consume` calls the callback function to process the message based on message type. However, if you don't set callback for a specific kind of message, `Subscriber::consume` will ignore the received message, i.e. no callback will be called. + +#### Examples + +The following example is a common pattern for using `Subscriber`: + +```C++ +// Create a Subscriber. +auto sub = redis.subscriber(); + +// Set callback functions. +sub.on_message([](std::string channel, std::string msg) { + // Process message of MESSAGE type. + }); + +sub.on_pmessage([](std::string pattern, std::string channel, std::string msg) { + // Process message of PMESSAGE type. + }); + +sub.on_meta([](Subscriber::MsgType type, OptionalString channel, long long num) { + // Process message of META type. + }); + +// Subscribe to channels and patterns. +sub.subscribe("channel1"); +sub.subscribe({"channel2", "channel3"}); + +sub.psubscribe("pattern1*"); + +// Consume messages in a loop. +while (true) { + try { + sub.consume(); + } catch (const Error &err) { + // Handle exceptions. + } +} +``` + +If `ConnectionOptions::socket_timeout` is set, you might get `TimeoutError` exception before receiving a message: + +```C++ +while (true) { + try { + sub.consume(); + } catch (const TimeoutError &e) { + // Try again. + continue; + } catch (const Error &err) { + // Handle other exceptions. + } +} +``` + +The above examples use lambda as callback. If you're not familiar with lambda, you can also set a free function as callback. Check [this issue](https://github.com/sewenew/redis-plus-plus/issues/16) for detail. + +### Pipeline + +[Pipeline](https://redis.io/topics/pipelining) is used to reduce *RTT* (Round Trip Time), and speed up Redis queries. *redis-plus-plus* supports pipeline with the `Pipeline` class. + +#### Create Pipeline + +You can create a pipeline with `Redis::pipeline` method, which returns a `Pipeline` object. + +```C++ +ConnectionOptions connection_options; +ConnectionPoolOptions pool_options; + +Redis redis(connection_options, pool_options); + +auto pipe = redis.pipeline(); +``` + +When creating a `Pipeline` object, `Redis::pipeline` method creates a new connection to Redis server. This connection is NOT picked from the connection pool, but a newly created connection. This connection has the same `ConnectionOptions` as other connections in the connection pool. `Pipeline` object maintains the new connection, and all piped commands are sent through this connection. + +**NOTE**: Creating a `Pipeline` object is NOT cheap, since it creates a new connection. So you'd better reuse the `Pipeline` object as much as possible. + +#### Send Commands + +You can send Redis commands through the `Pipeline` object. Just like the `Redis` class, `Pipeline` has one or more (overloaded) methods for each Redis command. However, you CANNOT get the replies until you call `Pipeline::exec`. So these methods do NOT return the reply, instead they return the `Pipeline` object itself. And you can chain these methods calls. + +```C++ +pipe.set("key", "val").incr("num").rpush("list", {0, 1, 2}).command("hset", "key", "field", "value"); +``` + +#### Get Replies + +Once you finish sending commands to Redis, you can call `Pipeline::exec` to get replies of these commands. You can also chain `Pipeline::exec` with other commands. + +```C++ +pipe.set("key", "val").incr("num"); +auto replies = pipe.exec(); + +// The same as: +replies = pipe.set("key", "val").incr("num).exec(); +``` + +In fact, these commands won't be sent to Redis, until you call `Pipeline::exec`. So `Pipeline::exec` does 2 work in order: send all piped commands, then get all replies from Redis. + +Also you can call `Pipeline::discard` to discard those piped commands. + +```C++ +pipe.set("key", "val").incr("num"); + +pipe.discard(); +``` + +#### Parse Replies + +`Pipeline::exec` returns a `QueuedReplies` object, which contains replies of all commands that have been sent to Redis. You can use `QueuedReplies::get` method to get and parse the `ith` reply. It has 3 overloads: + +- `template Result get(std::size_t idx)`: Return the `ith` reply as a return value, and you need to specify the return type as tempalte parameter. +- `template void get(std::size_t idx, Output output)`: If the reply is of type *Array Reply*, you can call this method to write the `ith` reply to an output iterator. Normally, compiler will deduce the type of the output iterator, and you don't need to specify the type parameter explicitly. +- `redisReply& get(std::size_t idx)`: If the reply is NOT a fixed type, call this method to get a reference to `redisReply` object. In this case, you need to call `template T reply::parse(redisReply &)` to parse the reply manually. + +Check the [Return Type section](#return-type) for details on the return types of the result. + +```C++ +auto replies = pipe.set("key", "val").incr("num").lrange("list", 0, -1).exec(); + +auto set_cmd_result = replies.get(0); + +auto incr_cmd_result = replies.get(1); + +std::vector list_cmd_result; +replies.get(2, std::back_inserter(list_cmd_result)); +``` + +#### Exception + +If any of `Pipeline`'s method throws an exception, the `Pipeline` object enters an invalid state. You CANNOT use it any more, but only destroy the object, and create a new one. + +#### Thread Safety + +`Pipeline` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually. + +### Transaction + +[Transaction](https://redis.io/topics/transactions) is used to make multiple commands runs atomically. + +#### Create Transaction + +You can create a transaction with `Redis::transaction` method, which returns a `Transaction` object. + +```C++ +ConnectionOptions connection_options; +ConnectionPoolOptions pool_options; + +Redis redis(connection_options, pool_options); + +auto tx = redis.transaction(); +``` + +As the `Pipeline` class, `Transaction` maintains a newly created connection to Redis. This connection has the same `ConnectionOptions` as the `Redis` object. + +**NOTE**: Creating a `Transaction` object is NOT cheap, since it creates a new connection. So you'd better reuse the `Transaction` as much as possible. + +Also you don't need to send [MULTI](https://redis.io/commands/multi) command to Redis. `Transaction` will do that for you automatically. + +#### Send Commands + +`Transaction` shares most of implementation with `Pipeline`. It has the same interfaces as `Pipeline`. You can send commands as what you do with `Pipeline` object. + +```C++ +tx.set("key", "val").incr("num").lpush("list", {0, 1, 2}).command("hset", "key", "field", "val"); +``` + +#### Execute Transaction + +When you call `Transaction::exec`, you explicitly ask Redis to execute those queued commands, and return the replies. Otherwise, these commands won't be executed. Also, you can call `Transaction::discard` to discard the execution, i.e. no command will be executed. Both `Transaction::exec` and `Transaction::discard` can be chained with other commands. + +```C++ +auto replies = tx.set("key", "val").incr("num").exec(); + +tx.set("key", "val").incr("num"); + +// Discard the transaction. +tx.discard(); +``` + +#### Parse Replies + +See [Pipeline's Parse Replies section](#parse-replies) for how to parse the replies. + +#### Piped Transaction + +Normally, we always send multiple commnds in a transaction. In order to improve the performance, you can send these commands in a pipeline. You can create a piped transaction by passing `true` as parameter of `Redis::transaction` method. + +```C++ +// Create a piped transaction +auto tx = redis.transaction(true); +``` + +With this piped transaction, all commands are sent to Redis in a pipeline. + +#### Exception + +If any of `Transaction`'s method throws an exception other than `WatchError`, the `Transaction` object enters an invalid state. You CANNOT use it any more, but only destroy the object and create a new one. + +#### Thread Safety + +`Transacation` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually. + +#### Watch + +[WATCH is used to provide a check-and-set(CAS) behavior to Redis transactions](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set). + +The `WATCH` command must be sent in the same connection as the transaction. And normally after the `WATCH` command, we also need to send some other commands to get data from Redis before executing the transaction. Take the following check-and-set case as an example: + +``` +WATCH key // watch a key +val = GET key // get value of the key +new_val = val + 1 // incr the value +MULTI // begin the transaction +SET key new_val // set value only if the value is NOT modified by others +EXEC // try to execute the transaction. + // if val has been modified, the transaction won't be executed. +``` + +However, with `Transaction` object, you CANNOT get the result of commands until the whole transaction has been finished. Instead, you need to create a `Redis` object from the `Transaction` object. The created `Redis` object shares the connection with `Transaction` object. With this created `Redis` object, you can send `WATCH` command and any other Redis commands to Redis server, and get the result immediately. + +Let's see how to implement the above example with *redis-plus-plus*: + +```C++ +auto redis = Redis("tcp://127.0.0.1"); + +// Create a transaction. +auto tx = redis.transaction(); + +// Create a Redis object from the Transaction object. Both objects share the same connection. +auto r = tx.redis(); + +// If the watched key has been modified by other clients, the transaction might fail. +// So we need to retry the transaction in a loop. +while (true) { + try { + // Watch a key. + r.watch("key"); + + // Get the old value. + auto val = r.get("key"); + auto num = 0; + if (val) { + num = std::stoi(*val); + } // else use default value, i.e. 0. + + // Incr value. + ++num; + + // Execute the transaction. + auto replies = tx.set("key", std::to_string(num)).exec(); + + // Transaction has been executed successfully. Check the result and break. + + assert(replies.size() == 1 && replies.get(0) == true); + + break; + } catch (const WatchError &err) { + // Key has been modified by other clients, retry. + continue; + } catch (const Error &err) { + // Something bad happens, and the Transaction object is no longer valid. + throw; + } +} +``` + +### Redis Cluster + +*redis-plus-plus* supports [Redis Cluster](https://redis.io/topics/cluster-tutorial). You can use `RedisCluster` class to send commands to Redis Cluster. It has similar interfaces as `Redis` class. + +#### Connection + +`RedisCluster` connects to all master nodes in the cluster. For each master node, it maintains a connection pool. By now, it doesn't connect to slave nodes. + +You can initialize a `RedisCluster` instance with `ConnectionOptions` and `ConnectionPoolOptions`. You only need to set one master node's host & port in `ConnectionOptions`, and `RedisCluster` will get other nodes' info automatically (with the *CLUSTER SLOTS* command). For each master node, it creates a connection pool with the specified `ConnectionPoolOptions`. If `ConnectionPoolOptions` is not specified, `RedisCluster` maintains a single connection to every master node. + +```C++ +// Set a master node's host & port. +ConnectionOptions connection_options; +connection_options.host = "127.0.0.1"; // Required. +connection_options.port = 7000; // Optional. The default port is 6379. +connection_options.password = "auth"; // Optional. No password by default. + +// Automatically get other nodes' info, +// and connect to every master node with a single connection. +RedisCluster cluster1(connection_options); + +ConnectionPoolOptions pool_options; +pool_options.size = 3; + +// For each master node, maintains a connection pool of size 3. +RedisCluster cluster2(connection_options, pool_options); +``` + +You can also specify connection option with an URI. However, in this way, you can only use default `ConnectionPoolOptions`, i.e. pool of size 1, and CANNOT specify password. + +```C++ +// Specify a master node's host & port. +RedisCluster cluster3("tcp://127.0.0.1:7000"); + +// Use default port, i.e. 6379. +RedisCluster cluster4("tcp://127.0.0.1"); +``` + +##### Note + +- `RedisCluster` only works with tcp connection. It CANNOT connect to Unix Domain Socket. If you specify Unix Domain Socket in `ConnectionOptions`, it throws an exception. +- All nodes in the cluster should have the same password. +- Since [Redis Cluster does NOT support multiple databses](https://redis.io/topics/cluster-spec#implemented-subset), `ConnectionOptions::db` is ignored. + +#### Interfaces + +As we mentioned above, `RedisCluster`'s interfaces are similar to `Redis`. It supports most of `Redis`' interfaces, including the [generic command interface](#generic-command-interface) (see `Redis`' [API Reference section](#api-reference) for details), except the following: + +- Not support commands without key as argument, e.g. `PING`, `INFO`. +- Not support Lua script without key parameters. + +Since there's no key parameter, `RedisCluster` has no idea on to which node these commands should be sent. However there're 2 workarounds for this problem: + +- If you want to send these commands to a specific node, you can create a `Redis` object with that node's host and port, and use the `Redis` object to do the work. +- Instead of host and port, you can also call `Redis RedisCluster::redis(const StringView &hash_tag)` to create a `Redis` object with a hash-tag specifying the node. In this case, the returned `Redis` object creates a new connection to Redis server. + +Also you can use the [hash tags](https://redis.io/topics/cluster-spec#keys-hash-tags) to send multiple-key commands. + +See the [example section](#examples-2) for details. + +##### Publish/Subscribe + +You can publish and subscribe messages with `RedisCluster`. The interfaces are exactly the same as `Redis`, i.e. use `RedisCluster::publish` to publish messages, and use `RedisCluster::subscriber` to create a subscriber to consume messages. See [Publish/Subscribe section](#publishsubscribe) for details. + +##### Pipeline and Transaction + +You can also create `Pipeline` and `Transaction` objects with `RedisCluster`, but the interfaces are different from `Redis`. Since all commands in the pipeline and transaction should be sent to a single node in a single connection, we need to tell `RedisCluster` with which node the pipeline or transaction should be created. + +Instead of specifing the node's IP and port, `RedisCluster`'s pipeline and transaction interfaces allow you to specify the node with a *hash tag*. `RedisCluster` will calculate the slot number with the given *hash tag*, and create a pipeline or transaction with the node holding the slot. + +```C++ +Pipeline RedisCluster::pipeline(const StringView &hash_tag); + +Transaction RedisCluster::transaction(const StringView &hash_tag, bool piped = false); +``` + +With the created `Pipeline` or `Transaction` object, you can send commands with keys located on the same node as the given *hash_tag*. See [Examples section](#examples-2) for an example. + +#### Examples + +```C++ +#include + +using namespace sw::redis; + +auto redis_cluster = RedisCluster("tcp://127.0.0.1:7000"); + +redis_cluster.set("key", "value"); +auto val = redis_cluster.get("key"); +if (val) { + std::cout << *val << std::endl; +} + +// With hash-tag. +redis_cluster.set("key{tag}1", "val1"); +redis_cluster.set("key{tag}2", "val2"); +redis_cluster.set("key{tag}3", "val3"); +std::vector hash_tag_res; +redis_cluster.mget({"key{tag}1", "key{tag}2", "key{tag}3"}, + std::back_inserter(hash_tag_res)); + +redis_cluster.lpush("list", {"1", "2", "3"}); +std::vector list; +redis_cluster.lrange("list", 0, -1, std::back_inserter(list)); + +// Pipline. +auto pipe = redis_cluster.pipeline("counter"); +auto replies = pipe.incr("{counter}:1").incr("{counter}:2").exec(); + +// Transaction. +auto tx = redis_cluster.transaction("key"); +replies = tx.incr("key").get("key").exec(); + +// Create a Redis object with hash-tag. +// It connects to the Redis instance that holds the given key, i.e. hash-tag. +auto r = redis_cluster.redis("hash-tag"); + +// And send command without key parameter to the server. +r.command("client", "setname", "connection-name"); +``` + +**NOTE**: When you use `RedisCluster::redis(const StringView &hash_tag)` to create a `Redis` object, instead of picking a connection from the underlying connection pool, it creates a new connection to the corresponding Redis server. So this is NOT a cheap operation, and you should try to reuse this newly created `Redis` object as much as possible. + +```C++ +// This is BAD! It's very inefficient. +// NEVER DO IT!!! +// After sending PING command, the newly created Redis object will be destroied. +cluster.redis("key").ping(); + +// Then it creates a connection to Redis, and closes the connection after sending the command. +cluster.redis("key").command("client", "setname", "hello"); + +// Instead you should reuse the Redis object. +// This is GOOD! +auto redis = cluster.redis("key"); + +redis.ping(); +redis.command("client", "setname", "hello"); +``` + +#### Details + +`RedisCluster` maintains the newest slot-node mapping, and sends command directly to the right node. Normally it works as fast as `Redis`. If the cluster reshards, `RedisCluster` will follow the redirection, and it will finally update the slot-node mapping. It can correctly handle the following resharding cases: + +- Data migration between exist nodes. +- Add new node to the cluster. +- Remove node from the cluster. + +`redis-plus-plus` is able to handle both [MOVED](https://redis.io/topics/cluster-spec#moved-redirection) and [ASK](https://redis.io/topics/cluster-spec#ask-redirection) redirections, so it's a complete Redis Cluster client. + +If master is down, the cluster will promote one of its replicas to be the new master. *redis-plus-plus* can also handle this case: + +- When the master is down, *redis-plus-plus* losts connection to it. In this case, if you try to send commands to this master, *redis-plus-plus* will try to update slot-node mapping from other nodes. If the mapping remains unchanged, i.e. new master hasn't been elected yet, it fails to send command to Redis Cluster and throws exception. +- When the new master has been elected, the slot-node mapping will be updated by the cluster. In this case, if you send commands to the cluster, *redis-plus-plus* can get an update-to-date mapping, and sends commands to the new master. + +### Redis Sentinel + +[Redis Sentinel provides high availability for Redis](https://redis.io/topics/sentinel). If Redis master is down, Redis Sentinels will elect a new master from slaves, i.e. failover. Besides, Redis Sentinel can also act like a configuration provider for clients, and clients can query master or slave address from Redis Sentinel. So that if a failover occurs, clients can ask the new master address from Redis Sentinel. + +*redis-plus-plus* supports getting Redis master or slave's IP and port from Redis Sentinel. In order to use this feature, you only need to initialize `Redis` object with Redis Sentinel info, which is composed with 3 parts: `std::shared_ptr`, master name and role (master or slave). + +Before using Redis Sentinel with *redis-plus-plus*, ensure that you have read Redis Sentinel's [doc](https://redis.io/topics/sentinel). + +#### Sentinel + +You can create a `std::shared_ptr` object with `SentinelOptions`. + +```C++ +SentinelOptions sentinel_opts; +sentinel_opts.nodes = {{"127.0.0.1", 9000}, + {"127.0.0.1", 9001}, + {"127.0.0.1", 9002}}; // Required. List of Redis Sentinel nodes. + +// Optional. Timeout before we successfully connect to Redis Sentinel. +// By default, the timeout is 100ms. +sentinel_opts.connect_timeout = std::chrono::milliseconds(200); + +// Optional. Timeout before we successfully send request to or receive response from Redis Sentinel. +// By default, the timeout is 100ms. +sentinel_opts.socket_timeout = std::chrono::milliseconds(200); + +auto sentinel = std::make_shared(sentinel_opts); +``` + +`SentinelOptions::connect_timeout` and `SentinelOptions::socket_timeout` CANNOT be 0ms, i.e. no timeout and block forever. Otherwise, *redis-plus-plus* will throw an exception. + +See [SentinelOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/sentinel.h#L33) for more options. + +#### Role + +Besides `std::shared_ptr` and master name, you also need to specify a role. There are two roles: `Role::MASTER`, and `Role::SLAVE`. + +With `Role::MASTER`, *redis-plus-plus* will always connect to current master instance, even if a failover occurs. Each time when *redis-plus-plus* needs to create a new connection to master, or a connection is broken, and it needs to reconnect to master, *redis-plus-plus* will ask master address from Redis Sentinel, and connects to current master. If a failover occurs, *redis-plus-plus* can automatically get the address of the new master, and refresh all connections in the underlying connection pool. + +Similarly, with `Role::SLAVE`, *redis-plus-plus* will always connect to a slave instance. A master might have several slaves, *redis-plus-plus* will randomly pick one, and connect to it, i.e. all connections in the underlying connection pool, connect to the same slave instance. If the connection is broken, while this slave instance is still an alive slave, *redis-plus-plus* will reconnect to this slave. However, if this slave instance is down, or it has been promoted to be the master, *redis-plus-plus* will randomly connect to another slave. If there's no slave alive, it throws an exception. + +#### Create Redis With Sentinel + +When creating a `Redis` object with sentinel, besides the sentinel info, you should also provide `ConnectionOptions` and `ConnectionPoolOptions`. These two options are used to connect to Redis instance. `ConnectionPoolOptions` is optional, if not specified, it creates a single connection the instance. + +```C++ +ConnectionOptions connection_opts; +connection_opts.password = "auth"; // Optional. No password by default. +connection_opts.connect_timeout = std::chrono::milliseconds(100); // Required. +connection_opts.socket_timeout = std::chrono::milliseconds(100); // Required. + +ConnectionPoolOptions pool_opts; +pool_opts.size = 3; // Optional. The default size is 1. + +auto redis = Redis(sentinel, "master_name", Role::MASTER, connection_opts, pool_opts); +``` + +You might have noticed that we didn't specify the `host` and `port` fields for `ConnectionOptions`. Because, `Redis` will get these info from Redis Sentinel. Also, in this case, `ConnectionOptions::connect_timeout` and `ConnectionOptions::socket_timeout` CANNOT be 0ms, otherwise, it throws an exception. So you always need to specify these two timeouts manually. + +After creating the `Redis` object with sentinel, you can send commands with it, just like an ordinary `Redis` object. + +If you want to write to master, and scale read with slaves. You can use the following pattern: + +```C++ +auto sentinel = std::make_shared(sentinel_opts); + +auto master = Redis(sentinel, "master_name", Role::MASTER, connection_opts, pool_opts); + +auto slave = Redis(sentinel, "master_name", Role::SLAVE, connection_opts, pool_opts); + +// Write to master. +master.set("key", "value"); + +// Read from slave. +slave.get("key"); +``` + +### Redis Stream + +Since Redis 5.0, it introduces a new data type: *Redis Stream*. *redis-plus-plus* has built-in methods for all stream commands except the *XINFO* command (of course, you can use the [Generic Command Interface](#generic-command-interface) to send *XINFO* command). + +However, the replies of some streams commands, i.e. *XPENDING*, *XREAD*, are complex. So I'll give some examples to show you how to work with these built-in methods. + +#### Examples + +```C++ +auto redis = Redis("tcp://127.0.0.1"); + +using Attrs = std::vector>; + +// You can also use std::unordered_map, if you don't care the order of attributes: +// using Attrs = std::unordered_map; + +Attrs attrs = { {"f1", "v1"}, {"f2", "v2"} }; + +// Add an item into the stream. This method returns the auto generated id. +auto id = redis.xadd("key", "*", attrs.begin(), attrs.end()); + +// Each item is assigned with an id: pair. +using Item = std::pair; +using ItemStream = std::vector; + +// If you don't care the order of items in the stream, you can also use unordered_map: +// using ItemStream = std::unordered_map; + +// Read items from a stream, and return at most 10 items. +// You need to specify a key and an id (timestamp + offset). +std::unordered_map result; +redis.xread("key", id, 10, std::inserter(result, result.end())); + +// Read from multiple streams. For each stream, you need to specify a key and an id. +std::unordered_map keys = { {"key", id}, {"another-key", "0-0"} }; +redis.xread(keys.begin(), keys.end(), 10, std::inserter(result, result.end())); + +// Block for at most 1 second if currently there's no data in the stream. +redis.xread("key", id, std::chrono::seconds(1), 10, std::inserter(result, result.end())); + +// Block for multiple streams. +redis.xread(keys.begin(), keys.end(), std::chrono::seconds(1), 10, std::inserter(result, result.end())); + +// Read items in a range: +ItemStream item_stream; +redis.xrange("key", "-", "+", std::back_inserter(item_stream)); + +// Trim the stream to a given number of items. After the operation, the stream length is NOT exactly +// 10. Instead, it might be much larger than 10. +// `XTRIM key MAXLEN 10` +redis.xtrim("key", 10); + +// In order to trim the stream to exactly 10 items, specify the third argument, i.e. approx, as false. +// `XTRIM key MAXLEN ~ 10` +redis.xtrim("key", 10, false); + +// Delete an item from the stream. +redis.xdel("key", id); + +// Create a consumer group. +redis.xgroup_create("key", "group", "$"); + +// If the stream doesn't exist, you can set the fourth argument, i.e. MKSTREAM, to be true. +// redis.xgroup_create("key", "group", "$", true); + +id = redis.xadd("key", "*", attrs.begin(), attrs.end()); + +// Read item by a consumer of a consumer group. +redis.xreadgroup("group", "consumer", "key", ">", 1, std::inserter(result, result.end())); + +using PendingItem = std::tuple; +std::vector pending_items; + +// Get pending items of a speicified consumer. +redis.xpending("key", "group", "-", "+", 1, "consumer", std::back_inserter(pending_items)); + +redis.xack("key", "group", id); + +redis.xgroup_delconsumer("key", "group", "consumer"); +redis.xgroup_destroy("key", "group"); +``` + +If you have any problem on sending stream commands to Redis, please feel free to let me know. + +## Author + +*redis-plus-plus* is written by sewenew, who is also active on [StackOverflow](https://stackoverflow.com/users/5384363/for-stack). diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command.h new file mode 100644 index 000000000..3a4b24c5e --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command.h @@ -0,0 +1,2233 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_H + +#include +#include +#include +#include +#include "connection.h" +#include "command_options.h" +#include "command_args.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace cmd { + +// CONNECTION command. +inline void auth(Connection &connection, const StringView &password) { + connection.send("AUTH %b", password.data(), password.size()); +} + +inline void echo(Connection &connection, const StringView &msg) { + connection.send("ECHO %b", msg.data(), msg.size()); +} + +inline void ping(Connection &connection) { + connection.send("PING"); +} + +inline void quit(Connection &connection) { + connection.send("QUIT"); +} + +inline void ping(Connection &connection, const StringView &msg) { + // If *msg* is empty, Redis returns am empty reply of REDIS_REPLY_STRING type. + connection.send("PING %b", msg.data(), msg.size()); +} + +inline void select(Connection &connection, long long idx) { + connection.send("SELECT %lld", idx); +} + +inline void swapdb(Connection &connection, long long idx1, long long idx2) { + connection.send("SWAPDB %lld %lld", idx1, idx2); +} + +// SERVER commands. + +inline void bgrewriteaof(Connection &connection) { + connection.send("BGREWRITEAOF"); +} + +inline void bgsave(Connection &connection) { + connection.send("BGSAVE"); +} + +inline void dbsize(Connection &connection) { + connection.send("DBSIZE"); +} + +inline void flushall(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHALL ASYNC"); + } else { + connection.send("FLUSHALL"); + } +} + +inline void flushdb(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHDB ASYNC"); + } else { + connection.send("FLUSHDB"); + } +} + +inline void info(Connection &connection) { + connection.send("INFO"); +} + +inline void info(Connection &connection, const StringView §ion) { + connection.send("INFO %b", section.data(), section.size()); +} + +inline void lastsave(Connection &connection) { + connection.send("LASTSAVE"); +} + +inline void save(Connection &connection) { + connection.send("SAVE"); +} + +// KEY commands. + +inline void del(Connection &connection, const StringView &key) { + connection.send("DEL %b", key.data(), key.size()); +} + +template +inline void del_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "DEL" << std::make_pair(first, last); + + connection.send(args); +} + +inline void dump(Connection &connection, const StringView &key) { + connection.send("DUMP %b", key.data(), key.size()); +} + +inline void exists(Connection &connection, const StringView &key) { + connection.send("EXISTS %b", key.data(), key.size()); +} + +template +inline void exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void expire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("EXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void expireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("EXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void keys(Connection &connection, const StringView &pattern) { + connection.send("KEYS %b", pattern.data(), pattern.size()); +} + +inline void move(Connection &connection, const StringView &key, long long db) { + connection.send("MOVE %b %lld", + key.data(), key.size(), + db); +} + +inline void persist(Connection &connection, const StringView &key) { + connection.send("PERSIST %b", key.data(), key.size()); +} + +inline void pexpire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("PEXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void pexpireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("PEXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void pttl(Connection &connection, const StringView &key) { + connection.send("PTTL %b", key.data(), key.size()); +} + +inline void randomkey(Connection &connection) { + connection.send("RANDOMKEY"); +} + +inline void rename(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAME %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +inline void renamenx(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAMENX %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace); + +inline void scan(Connection &connection, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SCAN %lld MATCH %b COUNT %lld", + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void touch(Connection &connection, const StringView &key) { + connection.send("TOUCH %b", key.data(), key.size()); +} + +template +inline void touch_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "TOUCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void ttl(Connection &connection, const StringView &key) { + connection.send("TTL %b", key.data(), key.size()); +} + +inline void type(Connection &connection, const StringView &key) { + connection.send("TYPE %b", key.data(), key.size()); +} + +inline void unlink(Connection &connection, const StringView &key) { + connection.send("UNLINK %b", key.data(), key.size()); +} + +template +inline void unlink_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "UNLINK" << std::make_pair(first, last); + + connection.send(args); +} + +inline void wait(Connection &connection, long long numslave, long long timeout) { + connection.send("WAIT %lld %lld", numslave, timeout); +} + +// STRING commands. + +inline void append(Connection &connection, const StringView &key, const StringView &str) { + connection.send("APPEND %b %b", + key.data(), key.size(), + str.data(), str.size()); +} + +inline void bitcount(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("BITCOUNT %b %lld %lld", + key.data(), key.size(), + start, end); +} + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key); + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last); + +inline void bitpos(Connection &connection, + const StringView &key, + long long bit, + long long start, + long long end) { + connection.send("BITPOS %b %lld %lld %lld", + key.data(), key.size(), + bit, + start, + end); +} + +inline void decr(Connection &connection, const StringView &key) { + connection.send("DECR %b", key.data(), key.size()); +} + +inline void decrby(Connection &connection, const StringView &key, long long decrement) { + connection.send("DECRBY %b %lld", + key.data(), key.size(), + decrement); +} + +inline void get(Connection &connection, const StringView &key) { + connection.send("GET %b", + key.data(), key.size()); +} + +inline void getbit(Connection &connection, const StringView &key, long long offset) { + connection.send("GETBIT %b %lld", + key.data(), key.size(), + offset); +} + +inline void getrange(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("GETRANGE %b %lld %lld", + key.data(), key.size(), + start, + end); +} + +inline void getset(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("GETSET %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void incr(Connection &connection, const StringView &key) { + connection.send("INCR %b", key.data(), key.size()); +} + +inline void incrby(Connection &connection, const StringView &key, long long increment) { + connection.send("INCRBY %b %lld", + key.data(), key.size(), + increment); +} + +inline void incrbyfloat(Connection &connection, const StringView &key, double increment) { + connection.send("INCRBYFLOAT %b %f", + key.data(), key.size(), + increment); +} + +template +inline void mget(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MGET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void mset(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void msetnx(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSETNX" << std::make_pair(first, last); + + connection.send(args); +} + +inline void psetex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("PSETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type); + +inline void setex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("SETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +inline void setnx(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("SETNX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void setrange(Connection &connection, + const StringView &key, + long long offset, + const StringView &val) { + connection.send("SETRANGE %b %lld %b", + key.data(), key.size(), + offset, + val.data(), val.size()); +} + +inline void strlen(Connection &connection, const StringView &key) { + connection.send("STRLEN %b", key.data(), key.size()); +} + +// LIST commands. + +inline void blpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BLPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void blpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BLPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BRPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void brpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BRPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpoplpush(Connection &connection, + const StringView &source, + const StringView &destination, + long long timeout) { + connection.send("BRPOPLPUSH %b %b %lld", + source.data(), source.size(), + destination.data(), destination.size(), + timeout); +} + +inline void lindex(Connection &connection, const StringView &key, long long index) { + connection.send("LINDEX %b %lld", + key.data(), key.size(), + index); +} + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + +inline void llen(Connection &connection, + const StringView &key) { + connection.send("LLEN %b", key.data(), key.size()); +} + +inline void lpop(Connection &connection, const StringView &key) { + connection.send("LPOP %b", + key.data(), key.size()); +} + +inline void lpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void lpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "LPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void lpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void lrange(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void lrem(Connection &connection, + const StringView &key, + long long count, + const StringView &val) { + connection.send("LREM %b %lld %b", + key.data(), key.size(), + count, + val.data(), val.size()); +} + +inline void lset(Connection &connection, + const StringView &key, + long long index, + const StringView &val) { + connection.send("LSET %b %lld %b", + key.data(), key.size(), + index, + val.data(), val.size()); +} + +inline void ltrim(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LTRIM %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void rpop(Connection &connection, const StringView &key) { + connection.send("RPOP %b", key.data(), key.size()); +} + +inline void rpoplpush(Connection &connection, + const StringView &source, + const StringView &destination) { + connection.send("RPOPLPUSH %b %b", + source.data(), source.size(), + destination.data(), destination.size()); +} + +inline void rpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void rpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "RPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void rpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +// HASH commands. + +inline void hdel(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HDEL %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +template +inline void hdel_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hexists(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HEXISTS %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hget(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HGET %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hgetall(Connection &connection, const StringView &key) { + connection.send("HGETALL %b", key.data(), key.size()); +} + +inline void hincrby(Connection &connection, + const StringView &key, + const StringView &field, + long long increment) { + connection.send("HINCRBY %b %b %lld", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hincrbyfloat(Connection &connection, + const StringView &key, + const StringView &field, + double increment) { + connection.send("HINCRBYFLOAT %b %b %f", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hkeys(Connection &connection, const StringView &key) { + connection.send("HKEYS %b", key.data(), key.size()); +} + +inline void hlen(Connection &connection, const StringView &key) { + connection.send("HLEN %b", key.data(), key.size()); +} + +template +inline void hmget(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMGET" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void hmset(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMSET" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("HSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void hset(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSET %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hsetnx(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSETNX %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hstrlen(Connection &connection, + const StringView &key, + const StringView &field) { + connection.send("HSTRLEN %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hvals(Connection &connection, const StringView &key) { + connection.send("HVALS %b", key.data(), key.size()); +} + +// SET commands + +inline void sadd(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SADD %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void sadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void scard(Connection &connection, const StringView &key) { + connection.send("SCARD %b", key.data(), key.size()); +} + +template +inline void sdiff(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFF" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sdiffstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SDIFFSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sdiffstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFFSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void sinter(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTER" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sinterstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SINTERSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTERSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +inline void sismember(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SISMEMBER %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void smembers(Connection &connection, const StringView &key) { + connection.send("SMEMBERS %b", key.data(), key.size()); +} + +inline void smove(Connection &connection, + const StringView &source, + const StringView &destination, + const StringView &member) { + connection.send("SMOVE %b %b %b", + source.data(), source.size(), + destination.data(), destination.size(), + member.data(), member.size()); +} + +inline void spop(Connection &connection, const StringView &key) { + connection.send("SPOP %b", key.data(), key.size()); +} + +inline void spop_range(Connection &connection, const StringView &key, long long count) { + connection.send("SPOP %b %lld", + key.data(), key.size(), + count); +} + +inline void srandmember(Connection &connection, const StringView &key) { + connection.send("SRANDMEMBER %b", key.data(), key.size()); +} + +inline void srandmember_range(Connection &connection, + const StringView &key, + long long count) { + connection.send("SRANDMEMBER %b %lld", + key.data(), key.size(), + count); +} + +inline void srem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void srem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void sscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +template +inline void sunion(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNION" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sunionstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SUNIONSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNIONSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// Sorted Set commands. + +inline void bzpopmax(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMAX %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmax_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMAX" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void bzpopmin(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMIN %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmin_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMIN" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed); + +inline void zadd(Connection &connection, + const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto tmp = {std::make_pair(member, score)}; + + zadd_range(connection, key, tmp.begin(), tmp.end(), type, changed); +} + +inline void zcard(Connection &connection, const StringView &key) { + connection.send("ZCARD %b", key.data(), key.size()); +} + +template +inline void zcount(Connection &connection, + const StringView &key, + const Interval &interval) { + connection.send("ZCOUNT %b %s %s", + key.data(), key.size(), + interval.min().c_str(), + interval.max().c_str()); +} + +inline void zincrby(Connection &connection, + const StringView &key, + double increment, + const StringView &member) { + connection.send("ZINCRBY %b %f %b", + key.data(), key.size(), + increment, + member.data(), member.size()); +} + +inline void zinterstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZINTERSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +template +inline void zlexcount(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZLEXCOUNT %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zpopmax(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMAX %b %lld", + key.data(), key.size(), + count); +} + +inline void zpopmin(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMIN %b %lld", + key.data(), key.size(), + count); +} + +inline void zrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +template +void zrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } else { + connection.send("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } +} + +inline void zrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zrem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void zrem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "ZREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void zremrangebylex(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYLEX %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zremrangebyrank(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("zremrangebyrank %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +template +inline void zremrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYSCORE %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zrevrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZREVRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZREVRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrevrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); +} + +template +void zrevrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZREVRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } else { + connection.send("ZREVRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } +} + +inline void zrevrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREVRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("ZSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void zscore(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZSCORE %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zunionstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZUNIONSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +// HYPERLOGLOG commands. + +inline void pfadd(Connection &connection, + const StringView &key, + const StringView &element) { + connection.send("PFADD %b %b", + key.data(), key.size(), + element.data(), element.size()); +} + +template +inline void pfadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfcount(Connection &connection, const StringView &key) { + connection.send("PFCOUNT %b", key.data(), key.size()); +} + +template +inline void pfcount_range(Connection &connection, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFCOUNT" << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfmerge(Connection &connection, const StringView &destination, const StringView &key) { + connection.send("PFMERGE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void pfmerge_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFMERGE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// GEO commands. + +inline void geoadd(Connection &connection, + const StringView &key, + const std::tuple &member) { + const auto &mem = std::get<0>(member); + + connection.send("GEOADD %b %f %f %b", + key.data(), key.size(), + std::get<1>(member), + std::get<2>(member), + mem.data(), mem.size()); +} + +template +inline void geoadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOADD" << key; + + while (first != last) { + const auto &member = *first; + args << std::get<1>(member) << std::get<2>(member) << std::get<0>(member); + ++first; + } + + connection.send(args); +} + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit); + +template +inline void geohash_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOHASH" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void geopos_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOPOS" << key << std::make_pair(first, last); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +// SCRIPTING commands. + +inline void eval(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVAL" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void evalsha(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVALSHA" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void script_exists(Connection &connection, const StringView &sha) { + connection.send("SCRIPT EXISTS %b", sha.data(), sha.size()); +} + +template +inline void script_exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SCRIPT" << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void script_flush(Connection &connection) { + connection.send("SCRIPT FLUSH"); +} + +inline void script_kill(Connection &connection) { + connection.send("SCRIPT KILL"); +} + +inline void script_load(Connection &connection, const StringView &script) { + connection.send("SCRIPT LOAD %b", script.data(), script.size()); +} + +// PUBSUB commands. + +inline void psubscribe(Connection &connection, const StringView &pattern) { + connection.send("PSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void psubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void publish(Connection &connection, + const StringView &channel, + const StringView &message) { + connection.send("PUBLISH %b %b", + channel.data(), channel.size(), + message.data(), message.size()); +} + +inline void punsubscribe(Connection &connection) { + connection.send("PUNSUBSCRIBE"); +} + +inline void punsubscribe(Connection &connection, const StringView &pattern) { + connection.send("PUNSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void punsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PUNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PUNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void subscribe(Connection &connection, const StringView &channel) { + connection.send("SUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void subscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("SUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "SUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void unsubscribe(Connection &connection) { + connection.send("UNSUBSCRIBE"); +} + +inline void unsubscribe(Connection &connection, const StringView &channel) { + connection.send("UNSUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void unsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "UNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +// Transaction commands. + +inline void discard(Connection &connection) { + connection.send("DISCARD"); +} + +inline void exec(Connection &connection) { + connection.send("EXEC"); +} + +inline void multi(Connection &connection) { + connection.send("MULTI"); +} + +inline void unwatch(Connection &connection, const StringView &key) { + connection.send("UNWATCH %b", key.data(), key.size()); +} + +template +inline void unwatch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNWATCH: no key specified"); + } + + CmdArgs args; + args << "UNWATCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void watch(Connection &connection, const StringView &key) { + connection.send("WATCH %b", key.data(), key.size()); +} + +template +inline void watch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("WATCH: no key specified"); + } + + CmdArgs args; + args << "WATCH" << std::make_pair(first, last); + + connection.send(args); +} + +// Stream commands. + +inline void xack(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XACK %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +template +void xack_range(Connection &connection, + const StringView &key, + const StringView &group, + Input first, + Input last) { + CmdArgs args; + args << "XACK" << key << group << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last) { + CmdArgs args; + args << "XADD" << key << id << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_maxlen_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + CmdArgs args; + args << "XADD" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count << id << std::make_pair(first, last); + + connection.send(args); +} + +inline void xclaim(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + const StringView &id) { + connection.send("XCLAIM %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size(), + min_idle_time, + id.data(), id.size()); +} + +template +void xclaim_range(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + Input first, + Input last) { + CmdArgs args; + args << "XCLAIM" << key << group << consumer << min_idle_time << std::make_pair(first, last); + + connection.send(args); +} + +inline void xdel(Connection &connection, const StringView &key, const StringView &id) { + connection.send("XDEL %b %b", key.data(), key.size(), id.data(), id.size()); +} + +template +void xdel_range(Connection &connection, const StringView &key, Input first, Input last) { + CmdArgs args; + args << "XDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void xgroup_create(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + CmdArgs args; + args << "XGROUP" << "CREATE" << key << group << id; + + if (mkstream) { + args << "MKSTREAM"; + } + + connection.send(args); +} + +inline void xgroup_setid(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XGROUP SETID %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +inline void xgroup_destroy(Connection &connection, + const StringView &key, + const StringView &group) { + connection.send("XGROUP DESTROY %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xgroup_delconsumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer) { + connection.send("XGROUP DELCONSUMER %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size()); +} + +inline void xlen(Connection &connection, const StringView &key) { + connection.send("XLEN %b", key.data(), key.size()); +} + +inline void xpending(Connection &connection, const StringView &key, const StringView &group) { + connection.send("XPENDING %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xpending_detail(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XPENDING %b %b %b %b %lld", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xpending_per_consumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + connection.send("XPENDING %b %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count, + consumer.data(), consumer.size()); +} + +inline void xrange(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end) { + connection.send("XRANGE %b %b %b", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size()); +} + +inline void xrange_count(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xread(Connection &connection, + const StringView &key, + const StringView &id, + long long count) { + connection.send("XREAD COUNT %lld STREAMS %b %b", + count, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_range(Connection &connection, Input first, Input last, long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xread_block(Connection &connection, + const StringView &key, + const StringView &id, + long long timeout, + long long count) { + connection.send("XREAD COUNT %lld BLOCK %lld STREAMS %b %b", + count, + timeout, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_block_range(Connection &connection, + Input first, + Input last, + long long timeout, + long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "BLOCK" << timeout << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup_block(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_block_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xrevrange(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start) { + connection.send("XREVRANGE %b %b %b", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size()); +} + +inline void xrevrange_count(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + connection.send("XREVRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size(), + count); +} + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx); + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op); + +void set_update_type(CmdArgs &args, UpdateType type); + +void set_aggregation_type(CmdArgs &args, Aggregation type); + +template +void zinterstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zinterstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit); + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +} + +} + +} + +} + +namespace sw { + +namespace redis { + +namespace cmd { + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + assert(first != last); + + CmdArgs args; + + args << "ZADD" << key; + + detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + while (first != last) { + // Swap the pair to pair. + args << first->second << first->first; + ++first; + } + + connection.send(args); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zinterstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zunionstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_args.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_args.h new file mode 100644 index 000000000..0beb71e5c --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_args.h @@ -0,0 +1,180 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +class CmdArgs { +public: + template + CmdArgs& append(Arg &&arg); + + template + CmdArgs& append(Arg &&arg, Args &&...args); + + // All overloads of operator<< are for internal use only. + CmdArgs& operator<<(const StringView &arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& operator<<(T &&arg); + + template + CmdArgs& operator<<(const std::pair &range); + + template + auto operator<<(const std::tuple &) -> + typename std::enable_if::type { + return *this; + } + + template + auto operator<<(const std::tuple &arg) -> + typename std::enable_if::type; + + const char** argv() { + return _argv.data(); + } + + const std::size_t* argv_len() { + return _argv_len.data(); + } + + std::size_t size() const { + return _argv.size(); + } + +private: + // Deep copy. + CmdArgs& _append(std::string arg); + + // Shallow copy. + CmdArgs& _append(const StringView &arg); + + // Shallow copy. + CmdArgs& _append(const char *arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& _append(T &&arg) { + return operator<<(std::forward(arg)); + } + + template + CmdArgs& _append(std::true_type, const std::pair &range); + + template + CmdArgs& _append(std::false_type, const std::pair &range); + + std::vector _argv; + std::vector _argv_len; + + std::list _args; +}; + +template +inline CmdArgs& CmdArgs::append(Arg &&arg) { + return _append(std::forward(arg)); +} + +template +inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) { + _append(std::forward(arg)); + + return append(std::forward(args)...); +} + +inline CmdArgs& CmdArgs::operator<<(const StringView &arg) { + _argv.push_back(arg.data()); + _argv_len.push_back(arg.size()); + + return *this; +} + +template +inline CmdArgs& CmdArgs::operator<<(const std::pair &range) { + return _append(IsKvPair())>::type>(), range); +} + +template ::type>::value, + int>::type> +inline CmdArgs& CmdArgs::operator<<(T &&arg) { + return _append(std::to_string(std::forward(arg))); +} + +template +auto CmdArgs::operator<<(const std::tuple &arg) -> + typename std::enable_if::type { + operator<<(std::get(arg)); + + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(std::string arg) { + _args.push_back(std::move(arg)); + return operator<<(_args.back()); +} + +inline CmdArgs& CmdArgs::_append(const StringView &arg) { + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(const char *arg) { + return operator<<(arg); +} + +template +CmdArgs& CmdArgs::_append(std::false_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << *first; + ++first; + } + + return *this; +} + +template +CmdArgs& CmdArgs::_append(std::true_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << first->first << first->second; + ++first; + } + + return *this; +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_options.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_options.h new file mode 100644 index 000000000..ca766c086 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/command_options.h @@ -0,0 +1,211 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class UpdateType { + EXIST, + NOT_EXIST, + ALWAYS +}; + +enum class InsertPosition { + BEFORE, + AFTER +}; + +enum class BoundType { + CLOSED, + OPEN, + LEFT_OPEN, + RIGHT_OPEN +}; + +// (-inf, +inf) +template +class UnboundedInterval; + +// [min, max], (min, max), (min, max], [min, max) +template +class BoundedInterval; + +// [min, +inf), (min, +inf) +template +class LeftBoundedInterval; + +// (-inf, max], (-inf, max) +template +class RightBoundedInterval; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(double min, double max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(double min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(double max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(const std::string &min, const std::string &max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(const std::string &min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(const std::string &max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +struct LimitOptions { + long long offset = 0; + long long count = -1; +}; + +enum class Aggregation { + SUM, + MIN, + MAX +}; + +enum class BitOp { + AND, + OR, + XOR, + NOT +}; + +enum class GeoUnit { + M, + KM, + MI, + FT +}; + +template +struct WithCoord : TupleWithType, T> {}; + +template +struct WithDist : TupleWithType {}; + +template +struct WithHash : TupleWithType {}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection.h new file mode 100644 index 000000000..5ad419225 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection.h @@ -0,0 +1,194 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "reply.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class ConnectionType { + TCP = 0, + UNIX +}; + +struct ConnectionOptions { +public: + ConnectionOptions() = default; + + explicit ConnectionOptions(const std::string &uri); + + ConnectionOptions(const ConnectionOptions &) = default; + ConnectionOptions& operator=(const ConnectionOptions &) = default; + + ConnectionOptions(ConnectionOptions &&) = default; + ConnectionOptions& operator=(ConnectionOptions &&) = default; + + ~ConnectionOptions() = default; + + ConnectionType type = ConnectionType::TCP; + + std::string host; + + int port = 6379; + + std::string path; + + std::string password; + + int db = 0; + + bool keep_alive = false; + + std::chrono::milliseconds connect_timeout{0}; + + std::chrono::milliseconds socket_timeout{0}; + +private: + ConnectionOptions _parse_options(const std::string &uri) const; + + ConnectionOptions _parse_tcp_options(const std::string &path) const; + + ConnectionOptions _parse_unix_options(const std::string &path) const; + + auto _split_string(const std::string &str, const std::string &delimiter) const -> + std::pair; +}; + +class CmdArgs; + +class Connection { +public: + explicit Connection(const ConnectionOptions &opts); + + Connection(const Connection &) = delete; + Connection& operator=(const Connection &) = delete; + + Connection(Connection &&) = default; + Connection& operator=(Connection &&) = default; + + ~Connection() = default; + + // Check if the connection is broken. Client needs to do this check + // before sending some command to the connection. If it's broken, + // client needs to reconnect it. + bool broken() const noexcept { + return _ctx->err != REDIS_OK; + } + + void reset() noexcept { + _ctx->err = 0; + } + + void reconnect(); + + auto last_active() const + -> std::chrono::time_point { + return _last_active; + } + + template + void send(const char *format, Args &&...args); + + void send(int argc, const char **argv, const std::size_t *argv_len); + + void send(CmdArgs &args); + + ReplyUPtr recv(); + + const ConnectionOptions& options() const { + return _opts; + } + + friend void swap(Connection &lhs, Connection &rhs) noexcept; + +private: + class Connector; + + struct ContextDeleter { + void operator()(redisContext *context) const { + if (context != nullptr) { + redisFree(context); + } + }; + }; + + using ContextUPtr = std::unique_ptr; + + void _set_options(); + + void _auth(); + + void _select_db(); + + redisContext* _context(); + + ContextUPtr _ctx; + + // The time that the connection is created or the time that + // the connection is used, i.e. *context()* is called. + std::chrono::time_point _last_active{}; + + ConnectionOptions _opts; +}; + +using ConnectionSPtr = std::shared_ptr; + +enum class Role { + MASTER, + SLAVE +}; + +// Inline implementaions. + +template +inline void Connection::send(const char *format, Args &&...args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommand(ctx, + format, + std::forward(args)...) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +inline redisContext* Connection::_context() { + _last_active = std::chrono::steady_clock::now(); + + return _ctx.get(); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection_pool.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection_pool.h new file mode 100644 index 000000000..6f2663ad7 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/connection_pool.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +struct ConnectionPoolOptions { + // Max number of connections, including both in-use and idle ones. + std::size_t size = 1; + + // Max time to wait for a connection. 0ms means client waits forever. + std::chrono::milliseconds wait_timeout{0}; + + // Max lifetime of a connection. 0ms means we never expire the connection. + std::chrono::milliseconds connection_lifetime{0}; +}; + +class ConnectionPool { +public: + ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool() = default; + + ConnectionPool(ConnectionPool &&that); + ConnectionPool& operator=(ConnectionPool &&that); + + ConnectionPool(const ConnectionPool &) = delete; + ConnectionPool& operator=(const ConnectionPool &) = delete; + + ~ConnectionPool() = default; + + // Fetch a connection from pool. + Connection fetch(); + + ConnectionOptions connection_options(); + + void release(Connection connection); + + // Create a new connection. + Connection create(); + +private: + void _move(ConnectionPool &&that); + + // NOT thread-safe + Connection _create(); + + Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked); + + Connection _fetch(); + + void _wait_for_connection(std::unique_lock &lock); + + bool _need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime) const; + + void _update_connection_opts(const std::string &host, int port) { + _opts.host = host; + _opts.port = port; + } + + bool _role_changed(const ConnectionOptions &opts) const { + return opts.port != _opts.port || opts.host != _opts.host; + } + + ConnectionOptions _opts; + + ConnectionPoolOptions _pool_opts; + + std::deque _pool; + + std::size_t _used_connections = 0; + + std::mutex _mutex; + + std::condition_variable _cv; + + SimpleSentinel _sentinel; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/errors.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/errors.h new file mode 100644 index 000000000..44d629e50 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/errors.h @@ -0,0 +1,159 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H +#define SEWENEW_REDISPLUSPLUS_ERRORS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +enum ReplyErrorType { + ERR, + MOVED, + ASK +}; + +class Error : public std::exception { +public: + explicit Error(const std::string &msg) : _msg(msg) {} + + Error(const Error &) = default; + Error& operator=(const Error &) = default; + + Error(Error &&) = default; + Error& operator=(Error &&) = default; + + virtual ~Error() = default; + + virtual const char* what() const noexcept { + return _msg.data(); + } + +private: + std::string _msg; +}; + +class IoError : public Error { +public: + explicit IoError(const std::string &msg) : Error(msg) {} + + IoError(const IoError &) = default; + IoError& operator=(const IoError &) = default; + + IoError(IoError &&) = default; + IoError& operator=(IoError &&) = default; + + virtual ~IoError() = default; +}; + +class TimeoutError : public IoError { +public: + explicit TimeoutError(const std::string &msg) : IoError(msg) {} + + TimeoutError(const TimeoutError &) = default; + TimeoutError& operator=(const TimeoutError &) = default; + + TimeoutError(TimeoutError &&) = default; + TimeoutError& operator=(TimeoutError &&) = default; + + virtual ~TimeoutError() = default; +}; + +class ClosedError : public Error { +public: + explicit ClosedError(const std::string &msg) : Error(msg) {} + + ClosedError(const ClosedError &) = default; + ClosedError& operator=(const ClosedError &) = default; + + ClosedError(ClosedError &&) = default; + ClosedError& operator=(ClosedError &&) = default; + + virtual ~ClosedError() = default; +}; + +class ProtoError : public Error { +public: + explicit ProtoError(const std::string &msg) : Error(msg) {} + + ProtoError(const ProtoError &) = default; + ProtoError& operator=(const ProtoError &) = default; + + ProtoError(ProtoError &&) = default; + ProtoError& operator=(ProtoError &&) = default; + + virtual ~ProtoError() = default; +}; + +class OomError : public Error { +public: + explicit OomError(const std::string &msg) : Error(msg) {} + + OomError(const OomError &) = default; + OomError& operator=(const OomError &) = default; + + OomError(OomError &&) = default; + OomError& operator=(OomError &&) = default; + + virtual ~OomError() = default; +}; + +class ReplyError : public Error { +public: + explicit ReplyError(const std::string &msg) : Error(msg) {} + + ReplyError(const ReplyError &) = default; + ReplyError& operator=(const ReplyError &) = default; + + ReplyError(ReplyError &&) = default; + ReplyError& operator=(ReplyError &&) = default; + + virtual ~ReplyError() = default; +}; + +class WatchError : public Error { +public: + explicit WatchError() : Error("Watched key has been modified") {} + + WatchError(const WatchError &) = default; + WatchError& operator=(const WatchError &) = default; + + WatchError(WatchError &&) = default; + WatchError& operator=(WatchError &&) = default; + + virtual ~WatchError() = default; +}; + + +// MovedError and AskError are defined in shards.h +class MovedError; + +class AskError; + +void throw_error(redisContext &context, const std::string &err_info); + +void throw_error(const redisReply &reply); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/pipeline.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/pipeline.h new file mode 100644 index 000000000..52b01253f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/pipeline.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H +#define SEWENEW_REDISPLUSPLUS_PIPELINE_H + +#include +#include +#include "connection.h" + +namespace sw { + +namespace redis { + +class PipelineImpl { +public: + template + void command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + } + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t /*cmd_num*/) { + // Reconnect to Redis to discard all commands. + connection.reconnect(); + } +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.h new file mode 100644 index 000000000..71d975ee3 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.h @@ -0,0 +1,1844 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H + +#include +#include +#include +#include +#include "connection.h" +#include "utils.h" +#include "reply.h" +#include "command.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +class QueuedReplies; + +// If any command throws, QueuedRedis resets the connection, and becomes invalid. +// In this case, the only thing we can do is to destory the QueuedRedis object. +template +class QueuedRedis { +public: + QueuedRedis(QueuedRedis &&) = default; + QueuedRedis& operator=(QueuedRedis &&) = default; + + // When it destructs, the underlying *Connection* will be closed, + // and any command that has NOT been executed will be ignored. + ~QueuedRedis() = default; + + Redis redis(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type; + + template + QueuedRedis& command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type; + + QueuedReplies exec(); + + void discard(); + + // CONNECTION commands. + + QueuedRedis& auth(const StringView &password) { + return command(cmd::auth, password); + } + + QueuedRedis& echo(const StringView &msg) { + return command(cmd::echo, msg); + } + + QueuedRedis& ping() { + return command(cmd::ping); + } + + QueuedRedis& ping(const StringView &msg) { + return command(cmd::ping, msg); + } + + // We DO NOT support the QUIT command. See *Redis::quit* doc for details. + // + // QueuedRedis& quit(); + + QueuedRedis& select(long long idx) { + return command(cmd::select, idx); + } + + QueuedRedis& swapdb(long long idx1, long long idx2) { + return command(cmd::swapdb, idx1, idx2); + } + + // SERVER commands. + + QueuedRedis& bgrewriteaof() { + return command(cmd::bgrewriteaof); + } + + QueuedRedis& bgsave() { + return command(cmd::bgsave); + } + + QueuedRedis& dbsize() { + return command(cmd::dbsize); + } + + QueuedRedis& flushall(bool async = false) { + return command(cmd::flushall, async); + } + + QueuedRedis& flushdb(bool async = false) { + return command(cmd::flushdb, async); + } + + QueuedRedis& info() { + return command(cmd::info); + } + + QueuedRedis& info(const StringView §ion) { + return command(cmd::info, section); + } + + QueuedRedis& lastsave() { + return command(cmd::lastsave); + } + + QueuedRedis& save() { + return command(cmd::save); + } + + // KEY commands. + + QueuedRedis& del(const StringView &key) { + return command(cmd::del, key); + } + + template + QueuedRedis& del(Input first, Input last) { + return command(cmd::del_range, first, last); + } + + template + QueuedRedis& del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + QueuedRedis& dump(const StringView &key) { + return command(cmd::dump, key); + } + + QueuedRedis& exists(const StringView &key) { + return command(cmd::exists, key); + } + + template + QueuedRedis& exists(Input first, Input last) { + return command(cmd::exists_range, first, last); + } + + template + QueuedRedis& exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + QueuedRedis& expire(const StringView &key, long long timeout) { + return command(cmd::expire, key, timeout); + } + + QueuedRedis& expire(const StringView &key, + const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); + } + + QueuedRedis& expireat(const StringView &key, long long timestamp) { + return command(cmd::expireat, key, timestamp); + } + + QueuedRedis& expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& keys(const StringView &pattern) { + return command(cmd::keys, pattern); + } + + QueuedRedis& move(const StringView &key, long long db) { + return command(cmd::move, key, db); + } + + QueuedRedis& persist(const StringView &key) { + return command(cmd::persist, key); + } + + QueuedRedis& pexpire(const StringView &key, long long timeout) { + return command(cmd::pexpire, key, timeout); + } + + QueuedRedis& pexpire(const StringView &key, + const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); + } + + QueuedRedis& pexpireat(const StringView &key, long long timestamp) { + return command(cmd::pexpireat, key, timestamp); + } + + QueuedRedis& pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& pttl(const StringView &key) { + return command(cmd::pttl, key); + } + + QueuedRedis& randomkey() { + return command(cmd::randomkey); + } + + QueuedRedis& rename(const StringView &key, const StringView &newkey) { + return command(cmd::rename, key, newkey); + } + + QueuedRedis& renamenx(const StringView &key, const StringView &newkey) { + return command(cmd::renamenx, key, newkey); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false) { + return command(cmd::restore, key, val, ttl, replace); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false) { + return restore(key, val, ttl.count(), replace); + } + + // TODO: sort + + QueuedRedis& scan(long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::scan, cursor, pattern, count); + } + + QueuedRedis& scan(long long cursor) { + return scan(cursor, "*", 10); + } + + QueuedRedis& scan(long long cursor, + const StringView &pattern) { + return scan(cursor, pattern, 10); + } + + QueuedRedis& scan(long long cursor, + long long count) { + return scan(cursor, "*", count); + } + + QueuedRedis& touch(const StringView &key) { + return command(cmd::touch, key); + } + + template + QueuedRedis& touch(Input first, Input last) { + return command(cmd::touch_range, first, last); + } + + template + QueuedRedis& touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + QueuedRedis& ttl(const StringView &key) { + return command(cmd::ttl, key); + } + + QueuedRedis& type(const StringView &key) { + return command(cmd::type, key); + } + + QueuedRedis& unlink(const StringView &key) { + return command(cmd::unlink, key); + } + + template + QueuedRedis& unlink(Input first, Input last) { + return command(cmd::unlink_range, first, last); + } + + template + QueuedRedis& unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + QueuedRedis& wait(long long numslaves, long long timeout) { + return command(cmd::wait, numslaves, timeout); + } + + QueuedRedis& wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); + } + + // STRING commands. + + QueuedRedis& append(const StringView &key, const StringView &str) { + return command(cmd::append, key, str); + } + + QueuedRedis& bitcount(const StringView &key, + long long start = 0, + long long end = -1) { + return command(cmd::bitcount, key, start, end); + } + + QueuedRedis& bitop(BitOp op, + const StringView &destination, + const StringView &key) { + return command(cmd::bitop, op, destination, key); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + Input first, + Input last) { + return command(cmd::bitop_range, op, destination, first, last); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + QueuedRedis& bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1) { + return command(cmd::bitpos, key, bit, start, end); + } + + QueuedRedis& decr(const StringView &key) { + return command(cmd::decr, key); + } + + QueuedRedis& decrby(const StringView &key, long long decrement) { + return command(cmd::decrby, key, decrement); + } + + QueuedRedis& get(const StringView &key) { + return command(cmd::get, key); + } + + QueuedRedis& getbit(const StringView &key, long long offset) { + return command(cmd::getbit, key, offset); + } + + QueuedRedis& getrange(const StringView &key, long long start, long long end) { + return command(cmd::getrange, key, start, end); + } + + QueuedRedis& getset(const StringView &key, const StringView &val) { + return command(cmd::getset, key, val); + } + + QueuedRedis& incr(const StringView &key) { + return command(cmd::incr, key); + } + + QueuedRedis& incrby(const StringView &key, long long increment) { + return command(cmd::incrby, key, increment); + } + + QueuedRedis& incrbyfloat(const StringView &key, double increment) { + return command(cmd::incrbyfloat, key, increment); + } + + template + QueuedRedis& mget(Input first, Input last) { + return command(cmd::mget, first, last); + } + + template + QueuedRedis& mget(std::initializer_list il) { + return mget(il.begin(), il.end()); + } + + template + QueuedRedis& mset(Input first, Input last) { + return command(cmd::mset, first, last); + } + + template + QueuedRedis& mset(std::initializer_list il) { + return mset(il.begin(), il.end()); + } + + template + QueuedRedis& msetnx(Input first, Input last) { + return command(cmd::msetnx, first, last); + } + + template + QueuedRedis& msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + QueuedRedis& psetex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::psetex, key, ttl, val); + } + + QueuedRedis& psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); + } + + QueuedRedis& set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS) { + _set_cmd_indexes.push_back(_cmd_num); + + return command(cmd::set, key, val, ttl.count(), type); + } + + QueuedRedis& setex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::setex, key, ttl, val); + } + + QueuedRedis& setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + return setex(key, ttl.count(), val); + } + + QueuedRedis& setnx(const StringView &key, const StringView &val) { + return command(cmd::setnx, key, val); + } + + QueuedRedis& setrange(const StringView &key, + long long offset, + const StringView &val) { + return command(cmd::setrange, key, offset, val); + } + + QueuedRedis& strlen(const StringView &key) { + return command(cmd::strlen, key); + } + + // LIST commands. + + QueuedRedis& blpop(const StringView &key, long long timeout) { + return command(cmd::blpop, key, timeout); + } + + QueuedRedis& blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(key, timeout.count()); + } + + template + QueuedRedis& blpop(Input first, Input last, long long timeout) { + return command(cmd::blpop_range, first, last, timeout); + } + + template + QueuedRedis& blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(first, last, timeout.count()); + } + + template + QueuedRedis& blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpop(const StringView &key, long long timeout) { + return command(cmd::brpop, key, timeout); + } + + QueuedRedis& brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(key, timeout.count()); + } + + template + QueuedRedis& brpop(Input first, Input last, long long timeout) { + return command(cmd::brpop_range, first, last, timeout); + } + + template + QueuedRedis& brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(first, last, timeout.count()); + } + + template + QueuedRedis& brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + return command(cmd::brpoplpush, source, destination, timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpoplpush(source, destination, timeout.count()); + } + + QueuedRedis& lindex(const StringView &key, long long index) { + return command(cmd::lindex, key, index); + } + + QueuedRedis& linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + return command(cmd::linsert, key, position, pivot, val); + } + + QueuedRedis& llen(const StringView &key) { + return command(cmd::llen, key); + } + + QueuedRedis& lpop(const StringView &key) { + return command(cmd::lpop, key); + } + + QueuedRedis& lpush(const StringView &key, const StringView &val) { + return command(cmd::lpush, key, val); + } + + template + QueuedRedis& lpush(const StringView &key, Input first, Input last) { + return command(cmd::lpush_range, key, first, last); + } + + template + QueuedRedis& lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + QueuedRedis& lpushx(const StringView &key, const StringView &val) { + return command(cmd::lpushx, key, val); + } + + QueuedRedis& lrange(const StringView &key, + long long start, + long long stop) { + return command(cmd::lrange, key, start, stop); + } + + QueuedRedis& lrem(const StringView &key, long long count, const StringView &val) { + return command(cmd::lrem, key, count, val); + } + + QueuedRedis& lset(const StringView &key, long long index, const StringView &val) { + return command(cmd::lset, key, index, val); + } + + QueuedRedis& ltrim(const StringView &key, long long start, long long stop) { + return command(cmd::ltrim, key, start, stop); + } + + QueuedRedis& rpop(const StringView &key) { + return command(cmd::rpop, key); + } + + QueuedRedis& rpoplpush(const StringView &source, const StringView &destination) { + return command(cmd::rpoplpush, source, destination); + } + + QueuedRedis& rpush(const StringView &key, const StringView &val) { + return command(cmd::rpush, key, val); + } + + template + QueuedRedis& rpush(const StringView &key, Input first, Input last) { + return command(cmd::rpush_range, key, first, last); + } + + template + QueuedRedis& rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + QueuedRedis& rpushx(const StringView &key, const StringView &val) { + return command(cmd::rpushx, key, val); + } + + // HASH commands. + + QueuedRedis& hdel(const StringView &key, const StringView &field) { + return command(cmd::hdel, key, field); + } + + template + QueuedRedis& hdel(const StringView &key, Input first, Input last) { + return command(cmd::hdel_range, key, first, last); + } + + template + QueuedRedis& hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + QueuedRedis& hexists(const StringView &key, const StringView &field) { + return command(cmd::hexists, key, field); + } + + QueuedRedis& hget(const StringView &key, const StringView &field) { + return command(cmd::hget, key, field); + } + + QueuedRedis& hgetall(const StringView &key) { + return command(cmd::hgetall, key); + } + + QueuedRedis& hincrby(const StringView &key, + const StringView &field, + long long increment) { + return command(cmd::hincrby, key, field, increment); + } + + QueuedRedis& hincrbyfloat(const StringView &key, + const StringView &field, + double increment) { + return command(cmd::hincrbyfloat, key, field, increment); + } + + QueuedRedis& hkeys(const StringView &key) { + return command(cmd::hkeys, key); + } + + QueuedRedis& hlen(const StringView &key) { + return command(cmd::hlen, key); + } + + template + QueuedRedis& hmget(const StringView &key, Input first, Input last) { + return command(cmd::hmget, key, first, last); + } + + template + QueuedRedis& hmget(const StringView &key, std::initializer_list il) { + return hmget(key, il.begin(), il.end()); + } + + template + QueuedRedis& hmset(const StringView &key, Input first, Input last) { + return command(cmd::hmset, key, first, last); + } + + template + QueuedRedis& hmset(const StringView &key, std::initializer_list il) { + return hmset(key, il.begin(), il.end()); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::hscan, key, cursor, pattern, count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return hscan(key, cursor, pattern, 10); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + long long count) { + return hscan(key, cursor, "*", count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor) { + return hscan(key, cursor, "*", 10); + } + + QueuedRedis& hset(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hset, key, field, val); + } + + QueuedRedis& hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); + } + + QueuedRedis& hsetnx(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hsetnx, key, field, val); + } + + QueuedRedis& hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); + } + + QueuedRedis& hstrlen(const StringView &key, const StringView &field) { + return command(cmd::hstrlen, key, field); + } + + QueuedRedis& hvals(const StringView &key) { + return command(cmd::hvals, key); + } + + // SET commands. + + QueuedRedis& sadd(const StringView &key, const StringView &member) { + return command(cmd::sadd, key, member); + } + + template + QueuedRedis& sadd(const StringView &key, Input first, Input last) { + return command(cmd::sadd_range, key, first, last); + } + + template + QueuedRedis& sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + QueuedRedis& scard(const StringView &key) { + return command(cmd::scard, key); + } + + template + QueuedRedis& sdiff(Input first, Input last) { + return command(cmd::sdiff, first, last); + } + + template + QueuedRedis& sdiff(std::initializer_list il) { + return sdiff(il.begin(), il.end()); + } + + QueuedRedis& sdiffstore(const StringView &destination, const StringView &key) { + return command(cmd::sdiffstore, destination, key); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sdiffstore_range, destination, first, last); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + QueuedRedis& sinter(Input first, Input last) { + return command(cmd::sinter, first, last); + } + + template + QueuedRedis& sinter(std::initializer_list il) { + return sinter(il.begin(), il.end()); + } + + QueuedRedis& sinterstore(const StringView &destination, const StringView &key) { + return command(cmd::sinterstore, destination, key); + } + + template + QueuedRedis& sinterstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sinterstore_range, destination, first, last); + } + + template + QueuedRedis& sinterstore(const StringView &destination, std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + QueuedRedis& sismember(const StringView &key, const StringView &member) { + return command(cmd::sismember, key, member); + } + + QueuedRedis& smembers(const StringView &key) { + return command(cmd::smembers, key); + } + + QueuedRedis& smove(const StringView &source, + const StringView &destination, + const StringView &member) { + return command(cmd::smove, source, destination, member); + } + + QueuedRedis& spop(const StringView &key) { + return command(cmd::spop, key); + } + + QueuedRedis& spop(const StringView &key, long long count) { + return command(cmd::spop_range, key, count); + } + + QueuedRedis& srandmember(const StringView &key) { + return command(cmd::srandmember, key); + } + + QueuedRedis& srandmember(const StringView &key, long long count) { + return command(cmd::srandmember_range, key, count); + } + + QueuedRedis& srem(const StringView &key, const StringView &member) { + return command(cmd::srem, key, member); + } + + template + QueuedRedis& srem(const StringView &key, Input first, Input last) { + return command(cmd::srem_range, key, first, last); + } + + template + QueuedRedis& srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::sscan, key, cursor, pattern, count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return sscan(key, cursor, pattern, 10); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + long long count) { + return sscan(key, cursor, "*", count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor) { + return sscan(key, cursor, "*", 10); + } + + template + QueuedRedis& sunion(Input first, Input last) { + return command(cmd::sunion, first, last); + } + + template + QueuedRedis& sunion(std::initializer_list il) { + return sunion(il.begin(), il.end()); + } + + QueuedRedis& sunionstore(const StringView &destination, const StringView &key) { + return command(cmd::sunionstore, destination, key); + } + + template + QueuedRedis& sunionstore(const StringView &destination, Input first, Input last) { + return command(cmd::sunionstore_range, destination, first, last); + } + + template + QueuedRedis& sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + QueuedRedis& bzpopmax(const StringView &key, long long timeout) { + return command(cmd::bzpopmax, key, timeout); + } + + QueuedRedis& bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(key, timeout.count()); + } + + template + QueuedRedis& bzpopmax(Input first, Input last, long long timeout) { + return command(cmd::bzpopmax_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, long long timeout) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, long long timeout) { + return command(cmd::bzpopmin, key, timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(key, timeout.count()); + } + + template + QueuedRedis& bzpopmin(Input first, Input last, long long timeout) { + return command(cmd::bzpopmin_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, long long timeout) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + QueuedRedis& zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd, key, member, score, type, changed); + } + + template + QueuedRedis& zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd_range, key, first, last, type, changed); + } + + QueuedRedis& zcard(const StringView &key) { + return command(cmd::zcard, key); + } + + template + QueuedRedis& zcount(const StringView &key, const Interval &interval) { + return command(cmd::zcount, key, interval); + } + + QueuedRedis& zincrby(const StringView &key, double increment, const StringView &member) { + return command(cmd::zincrby, key, increment, member); + } + + QueuedRedis& zinterstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zinterstore, destination, key, weight); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zinterstore_range, destination, first, last, type); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + QueuedRedis& zlexcount(const StringView &key, const Interval &interval) { + return command(cmd::zlexcount, key, interval); + } + + QueuedRedis& zpopmax(const StringView &key) { + return command(cmd::zpopmax, key, 1); + } + + QueuedRedis& zpopmax(const StringView &key, long long count) { + return command(cmd::zpopmax, key, count); + } + + QueuedRedis& zpopmin(const StringView &key) { + return command(cmd::zpopmin, key, 1); + } + + QueuedRedis& zpopmin(const StringView &key, long long count) { + return command(cmd::zpopmin, key, count); + } + + // NOTE: *QueuedRedis::zrange*'s parameters are different from *Redis::zrange*. + // *Redis::zrange* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::zrange*. So we have to use an extra parameter: *with_scores*, + // to decide whether we should send *WITHSCORES* option to Redis. This also applies to + // other commands with the *WITHSCORES* option, e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, + // *ZREVRANGEBYSCORE*. + QueuedRedis& zrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrangebylex(const StringView &key, const Interval &interval) { + return zrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrank(const StringView &key, const StringView &member) { + return command(cmd::zrank, key, member); + } + + QueuedRedis& zrem(const StringView &key, const StringView &member) { + return command(cmd::zrem, key, member); + } + + template + QueuedRedis& zrem(const StringView &key, Input first, Input last) { + return command(cmd::zrem_range, key, first, last); + } + + template + QueuedRedis& zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + QueuedRedis& zremrangebylex(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebylex, key, interval); + } + + QueuedRedis& zremrangebyrank(const StringView &key, long long start, long long stop) { + return command(cmd::zremrangebyrank, key, start, stop); + } + + template + QueuedRedis& zremrangebyscore(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebyscore, key, interval); + } + + // See comments on *ZRANGE*. + QueuedRedis& zrevrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrevrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrevrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, const Interval &interval) { + return zrevrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrevrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrevrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrevrank(const StringView &key, const StringView &member) { + return command(cmd::zrevrank, key, member); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::zscan, key, cursor, pattern, count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return zscan(key, cursor, pattern, 10); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + long long count) { + return zscan(key, cursor, "*", count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor) { + return zscan(key, cursor, "*", 10); + } + + QueuedRedis& zscore(const StringView &key, const StringView &member) { + return command(cmd::zscore, key, member); + } + + QueuedRedis& zunionstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zunionstore, destination, key, weight); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zunionstore_range, destination, first, last, type); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + QueuedRedis& pfadd(const StringView &key, const StringView &element) { + return command(cmd::pfadd, key, element); + } + + template + QueuedRedis& pfadd(const StringView &key, Input first, Input last) { + return command(cmd::pfadd_range, key, first, last); + } + + template + QueuedRedis& pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + QueuedRedis& pfcount(const StringView &key) { + return command(cmd::pfcount, key); + } + + template + QueuedRedis& pfcount(Input first, Input last) { + return command(cmd::pfcount_range, first, last); + } + + template + QueuedRedis& pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + QueuedRedis& pfmerge(const StringView &destination, const StringView &key) { + return command(cmd::pfmerge, destination, key); + } + + template + QueuedRedis& pfmerge(const StringView &destination, Input first, Input last) { + return command(cmd::pfmerge_range, destination, first, last); + } + + template + QueuedRedis& pfmerge(const StringView &destination, std::initializer_list il) { + return pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + QueuedRedis& geoadd(const StringView &key, + const std::tuple &member) { + return command(cmd::geoadd, key, member); + } + + template + QueuedRedis& geoadd(const StringView &key, + Input first, + Input last) { + return command(cmd::geoadd_range, key, first, last); + } + + template + QueuedRedis& geoadd(const StringView &key, std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + QueuedRedis& geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M) { + return command(cmd::geodist, key, member1, member2, unit); + } + + template + QueuedRedis& geohash(const StringView &key, Input first, Input last) { + return command(cmd::geohash_range, key, first, last); + } + + template + QueuedRedis& geohash(const StringView &key, std::initializer_list il) { + return geohash(key, il.begin(), il.end()); + } + + template + QueuedRedis& geopos(const StringView &key, Input first, Input last) { + return command(cmd::geopos_range, key, first, last); + } + + template + QueuedRedis& geopos(const StringView &key, std::initializer_list il) { + return geopos(key, il.begin(), il.end()); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + } + + // NOTE: *QueuedRedis::georadius*'s parameters are different from *Redis::georadius*. + // *Redis::georadius* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::georadius*. So we have to use extra parameters to decide + // whether we should send options to Redis. This also applies to *GEORADIUSBYMEMBER*. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + destination, + store_dist, + count); + } + + // See the comments on *GEORADIUS*. + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + // SCRIPTING commands. + + QueuedRedis& eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::eval, script, keys, args); + } + + QueuedRedis& evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::evalsha, script, keys, args); + } + + template + QueuedRedis& script_exists(Input first, Input last) { + return command(cmd::script_exists_range, first, last); + } + + template + QueuedRedis& script_exists(std::initializer_list il) { + return script_exists(il.begin(), il.end()); + } + + QueuedRedis& script_flush() { + return command(cmd::script_flush); + } + + QueuedRedis& script_kill() { + return command(cmd::script_kill); + } + + QueuedRedis& script_load(const StringView &script) { + return command(cmd::script_load, script); + } + + // PUBSUB commands. + + QueuedRedis& publish(const StringView &channel, const StringView &message) { + return command(cmd::publish, channel, message); + } + + // Stream commands. + + QueuedRedis& xack(const StringView &key, const StringView &group, const StringView &id) { + return command(cmd::xack, key, group, id); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, Input first, Input last) { + return command(cmd::xack_range, key, group, first, last); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, Input first, Input last) { + return command(cmd::xadd_range, key, id, first, last); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true) { + return command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id) { + return command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last) { + return command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il) { + return xclaim(key, group, consumer, min_idle_time, il.begin(), il.end()); + } + + QueuedRedis& xdel(const StringView &key, const StringView &id) { + return command(cmd::xdel, key, id); + } + + template + QueuedRedis& xdel(const StringView &key, Input first, Input last) { + return command(cmd::xdel_range, key, first, last); + } + + template + QueuedRedis& xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + QueuedRedis& xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false) { + return command(cmd::xgroup_create, key, group, id, mkstream); + } + + QueuedRedis& xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + return command(cmd::xgroup_setid, key, group, id); + } + + QueuedRedis& xgroup_destroy(const StringView &key, const StringView &group) { + return command(cmd::xgroup_destroy, key, group); + } + + QueuedRedis& xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + return command(cmd::xgroup_delconsumer, key, group, consumer); + } + + QueuedRedis& xlen(const StringView &key) { + return command(cmd::xlen, key); + } + + QueuedRedis& xpending(const StringView &key, const StringView &group) { + return command(cmd::xpending, key, group); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xpending_detail, key, group, start, end, count); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + return command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end) { + return command(cmd::xrange, key, start, end); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xrange, key, start, end, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id, long long count) { + return command(cmd::xread, key, id, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id) { + return xread(key, id, 0); + } + + template + auto xread(Input first, Input last, long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_range, first, last, count); + } + + template + auto xread(Input first, Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, 0); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return command(cmd::xread_block, key, id, timeout.count(), count); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xread(key, id, timeout, 0); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_block_range, first, last, timeout.count(), count); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, timeout, 0); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + return command(cmd::xreadgroup, group, consumer, key, id, count, noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count) { + return xreadgroup(group, consumer, key, id, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, 0, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) { + return command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return xreadgroup(group, consumer, key, id, timeout, count, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xreadgroup(group, consumer, key, id, timeout, 0, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, 0, false); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start) { + return command(cmd::xrevrange, key, end, start); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + return command(cmd::xrevrange, key, end, start, count); + } + + QueuedRedis& xtrim(const StringView &key, long long count, bool approx = true) { + return command(cmd::xtrim, key, count, approx); + } + +private: + friend class Redis; + + friend class RedisCluster; + + template + QueuedRedis(const ConnectionSPtr &connection, Args &&...args); + + void _sanity_check() const; + + void _reset(); + + void _invalidate(); + + void _rewrite_replies(std::vector &replies) const; + + template + void _rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const; + + ConnectionSPtr _connection; + + Impl _impl; + + std::size_t _cmd_num = 0; + + std::vector _set_cmd_indexes; + + std::vector _georadius_cmd_indexes; + + bool _valid = true; +}; + +class QueuedReplies { +public: + std::size_t size() const; + + redisReply& get(std::size_t idx); + + template + Result get(std::size_t idx); + + template + void get(std::size_t idx, Output output); + +private: + template + friend class QueuedRedis; + + explicit QueuedReplies(std::vector replies) : _replies(std::move(replies)) {} + + void _index_check(std::size_t idx) const; + + std::vector _replies; +}; + +} + +} + +#include "queued_redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.hpp b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.hpp new file mode 100644 index 000000000..409f48aca --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/queued_redis.hpp @@ -0,0 +1,208 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP + +namespace sw { + +namespace redis { + +template +template +QueuedRedis::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) : + _connection(connection), + _impl(std::forward(args)...) { + assert(_connection); +} + +template +Redis QueuedRedis::redis() { + return Redis(_connection); +} + +template +template +auto QueuedRedis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type { + try { + _sanity_check(); + + _impl.command(*_connection, cmd, std::forward(args)...); + + ++_cmd_num; + } catch (const Error &e) { + _invalidate(); + throw; + } + + return *this; +} + +template +template +QueuedRedis& QueuedRedis::command(const StringView &cmd_name, Args &&...args) { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +template +auto QueuedRedis::command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +QueuedReplies QueuedRedis::exec() { + try { + _sanity_check(); + + auto replies = _impl.exec(*_connection, _cmd_num); + + _rewrite_replies(replies); + + _reset(); + + return QueuedReplies(std::move(replies)); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::discard() { + try { + _sanity_check(); + + _impl.discard(*_connection, _cmd_num); + + _reset(); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::_sanity_check() const { + if (!_valid) { + throw Error("Not in valid state"); + } + + if (_connection->broken()) { + throw Error("Connection is broken"); + } +} + +template +inline void QueuedRedis::_reset() { + _cmd_num = 0; + + _set_cmd_indexes.clear(); + + _georadius_cmd_indexes.clear(); +} + +template +void QueuedRedis::_invalidate() { + _valid = false; + + _reset(); +} + +template +void QueuedRedis::_rewrite_replies(std::vector &replies) const { + _rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies); + + _rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies); +} + +template +template +void QueuedRedis::_rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const { + for (auto idx : indexes) { + assert(idx < replies.size()); + + auto &reply = replies[idx]; + + assert(reply); + + rewriter(*reply); + } +} + +inline std::size_t QueuedReplies::size() const { + return _replies.size(); +} + +inline redisReply& QueuedReplies::get(std::size_t idx) { + _index_check(idx); + + auto &reply = _replies[idx]; + + assert(reply); + + return *reply; +} + +template +inline Result QueuedReplies::get(std::size_t idx) { + auto &reply = get(idx); + + return reply::parse(reply); +} + +template +inline void QueuedReplies::get(std::size_t idx, Output output) { + auto &reply = get(idx); + + reply::to_array(reply, output); +} + +inline void QueuedReplies::_index_check(std::size_t idx) const { + if (idx >= size()) { + throw Error("Out of range"); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis++.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis++.h new file mode 100644 index 000000000..0da0ebb16 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis++.h @@ -0,0 +1,25 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H +#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H + +#include "redis.h" +#include "redis_cluster.h" +#include "queued_redis.h" +#include "sentinel.h" + +#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.h new file mode 100644 index 000000000..b54afb96b --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.h @@ -0,0 +1,1523 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_H +#define SEWENEW_REDISPLUSPLUS_REDIS_H + +#include +#include +#include +#include +#include +#include "connection_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class Redis { +public: + Redis(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : _pool(pool_opts, connection_opts) {} + + // Construct Redis instance with URI: + // "tcp://127.0.0.1", "tcp://127.0.0.1:6379", or "unix://path/to/socket" + explicit Redis(const std::string &uri); + + Redis(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role, + const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(SimpleSentinel(sentinel, master_name, role), pool_opts, connection_opts) {} + + Redis(const Redis &) = delete; + Redis& operator=(const Redis &) = delete; + + Redis(Redis &&) = default; + Redis& operator=(Redis &&) = default; + + Pipeline pipeline(); + + Transaction transaction(bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type; + + template + Result command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // CONNECTION commands. + + void auth(const StringView &password); + + std::string echo(const StringView &msg); + + std::string ping(); + + std::string ping(const StringView &msg); + + // After sending QUIT, only the current connection will be close, while + // other connections in the pool is still open. This is a strange behavior. + // So we DO NOT support the QUIT command. If you want to quit connection to + // server, just destroy the Redis object. + // + // void quit(); + + // We get a connection from the pool, and send the SELECT command to switch + // to a specified DB. However, when we try to send other commands to the + // given DB, we might get a different connection from the pool, and these + // commands, in fact, work on other DB. e.g. + // + // redis.select(1); // get a connection from the pool and switch to the 1th DB + // redis.get("key"); // might get another connection from the pool, + // // and try to get 'key' on the default DB + // + // Obviously, this is NOT what we expect. So we DO NOT support SELECT command. + // In order to select a DB, we can specify the DB index with the ConnectionOptions. + // + // However, since Pipeline and Transaction always send multiple commands on a + // single connection, these two classes have a *select* method. + // + // void select(long long idx); + + void swapdb(long long idx1, long long idx2); + + // SERVER commands. + + void bgrewriteaof(); + + void bgsave(); + + long long dbsize(); + + void flushall(bool async = false); + + void flushdb(bool async = false); + + std::string info(); + + std::string info(const StringView §ion); + + long long lastsave(); + + void save(); + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + template + void keys(const StringView &pattern, Output output); + + bool move(const StringView &key, long long db); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + OptionalString randomkey(); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + template + long long scan(long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long scan(long long cursor, + Output output); + + template + long long scan(long long cursor, + const StringView &pattern, + Output output); + + template + long long scan(long long cursor, + long long count, + Output output); + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + long long wait(long long numslaves, long long timeout); + + long long wait(long long numslaves, const std::chrono::milliseconds &timeout); + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + // If *Input* is an iterator of a container of string, + // we use the default weight, i.e. 1, and send + // *ZINTERSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + // If *Input* is an iterator of a container of pair, i.e. key-weight pair, + // we send the command with the given weights: + // *ZINTERSTORE destination numkeys key [key ...] + // [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]* + // + // The following code use the default weight: + // + // vector keys = {"k1", "k2", "k3"}; + // redis.zinterstore(destination, keys.begin(), keys.end()); + // + // On the other hand, the following code use the given weights: + // + // vector> keys_with_weights = {{"k1", 1}, {"k2", 2}, {"k3", 3}}; + // redis.zinterstore(destination, keys_with_weights.begin(), keys_with_weights.end()); + // + // NOTE: `keys_with_weights` can also be of type `unordered_map`. + // However, it will be slower than vector>, since we use + // `distance(first, last)` to calculate the *numkeys* parameter. + // + // This also applies to *ZUNIONSTORE* command. + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + // See *zinterstore* comment for how to use this method. + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + void script_exists(Input first, Input last, Output output); + + template + void script_exists(std::initializer_list il, Output output) { + script_exists(il.begin(), il.end(), output); + } + + void script_flush(); + + void script_kill(); + + std::string script_load(const StringView &script); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Transaction commands. + void watch(const StringView &key); + + template + void watch(Input first, Input last); + + template + void watch(std::initializer_list il) { + watch(il.begin(), il.end()); + } + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first ,last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class ConnectionPoolGuard { + public: + ConnectionPoolGuard(ConnectionPool &pool, + Connection &connection) : _pool(pool), _connection(connection) {} + + ~ConnectionPoolGuard() { + _pool.release(std::move(_connection)); + } + + private: + ConnectionPool &_pool; + Connection &_connection; + }; + + template + friend class QueuedRedis; + + friend class RedisCluster; + + // For internal use. + explicit Redis(const ConnectionSPtr &connection); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + // Pool Mode. + // Public constructors create a *Redis* instance with a pool. + // In this case, *_connection* is a null pointer, and is never used. + ConnectionPool _pool; + + // Single Connection Mode. + // Private constructor creats a *Redis* instance with a single connection. + // This is used when we create Transaction, Pipeline and Subscriber. + // In this case, *_pool* is empty, and is never used. + ConnectionSPtr _connection; +}; + +} + +} + +#include "redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.hpp b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.hpp new file mode 100644 index 000000000..3a227a6f1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis.hpp @@ -0,0 +1,1365 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_HPP + +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +template +auto Redis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (_connection) { + // Single Connection Mode. + // TODO: In this case, should we reconnect? + if (_connection->broken()) { + throw Error("Connection is broken"); + } + + return _command(*_connection, cmd, std::forward(args)...); + } else { + // Pool Mode, i.e. get connection from pool. + auto connection = _pool.fetch(); + + assert(!connection.broken()); + + ConnectionPoolGuard guard(_pool, connection); + + return _command(connection, cmd, std::forward(args)...); + } +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, ReplyUPtr>::type { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +Result Redis::command(const StringView &cmd_name, Args &&...args) { + auto r = command(cmd_name, std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long Redis::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool Redis::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool Redis::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +template +void Redis::keys(const StringView &pattern, Output output) { + auto reply = command(cmd::keys, pattern); + + reply::to_array(*reply, output); +} + +inline bool Redis::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool Redis::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void Redis::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long Redis::scan(long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::scan, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::scan(long long cursor, + const StringView &pattern, + Output output) { + return scan(cursor, pattern, 10, output); +} + +template +inline long long Redis::scan(long long cursor, + long long count, + Output output) { + return scan(cursor, "*", count, output); +} + +template +inline long long Redis::scan(long long cursor, + Output output) { + return scan(cursor, "*", 10, output); +} + +template +long long Redis::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); +} + +// STRING commands. + +template +long long Redis::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = command(cmd::bitop_range, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool Redis::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void Redis::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void Redis::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair Redis::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair Redis::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long Redis::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long Redis::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long Redis::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void Redis::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long Redis::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void Redis::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long Redis::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void Redis::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto Redis::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto Redis::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long Redis::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long Redis::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long Redis::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void Redis::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long Redis::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool Redis::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void Redis::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long Redis::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::eval, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::eval, script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::evalsha, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::evalsha, script, keys, args); + + reply::to_array(*reply, output); +} + +template +void Redis::script_exists(Input first, Input last, Output output) { + if (first == last) { + throw Error("SCRIPT EXISTS: no key specified"); + } + + auto reply = command(cmd::script_exists_range, first, last); + + reply::to_array(*reply, output); +} + +// Transaction commands. + +template +void Redis::watch(Input first, Input last) { + auto reply = command(cmd::watch_range, first, last); + + reply::parse(*reply); +} + +// Stream commands. + +template +long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long Redis::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto Redis::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + auto reply = connection.recv(); + + return reply; +} + +template +inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.h new file mode 100644 index 000000000..50a221367 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.h @@ -0,0 +1,1395 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H + +#include +#include +#include +#include +#include "shards_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class RedisCluster { +public: + RedisCluster(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(pool_opts, connection_opts) {} + + // Construct RedisCluster with URI: + // "tcp://127.0.0.1" or "tcp://127.0.0.1:6379" + // Only need to specify one URI. + explicit RedisCluster(const std::string &uri); + + RedisCluster(const RedisCluster &) = delete; + RedisCluster& operator=(const RedisCluster &) = delete; + + RedisCluster(RedisCluster &&) = default; + RedisCluster& operator=(RedisCluster &&) = default; + + Redis redis(const StringView &hash_tag); + + Pipeline pipeline(const StringView &hash_tag); + + Transaction transaction(const StringView &hash_tag, bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first, last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class Command { + public: + explicit Command(const StringView &cmd_name) : _cmd_name(cmd_name) {} + + template + void operator()(Connection &connection, Args &&...args) const { + CmdArgs cmd_args; + cmd_args.append(_cmd_name, std::forward(args)...); + connection.send(cmd_args); + } + + private: + StringView _cmd_name; + }; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + ReplyUPtr _command(Cmd cmd, Connection &connection, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::true_type, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::false_type, Input &&first, Args &&...args); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _range_command(Cmd cmd, std::true_type, Input input, Args &&...args); + + template + ReplyUPtr _range_command(Cmd cmd, std::false_type, Input input, Args &&...args); + + void _asking(Connection &connection); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + ShardsPool _pool; +}; + +} + +} + +#include "redis_cluster.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.hpp b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.hpp new file mode 100644 index 000000000..61da3f062 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/redis_cluster.hpp @@ -0,0 +1,1415 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP + +#include +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" +#include "shards_pool.h" + +namespace sw { + +namespace redis { + +template +auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + return _command(cmd, + std::is_convertible::type, StringView>(), + std::forward(key), + std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type { + auto cmd = Command(cmd_name); + + return _generic_command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type { + auto r = command(cmd_name, std::forward(key), std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(key), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last || std::next(first) == last) { + throw Error("command: invalid range"); + } + + const auto &key = *first; + ++first; + + auto cmd = [&key](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + cmd_args.append(key); + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long RedisCluster::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool RedisCluster::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool RedisCluster::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void RedisCluster::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long RedisCluster::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +// STRING commands. + +template +long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = _command(cmd::bitop_range, destination, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool RedisCluster::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void RedisCluster::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void RedisCluster::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void RedisCluster::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long RedisCluster::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void RedisCluster::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long RedisCluster::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long RedisCluster::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool RedisCluster::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long RedisCluster::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::evalsha, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = command(cmd::evalsha, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +// Stream commands. + +template +long long RedisCluster::xack(const StringView &key, + const StringView &group, + Input first, + Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_range, + first->first, + group, + consumer, + first, + last, + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup_block, + key, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_block_range, + first->first, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type { + return command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type { + auto k = std::to_string(std::forward(key)); + return command(cmd, k, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) { + return _command(cmd, key, key, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) { + return _range_command(cmd, + std::is_convertible< + typename std::decay< + decltype(*std::declval())>::type, StringView>(), + std::forward(first), + std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) { + return _command(cmd, *input, input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) { + return _command(cmd, std::get<0>(*input), input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + return connection.recv(); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) { + for (auto idx = 0; idx < 2; ++idx) { + try { + auto guarded_connection = _pool.fetch(key); + + return _command(cmd, guarded_connection.connection(), std::forward(args)...); + } catch (const IoError &err) { + // When master is down, one of its replicas will be promoted to be the new master. + // If we try to send command to the old master, we'll get an *IoError*. + // In this case, we need to update the slots mapping. + _pool.update(); + } catch (const ClosedError &err) { + // Node might be removed. + // 1. Get up-to-date slot mapping to check if the node still exists. + _pool.update(); + + // TODO: + // 2. If it's NOT exist, update slot mapping, and retry. + // 3. If it's still exist, that means the node is down, NOT removed, throw exception. + } catch (const MovedError &err) { + // Slot mapping has been changed, update it and try again. + _pool.update(); + } catch (const AskError &err) { + auto guarded_connection = _pool.fetch(err.node()); + auto &connection = guarded_connection.connection(); + + // 1. send ASKING command. + _asking(connection); + + // 2. resend last command. + try { + return _command(cmd, connection, std::forward(args)...); + } catch (const MovedError &err) { + throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state"); + } + } // For other exceptions, just throw it. + } + + // Possible failures: + // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx', + // while the destination node has NOT run it. + // In this case, client will be redirected by both nodes with MovedError. + // 2. Other failures... + throw Error("Failed to send command with key: " + std::string(key.data(), key.size())); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/reply.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/reply.h new file mode 100644 index 000000000..b309de5bb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/reply.h @@ -0,0 +1,363 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H +#define SEWENEW_REDISPLUSPLUS_REPLY_H + +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +struct ReplyDeleter { + void operator()(redisReply *reply) const { + if (reply != nullptr) { + freeReplyObject(reply); + } + } +}; + +using ReplyUPtr = std::unique_ptr; + +namespace reply { + +template +struct ParseTag {}; + +template +inline T parse(redisReply &reply) { + return parse(ParseTag(), reply); +} + +void parse(ParseTag, redisReply &reply); + +std::string parse(ParseTag, redisReply &reply); + +long long parse(ParseTag, redisReply &reply); + +double parse(ParseTag, redisReply &reply); + +bool parse(ParseTag, redisReply &reply); + +template +Optional parse(ParseTag>, redisReply &reply); + +template +std::pair parse(ParseTag>, redisReply &reply); + +template +std::tuple parse(ParseTag>, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template +long long parse_scan_reply(redisReply &reply, Output output); + +inline bool is_error(redisReply &reply) { + return reply.type == REDIS_REPLY_ERROR; +} + +inline bool is_nil(redisReply &reply) { + return reply.type == REDIS_REPLY_NIL; +} + +inline bool is_string(redisReply &reply) { + return reply.type == REDIS_REPLY_STRING; +} + +inline bool is_status(redisReply &reply) { + return reply.type == REDIS_REPLY_STATUS; +} + +inline bool is_integer(redisReply &reply) { + return reply.type == REDIS_REPLY_INTEGER; +} + +inline bool is_array(redisReply &reply) { + return reply.type == REDIS_REPLY_ARRAY; +} + +std::string to_status(redisReply &reply); + +template +void to_array(redisReply &reply, Output output); + +// Rewrite set reply to bool type +void rewrite_set_reply(redisReply &reply); + +// Rewrite georadius reply to OptionalLongLong type +void rewrite_georadius_reply(redisReply &reply); + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple; + +} + +// Inline implementations. + +namespace reply { + +namespace detail { + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr) { + // Empty array. + return; + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null array element reply"); + } + + *output = parse::type>(*sub_reply); + + ++output; + } +} + +bool is_flat_array(redisReply &reply); + +template +void to_flat_array(redisReply &reply, Output output) { + if (reply.element == nullptr) { + // Empty array. + return; + } + + if (reply.elements % 2 != 0) { + throw ProtoError("Not string pair array reply"); + } + + for (std::size_t idx = 0; idx != reply.elements; idx += 2) { + auto *key_reply = reply.element[idx]; + auto *val_reply = reply.element[idx + 1]; + if (key_reply == nullptr || val_reply == nullptr) { + throw ProtoError("Null string array reply"); + } + + using Pair = typename IterType::type; + using FirstType = typename std::decay::type; + using SecondType = typename std::decay::type; + *output = std::make_pair(parse(*key_reply), + parse(*val_reply)); + + ++output; + } +} + +template +void to_array(std::true_type, redisReply &reply, Output output) { + if (is_flat_array(reply)) { + to_flat_array(reply, output); + } else { + to_array(reply, output); + } +} + +template +void to_array(std::false_type, redisReply &reply, Output output) { + to_array(reply, output); +} + +template +std::tuple parse_tuple(redisReply **reply, std::size_t idx) { + assert(reply != nullptr); + + auto *sub_reply = reply[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null reply"); + } + + return std::make_tuple(parse(*sub_reply)); +} + +template +auto parse_tuple(redisReply **reply, std::size_t idx) -> + typename std::enable_if>::type { + assert(reply != nullptr); + + return std::tuple_cat(parse_tuple(reply, idx), + parse_tuple(reply, idx + 1)); +} + +} + +template +Optional parse(ParseTag>, redisReply &reply) { + if (reply::is_nil(reply)) { + return {}; + } + + return Optional(parse(reply)); +} + +template +std::pair parse(ParseTag>, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != 2) { + throw ProtoError("NOT key-value PAIR reply"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null PAIR reply"); + } + + auto *first = reply.element[0]; + auto *second = reply.element[1]; + if (first == nullptr || second == nullptr) { + throw ProtoError("Null pair reply"); + } + + return std::make_pair(parse::type>(*first), + parse::type>(*second)); +} + +template +std::tuple parse(ParseTag>, redisReply &reply) { + constexpr auto size = sizeof...(Args); + + static_assert(size > 0, "DO NOT support parsing tuple with 0 element"); + + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != size) { + throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null TUPLE reply"); + } + + return detail::parse_tuple(reply.element, 0); +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::back_inserter(container)); + + return container; +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::inserter(container, container.end())); + + return container; +} + +template +long long parse_scan_reply(redisReply &reply, Output output) { + if (reply.elements != 2 || reply.element == nullptr) { + throw ProtoError("Invalid scan reply"); + } + + auto *cursor_reply = reply.element[0]; + auto *data_reply = reply.element[1]; + if (cursor_reply == nullptr || data_reply == nullptr) { + throw ProtoError("Invalid cursor reply or data reply"); + } + + auto cursor_str = reply::parse(*cursor_reply); + auto new_cursor = 0; + try { + new_cursor = std::stoll(cursor_str); + } catch (const std::exception &e) { + throw ProtoError("Invalid cursor reply: " + cursor_str); + } + + reply::to_array(*data_reply, output); + + return new_cursor; +} + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + detail::to_array(typename IsKvPairIter::type(), reply, output); +} + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple { + if (!is_array(reply) || reply.elements != 4) { + throw ProtoError("expect array reply with 4 elements"); + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + if (reply.element[idx] == nullptr) { + throw ProtoError("null array reply"); + } + } + + auto num = parse(*(reply.element[0])); + auto start = parse(*(reply.element[1])); + auto end = parse(*(reply.element[2])); + + auto &entry_reply = *(reply.element[3]); + if (!is_nil(entry_reply)) { + to_array(entry_reply, output); + } + + return std::make_tuple(num, std::move(start), std::move(end)); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/sentinel.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/sentinel.h new file mode 100644 index 000000000..e80d1e56a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/sentinel.h @@ -0,0 +1,138 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H +#define SEWENEW_REDISPLUSPLUS_SENTINEL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "shards.h" +#include "reply.h" + +namespace sw { + +namespace redis { + +struct SentinelOptions { + std::vector> nodes; + + std::string password; + + bool keep_alive = true; + + std::chrono::milliseconds connect_timeout{100}; + + std::chrono::milliseconds socket_timeout{100}; + + std::chrono::milliseconds retry_interval{100}; + + std::size_t max_retry = 2; +}; + +class Sentinel { +public: + explicit Sentinel(const SentinelOptions &sentinel_opts); + + Sentinel(const Sentinel &) = delete; + Sentinel& operator=(const Sentinel &) = delete; + + Sentinel(Sentinel &&) = delete; + Sentinel& operator=(Sentinel &&) = delete; + + ~Sentinel() = default; + +private: + Connection master(const std::string &master_name, const ConnectionOptions &opts); + + Connection slave(const std::string &master_name, const ConnectionOptions &opts); + + class Iterator; + + friend class SimpleSentinel; + + std::list _parse_options(const SentinelOptions &opts) const; + + Optional _get_master_addr_by_name(Connection &connection, const StringView &name); + + std::vector _get_slave_addr_by_name(Connection &connection, const StringView &name); + + Connection _connect_redis(const Node &node, ConnectionOptions opts); + + Role _get_role(Connection &connection); + + std::vector _parse_slave_info(redisReply &reply) const; + + std::list _healthy_sentinels; + + std::list _broken_sentinels; + + SentinelOptions _sentinel_opts; + + std::mutex _mutex; +}; + +class SimpleSentinel { +public: + SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role); + + SimpleSentinel() = default; + + SimpleSentinel(const SimpleSentinel &) = default; + SimpleSentinel& operator=(const SimpleSentinel &) = default; + + SimpleSentinel(SimpleSentinel &&) = default; + SimpleSentinel& operator=(SimpleSentinel &&) = default; + + ~SimpleSentinel() = default; + + explicit operator bool() const { + return bool(_sentinel); + } + + Connection create(const ConnectionOptions &opts); + +private: + std::shared_ptr _sentinel; + + std::string _master_name; + + Role _role = Role::MASTER; +}; + +class StopIterError : public Error { +public: + StopIterError() : Error("StopIterError") {} + + StopIterError(const StopIterError &) = default; + StopIterError& operator=(const StopIterError &) = default; + + StopIterError(StopIterError &&) = default; + StopIterError& operator=(StopIterError &&) = default; + + virtual ~StopIterError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards.h new file mode 100644 index 000000000..a0593acbc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_H + +#include +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +using Slot = std::size_t; + +struct SlotRange { + Slot min; + Slot max; +}; + +inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) { + return lhs.max < rhs.max; +} + +struct Node { + std::string host; + int port; +}; + +inline bool operator==(const Node &lhs, const Node &rhs) { + return lhs.host == rhs.host && lhs.port == rhs.port; +} + +struct NodeHash { + std::size_t operator()(const Node &node) const noexcept { + auto host_hash = std::hash{}(node.host); + auto port_hash = std::hash{}(node.port); + return host_hash ^ (port_hash << 1); + } +}; + +using Shards = std::map; + +class RedirectionError : public ReplyError { +public: + RedirectionError(const std::string &msg); + + RedirectionError(const RedirectionError &) = default; + RedirectionError& operator=(const RedirectionError &) = default; + + RedirectionError(RedirectionError &&) = default; + RedirectionError& operator=(RedirectionError &&) = default; + + virtual ~RedirectionError() = default; + + Slot slot() const { + return _slot; + } + + const Node& node() const { + return _node; + } + +private: + std::pair _parse_error(const std::string &msg) const; + + Slot _slot = 0; + Node _node; +}; + +class MovedError : public RedirectionError { +public: + explicit MovedError(const std::string &msg) : RedirectionError(msg) {} + + MovedError(const MovedError &) = default; + MovedError& operator=(const MovedError &) = default; + + MovedError(MovedError &&) = default; + MovedError& operator=(MovedError &&) = default; + + virtual ~MovedError() = default; +}; + +class AskError : public RedirectionError { +public: + explicit AskError(const std::string &msg) : RedirectionError(msg) {} + + AskError(const AskError &) = default; + AskError& operator=(const AskError &) = default; + + AskError(AskError &&) = default; + AskError& operator=(AskError &&) = default; + + virtual ~AskError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards_pool.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards_pool.h new file mode 100644 index 000000000..1184806e9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/shards_pool.h @@ -0,0 +1,137 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H + +#include +#include +#include +#include +#include +#include "reply.h" +#include "connection_pool.h" +#include "shards.h" + +namespace sw { + +namespace redis { + +using ConnectionPoolSPtr = std::shared_ptr; + +class GuardedConnection { +public: + GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool), + _connection(_pool->fetch()) { + assert(!_connection.broken()); + } + + GuardedConnection(const GuardedConnection &) = delete; + GuardedConnection& operator=(const GuardedConnection &) = delete; + + GuardedConnection(GuardedConnection &&) = default; + GuardedConnection& operator=(GuardedConnection &&) = default; + + ~GuardedConnection() { + _pool->release(std::move(_connection)); + } + + Connection& connection() { + return _connection; + } + +private: + ConnectionPoolSPtr _pool; + Connection _connection; +}; + +class ShardsPool { +public: + ShardsPool() = default; + + ShardsPool(const ShardsPool &that) = delete; + ShardsPool& operator=(const ShardsPool &that) = delete; + + ShardsPool(ShardsPool &&that); + ShardsPool& operator=(ShardsPool &&that); + + ~ShardsPool() = default; + + ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + // Fetch a connection by key. + GuardedConnection fetch(const StringView &key); + + // Randomly pick a connection. + GuardedConnection fetch(); + + // Fetch a connection by node. + GuardedConnection fetch(const Node &node); + + void update(); + + ConnectionOptions connection_options(const StringView &key); + + ConnectionOptions connection_options(); + +private: + void _move(ShardsPool &&that); + + void _init_pool(const Shards &shards); + + Shards _cluster_slots(Connection &connection) const; + + ReplyUPtr _cluster_slots_command(Connection &connection) const; + + Shards _parse_reply(redisReply &reply) const; + + std::pair _parse_slot_info(redisReply &reply) const; + + // Get slot by key. + std::size_t _slot(const StringView &key) const; + + // Randomly pick a slot. + std::size_t _slot() const; + + ConnectionPoolSPtr& _get_pool(Slot slot); + + GuardedConnection _fetch(Slot slot); + + ConnectionOptions _connection_options(Slot slot); + + using NodeMap = std::unordered_map; + + NodeMap::iterator _add_node(const Node &node); + + ConnectionPoolOptions _pool_opts; + + ConnectionOptions _connection_opts; + + Shards _shards; + + NodeMap _pools; + + std::mutex _mutex; + + static const std::size_t SHARDS = 16383; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/subscriber.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/subscriber.h new file mode 100644 index 000000000..8b7c5cfb4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/subscriber.h @@ -0,0 +1,231 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H +#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H + +#include +#include +#include +#include "connection.h" +#include "reply.h" +#include "command.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +// @NOTE: Subscriber is NOT thread-safe. +// Subscriber uses callbacks to handle messages. There are 6 kinds of messages: +// 1) MESSAGE: message sent to a channel. +// 2) PMESSAGE: message sent to channels of a given pattern. +// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel. +// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel. +// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern. +// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern. +// +// Use Subscriber::on_message(MsgCallback) to set the callback function for message of +// *MESSAGE* type, and the callback interface is: +// void (std::string channel, std::string msg) +// +// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of +// *PMESSAGE* type, and the callback interface is: +// void (std::string pattern, std::string channel, std::string msg) +// +// Messages of other types are called *META MESSAGE*, they have the same callback interface. +// Use Subscriber::on_meta(MetaCallback) to set the callback function: +// void (Subscriber::MsgType type, OptionalString channel, long long num) +// +// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to +// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all +// channels/patterns, *channel* will be null. So the second parameter of meta callback +// is of type *OptionalString*. +// +// All these callback interfaces pass std::string by value, and you can take their ownership +// (i.e. std::move) safely. +// +// If you don't set callback for a specific kind of message, Subscriber::consume() will +// receive the message, and ignore it, i.e. no callback will be called. +class Subscriber { +public: + Subscriber(const Subscriber &) = delete; + Subscriber& operator=(const Subscriber &) = delete; + + Subscriber(Subscriber &&) = default; + Subscriber& operator=(Subscriber &&) = default; + + ~Subscriber() = default; + + enum class MsgType { + SUBSCRIBE, + UNSUBSCRIBE, + PSUBSCRIBE, + PUNSUBSCRIBE, + MESSAGE, + PMESSAGE + }; + + template + void on_message(MsgCb msg_callback); + + template + void on_pmessage(PMsgCb pmsg_callback); + + template + void on_meta(MetaCb meta_callback); + + void subscribe(const StringView &channel); + + template + void subscribe(Input first, Input last); + + template + void subscribe(std::initializer_list channels) { + subscribe(channels.begin(), channels.end()); + } + + void unsubscribe(); + + void unsubscribe(const StringView &channel); + + template + void unsubscribe(Input first, Input last); + + template + void unsubscribe(std::initializer_list channels) { + unsubscribe(channels.begin(), channels.end()); + } + + void psubscribe(const StringView &pattern); + + template + void psubscribe(Input first, Input last); + + template + void psubscribe(std::initializer_list channels) { + psubscribe(channels.begin(), channels.end()); + } + + void punsubscribe(); + + void punsubscribe(const StringView &channel); + + template + void punsubscribe(Input first, Input last); + + template + void punsubscribe(std::initializer_list channels) { + punsubscribe(channels.begin(), channels.end()); + } + + void consume(); + +private: + friend class Redis; + + friend class RedisCluster; + + explicit Subscriber(Connection connection); + + MsgType _msg_type(redisReply *reply) const; + + void _check_connection(); + + void _handle_message(redisReply &reply); + + void _handle_pmessage(redisReply &reply); + + void _handle_meta(MsgType type, redisReply &reply); + + using MsgCallback = std::function; + + using PatternMsgCallback = std::function; + + using MetaCallback = std::function; + + using TypeIndex = std::unordered_map; + static const TypeIndex _msg_type_index; + + Connection _connection; + + MsgCallback _msg_callback = nullptr; + + PatternMsgCallback _pmsg_callback = nullptr; + + MetaCallback _meta_callback = nullptr; +}; + +template +void Subscriber::on_message(MsgCb msg_callback) { + _msg_callback = msg_callback; +} + +template +void Subscriber::on_pmessage(PMsgCb pmsg_callback) { + _pmsg_callback = pmsg_callback; +} + +template +void Subscriber::on_meta(MetaCb meta_callback) { + _meta_callback = meta_callback; +} + +template +void Subscriber::subscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::subscribe_range(_connection, first, last); +} + +template +void Subscriber::unsubscribe(Input first, Input last) { + _check_connection(); + + cmd::unsubscribe_range(_connection, first, last); +} + +template +void Subscriber::psubscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::psubscribe_range(_connection, first, last); +} + +template +void Subscriber::punsubscribe(Input first, Input last) { + _check_connection(); + + cmd::punsubscribe_range(_connection, first, last); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/transaction.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/transaction.h new file mode 100644 index 000000000..f19f24889 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/transaction.h @@ -0,0 +1,77 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H +#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H + +#include +#include +#include "connection.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class TransactionImpl { +public: + explicit TransactionImpl(bool piped) : _piped(piped) {} + + template + void command(Connection &connection, Cmd cmd, Args &&...args); + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t cmd_num); + +private: + void _open_transaction(Connection &connection); + + void _close_transaction(); + + void _get_queued_reply(Connection &connection); + + void _get_queued_replies(Connection &connection, std::size_t cmd_num); + + std::vector _exec(Connection &connection); + + void _discard(Connection &connection); + + bool _in_transaction = false; + + bool _piped; +}; + +template +void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + if (!_in_transaction) { + _open_transaction(connection); + } + + cmd(connection, std::forward(args)...); + + if (!_piped) { + _get_queued_reply(connection); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H diff --git a/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/utils.h b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/utils.h new file mode 100644 index 000000000..e29e64e14 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/macos/include/sw/redis++/utils.h @@ -0,0 +1,269 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H +#define SEWENEW_REDISPLUSPLUS_UTILS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +// By now, not all compilers support std::string_view, +// so we make our own implementation. +class StringView { +public: + constexpr StringView() noexcept = default; + + constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {} + + StringView(const char *data) : _data(data), _size(std::strlen(data)) {} + + StringView(const std::string &str) : _data(str.data()), _size(str.size()) {} + + constexpr StringView(const StringView &) noexcept = default; + + StringView& operator=(const StringView &) noexcept = default; + + constexpr const char* data() const noexcept { + return _data; + } + + constexpr std::size_t size() const noexcept { + return _size; + } + +private: + const char *_data = nullptr; + std::size_t _size = 0; +}; + +template +class Optional { +public: + Optional() = default; + + Optional(const Optional &) = default; + Optional& operator=(const Optional &) = default; + + Optional(Optional &&) = default; + Optional& operator=(Optional &&) = default; + + ~Optional() = default; + + template + explicit Optional(Args &&...args) : _value(true, T(std::forward(args)...)) {} + + explicit operator bool() const { + return _value.first; + } + + T& value() { + return _value.second; + } + + const T& value() const { + return _value.second; + } + + T* operator->() { + return &(_value.second); + } + + const T* operator->() const { + return &(_value.second); + } + + T& operator*() { + return _value.second; + } + + const T& operator*() const { + return _value.second; + } + +private: + std::pair _value; +}; + +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; + +template +struct IsKvPair : std::false_type {}; + +template +struct IsKvPair> : std::true_type {}; + +template +using Void = void; + +template > +struct IsInserter : std::false_type {}; + +template +//struct IsInserter> : std::true_type {}; +struct IsInserter::value>::type> + : std::true_type {}; + +template > +struct IterType { + using type = typename std::iterator_traits::value_type; +}; + +template +//struct IterType> { +struct IterType::value>::type> { + typename std::enable_if::value>::type> { + using type = typename std::decay::type; +}; + +template > +struct IsIter : std::false_type {}; + +template +struct IsIter::value>::type> : std::true_type {}; + +template +//struct IsIter::iterator_category>> +struct IsIter::value_type>::value>::type> + : std::integral_constant::value> {}; + +template +struct IsKvPairIter : IsKvPair::type> {}; + +template +struct TupleWithType : std::false_type {}; + +template +struct TupleWithType> : std::false_type {}; + +template +struct TupleWithType> : TupleWithType> {}; + +template +struct TupleWithType> : std::true_type {}; + +template +struct IndexSequence {}; + +template +struct MakeIndexSequence : MakeIndexSequence {}; + +template +struct MakeIndexSequence<0, Is...> : IndexSequence {}; + +// NthType and NthValue are taken from +// https://stackoverflow.com/questions/14261183 +template +struct NthType {}; + +template +struct NthType<0, Arg, Args...> { + using type = Arg; +}; + +template +struct NthType { + using type = typename NthType::type; +}; + +template +struct LastType { + using type = typename NthType::type; +}; + +struct InvalidLastType {}; + +template <> +struct LastType<> { + using type = InvalidLastType; +}; + +template +auto NthValue(Arg &&arg, Args &&...) + -> typename std::enable_if<(I == 0), decltype(std::forward(arg))>::type { + return std::forward(arg); +} + +template +auto NthValue(Arg &&, Args &&...args) + -> typename std::enable_if<(I > 0), + decltype(std::forward::type>( + std::declval::type>()))>::type { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template +auto LastValue(Args &&...args) + -> decltype(std::forward::type>( + std::declval::type>())) { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template > +struct HasPushBack : std::false_type {}; + +template +struct HasPushBack().push_back(std::declval()) + )>::value>::type> : std::true_type {}; + +template > +struct HasInsert : std::false_type {}; + +template +struct HasInsert().insert(std::declval(), + std::declval())), + typename T::iterator>::value>::type> : std::true_type {}; + +template +struct IsSequenceContainer + : std::integral_constant::value + && !std::is_same::type, std::string>::value> {}; + +template +struct IsAssociativeContainer + : std::integral_constant::value && !HasPushBack::value> {}; + +uint16_t crc16(const char *buf, int len); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.cpp new file mode 100644 index 000000000..bb1afb2a9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.cpp @@ -0,0 +1,376 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "command.h" +#include + +namespace sw { + +namespace redis { + +namespace cmd { + +// KEY commands. + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace) { + CmdArgs args; + args << "RESTORE" << key << ttl << val; + + if (replace) { + args << "REPLACE"; + } + + connection.send(args); +} + +// STRING commands. + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key) { + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << key; + + connection.send(args); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type) { + CmdArgs args; + args << "SET" << key << val; + + if (ttl > 0) { + args << "PX" << ttl; + } + + detail::set_update_type(args, type); + + connection.send(args); +} + +// LIST commands. + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + std::string pos; + switch (position) { + case InsertPosition::BEFORE: + pos = "BEFORE"; + break; + + case InsertPosition::AFTER: + pos = "AFTER"; + break; + + default: + assert(false); + } + + connection.send("LINSERT %b %s %b %b", + key.data(), key.size(), + pos.c_str(), + pivot.data(), pivot.size(), + val.data(), val.size()); +} + +// GEO commands. + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit) { + CmdArgs args; + args << "GEODIST" << key << member1 << member2; + + detail::set_geo_unit(args, unit); + + connection.send(args); +} + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + CmdArgs args; + args << "GEORADIUS" << key << loc.first << loc.second; + + detail::set_georadius_store_parameters(args, + radius, + unit, + destination, + store_dist, + count); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + CmdArgs args; + args << "GEORADIUS" << key << loc.first << loc.second; + + detail::set_georadius_parameters(args, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + + connection.send(args); +} + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + CmdArgs args; + args << "GEORADIUSBYMEMBER" << key << member; + + detail::set_georadius_parameters(args, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + + connection.send(args); +} + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + CmdArgs args; + args << "GEORADIUSBYMEMBER" << key << member; + + detail::set_georadius_store_parameters(args, + radius, + unit, + destination, + store_dist, + count); + + connection.send(args); +} + +// Stream commands. + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx) { + CmdArgs args; + args << "XTRIM" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count; + + connection.send(args); +} + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op) { + args << "BITOP"; + + switch (op) { + case BitOp::AND: + args << "AND"; + break; + + case BitOp::OR: + args << "OR"; + break; + + case BitOp::XOR: + args << "XOR"; + break; + + case BitOp::NOT: + args << "NOT"; + break; + + default: + throw Error("Unknown bit operations"); + } +} + +void set_update_type(CmdArgs &args, UpdateType type) { + switch (type) { + case UpdateType::EXIST: + args << "XX"; + break; + + case UpdateType::NOT_EXIST: + args << "NX"; + break; + + case UpdateType::ALWAYS: + // Do nothing. + break; + + default: + throw Error("Unknown update type"); + } +} + +void set_aggregation_type(CmdArgs &args, Aggregation aggr) { + args << "AGGREGATE"; + + switch (aggr) { + case Aggregation::SUM: + args << "SUM"; + break; + + case Aggregation::MIN: + args << "MIN"; + break; + + case Aggregation::MAX: + args << "MAX"; + break; + + default: + throw Error("Unknown aggregation type"); + } +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit) { + switch (unit) { + case GeoUnit::M: + args << "m"; + break; + + case GeoUnit::KM: + args << "km"; + break; + + case GeoUnit::MI: + args << "mi"; + break; + + case GeoUnit::FT: + args << "ft"; + break; + + default: + throw Error("Unknown geo unit type"); + break; + } +} + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + args << radius; + + detail::set_geo_unit(args, unit); + + args << "COUNT" << count; + + if (store_dist) { + args << "STOREDIST"; + } else { + args << "STORE"; + } + + args << destination; +} + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + args << radius; + + detail::set_geo_unit(args, unit); + + if (with_coord) { + args << "WITHCOORD"; + } + + if (with_dist) { + args << "WITHDIST"; + } + + if (with_hash) { + args << "WITHHASH"; + } + + args << "COUNT" << count; + + if (asc) { + args << "ASC"; + } else { + args << "DESC"; + } +} + +} + +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.h new file mode 100644 index 000000000..3a4b24c5e --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command.h @@ -0,0 +1,2233 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_H + +#include +#include +#include +#include +#include "connection.h" +#include "command_options.h" +#include "command_args.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace cmd { + +// CONNECTION command. +inline void auth(Connection &connection, const StringView &password) { + connection.send("AUTH %b", password.data(), password.size()); +} + +inline void echo(Connection &connection, const StringView &msg) { + connection.send("ECHO %b", msg.data(), msg.size()); +} + +inline void ping(Connection &connection) { + connection.send("PING"); +} + +inline void quit(Connection &connection) { + connection.send("QUIT"); +} + +inline void ping(Connection &connection, const StringView &msg) { + // If *msg* is empty, Redis returns am empty reply of REDIS_REPLY_STRING type. + connection.send("PING %b", msg.data(), msg.size()); +} + +inline void select(Connection &connection, long long idx) { + connection.send("SELECT %lld", idx); +} + +inline void swapdb(Connection &connection, long long idx1, long long idx2) { + connection.send("SWAPDB %lld %lld", idx1, idx2); +} + +// SERVER commands. + +inline void bgrewriteaof(Connection &connection) { + connection.send("BGREWRITEAOF"); +} + +inline void bgsave(Connection &connection) { + connection.send("BGSAVE"); +} + +inline void dbsize(Connection &connection) { + connection.send("DBSIZE"); +} + +inline void flushall(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHALL ASYNC"); + } else { + connection.send("FLUSHALL"); + } +} + +inline void flushdb(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHDB ASYNC"); + } else { + connection.send("FLUSHDB"); + } +} + +inline void info(Connection &connection) { + connection.send("INFO"); +} + +inline void info(Connection &connection, const StringView §ion) { + connection.send("INFO %b", section.data(), section.size()); +} + +inline void lastsave(Connection &connection) { + connection.send("LASTSAVE"); +} + +inline void save(Connection &connection) { + connection.send("SAVE"); +} + +// KEY commands. + +inline void del(Connection &connection, const StringView &key) { + connection.send("DEL %b", key.data(), key.size()); +} + +template +inline void del_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "DEL" << std::make_pair(first, last); + + connection.send(args); +} + +inline void dump(Connection &connection, const StringView &key) { + connection.send("DUMP %b", key.data(), key.size()); +} + +inline void exists(Connection &connection, const StringView &key) { + connection.send("EXISTS %b", key.data(), key.size()); +} + +template +inline void exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void expire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("EXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void expireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("EXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void keys(Connection &connection, const StringView &pattern) { + connection.send("KEYS %b", pattern.data(), pattern.size()); +} + +inline void move(Connection &connection, const StringView &key, long long db) { + connection.send("MOVE %b %lld", + key.data(), key.size(), + db); +} + +inline void persist(Connection &connection, const StringView &key) { + connection.send("PERSIST %b", key.data(), key.size()); +} + +inline void pexpire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("PEXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void pexpireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("PEXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void pttl(Connection &connection, const StringView &key) { + connection.send("PTTL %b", key.data(), key.size()); +} + +inline void randomkey(Connection &connection) { + connection.send("RANDOMKEY"); +} + +inline void rename(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAME %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +inline void renamenx(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAMENX %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace); + +inline void scan(Connection &connection, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SCAN %lld MATCH %b COUNT %lld", + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void touch(Connection &connection, const StringView &key) { + connection.send("TOUCH %b", key.data(), key.size()); +} + +template +inline void touch_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "TOUCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void ttl(Connection &connection, const StringView &key) { + connection.send("TTL %b", key.data(), key.size()); +} + +inline void type(Connection &connection, const StringView &key) { + connection.send("TYPE %b", key.data(), key.size()); +} + +inline void unlink(Connection &connection, const StringView &key) { + connection.send("UNLINK %b", key.data(), key.size()); +} + +template +inline void unlink_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "UNLINK" << std::make_pair(first, last); + + connection.send(args); +} + +inline void wait(Connection &connection, long long numslave, long long timeout) { + connection.send("WAIT %lld %lld", numslave, timeout); +} + +// STRING commands. + +inline void append(Connection &connection, const StringView &key, const StringView &str) { + connection.send("APPEND %b %b", + key.data(), key.size(), + str.data(), str.size()); +} + +inline void bitcount(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("BITCOUNT %b %lld %lld", + key.data(), key.size(), + start, end); +} + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key); + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last); + +inline void bitpos(Connection &connection, + const StringView &key, + long long bit, + long long start, + long long end) { + connection.send("BITPOS %b %lld %lld %lld", + key.data(), key.size(), + bit, + start, + end); +} + +inline void decr(Connection &connection, const StringView &key) { + connection.send("DECR %b", key.data(), key.size()); +} + +inline void decrby(Connection &connection, const StringView &key, long long decrement) { + connection.send("DECRBY %b %lld", + key.data(), key.size(), + decrement); +} + +inline void get(Connection &connection, const StringView &key) { + connection.send("GET %b", + key.data(), key.size()); +} + +inline void getbit(Connection &connection, const StringView &key, long long offset) { + connection.send("GETBIT %b %lld", + key.data(), key.size(), + offset); +} + +inline void getrange(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("GETRANGE %b %lld %lld", + key.data(), key.size(), + start, + end); +} + +inline void getset(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("GETSET %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void incr(Connection &connection, const StringView &key) { + connection.send("INCR %b", key.data(), key.size()); +} + +inline void incrby(Connection &connection, const StringView &key, long long increment) { + connection.send("INCRBY %b %lld", + key.data(), key.size(), + increment); +} + +inline void incrbyfloat(Connection &connection, const StringView &key, double increment) { + connection.send("INCRBYFLOAT %b %f", + key.data(), key.size(), + increment); +} + +template +inline void mget(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MGET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void mset(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void msetnx(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSETNX" << std::make_pair(first, last); + + connection.send(args); +} + +inline void psetex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("PSETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type); + +inline void setex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("SETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +inline void setnx(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("SETNX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void setrange(Connection &connection, + const StringView &key, + long long offset, + const StringView &val) { + connection.send("SETRANGE %b %lld %b", + key.data(), key.size(), + offset, + val.data(), val.size()); +} + +inline void strlen(Connection &connection, const StringView &key) { + connection.send("STRLEN %b", key.data(), key.size()); +} + +// LIST commands. + +inline void blpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BLPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void blpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BLPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BRPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void brpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BRPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpoplpush(Connection &connection, + const StringView &source, + const StringView &destination, + long long timeout) { + connection.send("BRPOPLPUSH %b %b %lld", + source.data(), source.size(), + destination.data(), destination.size(), + timeout); +} + +inline void lindex(Connection &connection, const StringView &key, long long index) { + connection.send("LINDEX %b %lld", + key.data(), key.size(), + index); +} + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + +inline void llen(Connection &connection, + const StringView &key) { + connection.send("LLEN %b", key.data(), key.size()); +} + +inline void lpop(Connection &connection, const StringView &key) { + connection.send("LPOP %b", + key.data(), key.size()); +} + +inline void lpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void lpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "LPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void lpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void lrange(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void lrem(Connection &connection, + const StringView &key, + long long count, + const StringView &val) { + connection.send("LREM %b %lld %b", + key.data(), key.size(), + count, + val.data(), val.size()); +} + +inline void lset(Connection &connection, + const StringView &key, + long long index, + const StringView &val) { + connection.send("LSET %b %lld %b", + key.data(), key.size(), + index, + val.data(), val.size()); +} + +inline void ltrim(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LTRIM %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void rpop(Connection &connection, const StringView &key) { + connection.send("RPOP %b", key.data(), key.size()); +} + +inline void rpoplpush(Connection &connection, + const StringView &source, + const StringView &destination) { + connection.send("RPOPLPUSH %b %b", + source.data(), source.size(), + destination.data(), destination.size()); +} + +inline void rpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void rpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "RPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void rpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +// HASH commands. + +inline void hdel(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HDEL %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +template +inline void hdel_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hexists(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HEXISTS %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hget(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HGET %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hgetall(Connection &connection, const StringView &key) { + connection.send("HGETALL %b", key.data(), key.size()); +} + +inline void hincrby(Connection &connection, + const StringView &key, + const StringView &field, + long long increment) { + connection.send("HINCRBY %b %b %lld", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hincrbyfloat(Connection &connection, + const StringView &key, + const StringView &field, + double increment) { + connection.send("HINCRBYFLOAT %b %b %f", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hkeys(Connection &connection, const StringView &key) { + connection.send("HKEYS %b", key.data(), key.size()); +} + +inline void hlen(Connection &connection, const StringView &key) { + connection.send("HLEN %b", key.data(), key.size()); +} + +template +inline void hmget(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMGET" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void hmset(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMSET" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("HSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void hset(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSET %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hsetnx(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSETNX %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hstrlen(Connection &connection, + const StringView &key, + const StringView &field) { + connection.send("HSTRLEN %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hvals(Connection &connection, const StringView &key) { + connection.send("HVALS %b", key.data(), key.size()); +} + +// SET commands + +inline void sadd(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SADD %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void sadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void scard(Connection &connection, const StringView &key) { + connection.send("SCARD %b", key.data(), key.size()); +} + +template +inline void sdiff(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFF" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sdiffstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SDIFFSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sdiffstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFFSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void sinter(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTER" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sinterstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SINTERSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTERSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +inline void sismember(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SISMEMBER %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void smembers(Connection &connection, const StringView &key) { + connection.send("SMEMBERS %b", key.data(), key.size()); +} + +inline void smove(Connection &connection, + const StringView &source, + const StringView &destination, + const StringView &member) { + connection.send("SMOVE %b %b %b", + source.data(), source.size(), + destination.data(), destination.size(), + member.data(), member.size()); +} + +inline void spop(Connection &connection, const StringView &key) { + connection.send("SPOP %b", key.data(), key.size()); +} + +inline void spop_range(Connection &connection, const StringView &key, long long count) { + connection.send("SPOP %b %lld", + key.data(), key.size(), + count); +} + +inline void srandmember(Connection &connection, const StringView &key) { + connection.send("SRANDMEMBER %b", key.data(), key.size()); +} + +inline void srandmember_range(Connection &connection, + const StringView &key, + long long count) { + connection.send("SRANDMEMBER %b %lld", + key.data(), key.size(), + count); +} + +inline void srem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void srem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void sscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +template +inline void sunion(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNION" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sunionstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SUNIONSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNIONSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// Sorted Set commands. + +inline void bzpopmax(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMAX %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmax_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMAX" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void bzpopmin(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMIN %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmin_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMIN" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed); + +inline void zadd(Connection &connection, + const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto tmp = {std::make_pair(member, score)}; + + zadd_range(connection, key, tmp.begin(), tmp.end(), type, changed); +} + +inline void zcard(Connection &connection, const StringView &key) { + connection.send("ZCARD %b", key.data(), key.size()); +} + +template +inline void zcount(Connection &connection, + const StringView &key, + const Interval &interval) { + connection.send("ZCOUNT %b %s %s", + key.data(), key.size(), + interval.min().c_str(), + interval.max().c_str()); +} + +inline void zincrby(Connection &connection, + const StringView &key, + double increment, + const StringView &member) { + connection.send("ZINCRBY %b %f %b", + key.data(), key.size(), + increment, + member.data(), member.size()); +} + +inline void zinterstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZINTERSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +template +inline void zlexcount(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZLEXCOUNT %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zpopmax(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMAX %b %lld", + key.data(), key.size(), + count); +} + +inline void zpopmin(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMIN %b %lld", + key.data(), key.size(), + count); +} + +inline void zrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +template +void zrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } else { + connection.send("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } +} + +inline void zrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zrem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void zrem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "ZREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void zremrangebylex(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYLEX %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zremrangebyrank(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("zremrangebyrank %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +template +inline void zremrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYSCORE %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zrevrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZREVRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZREVRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrevrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); +} + +template +void zrevrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZREVRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } else { + connection.send("ZREVRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } +} + +inline void zrevrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREVRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("ZSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void zscore(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZSCORE %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zunionstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZUNIONSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +// HYPERLOGLOG commands. + +inline void pfadd(Connection &connection, + const StringView &key, + const StringView &element) { + connection.send("PFADD %b %b", + key.data(), key.size(), + element.data(), element.size()); +} + +template +inline void pfadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfcount(Connection &connection, const StringView &key) { + connection.send("PFCOUNT %b", key.data(), key.size()); +} + +template +inline void pfcount_range(Connection &connection, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFCOUNT" << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfmerge(Connection &connection, const StringView &destination, const StringView &key) { + connection.send("PFMERGE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void pfmerge_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFMERGE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// GEO commands. + +inline void geoadd(Connection &connection, + const StringView &key, + const std::tuple &member) { + const auto &mem = std::get<0>(member); + + connection.send("GEOADD %b %f %f %b", + key.data(), key.size(), + std::get<1>(member), + std::get<2>(member), + mem.data(), mem.size()); +} + +template +inline void geoadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOADD" << key; + + while (first != last) { + const auto &member = *first; + args << std::get<1>(member) << std::get<2>(member) << std::get<0>(member); + ++first; + } + + connection.send(args); +} + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit); + +template +inline void geohash_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOHASH" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void geopos_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOPOS" << key << std::make_pair(first, last); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +// SCRIPTING commands. + +inline void eval(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVAL" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void evalsha(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVALSHA" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void script_exists(Connection &connection, const StringView &sha) { + connection.send("SCRIPT EXISTS %b", sha.data(), sha.size()); +} + +template +inline void script_exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SCRIPT" << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void script_flush(Connection &connection) { + connection.send("SCRIPT FLUSH"); +} + +inline void script_kill(Connection &connection) { + connection.send("SCRIPT KILL"); +} + +inline void script_load(Connection &connection, const StringView &script) { + connection.send("SCRIPT LOAD %b", script.data(), script.size()); +} + +// PUBSUB commands. + +inline void psubscribe(Connection &connection, const StringView &pattern) { + connection.send("PSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void psubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void publish(Connection &connection, + const StringView &channel, + const StringView &message) { + connection.send("PUBLISH %b %b", + channel.data(), channel.size(), + message.data(), message.size()); +} + +inline void punsubscribe(Connection &connection) { + connection.send("PUNSUBSCRIBE"); +} + +inline void punsubscribe(Connection &connection, const StringView &pattern) { + connection.send("PUNSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void punsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PUNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PUNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void subscribe(Connection &connection, const StringView &channel) { + connection.send("SUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void subscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("SUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "SUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void unsubscribe(Connection &connection) { + connection.send("UNSUBSCRIBE"); +} + +inline void unsubscribe(Connection &connection, const StringView &channel) { + connection.send("UNSUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void unsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "UNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +// Transaction commands. + +inline void discard(Connection &connection) { + connection.send("DISCARD"); +} + +inline void exec(Connection &connection) { + connection.send("EXEC"); +} + +inline void multi(Connection &connection) { + connection.send("MULTI"); +} + +inline void unwatch(Connection &connection, const StringView &key) { + connection.send("UNWATCH %b", key.data(), key.size()); +} + +template +inline void unwatch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNWATCH: no key specified"); + } + + CmdArgs args; + args << "UNWATCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void watch(Connection &connection, const StringView &key) { + connection.send("WATCH %b", key.data(), key.size()); +} + +template +inline void watch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("WATCH: no key specified"); + } + + CmdArgs args; + args << "WATCH" << std::make_pair(first, last); + + connection.send(args); +} + +// Stream commands. + +inline void xack(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XACK %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +template +void xack_range(Connection &connection, + const StringView &key, + const StringView &group, + Input first, + Input last) { + CmdArgs args; + args << "XACK" << key << group << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last) { + CmdArgs args; + args << "XADD" << key << id << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_maxlen_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + CmdArgs args; + args << "XADD" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count << id << std::make_pair(first, last); + + connection.send(args); +} + +inline void xclaim(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + const StringView &id) { + connection.send("XCLAIM %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size(), + min_idle_time, + id.data(), id.size()); +} + +template +void xclaim_range(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + Input first, + Input last) { + CmdArgs args; + args << "XCLAIM" << key << group << consumer << min_idle_time << std::make_pair(first, last); + + connection.send(args); +} + +inline void xdel(Connection &connection, const StringView &key, const StringView &id) { + connection.send("XDEL %b %b", key.data(), key.size(), id.data(), id.size()); +} + +template +void xdel_range(Connection &connection, const StringView &key, Input first, Input last) { + CmdArgs args; + args << "XDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void xgroup_create(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + CmdArgs args; + args << "XGROUP" << "CREATE" << key << group << id; + + if (mkstream) { + args << "MKSTREAM"; + } + + connection.send(args); +} + +inline void xgroup_setid(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XGROUP SETID %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +inline void xgroup_destroy(Connection &connection, + const StringView &key, + const StringView &group) { + connection.send("XGROUP DESTROY %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xgroup_delconsumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer) { + connection.send("XGROUP DELCONSUMER %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size()); +} + +inline void xlen(Connection &connection, const StringView &key) { + connection.send("XLEN %b", key.data(), key.size()); +} + +inline void xpending(Connection &connection, const StringView &key, const StringView &group) { + connection.send("XPENDING %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xpending_detail(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XPENDING %b %b %b %b %lld", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xpending_per_consumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + connection.send("XPENDING %b %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count, + consumer.data(), consumer.size()); +} + +inline void xrange(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end) { + connection.send("XRANGE %b %b %b", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size()); +} + +inline void xrange_count(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xread(Connection &connection, + const StringView &key, + const StringView &id, + long long count) { + connection.send("XREAD COUNT %lld STREAMS %b %b", + count, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_range(Connection &connection, Input first, Input last, long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xread_block(Connection &connection, + const StringView &key, + const StringView &id, + long long timeout, + long long count) { + connection.send("XREAD COUNT %lld BLOCK %lld STREAMS %b %b", + count, + timeout, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_block_range(Connection &connection, + Input first, + Input last, + long long timeout, + long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "BLOCK" << timeout << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup_block(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_block_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xrevrange(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start) { + connection.send("XREVRANGE %b %b %b", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size()); +} + +inline void xrevrange_count(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + connection.send("XREVRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size(), + count); +} + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx); + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op); + +void set_update_type(CmdArgs &args, UpdateType type); + +void set_aggregation_type(CmdArgs &args, Aggregation type); + +template +void zinterstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zinterstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit); + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +} + +} + +} + +} + +namespace sw { + +namespace redis { + +namespace cmd { + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + assert(first != last); + + CmdArgs args; + + args << "ZADD" << key; + + detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + while (first != last) { + // Swap the pair to pair. + args << first->second << first->first; + ++first; + } + + connection.send(args); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zinterstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zunionstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_args.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_args.h new file mode 100644 index 000000000..0beb71e5c --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_args.h @@ -0,0 +1,180 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +class CmdArgs { +public: + template + CmdArgs& append(Arg &&arg); + + template + CmdArgs& append(Arg &&arg, Args &&...args); + + // All overloads of operator<< are for internal use only. + CmdArgs& operator<<(const StringView &arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& operator<<(T &&arg); + + template + CmdArgs& operator<<(const std::pair &range); + + template + auto operator<<(const std::tuple &) -> + typename std::enable_if::type { + return *this; + } + + template + auto operator<<(const std::tuple &arg) -> + typename std::enable_if::type; + + const char** argv() { + return _argv.data(); + } + + const std::size_t* argv_len() { + return _argv_len.data(); + } + + std::size_t size() const { + return _argv.size(); + } + +private: + // Deep copy. + CmdArgs& _append(std::string arg); + + // Shallow copy. + CmdArgs& _append(const StringView &arg); + + // Shallow copy. + CmdArgs& _append(const char *arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& _append(T &&arg) { + return operator<<(std::forward(arg)); + } + + template + CmdArgs& _append(std::true_type, const std::pair &range); + + template + CmdArgs& _append(std::false_type, const std::pair &range); + + std::vector _argv; + std::vector _argv_len; + + std::list _args; +}; + +template +inline CmdArgs& CmdArgs::append(Arg &&arg) { + return _append(std::forward(arg)); +} + +template +inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) { + _append(std::forward(arg)); + + return append(std::forward(args)...); +} + +inline CmdArgs& CmdArgs::operator<<(const StringView &arg) { + _argv.push_back(arg.data()); + _argv_len.push_back(arg.size()); + + return *this; +} + +template +inline CmdArgs& CmdArgs::operator<<(const std::pair &range) { + return _append(IsKvPair())>::type>(), range); +} + +template ::type>::value, + int>::type> +inline CmdArgs& CmdArgs::operator<<(T &&arg) { + return _append(std::to_string(std::forward(arg))); +} + +template +auto CmdArgs::operator<<(const std::tuple &arg) -> + typename std::enable_if::type { + operator<<(std::get(arg)); + + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(std::string arg) { + _args.push_back(std::move(arg)); + return operator<<(_args.back()); +} + +inline CmdArgs& CmdArgs::_append(const StringView &arg) { + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(const char *arg) { + return operator<<(arg); +} + +template +CmdArgs& CmdArgs::_append(std::false_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << *first; + ++first; + } + + return *this; +} + +template +CmdArgs& CmdArgs::_append(std::true_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << first->first << first->second; + ++first; + } + + return *this; +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.cpp new file mode 100644 index 000000000..0c9254e86 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.cpp @@ -0,0 +1,201 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "command_options.h" +#include "errors.h" + +namespace { + +const std::string NEGATIVE_INFINITY_NUMERIC = "-inf"; +const std::string POSITIVE_INFINITY_NUMERIC = "+inf"; + +const std::string NEGATIVE_INFINITY_STRING = "-"; +const std::string POSITIVE_INFINITY_STRING = "+"; + +std::string unbound(const std::string &bnd); + +std::string bound(const std::string &bnd); + +} + +namespace sw { + +namespace redis { + +const std::string& UnboundedInterval::min() const { + return NEGATIVE_INFINITY_NUMERIC; +} + +const std::string& UnboundedInterval::max() const { + return POSITIVE_INFINITY_NUMERIC; +} + +BoundedInterval::BoundedInterval(double min, double max, BoundType type) : + _min(std::to_string(min)), + _max(std::to_string(max)) { + switch (type) { + case BoundType::CLOSED: + // Do nothing + break; + + case BoundType::OPEN: + _min = unbound(_min); + _max = unbound(_max); + break; + + case BoundType::LEFT_OPEN: + _min = unbound(_min); + break; + + case BoundType::RIGHT_OPEN: + _max = unbound(_max); + break; + + default: + throw Error("Unknow BoundType"); + } +} + +LeftBoundedInterval::LeftBoundedInterval(double min, BoundType type) : + _min(std::to_string(min)) { + switch (type) { + case BoundType::OPEN: + _min = unbound(_min); + break; + + case BoundType::RIGHT_OPEN: + // Do nothing. + break; + + default: + throw Error("Bound type can only be OPEN or RIGHT_OPEN"); + } +} + +const std::string& LeftBoundedInterval::max() const { + return POSITIVE_INFINITY_NUMERIC; +} + +RightBoundedInterval::RightBoundedInterval(double max, BoundType type) : + _max(std::to_string(max)) { + switch (type) { + case BoundType::OPEN: + _max = unbound(_max); + break; + + case BoundType::LEFT_OPEN: + // Do nothing. + break; + + default: + throw Error("Bound type can only be OPEN or LEFT_OPEN"); + } +} + +const std::string& RightBoundedInterval::min() const { + return NEGATIVE_INFINITY_NUMERIC; +} + +const std::string& UnboundedInterval::min() const { + return NEGATIVE_INFINITY_STRING; +} + +const std::string& UnboundedInterval::max() const { + return POSITIVE_INFINITY_STRING; +} + +BoundedInterval::BoundedInterval(const std::string &min, + const std::string &max, + BoundType type) { + switch (type) { + case BoundType::CLOSED: + _min = bound(min); + _max = bound(max); + break; + + case BoundType::OPEN: + _min = unbound(min); + _max = unbound(max); + break; + + case BoundType::LEFT_OPEN: + _min = unbound(min); + _max = bound(max); + break; + + case BoundType::RIGHT_OPEN: + _min = bound(min); + _max = unbound(max); + break; + + default: + throw Error("Unknow BoundType"); + } +} + +LeftBoundedInterval::LeftBoundedInterval(const std::string &min, BoundType type) { + switch (type) { + case BoundType::OPEN: + _min = unbound(min); + break; + + case BoundType::RIGHT_OPEN: + _min = bound(min); + break; + + default: + throw Error("Bound type can only be OPEN or RIGHT_OPEN"); + } +} + +const std::string& LeftBoundedInterval::max() const { + return POSITIVE_INFINITY_STRING; +} + +RightBoundedInterval::RightBoundedInterval(const std::string &max, BoundType type) { + switch (type) { + case BoundType::OPEN: + _max = unbound(max); + break; + + case BoundType::LEFT_OPEN: + _max = bound(max); + break; + + default: + throw Error("Bound type can only be OPEN or LEFT_OPEN"); + } +} + +const std::string& RightBoundedInterval::min() const { + return NEGATIVE_INFINITY_STRING; +} + +} + +} + +namespace { + +std::string unbound(const std::string &bnd) { + return "(" + bnd; +} + +std::string bound(const std::string &bnd) { + return "[" + bnd; +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.h new file mode 100644 index 000000000..ca766c086 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/command_options.h @@ -0,0 +1,211 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class UpdateType { + EXIST, + NOT_EXIST, + ALWAYS +}; + +enum class InsertPosition { + BEFORE, + AFTER +}; + +enum class BoundType { + CLOSED, + OPEN, + LEFT_OPEN, + RIGHT_OPEN +}; + +// (-inf, +inf) +template +class UnboundedInterval; + +// [min, max], (min, max), (min, max], [min, max) +template +class BoundedInterval; + +// [min, +inf), (min, +inf) +template +class LeftBoundedInterval; + +// (-inf, max], (-inf, max) +template +class RightBoundedInterval; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(double min, double max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(double min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(double max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(const std::string &min, const std::string &max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(const std::string &min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(const std::string &max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +struct LimitOptions { + long long offset = 0; + long long count = -1; +}; + +enum class Aggregation { + SUM, + MIN, + MAX +}; + +enum class BitOp { + AND, + OR, + XOR, + NOT +}; + +enum class GeoUnit { + M, + KM, + MI, + FT +}; + +template +struct WithCoord : TupleWithType, T> {}; + +template +struct WithDist : TupleWithType {}; + +template +struct WithHash : TupleWithType {}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.cpp new file mode 100644 index 000000000..87c204084 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.cpp @@ -0,0 +1,305 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "connection.h" +#include +#include "reply.h" +#include "command.h" +#include "command_args.h" + +namespace sw { + +namespace redis { + +ConnectionOptions::ConnectionOptions(const std::string &uri) : + ConnectionOptions(_parse_options(uri)) {} + +ConnectionOptions ConnectionOptions::_parse_options(const std::string &uri) const { + std::string type; + std::string path; + std::tie(type, path) = _split_string(uri, "://"); + + if (path.empty()) { + throw Error("Invalid URI: no path"); + } + + if (type == "tcp") { + return _parse_tcp_options(path); + } else if (type == "unix") { + return _parse_unix_options(path); + } else { + throw Error("Invalid URI: invalid type"); + } +} + +ConnectionOptions ConnectionOptions::_parse_tcp_options(const std::string &path) const { + ConnectionOptions options; + + options.type = ConnectionType::TCP; + + std::string host; + std::string port; + std::tie(host, port) = _split_string(path, ":"); + + options.host = host; + try { + if (!port.empty()) { + options.port = std::stoi(port); + } // else use default port, i.e. 6379. + } catch (const std::exception &) { + throw Error("Invalid URL: invalid port"); + } + + return options; +} + +ConnectionOptions ConnectionOptions::_parse_unix_options(const std::string &path) const { + ConnectionOptions options; + + options.type = ConnectionType::UNIX; + options.path = path; + + return options; +} + +auto ConnectionOptions::_split_string(const std::string &str, const std::string &delimiter) const -> + std::pair { + auto pos = str.rfind(delimiter); + if (pos == std::string::npos) { + return {str, ""}; + } + + return {str.substr(0, pos), str.substr(pos + delimiter.size())}; +} + +class Connection::Connector { +public: + explicit Connector(const ConnectionOptions &opts); + + ContextUPtr connect() const; + +private: + ContextUPtr _connect() const; + + redisContext* _connect_tcp() const; + + redisContext* _connect_unix() const; + + void _set_socket_timeout(redisContext &ctx) const; + + void _enable_keep_alive(redisContext &ctx) const; + + timeval _to_timeval(const std::chrono::milliseconds &dur) const; + + const ConnectionOptions &_opts; +}; + +Connection::Connector::Connector(const ConnectionOptions &opts) : _opts(opts) {} + +Connection::ContextUPtr Connection::Connector::connect() const { + auto ctx = _connect(); + + assert(ctx); + + if (ctx->err != REDIS_OK) { + throw_error(*ctx, "Failed to connect to Redis"); + } + + _set_socket_timeout(*ctx); + + _enable_keep_alive(*ctx); + + return ctx; +} + +Connection::ContextUPtr Connection::Connector::_connect() const { + redisContext *context = nullptr; + switch (_opts.type) { + case ConnectionType::TCP: + context = _connect_tcp(); + break; + + case ConnectionType::UNIX: + context = _connect_unix(); + break; + + default: + // Never goes here. + throw Error("Unkonw connection type"); + } + + if (context == nullptr) { + throw Error("Failed to allocate memory for connection."); + } + + return ContextUPtr(context); +} + +redisContext* Connection::Connector::_connect_tcp() const { + if (_opts.connect_timeout > std::chrono::milliseconds(0)) { + return redisConnectWithTimeout(_opts.host.c_str(), + _opts.port, + _to_timeval(_opts.connect_timeout)); + } else { + return redisConnect(_opts.host.c_str(), _opts.port); + } +} + +redisContext* Connection::Connector::_connect_unix() const { + if (_opts.connect_timeout > std::chrono::milliseconds(0)) { + return redisConnectUnixWithTimeout( + _opts.path.c_str(), + _to_timeval(_opts.connect_timeout)); + } else { + return redisConnectUnix(_opts.path.c_str()); + } +} + +void Connection::Connector::_set_socket_timeout(redisContext &ctx) const { + if (_opts.socket_timeout <= std::chrono::milliseconds(0)) { + return; + } + + if (redisSetTimeout(&ctx, _to_timeval(_opts.socket_timeout)) != REDIS_OK) { + throw_error(ctx, "Failed to set socket timeout"); + } +} + +void Connection::Connector::_enable_keep_alive(redisContext &ctx) const { + if (!_opts.keep_alive) { + return; + } + + if (redisEnableKeepAlive(&ctx) != REDIS_OK) { + throw_error(ctx, "Failed to enable keep alive option"); + } +} + +timeval Connection::Connector::_to_timeval(const std::chrono::milliseconds &dur) const { + auto sec = std::chrono::duration_cast(dur); + auto msec = std::chrono::duration_cast(dur - sec); + + return { + static_cast(sec.count()), + static_cast(msec.count()) + }; +} + +void swap(Connection &lhs, Connection &rhs) noexcept { + std::swap(lhs._ctx, rhs._ctx); + std::swap(lhs._last_active, rhs._last_active); + std::swap(lhs._opts, rhs._opts); +} + +Connection::Connection(const ConnectionOptions &opts) : + _ctx(Connector(opts).connect()), + _last_active(std::chrono::steady_clock::now()), + _opts(opts) { + assert(_ctx && !broken()); + + _set_options(); +} + +void Connection::reconnect() { + Connection connection(_opts); + + swap(*this, connection); +} + +void Connection::send(int argc, const char **argv, const std::size_t *argv_len) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommandArgv(ctx, + argc, + argv, + argv_len) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +void Connection::send(CmdArgs &args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommandArgv(ctx, + args.size(), + args.argv(), + args.argv_len()) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +ReplyUPtr Connection::recv() { + auto *ctx = _context(); + + assert(ctx != nullptr); + + void *r = nullptr; + if (redisGetReply(ctx, &r) != REDIS_OK) { + throw_error(*ctx, "Failed to get reply"); + } + + assert(!broken() && r != nullptr); + + auto reply = ReplyUPtr(static_cast(r)); + + if (reply::is_error(*reply)) { + throw_error(*reply); + } + + return reply; +} + +void Connection::_set_options() { + _auth(); + + _select_db(); +} + +void Connection::_auth() { + if (_opts.password.empty()) { + return; + } + + cmd::auth(*this, _opts.password); + + auto reply = recv(); + + reply::parse(*reply); +} + +void Connection::_select_db() { + if (_opts.db == 0) { + return; + } + + cmd::select(*this, _opts.db); + + auto reply = recv(); + + reply::parse(*reply); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.h new file mode 100644 index 000000000..5ad419225 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection.h @@ -0,0 +1,194 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "reply.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class ConnectionType { + TCP = 0, + UNIX +}; + +struct ConnectionOptions { +public: + ConnectionOptions() = default; + + explicit ConnectionOptions(const std::string &uri); + + ConnectionOptions(const ConnectionOptions &) = default; + ConnectionOptions& operator=(const ConnectionOptions &) = default; + + ConnectionOptions(ConnectionOptions &&) = default; + ConnectionOptions& operator=(ConnectionOptions &&) = default; + + ~ConnectionOptions() = default; + + ConnectionType type = ConnectionType::TCP; + + std::string host; + + int port = 6379; + + std::string path; + + std::string password; + + int db = 0; + + bool keep_alive = false; + + std::chrono::milliseconds connect_timeout{0}; + + std::chrono::milliseconds socket_timeout{0}; + +private: + ConnectionOptions _parse_options(const std::string &uri) const; + + ConnectionOptions _parse_tcp_options(const std::string &path) const; + + ConnectionOptions _parse_unix_options(const std::string &path) const; + + auto _split_string(const std::string &str, const std::string &delimiter) const -> + std::pair; +}; + +class CmdArgs; + +class Connection { +public: + explicit Connection(const ConnectionOptions &opts); + + Connection(const Connection &) = delete; + Connection& operator=(const Connection &) = delete; + + Connection(Connection &&) = default; + Connection& operator=(Connection &&) = default; + + ~Connection() = default; + + // Check if the connection is broken. Client needs to do this check + // before sending some command to the connection. If it's broken, + // client needs to reconnect it. + bool broken() const noexcept { + return _ctx->err != REDIS_OK; + } + + void reset() noexcept { + _ctx->err = 0; + } + + void reconnect(); + + auto last_active() const + -> std::chrono::time_point { + return _last_active; + } + + template + void send(const char *format, Args &&...args); + + void send(int argc, const char **argv, const std::size_t *argv_len); + + void send(CmdArgs &args); + + ReplyUPtr recv(); + + const ConnectionOptions& options() const { + return _opts; + } + + friend void swap(Connection &lhs, Connection &rhs) noexcept; + +private: + class Connector; + + struct ContextDeleter { + void operator()(redisContext *context) const { + if (context != nullptr) { + redisFree(context); + } + }; + }; + + using ContextUPtr = std::unique_ptr; + + void _set_options(); + + void _auth(); + + void _select_db(); + + redisContext* _context(); + + ContextUPtr _ctx; + + // The time that the connection is created or the time that + // the connection is used, i.e. *context()* is called. + std::chrono::time_point _last_active{}; + + ConnectionOptions _opts; +}; + +using ConnectionSPtr = std::shared_ptr; + +enum class Role { + MASTER, + SLAVE +}; + +// Inline implementaions. + +template +inline void Connection::send(const char *format, Args &&...args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommand(ctx, + format, + std::forward(args)...) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +inline redisContext* Connection::_context() { + _last_active = std::chrono::steady_clock::now(); + + return _ctx.get(); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.cpp new file mode 100644 index 000000000..f00edb027 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.cpp @@ -0,0 +1,249 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "connection_pool.h" +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +ConnectionPool::ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts) : + _opts(connection_opts), + _pool_opts(pool_opts) { + if (_pool_opts.size == 0) { + throw Error("CANNOT create an empty pool"); + } + + // Lazily create connections. +} + +ConnectionPool::ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts) : + _opts(connection_opts), + _pool_opts(pool_opts), + _sentinel(std::move(sentinel)) { + // In this case, the connection must be of TCP type. + if (_opts.type != ConnectionType::TCP) { + throw Error("Sentinel only supports TCP connection"); + } + + if (_opts.connect_timeout == std::chrono::milliseconds(0) + || _opts.socket_timeout == std::chrono::milliseconds(0)) { + throw Error("With sentinel, connection timeout and socket timeout cannot be 0"); + } + + // Cleanup connection options. + _update_connection_opts("", -1); + + assert(_sentinel); +} + +ConnectionPool::ConnectionPool(ConnectionPool &&that) { + std::lock_guard lock(that._mutex); + + _move(std::move(that)); +} + +ConnectionPool& ConnectionPool::operator=(ConnectionPool &&that) { + if (this != &that) { + std::lock(_mutex, that._mutex); + std::lock_guard lock_this(_mutex, std::adopt_lock); + std::lock_guard lock_that(that._mutex, std::adopt_lock); + + _move(std::move(that)); + } + + return *this; +} + +Connection ConnectionPool::fetch() { + std::unique_lock lock(_mutex); + + if (_pool.empty()) { + if (_used_connections == _pool_opts.size) { + _wait_for_connection(lock); + } else { + // Lazily create a new connection. + auto connection = _create(); + + ++_used_connections; + + return connection; + } + } + + // _pool is NOT empty. + auto connection = _fetch(); + + auto connection_lifetime = _pool_opts.connection_lifetime; + + if (_sentinel) { + auto opts = _opts; + auto role_changed = _role_changed(connection.options()); + auto sentinel = _sentinel; + + lock.unlock(); + + if (role_changed || _need_reconnect(connection, connection_lifetime)) { + try { + connection = _create(sentinel, opts, false); + } catch (const Error &e) { + // Failed to reconnect, return it to the pool, and retry latter. + release(std::move(connection)); + throw; + } + } + + return connection; + } + + lock.unlock(); + + if (_need_reconnect(connection, connection_lifetime)) { + try { + connection.reconnect(); + } catch (const Error &e) { + // Failed to reconnect, return it to the pool, and retry latter. + release(std::move(connection)); + throw; + } + } + + return connection; +} + +ConnectionOptions ConnectionPool::connection_options() { + std::lock_guard lock(_mutex); + + return _opts; +} + +void ConnectionPool::release(Connection connection) { + { + std::lock_guard lock(_mutex); + + _pool.push_back(std::move(connection)); + } + + _cv.notify_one(); +} + +Connection ConnectionPool::create() { + std::unique_lock lock(_mutex); + + auto opts = _opts; + + if (_sentinel) { + auto sentinel = _sentinel; + + lock.unlock(); + + return _create(sentinel, opts, false); + } else { + lock.unlock(); + + return Connection(opts); + } +} + +void ConnectionPool::_move(ConnectionPool &&that) { + _opts = std::move(that._opts); + _pool_opts = std::move(that._pool_opts); + _pool = std::move(that._pool); + _used_connections = that._used_connections; + _sentinel = std::move(that._sentinel); +} + +Connection ConnectionPool::_create() { + if (_sentinel) { + // Get Redis host and port info from sentinel. + return _create(_sentinel, _opts, true); + } + + return Connection(_opts); +} + +Connection ConnectionPool::_create(SimpleSentinel &sentinel, + const ConnectionOptions &opts, + bool locked) { + try { + auto connection = sentinel.create(opts); + + std::unique_lock lock(_mutex, std::defer_lock); + if (!locked) { + lock.lock(); + } + + const auto &connection_opts = connection.options(); + if (_role_changed(connection_opts)) { + // Master/Slave has been changed, reconnect all connections. + _update_connection_opts(connection_opts.host, connection_opts.port); + } + + return connection; + } catch (const StopIterError &e) { + throw Error("Failed to create connection with sentinel"); + } +} + +Connection ConnectionPool::_fetch() { + assert(!_pool.empty()); + + auto connection = std::move(_pool.front()); + _pool.pop_front(); + + return connection; +} + +void ConnectionPool::_wait_for_connection(std::unique_lock &lock) { + auto timeout = _pool_opts.wait_timeout; + if (timeout > std::chrono::milliseconds(0)) { + // Wait until _pool is no longer empty or timeout. + if (!_cv.wait_for(lock, + timeout, + [this] { return !(this->_pool).empty(); })) { + throw Error("Failed to fetch a connection in " + + std::to_string(timeout.count()) + " milliseconds"); + } + } else { + // Wait forever. + _cv.wait(lock, [this] { return !(this->_pool).empty(); }); + } +} + +bool ConnectionPool::_need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime) const { + if (connection.broken()) { + return true; + } + + if (connection_lifetime > std::chrono::milliseconds(0)) { + auto now = std::chrono::steady_clock::now(); + if (now - connection.last_active() > connection_lifetime) { + return true; + } + } + + return false; +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.h new file mode 100644 index 000000000..6f2663ad7 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/connection_pool.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +struct ConnectionPoolOptions { + // Max number of connections, including both in-use and idle ones. + std::size_t size = 1; + + // Max time to wait for a connection. 0ms means client waits forever. + std::chrono::milliseconds wait_timeout{0}; + + // Max lifetime of a connection. 0ms means we never expire the connection. + std::chrono::milliseconds connection_lifetime{0}; +}; + +class ConnectionPool { +public: + ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool() = default; + + ConnectionPool(ConnectionPool &&that); + ConnectionPool& operator=(ConnectionPool &&that); + + ConnectionPool(const ConnectionPool &) = delete; + ConnectionPool& operator=(const ConnectionPool &) = delete; + + ~ConnectionPool() = default; + + // Fetch a connection from pool. + Connection fetch(); + + ConnectionOptions connection_options(); + + void release(Connection connection); + + // Create a new connection. + Connection create(); + +private: + void _move(ConnectionPool &&that); + + // NOT thread-safe + Connection _create(); + + Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked); + + Connection _fetch(); + + void _wait_for_connection(std::unique_lock &lock); + + bool _need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime) const; + + void _update_connection_opts(const std::string &host, int port) { + _opts.host = host; + _opts.port = port; + } + + bool _role_changed(const ConnectionOptions &opts) const { + return opts.port != _opts.port || opts.host != _opts.host; + } + + ConnectionOptions _opts; + + ConnectionPoolOptions _pool_opts; + + std::deque _pool; + + std::size_t _used_connections = 0; + + std::mutex _mutex; + + std::condition_variable _cv; + + SimpleSentinel _sentinel; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/crc16.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/crc16.cpp new file mode 100644 index 000000000..c94a08d30 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/crc16.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2001-2010 Georges Menie (www.menie.org) + * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* CRC16 implementation according to CCITT standards. + * + * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the + * following parameters: + * + * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" + * Width : 16 bit + * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) + * Initialization : 0000 + * Reflect Input byte : False + * Reflect Output CRC : False + * Xor constant to output CRC : 0000 + * Output for "123456789" : 31C3 + */ + +#include + +namespace sw { + +namespace redis { + +static const uint16_t crc16tab[256]= { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, + 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, + 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, + 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, + 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, + 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, + 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, + 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, + 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, + 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, + 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, + 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, + 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, + 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, + 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, + 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, + 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, + 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, + 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, + 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, + 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, + 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, + 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, + 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, + 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, + 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, + 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, + 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, + 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, + 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, + 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 +}; + +uint16_t crc16(const char *buf, int len) { + int counter; + uint16_t crc = 0; + for (counter = 0; counter < len; counter++) + crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; + return crc; +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.cpp new file mode 100644 index 000000000..5d5fe7dc9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.cpp @@ -0,0 +1,136 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "errors.h" +#include +#include +#include +#include +#include "shards.h" + +namespace { + +using namespace sw::redis; + +std::pair parse_error(const std::string &msg); + +std::unordered_map error_map = { + {"MOVED", ReplyErrorType::MOVED}, + {"ASK", ReplyErrorType::ASK} +}; + +} + +namespace sw { + +namespace redis { + +void throw_error(redisContext &context, const std::string &err_info) { + auto err_code = context.err; + const auto *err_str = context.errstr; + if (err_str == nullptr) { + throw Error(err_info + ": null error message: " + std::to_string(err_code)); + } + + auto err_msg = err_info + ": " + err_str; + + switch (err_code) { + case REDIS_ERR_IO: + if (errno == EAGAIN || errno == EINTR) { + throw TimeoutError(err_msg); + } else { + throw IoError(err_msg); + } + break; + + case REDIS_ERR_EOF: + throw ClosedError(err_msg); + break; + + case REDIS_ERR_PROTOCOL: + throw ProtoError(err_msg); + break; + + case REDIS_ERR_OOM: + throw OomError(err_msg); + break; + + case REDIS_ERR_OTHER: + throw Error(err_msg); + break; + + default: + throw Error(err_info + ": Unknown error code"); + } +} + +void throw_error(const redisReply &reply) { + assert(reply.type == REDIS_REPLY_ERROR); + + if (reply.str == nullptr) { + throw Error("Null error reply"); + } + + auto err_str = std::string(reply.str, reply.len); + + auto err_type = ReplyErrorType::ERR; + std::string err_msg; + std::tie(err_type, err_msg) = parse_error(err_str); + + switch (err_type) { + case ReplyErrorType::MOVED: + throw MovedError(err_msg); + break; + + case ReplyErrorType::ASK: + throw AskError(err_msg); + break; + + default: + throw ReplyError(err_str); + break; + } +} + +} + +} + +namespace { + +using namespace sw::redis; + +std::pair parse_error(const std::string &err) { + // The error contains an Error Prefix, and an optional error message. + auto idx = err.find_first_of(" \n"); + + if (idx == std::string::npos) { + throw ProtoError("No Error Prefix: " + err); + } + + auto err_prefix = err.substr(0, idx); + auto err_type = ReplyErrorType::ERR; + + auto iter = error_map.find(err_prefix); + if (iter != error_map.end()) { + // Specific error. + err_type = iter->second; + } // else Generic error. + + return {err_type, err.substr(idx + 1)}; +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.h new file mode 100644 index 000000000..44d629e50 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/errors.h @@ -0,0 +1,159 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H +#define SEWENEW_REDISPLUSPLUS_ERRORS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +enum ReplyErrorType { + ERR, + MOVED, + ASK +}; + +class Error : public std::exception { +public: + explicit Error(const std::string &msg) : _msg(msg) {} + + Error(const Error &) = default; + Error& operator=(const Error &) = default; + + Error(Error &&) = default; + Error& operator=(Error &&) = default; + + virtual ~Error() = default; + + virtual const char* what() const noexcept { + return _msg.data(); + } + +private: + std::string _msg; +}; + +class IoError : public Error { +public: + explicit IoError(const std::string &msg) : Error(msg) {} + + IoError(const IoError &) = default; + IoError& operator=(const IoError &) = default; + + IoError(IoError &&) = default; + IoError& operator=(IoError &&) = default; + + virtual ~IoError() = default; +}; + +class TimeoutError : public IoError { +public: + explicit TimeoutError(const std::string &msg) : IoError(msg) {} + + TimeoutError(const TimeoutError &) = default; + TimeoutError& operator=(const TimeoutError &) = default; + + TimeoutError(TimeoutError &&) = default; + TimeoutError& operator=(TimeoutError &&) = default; + + virtual ~TimeoutError() = default; +}; + +class ClosedError : public Error { +public: + explicit ClosedError(const std::string &msg) : Error(msg) {} + + ClosedError(const ClosedError &) = default; + ClosedError& operator=(const ClosedError &) = default; + + ClosedError(ClosedError &&) = default; + ClosedError& operator=(ClosedError &&) = default; + + virtual ~ClosedError() = default; +}; + +class ProtoError : public Error { +public: + explicit ProtoError(const std::string &msg) : Error(msg) {} + + ProtoError(const ProtoError &) = default; + ProtoError& operator=(const ProtoError &) = default; + + ProtoError(ProtoError &&) = default; + ProtoError& operator=(ProtoError &&) = default; + + virtual ~ProtoError() = default; +}; + +class OomError : public Error { +public: + explicit OomError(const std::string &msg) : Error(msg) {} + + OomError(const OomError &) = default; + OomError& operator=(const OomError &) = default; + + OomError(OomError &&) = default; + OomError& operator=(OomError &&) = default; + + virtual ~OomError() = default; +}; + +class ReplyError : public Error { +public: + explicit ReplyError(const std::string &msg) : Error(msg) {} + + ReplyError(const ReplyError &) = default; + ReplyError& operator=(const ReplyError &) = default; + + ReplyError(ReplyError &&) = default; + ReplyError& operator=(ReplyError &&) = default; + + virtual ~ReplyError() = default; +}; + +class WatchError : public Error { +public: + explicit WatchError() : Error("Watched key has been modified") {} + + WatchError(const WatchError &) = default; + WatchError& operator=(const WatchError &) = default; + + WatchError(WatchError &&) = default; + WatchError& operator=(WatchError &&) = default; + + virtual ~WatchError() = default; +}; + + +// MovedError and AskError are defined in shards.h +class MovedError; + +class AskError; + +void throw_error(redisContext &context, const std::string &err_info); + +void throw_error(const redisReply &reply); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.cpp new file mode 100644 index 000000000..141f96ea2 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.cpp @@ -0,0 +1,35 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "pipeline.h" + +namespace sw { + +namespace redis { + +std::vector PipelineImpl::exec(Connection &connection, std::size_t cmd_num) { + std::vector replies; + while (cmd_num > 0) { + replies.push_back(connection.recv()); + --cmd_num; + } + + return replies; +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.h new file mode 100644 index 000000000..52b01253f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/pipeline.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H +#define SEWENEW_REDISPLUSPLUS_PIPELINE_H + +#include +#include +#include "connection.h" + +namespace sw { + +namespace redis { + +class PipelineImpl { +public: + template + void command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + } + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t /*cmd_num*/) { + // Reconnect to Redis to discard all commands. + connection.reconnect(); + } +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.h new file mode 100644 index 000000000..71d975ee3 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.h @@ -0,0 +1,1844 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H + +#include +#include +#include +#include +#include "connection.h" +#include "utils.h" +#include "reply.h" +#include "command.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +class QueuedReplies; + +// If any command throws, QueuedRedis resets the connection, and becomes invalid. +// In this case, the only thing we can do is to destory the QueuedRedis object. +template +class QueuedRedis { +public: + QueuedRedis(QueuedRedis &&) = default; + QueuedRedis& operator=(QueuedRedis &&) = default; + + // When it destructs, the underlying *Connection* will be closed, + // and any command that has NOT been executed will be ignored. + ~QueuedRedis() = default; + + Redis redis(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type; + + template + QueuedRedis& command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type; + + QueuedReplies exec(); + + void discard(); + + // CONNECTION commands. + + QueuedRedis& auth(const StringView &password) { + return command(cmd::auth, password); + } + + QueuedRedis& echo(const StringView &msg) { + return command(cmd::echo, msg); + } + + QueuedRedis& ping() { + return command(cmd::ping); + } + + QueuedRedis& ping(const StringView &msg) { + return command(cmd::ping, msg); + } + + // We DO NOT support the QUIT command. See *Redis::quit* doc for details. + // + // QueuedRedis& quit(); + + QueuedRedis& select(long long idx) { + return command(cmd::select, idx); + } + + QueuedRedis& swapdb(long long idx1, long long idx2) { + return command(cmd::swapdb, idx1, idx2); + } + + // SERVER commands. + + QueuedRedis& bgrewriteaof() { + return command(cmd::bgrewriteaof); + } + + QueuedRedis& bgsave() { + return command(cmd::bgsave); + } + + QueuedRedis& dbsize() { + return command(cmd::dbsize); + } + + QueuedRedis& flushall(bool async = false) { + return command(cmd::flushall, async); + } + + QueuedRedis& flushdb(bool async = false) { + return command(cmd::flushdb, async); + } + + QueuedRedis& info() { + return command(cmd::info); + } + + QueuedRedis& info(const StringView §ion) { + return command(cmd::info, section); + } + + QueuedRedis& lastsave() { + return command(cmd::lastsave); + } + + QueuedRedis& save() { + return command(cmd::save); + } + + // KEY commands. + + QueuedRedis& del(const StringView &key) { + return command(cmd::del, key); + } + + template + QueuedRedis& del(Input first, Input last) { + return command(cmd::del_range, first, last); + } + + template + QueuedRedis& del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + QueuedRedis& dump(const StringView &key) { + return command(cmd::dump, key); + } + + QueuedRedis& exists(const StringView &key) { + return command(cmd::exists, key); + } + + template + QueuedRedis& exists(Input first, Input last) { + return command(cmd::exists_range, first, last); + } + + template + QueuedRedis& exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + QueuedRedis& expire(const StringView &key, long long timeout) { + return command(cmd::expire, key, timeout); + } + + QueuedRedis& expire(const StringView &key, + const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); + } + + QueuedRedis& expireat(const StringView &key, long long timestamp) { + return command(cmd::expireat, key, timestamp); + } + + QueuedRedis& expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& keys(const StringView &pattern) { + return command(cmd::keys, pattern); + } + + QueuedRedis& move(const StringView &key, long long db) { + return command(cmd::move, key, db); + } + + QueuedRedis& persist(const StringView &key) { + return command(cmd::persist, key); + } + + QueuedRedis& pexpire(const StringView &key, long long timeout) { + return command(cmd::pexpire, key, timeout); + } + + QueuedRedis& pexpire(const StringView &key, + const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); + } + + QueuedRedis& pexpireat(const StringView &key, long long timestamp) { + return command(cmd::pexpireat, key, timestamp); + } + + QueuedRedis& pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& pttl(const StringView &key) { + return command(cmd::pttl, key); + } + + QueuedRedis& randomkey() { + return command(cmd::randomkey); + } + + QueuedRedis& rename(const StringView &key, const StringView &newkey) { + return command(cmd::rename, key, newkey); + } + + QueuedRedis& renamenx(const StringView &key, const StringView &newkey) { + return command(cmd::renamenx, key, newkey); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false) { + return command(cmd::restore, key, val, ttl, replace); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false) { + return restore(key, val, ttl.count(), replace); + } + + // TODO: sort + + QueuedRedis& scan(long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::scan, cursor, pattern, count); + } + + QueuedRedis& scan(long long cursor) { + return scan(cursor, "*", 10); + } + + QueuedRedis& scan(long long cursor, + const StringView &pattern) { + return scan(cursor, pattern, 10); + } + + QueuedRedis& scan(long long cursor, + long long count) { + return scan(cursor, "*", count); + } + + QueuedRedis& touch(const StringView &key) { + return command(cmd::touch, key); + } + + template + QueuedRedis& touch(Input first, Input last) { + return command(cmd::touch_range, first, last); + } + + template + QueuedRedis& touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + QueuedRedis& ttl(const StringView &key) { + return command(cmd::ttl, key); + } + + QueuedRedis& type(const StringView &key) { + return command(cmd::type, key); + } + + QueuedRedis& unlink(const StringView &key) { + return command(cmd::unlink, key); + } + + template + QueuedRedis& unlink(Input first, Input last) { + return command(cmd::unlink_range, first, last); + } + + template + QueuedRedis& unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + QueuedRedis& wait(long long numslaves, long long timeout) { + return command(cmd::wait, numslaves, timeout); + } + + QueuedRedis& wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); + } + + // STRING commands. + + QueuedRedis& append(const StringView &key, const StringView &str) { + return command(cmd::append, key, str); + } + + QueuedRedis& bitcount(const StringView &key, + long long start = 0, + long long end = -1) { + return command(cmd::bitcount, key, start, end); + } + + QueuedRedis& bitop(BitOp op, + const StringView &destination, + const StringView &key) { + return command(cmd::bitop, op, destination, key); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + Input first, + Input last) { + return command(cmd::bitop_range, op, destination, first, last); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + QueuedRedis& bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1) { + return command(cmd::bitpos, key, bit, start, end); + } + + QueuedRedis& decr(const StringView &key) { + return command(cmd::decr, key); + } + + QueuedRedis& decrby(const StringView &key, long long decrement) { + return command(cmd::decrby, key, decrement); + } + + QueuedRedis& get(const StringView &key) { + return command(cmd::get, key); + } + + QueuedRedis& getbit(const StringView &key, long long offset) { + return command(cmd::getbit, key, offset); + } + + QueuedRedis& getrange(const StringView &key, long long start, long long end) { + return command(cmd::getrange, key, start, end); + } + + QueuedRedis& getset(const StringView &key, const StringView &val) { + return command(cmd::getset, key, val); + } + + QueuedRedis& incr(const StringView &key) { + return command(cmd::incr, key); + } + + QueuedRedis& incrby(const StringView &key, long long increment) { + return command(cmd::incrby, key, increment); + } + + QueuedRedis& incrbyfloat(const StringView &key, double increment) { + return command(cmd::incrbyfloat, key, increment); + } + + template + QueuedRedis& mget(Input first, Input last) { + return command(cmd::mget, first, last); + } + + template + QueuedRedis& mget(std::initializer_list il) { + return mget(il.begin(), il.end()); + } + + template + QueuedRedis& mset(Input first, Input last) { + return command(cmd::mset, first, last); + } + + template + QueuedRedis& mset(std::initializer_list il) { + return mset(il.begin(), il.end()); + } + + template + QueuedRedis& msetnx(Input first, Input last) { + return command(cmd::msetnx, first, last); + } + + template + QueuedRedis& msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + QueuedRedis& psetex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::psetex, key, ttl, val); + } + + QueuedRedis& psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); + } + + QueuedRedis& set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS) { + _set_cmd_indexes.push_back(_cmd_num); + + return command(cmd::set, key, val, ttl.count(), type); + } + + QueuedRedis& setex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::setex, key, ttl, val); + } + + QueuedRedis& setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + return setex(key, ttl.count(), val); + } + + QueuedRedis& setnx(const StringView &key, const StringView &val) { + return command(cmd::setnx, key, val); + } + + QueuedRedis& setrange(const StringView &key, + long long offset, + const StringView &val) { + return command(cmd::setrange, key, offset, val); + } + + QueuedRedis& strlen(const StringView &key) { + return command(cmd::strlen, key); + } + + // LIST commands. + + QueuedRedis& blpop(const StringView &key, long long timeout) { + return command(cmd::blpop, key, timeout); + } + + QueuedRedis& blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(key, timeout.count()); + } + + template + QueuedRedis& blpop(Input first, Input last, long long timeout) { + return command(cmd::blpop_range, first, last, timeout); + } + + template + QueuedRedis& blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(first, last, timeout.count()); + } + + template + QueuedRedis& blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpop(const StringView &key, long long timeout) { + return command(cmd::brpop, key, timeout); + } + + QueuedRedis& brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(key, timeout.count()); + } + + template + QueuedRedis& brpop(Input first, Input last, long long timeout) { + return command(cmd::brpop_range, first, last, timeout); + } + + template + QueuedRedis& brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(first, last, timeout.count()); + } + + template + QueuedRedis& brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + return command(cmd::brpoplpush, source, destination, timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpoplpush(source, destination, timeout.count()); + } + + QueuedRedis& lindex(const StringView &key, long long index) { + return command(cmd::lindex, key, index); + } + + QueuedRedis& linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + return command(cmd::linsert, key, position, pivot, val); + } + + QueuedRedis& llen(const StringView &key) { + return command(cmd::llen, key); + } + + QueuedRedis& lpop(const StringView &key) { + return command(cmd::lpop, key); + } + + QueuedRedis& lpush(const StringView &key, const StringView &val) { + return command(cmd::lpush, key, val); + } + + template + QueuedRedis& lpush(const StringView &key, Input first, Input last) { + return command(cmd::lpush_range, key, first, last); + } + + template + QueuedRedis& lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + QueuedRedis& lpushx(const StringView &key, const StringView &val) { + return command(cmd::lpushx, key, val); + } + + QueuedRedis& lrange(const StringView &key, + long long start, + long long stop) { + return command(cmd::lrange, key, start, stop); + } + + QueuedRedis& lrem(const StringView &key, long long count, const StringView &val) { + return command(cmd::lrem, key, count, val); + } + + QueuedRedis& lset(const StringView &key, long long index, const StringView &val) { + return command(cmd::lset, key, index, val); + } + + QueuedRedis& ltrim(const StringView &key, long long start, long long stop) { + return command(cmd::ltrim, key, start, stop); + } + + QueuedRedis& rpop(const StringView &key) { + return command(cmd::rpop, key); + } + + QueuedRedis& rpoplpush(const StringView &source, const StringView &destination) { + return command(cmd::rpoplpush, source, destination); + } + + QueuedRedis& rpush(const StringView &key, const StringView &val) { + return command(cmd::rpush, key, val); + } + + template + QueuedRedis& rpush(const StringView &key, Input first, Input last) { + return command(cmd::rpush_range, key, first, last); + } + + template + QueuedRedis& rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + QueuedRedis& rpushx(const StringView &key, const StringView &val) { + return command(cmd::rpushx, key, val); + } + + // HASH commands. + + QueuedRedis& hdel(const StringView &key, const StringView &field) { + return command(cmd::hdel, key, field); + } + + template + QueuedRedis& hdel(const StringView &key, Input first, Input last) { + return command(cmd::hdel_range, key, first, last); + } + + template + QueuedRedis& hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + QueuedRedis& hexists(const StringView &key, const StringView &field) { + return command(cmd::hexists, key, field); + } + + QueuedRedis& hget(const StringView &key, const StringView &field) { + return command(cmd::hget, key, field); + } + + QueuedRedis& hgetall(const StringView &key) { + return command(cmd::hgetall, key); + } + + QueuedRedis& hincrby(const StringView &key, + const StringView &field, + long long increment) { + return command(cmd::hincrby, key, field, increment); + } + + QueuedRedis& hincrbyfloat(const StringView &key, + const StringView &field, + double increment) { + return command(cmd::hincrbyfloat, key, field, increment); + } + + QueuedRedis& hkeys(const StringView &key) { + return command(cmd::hkeys, key); + } + + QueuedRedis& hlen(const StringView &key) { + return command(cmd::hlen, key); + } + + template + QueuedRedis& hmget(const StringView &key, Input first, Input last) { + return command(cmd::hmget, key, first, last); + } + + template + QueuedRedis& hmget(const StringView &key, std::initializer_list il) { + return hmget(key, il.begin(), il.end()); + } + + template + QueuedRedis& hmset(const StringView &key, Input first, Input last) { + return command(cmd::hmset, key, first, last); + } + + template + QueuedRedis& hmset(const StringView &key, std::initializer_list il) { + return hmset(key, il.begin(), il.end()); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::hscan, key, cursor, pattern, count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return hscan(key, cursor, pattern, 10); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + long long count) { + return hscan(key, cursor, "*", count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor) { + return hscan(key, cursor, "*", 10); + } + + QueuedRedis& hset(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hset, key, field, val); + } + + QueuedRedis& hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); + } + + QueuedRedis& hsetnx(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hsetnx, key, field, val); + } + + QueuedRedis& hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); + } + + QueuedRedis& hstrlen(const StringView &key, const StringView &field) { + return command(cmd::hstrlen, key, field); + } + + QueuedRedis& hvals(const StringView &key) { + return command(cmd::hvals, key); + } + + // SET commands. + + QueuedRedis& sadd(const StringView &key, const StringView &member) { + return command(cmd::sadd, key, member); + } + + template + QueuedRedis& sadd(const StringView &key, Input first, Input last) { + return command(cmd::sadd_range, key, first, last); + } + + template + QueuedRedis& sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + QueuedRedis& scard(const StringView &key) { + return command(cmd::scard, key); + } + + template + QueuedRedis& sdiff(Input first, Input last) { + return command(cmd::sdiff, first, last); + } + + template + QueuedRedis& sdiff(std::initializer_list il) { + return sdiff(il.begin(), il.end()); + } + + QueuedRedis& sdiffstore(const StringView &destination, const StringView &key) { + return command(cmd::sdiffstore, destination, key); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sdiffstore_range, destination, first, last); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + QueuedRedis& sinter(Input first, Input last) { + return command(cmd::sinter, first, last); + } + + template + QueuedRedis& sinter(std::initializer_list il) { + return sinter(il.begin(), il.end()); + } + + QueuedRedis& sinterstore(const StringView &destination, const StringView &key) { + return command(cmd::sinterstore, destination, key); + } + + template + QueuedRedis& sinterstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sinterstore_range, destination, first, last); + } + + template + QueuedRedis& sinterstore(const StringView &destination, std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + QueuedRedis& sismember(const StringView &key, const StringView &member) { + return command(cmd::sismember, key, member); + } + + QueuedRedis& smembers(const StringView &key) { + return command(cmd::smembers, key); + } + + QueuedRedis& smove(const StringView &source, + const StringView &destination, + const StringView &member) { + return command(cmd::smove, source, destination, member); + } + + QueuedRedis& spop(const StringView &key) { + return command(cmd::spop, key); + } + + QueuedRedis& spop(const StringView &key, long long count) { + return command(cmd::spop_range, key, count); + } + + QueuedRedis& srandmember(const StringView &key) { + return command(cmd::srandmember, key); + } + + QueuedRedis& srandmember(const StringView &key, long long count) { + return command(cmd::srandmember_range, key, count); + } + + QueuedRedis& srem(const StringView &key, const StringView &member) { + return command(cmd::srem, key, member); + } + + template + QueuedRedis& srem(const StringView &key, Input first, Input last) { + return command(cmd::srem_range, key, first, last); + } + + template + QueuedRedis& srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::sscan, key, cursor, pattern, count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return sscan(key, cursor, pattern, 10); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + long long count) { + return sscan(key, cursor, "*", count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor) { + return sscan(key, cursor, "*", 10); + } + + template + QueuedRedis& sunion(Input first, Input last) { + return command(cmd::sunion, first, last); + } + + template + QueuedRedis& sunion(std::initializer_list il) { + return sunion(il.begin(), il.end()); + } + + QueuedRedis& sunionstore(const StringView &destination, const StringView &key) { + return command(cmd::sunionstore, destination, key); + } + + template + QueuedRedis& sunionstore(const StringView &destination, Input first, Input last) { + return command(cmd::sunionstore_range, destination, first, last); + } + + template + QueuedRedis& sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + QueuedRedis& bzpopmax(const StringView &key, long long timeout) { + return command(cmd::bzpopmax, key, timeout); + } + + QueuedRedis& bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(key, timeout.count()); + } + + template + QueuedRedis& bzpopmax(Input first, Input last, long long timeout) { + return command(cmd::bzpopmax_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, long long timeout) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, long long timeout) { + return command(cmd::bzpopmin, key, timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(key, timeout.count()); + } + + template + QueuedRedis& bzpopmin(Input first, Input last, long long timeout) { + return command(cmd::bzpopmin_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, long long timeout) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + QueuedRedis& zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd, key, member, score, type, changed); + } + + template + QueuedRedis& zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd_range, key, first, last, type, changed); + } + + QueuedRedis& zcard(const StringView &key) { + return command(cmd::zcard, key); + } + + template + QueuedRedis& zcount(const StringView &key, const Interval &interval) { + return command(cmd::zcount, key, interval); + } + + QueuedRedis& zincrby(const StringView &key, double increment, const StringView &member) { + return command(cmd::zincrby, key, increment, member); + } + + QueuedRedis& zinterstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zinterstore, destination, key, weight); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zinterstore_range, destination, first, last, type); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + QueuedRedis& zlexcount(const StringView &key, const Interval &interval) { + return command(cmd::zlexcount, key, interval); + } + + QueuedRedis& zpopmax(const StringView &key) { + return command(cmd::zpopmax, key, 1); + } + + QueuedRedis& zpopmax(const StringView &key, long long count) { + return command(cmd::zpopmax, key, count); + } + + QueuedRedis& zpopmin(const StringView &key) { + return command(cmd::zpopmin, key, 1); + } + + QueuedRedis& zpopmin(const StringView &key, long long count) { + return command(cmd::zpopmin, key, count); + } + + // NOTE: *QueuedRedis::zrange*'s parameters are different from *Redis::zrange*. + // *Redis::zrange* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::zrange*. So we have to use an extra parameter: *with_scores*, + // to decide whether we should send *WITHSCORES* option to Redis. This also applies to + // other commands with the *WITHSCORES* option, e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, + // *ZREVRANGEBYSCORE*. + QueuedRedis& zrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrangebylex(const StringView &key, const Interval &interval) { + return zrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrank(const StringView &key, const StringView &member) { + return command(cmd::zrank, key, member); + } + + QueuedRedis& zrem(const StringView &key, const StringView &member) { + return command(cmd::zrem, key, member); + } + + template + QueuedRedis& zrem(const StringView &key, Input first, Input last) { + return command(cmd::zrem_range, key, first, last); + } + + template + QueuedRedis& zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + QueuedRedis& zremrangebylex(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebylex, key, interval); + } + + QueuedRedis& zremrangebyrank(const StringView &key, long long start, long long stop) { + return command(cmd::zremrangebyrank, key, start, stop); + } + + template + QueuedRedis& zremrangebyscore(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebyscore, key, interval); + } + + // See comments on *ZRANGE*. + QueuedRedis& zrevrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrevrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrevrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, const Interval &interval) { + return zrevrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrevrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrevrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrevrank(const StringView &key, const StringView &member) { + return command(cmd::zrevrank, key, member); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::zscan, key, cursor, pattern, count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return zscan(key, cursor, pattern, 10); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + long long count) { + return zscan(key, cursor, "*", count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor) { + return zscan(key, cursor, "*", 10); + } + + QueuedRedis& zscore(const StringView &key, const StringView &member) { + return command(cmd::zscore, key, member); + } + + QueuedRedis& zunionstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zunionstore, destination, key, weight); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zunionstore_range, destination, first, last, type); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + QueuedRedis& pfadd(const StringView &key, const StringView &element) { + return command(cmd::pfadd, key, element); + } + + template + QueuedRedis& pfadd(const StringView &key, Input first, Input last) { + return command(cmd::pfadd_range, key, first, last); + } + + template + QueuedRedis& pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + QueuedRedis& pfcount(const StringView &key) { + return command(cmd::pfcount, key); + } + + template + QueuedRedis& pfcount(Input first, Input last) { + return command(cmd::pfcount_range, first, last); + } + + template + QueuedRedis& pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + QueuedRedis& pfmerge(const StringView &destination, const StringView &key) { + return command(cmd::pfmerge, destination, key); + } + + template + QueuedRedis& pfmerge(const StringView &destination, Input first, Input last) { + return command(cmd::pfmerge_range, destination, first, last); + } + + template + QueuedRedis& pfmerge(const StringView &destination, std::initializer_list il) { + return pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + QueuedRedis& geoadd(const StringView &key, + const std::tuple &member) { + return command(cmd::geoadd, key, member); + } + + template + QueuedRedis& geoadd(const StringView &key, + Input first, + Input last) { + return command(cmd::geoadd_range, key, first, last); + } + + template + QueuedRedis& geoadd(const StringView &key, std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + QueuedRedis& geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M) { + return command(cmd::geodist, key, member1, member2, unit); + } + + template + QueuedRedis& geohash(const StringView &key, Input first, Input last) { + return command(cmd::geohash_range, key, first, last); + } + + template + QueuedRedis& geohash(const StringView &key, std::initializer_list il) { + return geohash(key, il.begin(), il.end()); + } + + template + QueuedRedis& geopos(const StringView &key, Input first, Input last) { + return command(cmd::geopos_range, key, first, last); + } + + template + QueuedRedis& geopos(const StringView &key, std::initializer_list il) { + return geopos(key, il.begin(), il.end()); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + } + + // NOTE: *QueuedRedis::georadius*'s parameters are different from *Redis::georadius*. + // *Redis::georadius* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::georadius*. So we have to use extra parameters to decide + // whether we should send options to Redis. This also applies to *GEORADIUSBYMEMBER*. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + destination, + store_dist, + count); + } + + // See the comments on *GEORADIUS*. + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + // SCRIPTING commands. + + QueuedRedis& eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::eval, script, keys, args); + } + + QueuedRedis& evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::evalsha, script, keys, args); + } + + template + QueuedRedis& script_exists(Input first, Input last) { + return command(cmd::script_exists_range, first, last); + } + + template + QueuedRedis& script_exists(std::initializer_list il) { + return script_exists(il.begin(), il.end()); + } + + QueuedRedis& script_flush() { + return command(cmd::script_flush); + } + + QueuedRedis& script_kill() { + return command(cmd::script_kill); + } + + QueuedRedis& script_load(const StringView &script) { + return command(cmd::script_load, script); + } + + // PUBSUB commands. + + QueuedRedis& publish(const StringView &channel, const StringView &message) { + return command(cmd::publish, channel, message); + } + + // Stream commands. + + QueuedRedis& xack(const StringView &key, const StringView &group, const StringView &id) { + return command(cmd::xack, key, group, id); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, Input first, Input last) { + return command(cmd::xack_range, key, group, first, last); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, Input first, Input last) { + return command(cmd::xadd_range, key, id, first, last); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true) { + return command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id) { + return command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last) { + return command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il) { + return xclaim(key, group, consumer, min_idle_time, il.begin(), il.end()); + } + + QueuedRedis& xdel(const StringView &key, const StringView &id) { + return command(cmd::xdel, key, id); + } + + template + QueuedRedis& xdel(const StringView &key, Input first, Input last) { + return command(cmd::xdel_range, key, first, last); + } + + template + QueuedRedis& xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + QueuedRedis& xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false) { + return command(cmd::xgroup_create, key, group, id, mkstream); + } + + QueuedRedis& xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + return command(cmd::xgroup_setid, key, group, id); + } + + QueuedRedis& xgroup_destroy(const StringView &key, const StringView &group) { + return command(cmd::xgroup_destroy, key, group); + } + + QueuedRedis& xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + return command(cmd::xgroup_delconsumer, key, group, consumer); + } + + QueuedRedis& xlen(const StringView &key) { + return command(cmd::xlen, key); + } + + QueuedRedis& xpending(const StringView &key, const StringView &group) { + return command(cmd::xpending, key, group); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xpending_detail, key, group, start, end, count); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + return command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end) { + return command(cmd::xrange, key, start, end); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xrange, key, start, end, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id, long long count) { + return command(cmd::xread, key, id, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id) { + return xread(key, id, 0); + } + + template + auto xread(Input first, Input last, long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_range, first, last, count); + } + + template + auto xread(Input first, Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, 0); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return command(cmd::xread_block, key, id, timeout.count(), count); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xread(key, id, timeout, 0); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_block_range, first, last, timeout.count(), count); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, timeout, 0); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + return command(cmd::xreadgroup, group, consumer, key, id, count, noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count) { + return xreadgroup(group, consumer, key, id, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, 0, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) { + return command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return xreadgroup(group, consumer, key, id, timeout, count, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xreadgroup(group, consumer, key, id, timeout, 0, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, 0, false); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start) { + return command(cmd::xrevrange, key, end, start); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + return command(cmd::xrevrange, key, end, start, count); + } + + QueuedRedis& xtrim(const StringView &key, long long count, bool approx = true) { + return command(cmd::xtrim, key, count, approx); + } + +private: + friend class Redis; + + friend class RedisCluster; + + template + QueuedRedis(const ConnectionSPtr &connection, Args &&...args); + + void _sanity_check() const; + + void _reset(); + + void _invalidate(); + + void _rewrite_replies(std::vector &replies) const; + + template + void _rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const; + + ConnectionSPtr _connection; + + Impl _impl; + + std::size_t _cmd_num = 0; + + std::vector _set_cmd_indexes; + + std::vector _georadius_cmd_indexes; + + bool _valid = true; +}; + +class QueuedReplies { +public: + std::size_t size() const; + + redisReply& get(std::size_t idx); + + template + Result get(std::size_t idx); + + template + void get(std::size_t idx, Output output); + +private: + template + friend class QueuedRedis; + + explicit QueuedReplies(std::vector replies) : _replies(std::move(replies)) {} + + void _index_check(std::size_t idx) const; + + std::vector _replies; +}; + +} + +} + +#include "queued_redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.hpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.hpp new file mode 100644 index 000000000..409f48aca --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/queued_redis.hpp @@ -0,0 +1,208 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP + +namespace sw { + +namespace redis { + +template +template +QueuedRedis::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) : + _connection(connection), + _impl(std::forward(args)...) { + assert(_connection); +} + +template +Redis QueuedRedis::redis() { + return Redis(_connection); +} + +template +template +auto QueuedRedis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type { + try { + _sanity_check(); + + _impl.command(*_connection, cmd, std::forward(args)...); + + ++_cmd_num; + } catch (const Error &e) { + _invalidate(); + throw; + } + + return *this; +} + +template +template +QueuedRedis& QueuedRedis::command(const StringView &cmd_name, Args &&...args) { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +template +auto QueuedRedis::command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +QueuedReplies QueuedRedis::exec() { + try { + _sanity_check(); + + auto replies = _impl.exec(*_connection, _cmd_num); + + _rewrite_replies(replies); + + _reset(); + + return QueuedReplies(std::move(replies)); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::discard() { + try { + _sanity_check(); + + _impl.discard(*_connection, _cmd_num); + + _reset(); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::_sanity_check() const { + if (!_valid) { + throw Error("Not in valid state"); + } + + if (_connection->broken()) { + throw Error("Connection is broken"); + } +} + +template +inline void QueuedRedis::_reset() { + _cmd_num = 0; + + _set_cmd_indexes.clear(); + + _georadius_cmd_indexes.clear(); +} + +template +void QueuedRedis::_invalidate() { + _valid = false; + + _reset(); +} + +template +void QueuedRedis::_rewrite_replies(std::vector &replies) const { + _rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies); + + _rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies); +} + +template +template +void QueuedRedis::_rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const { + for (auto idx : indexes) { + assert(idx < replies.size()); + + auto &reply = replies[idx]; + + assert(reply); + + rewriter(*reply); + } +} + +inline std::size_t QueuedReplies::size() const { + return _replies.size(); +} + +inline redisReply& QueuedReplies::get(std::size_t idx) { + _index_check(idx); + + auto &reply = _replies[idx]; + + assert(reply); + + return *reply; +} + +template +inline Result QueuedReplies::get(std::size_t idx) { + auto &reply = get(idx); + + return reply::parse(reply); +} + +template +inline void QueuedReplies::get(std::size_t idx, Output output) { + auto &reply = get(idx); + + reply::to_array(reply, output); +} + +inline void QueuedReplies::_index_check(std::size_t idx) const { + if (idx >= size()) { + throw Error("Out of range"); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis++.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis++.h new file mode 100644 index 000000000..0da0ebb16 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis++.h @@ -0,0 +1,25 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H +#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H + +#include "redis.h" +#include "redis_cluster.h" +#include "queued_redis.h" +#include "sentinel.h" + +#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.cpp new file mode 100644 index 000000000..be96967fe --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.cpp @@ -0,0 +1,882 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "redis.h" +#include +#include "command.h" +#include "errors.h" +#include "queued_redis.h" + +namespace sw { + +namespace redis { + +Redis::Redis(const std::string &uri) : Redis(ConnectionOptions(uri)) {} + +Redis::Redis(const ConnectionSPtr &connection) : _connection(connection) { + assert(_connection); +} + +Pipeline Redis::pipeline() { + return Pipeline(std::make_shared(_pool.create())); +} + +Transaction Redis::transaction(bool piped) { + return Transaction(std::make_shared(_pool.create()), piped); +} + +Subscriber Redis::subscriber() { + return Subscriber(_pool.create()); +} + +// CONNECTION commands. + +void Redis::auth(const StringView &password) { + auto reply = command(cmd::auth, password); + + reply::parse(*reply); +} + +std::string Redis::echo(const StringView &msg) { + auto reply = command(cmd::echo, msg); + + return reply::parse(*reply); +} + +std::string Redis::ping() { + auto reply = command(cmd::ping); + + return reply::to_status(*reply); +} + +std::string Redis::ping(const StringView &msg) { + auto reply = command(cmd::ping, msg); + + return reply::parse(*reply); +} + +void Redis::swapdb(long long idx1, long long idx2) { + auto reply = command(cmd::swapdb, idx1, idx2); + + reply::parse(*reply); +} + +// SERVER commands. + +void Redis::bgrewriteaof() { + auto reply = command(cmd::bgrewriteaof); + + reply::parse(*reply); +} + +void Redis::bgsave() { + auto reply = command(cmd::bgsave); + + reply::parse(*reply); +} + +long long Redis::dbsize() { + auto reply = command(cmd::dbsize); + + return reply::parse(*reply); +} + +void Redis::flushall(bool async) { + auto reply = command(cmd::flushall, async); + + reply::parse(*reply); +} + +void Redis::flushdb(bool async) { + auto reply = command(cmd::flushdb, async); + + reply::parse(*reply); +} + +std::string Redis::info() { + auto reply = command(cmd::info); + + return reply::parse(*reply); +} + +std::string Redis::info(const StringView §ion) { + auto reply = command(cmd::info, section); + + return reply::parse(*reply); +} + +long long Redis::lastsave() { + auto reply = command(cmd::lastsave); + + return reply::parse(*reply); +} + +void Redis::save() { + auto reply = command(cmd::save); + + reply::parse(*reply); +} + +// KEY commands. + +long long Redis::del(const StringView &key) { + auto reply = command(cmd::del, key); + + return reply::parse(*reply); +} + +OptionalString Redis::dump(const StringView &key) { + auto reply = command(cmd::dump, key); + + return reply::parse(*reply); +} + +long long Redis::exists(const StringView &key) { + auto reply = command(cmd::exists, key); + + return reply::parse(*reply); +} + +bool Redis::expire(const StringView &key, long long timeout) { + auto reply = command(cmd::expire, key, timeout); + + return reply::parse(*reply); +} + +bool Redis::expireat(const StringView &key, long long timestamp) { + auto reply = command(cmd::expireat, key, timestamp); + + return reply::parse(*reply); +} + +bool Redis::move(const StringView &key, long long db) { + auto reply = command(cmd::move, key, db); + + return reply::parse(*reply); +} + +bool Redis::persist(const StringView &key) { + auto reply = command(cmd::persist, key); + + return reply::parse(*reply); +} + +bool Redis::pexpire(const StringView &key, long long timeout) { + auto reply = command(cmd::pexpire, key, timeout); + + return reply::parse(*reply); +} + +bool Redis::pexpireat(const StringView &key, long long timestamp) { + auto reply = command(cmd::pexpireat, key, timestamp); + + return reply::parse(*reply); +} + +long long Redis::pttl(const StringView &key) { + auto reply = command(cmd::pttl, key); + + return reply::parse(*reply); +} + +OptionalString Redis::randomkey() { + auto reply = command(cmd::randomkey); + + return reply::parse(*reply); +} + +void Redis::rename(const StringView &key, const StringView &newkey) { + auto reply = command(cmd::rename, key, newkey); + + reply::parse(*reply); +} + +bool Redis::renamenx(const StringView &key, const StringView &newkey) { + auto reply = command(cmd::renamenx, key, newkey); + + return reply::parse(*reply); +} + +void Redis::restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace) { + auto reply = command(cmd::restore, key, val, ttl, replace); + + reply::parse(*reply); +} + +long long Redis::touch(const StringView &key) { + auto reply = command(cmd::touch, key); + + return reply::parse(*reply); +} + +long long Redis::ttl(const StringView &key) { + auto reply = command(cmd::ttl, key); + + return reply::parse(*reply); +} + +std::string Redis::type(const StringView &key) { + auto reply = command(cmd::type, key); + + return reply::parse(*reply); +} + +long long Redis::unlink(const StringView &key) { + auto reply = command(cmd::unlink, key); + + return reply::parse(*reply); +} + +long long Redis::wait(long long numslaves, long long timeout) { + auto reply = command(cmd::wait, numslaves, timeout); + + return reply::parse(*reply); +} + +// STRING commands. + +long long Redis::append(const StringView &key, const StringView &val) { + auto reply = command(cmd::append, key, val); + + return reply::parse(*reply); +} + +long long Redis::bitcount(const StringView &key, long long start, long long end) { + auto reply = command(cmd::bitcount, key, start, end); + + return reply::parse(*reply); +} + +long long Redis::bitop(BitOp op, const StringView &destination, const StringView &key) { + auto reply = command(cmd::bitop, op, destination, key); + + return reply::parse(*reply); +} + +long long Redis::bitpos(const StringView &key, + long long bit, + long long start, + long long end) { + auto reply = command(cmd::bitpos, key, bit, start, end); + + return reply::parse(*reply); +} + +long long Redis::decr(const StringView &key) { + auto reply = command(cmd::decr, key); + + return reply::parse(*reply); +} + +long long Redis::decrby(const StringView &key, long long decrement) { + auto reply = command(cmd::decrby, key, decrement); + + return reply::parse(*reply); +} + +OptionalString Redis::get(const StringView &key) { + auto reply = command(cmd::get, key); + + return reply::parse(*reply); +} + +long long Redis::getbit(const StringView &key, long long offset) { + auto reply = command(cmd::getbit, key, offset); + + return reply::parse(*reply); +} + +std::string Redis::getrange(const StringView &key, long long start, long long end) { + auto reply = command(cmd::getrange, key, start, end); + + return reply::parse(*reply); +} + +OptionalString Redis::getset(const StringView &key, const StringView &val) { + auto reply = command(cmd::getset, key, val); + + return reply::parse(*reply); +} + +long long Redis::incr(const StringView &key) { + auto reply = command(cmd::incr, key); + + return reply::parse(*reply); +} + +long long Redis::incrby(const StringView &key, long long increment) { + auto reply = command(cmd::incrby, key, increment); + + return reply::parse(*reply); +} + +double Redis::incrbyfloat(const StringView &key, double increment) { + auto reply = command(cmd::incrbyfloat, key, increment); + + return reply::parse(*reply); +} + +void Redis::psetex(const StringView &key, + long long ttl, + const StringView &val) { + auto reply = command(cmd::psetex, key, ttl, val); + + reply::parse(*reply); +} + +bool Redis::set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + UpdateType type) { + auto reply = command(cmd::set, key, val, ttl.count(), type); + + reply::rewrite_set_reply(*reply); + + return reply::parse(*reply); +} + +void Redis::setex(const StringView &key, + long long ttl, + const StringView &val) { + auto reply = command(cmd::setex, key, ttl, val); + + reply::parse(*reply); +} + +bool Redis::setnx(const StringView &key, const StringView &val) { + auto reply = command(cmd::setnx, key, val); + + return reply::parse(*reply); +} + +long long Redis::setrange(const StringView &key, long long offset, const StringView &val) { + auto reply = command(cmd::setrange, key, offset, val); + + return reply::parse(*reply); +} + +long long Redis::strlen(const StringView &key) { + auto reply = command(cmd::strlen, key); + + return reply::parse(*reply); +} + +// LIST commands. + +OptionalStringPair Redis::blpop(const StringView &key, long long timeout) { + auto reply = command(cmd::blpop, key, timeout); + + return reply::parse(*reply); +} + +OptionalStringPair Redis::blpop(const StringView &key, const std::chrono::seconds &timeout) { + return blpop(key, timeout.count()); +} + +OptionalStringPair Redis::brpop(const StringView &key, long long timeout) { + auto reply = command(cmd::brpop, key, timeout); + + return reply::parse(*reply); +} + +OptionalStringPair Redis::brpop(const StringView &key, const std::chrono::seconds &timeout) { + return brpop(key, timeout.count()); +} + +OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + auto reply = command(cmd::brpoplpush, source, destination, timeout); + + return reply::parse(*reply); +} + +OptionalString Redis::lindex(const StringView &key, long long index) { + auto reply = command(cmd::lindex, key, index); + + return reply::parse(*reply); +} + +long long Redis::linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + auto reply = command(cmd::linsert, key, position, pivot, val); + + return reply::parse(*reply); +} + +long long Redis::llen(const StringView &key) { + auto reply = command(cmd::llen, key); + + return reply::parse(*reply); +} + +OptionalString Redis::lpop(const StringView &key) { + auto reply = command(cmd::lpop, key); + + return reply::parse(*reply); +} + +long long Redis::lpush(const StringView &key, const StringView &val) { + auto reply = command(cmd::lpush, key, val); + + return reply::parse(*reply); +} + +long long Redis::lpushx(const StringView &key, const StringView &val) { + auto reply = command(cmd::lpushx, key, val); + + return reply::parse(*reply); +} + +long long Redis::lrem(const StringView &key, long long count, const StringView &val) { + auto reply = command(cmd::lrem, key, count, val); + + return reply::parse(*reply); +} + +void Redis::lset(const StringView &key, long long index, const StringView &val) { + auto reply = command(cmd::lset, key, index, val); + + reply::parse(*reply); +} + +void Redis::ltrim(const StringView &key, long long start, long long stop) { + auto reply = command(cmd::ltrim, key, start, stop); + + reply::parse(*reply); +} + +OptionalString Redis::rpop(const StringView &key) { + auto reply = command(cmd::rpop, key); + + return reply::parse(*reply); +} + +OptionalString Redis::rpoplpush(const StringView &source, const StringView &destination) { + auto reply = command(cmd::rpoplpush, source, destination); + + return reply::parse(*reply); +} + +long long Redis::rpush(const StringView &key, const StringView &val) { + auto reply = command(cmd::rpush, key, val); + + return reply::parse(*reply); +} + +long long Redis::rpushx(const StringView &key, const StringView &val) { + auto reply = command(cmd::rpushx, key, val); + + return reply::parse(*reply); +} + +long long Redis::hdel(const StringView &key, const StringView &field) { + auto reply = command(cmd::hdel, key, field); + + return reply::parse(*reply); +} + +bool Redis::hexists(const StringView &key, const StringView &field) { + auto reply = command(cmd::hexists, key, field); + + return reply::parse(*reply); +} + +OptionalString Redis::hget(const StringView &key, const StringView &field) { + auto reply = command(cmd::hget, key, field); + + return reply::parse(*reply); +} + +long long Redis::hincrby(const StringView &key, const StringView &field, long long increment) { + auto reply = command(cmd::hincrby, key, field, increment); + + return reply::parse(*reply); +} + +double Redis::hincrbyfloat(const StringView &key, const StringView &field, double increment) { + auto reply = command(cmd::hincrbyfloat, key, field, increment); + + return reply::parse(*reply); +} + +long long Redis::hlen(const StringView &key) { + auto reply = command(cmd::hlen, key); + + return reply::parse(*reply); +} + +bool Redis::hset(const StringView &key, const StringView &field, const StringView &val) { + auto reply = command(cmd::hset, key, field, val); + + return reply::parse(*reply); +} + +bool Redis::hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); +} + +bool Redis::hsetnx(const StringView &key, const StringView &field, const StringView &val) { + auto reply = command(cmd::hsetnx, key, field, val); + + return reply::parse(*reply); +} + +bool Redis::hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); +} + +long long Redis::hstrlen(const StringView &key, const StringView &field) { + auto reply = command(cmd::hstrlen, key, field); + + return reply::parse(*reply); +} + +// SET commands. + +long long Redis::sadd(const StringView &key, const StringView &member) { + auto reply = command(cmd::sadd, key, member); + + return reply::parse(*reply); +} + +long long Redis::scard(const StringView &key) { + auto reply = command(cmd::scard, key); + + return reply::parse(*reply); +} + +long long Redis::sdiffstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sdiffstore, destination, key); + + return reply::parse(*reply); +} + +long long Redis::sinterstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sinterstore, destination, key); + + return reply::parse(*reply); +} + +bool Redis::sismember(const StringView &key, const StringView &member) { + auto reply = command(cmd::sismember, key, member); + + return reply::parse(*reply); +} + +bool Redis::smove(const StringView &source, + const StringView &destination, + const StringView &member) { + auto reply = command(cmd::smove, source, destination, member); + + return reply::parse(*reply); +} + +OptionalString Redis::spop(const StringView &key) { + auto reply = command(cmd::spop, key); + + return reply::parse(*reply); +} + +OptionalString Redis::srandmember(const StringView &key) { + auto reply = command(cmd::srandmember, key); + + return reply::parse(*reply); +} + +long long Redis::srem(const StringView &key, const StringView &member) { + auto reply = command(cmd::srem, key, member); + + return reply::parse(*reply); +} + +long long Redis::sunionstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sunionstore, destination, key); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +auto Redis::bzpopmax(const StringView &key, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax, key, timeout); + + return reply::parse>>(*reply); +} + +auto Redis::bzpopmin(const StringView &key, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin, key, timeout); + + return reply::parse>>(*reply); +} + +long long Redis::zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto reply = command(cmd::zadd, key, member, score, type, changed); + + return reply::parse(*reply); +} + +long long Redis::zcard(const StringView &key) { + auto reply = command(cmd::zcard, key); + + return reply::parse(*reply); +} + +double Redis::zincrby(const StringView &key, double increment, const StringView &member) { + auto reply = command(cmd::zincrby, key, increment, member); + + return reply::parse(*reply); +} + +long long Redis::zinterstore(const StringView &destination, const StringView &key, double weight) { + auto reply = command(cmd::zinterstore, destination, key, weight); + + return reply::parse(*reply); +} + +Optional> Redis::zpopmax(const StringView &key) { + auto reply = command(cmd::zpopmax, key, 1); + + return reply::parse>>(*reply); +} + +Optional> Redis::zpopmin(const StringView &key) { + auto reply = command(cmd::zpopmin, key, 1); + + return reply::parse>>(*reply); +} + +OptionalLongLong Redis::zrank(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrank, key, member); + + return reply::parse(*reply); +} + +long long Redis::zrem(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrem, key, member); + + return reply::parse(*reply); +} + +long long Redis::zremrangebyrank(const StringView &key, long long start, long long stop) { + auto reply = command(cmd::zremrangebyrank, key, start, stop); + + return reply::parse(*reply); +} + +OptionalLongLong Redis::zrevrank(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrevrank, key, member); + + return reply::parse(*reply); +} + +OptionalDouble Redis::zscore(const StringView &key, const StringView &member) { + auto reply = command(cmd::zscore, key, member); + + return reply::parse(*reply); +} + +long long Redis::zunionstore(const StringView &destination, const StringView &key, double weight) { + auto reply = command(cmd::zunionstore, destination, key, weight); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +bool Redis::pfadd(const StringView &key, const StringView &element) { + auto reply = command(cmd::pfadd, key, element); + + return reply::parse(*reply); +} + +long long Redis::pfcount(const StringView &key) { + auto reply = command(cmd::pfcount, key); + + return reply::parse(*reply); +} + +void Redis::pfmerge(const StringView &destination, const StringView &key) { + auto reply = command(cmd::pfmerge, destination, key); + + reply::parse(*reply); +} + +// GEO commands. + +long long Redis::geoadd(const StringView &key, + const std::tuple &member) { + auto reply = command(cmd::geoadd, key, member); + + return reply::parse(*reply); +} + +OptionalDouble Redis::geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit) { + auto reply = command(cmd::geodist, key, member1, member2, unit); + + return reply::parse(*reply); +} + +OptionalLongLong Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + auto reply = command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + + reply::rewrite_georadius_reply(*reply); + + return reply::parse(*reply); +} + +OptionalLongLong Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + auto reply = command(cmd::georadiusbymember_store, + key, + member, + radius, + unit, + destination, + store_dist, + count); + + reply::rewrite_georadius_reply(*reply); + + return reply::parse(*reply); +} + +// SCRIPTING commands. + +void Redis::script_flush() { + auto reply = command(cmd::script_flush); + + reply::parse(*reply); +} + +void Redis::script_kill() { + auto reply = command(cmd::script_kill); + + reply::parse(*reply); +} + +std::string Redis::script_load(const StringView &script) { + auto reply = command(cmd::script_load, script); + + return reply::parse(*reply); +} + +// PUBSUB commands. + +long long Redis::publish(const StringView &channel, const StringView &message) { + auto reply = command(cmd::publish, channel, message); + + return reply::parse(*reply); +} + +// Transaction commands. + +void Redis::watch(const StringView &key) { + auto reply = command(cmd::watch, key); + + reply::parse(*reply); +} + +// Stream commands. + +long long Redis::xack(const StringView &key, const StringView &group, const StringView &id) { + auto reply = command(cmd::xack, key, group, id); + + return reply::parse(*reply); +} + +long long Redis::xdel(const StringView &key, const StringView &id) { + auto reply = command(cmd::xdel, key, id); + + return reply::parse(*reply); +} + +void Redis::xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + auto reply = command(cmd::xgroup_create, key, group, id, mkstream); + + reply::parse(*reply); +} + +void Redis::xgroup_setid(const StringView &key, const StringView &group, const StringView &id) { + auto reply = command(cmd::xgroup_setid, key, group, id); + + reply::parse(*reply); +} + +long long Redis::xgroup_destroy(const StringView &key, const StringView &group) { + auto reply = command(cmd::xgroup_destroy, key, group); + + return reply::parse(*reply); +} + +long long Redis::xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + auto reply = command(cmd::xgroup_delconsumer, key, group, consumer); + + return reply::parse(*reply); +} + +long long Redis::xlen(const StringView &key) { + auto reply = command(cmd::xlen, key); + + return reply::parse(*reply); +} + +long long Redis::xtrim(const StringView &key, long long count, bool approx) { + auto reply = command(cmd::xtrim, key, count, approx); + + return reply::parse(*reply); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.h new file mode 100644 index 000000000..b54afb96b --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.h @@ -0,0 +1,1523 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_H +#define SEWENEW_REDISPLUSPLUS_REDIS_H + +#include +#include +#include +#include +#include +#include "connection_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class Redis { +public: + Redis(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : _pool(pool_opts, connection_opts) {} + + // Construct Redis instance with URI: + // "tcp://127.0.0.1", "tcp://127.0.0.1:6379", or "unix://path/to/socket" + explicit Redis(const std::string &uri); + + Redis(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role, + const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(SimpleSentinel(sentinel, master_name, role), pool_opts, connection_opts) {} + + Redis(const Redis &) = delete; + Redis& operator=(const Redis &) = delete; + + Redis(Redis &&) = default; + Redis& operator=(Redis &&) = default; + + Pipeline pipeline(); + + Transaction transaction(bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type; + + template + Result command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // CONNECTION commands. + + void auth(const StringView &password); + + std::string echo(const StringView &msg); + + std::string ping(); + + std::string ping(const StringView &msg); + + // After sending QUIT, only the current connection will be close, while + // other connections in the pool is still open. This is a strange behavior. + // So we DO NOT support the QUIT command. If you want to quit connection to + // server, just destroy the Redis object. + // + // void quit(); + + // We get a connection from the pool, and send the SELECT command to switch + // to a specified DB. However, when we try to send other commands to the + // given DB, we might get a different connection from the pool, and these + // commands, in fact, work on other DB. e.g. + // + // redis.select(1); // get a connection from the pool and switch to the 1th DB + // redis.get("key"); // might get another connection from the pool, + // // and try to get 'key' on the default DB + // + // Obviously, this is NOT what we expect. So we DO NOT support SELECT command. + // In order to select a DB, we can specify the DB index with the ConnectionOptions. + // + // However, since Pipeline and Transaction always send multiple commands on a + // single connection, these two classes have a *select* method. + // + // void select(long long idx); + + void swapdb(long long idx1, long long idx2); + + // SERVER commands. + + void bgrewriteaof(); + + void bgsave(); + + long long dbsize(); + + void flushall(bool async = false); + + void flushdb(bool async = false); + + std::string info(); + + std::string info(const StringView §ion); + + long long lastsave(); + + void save(); + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + template + void keys(const StringView &pattern, Output output); + + bool move(const StringView &key, long long db); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + OptionalString randomkey(); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + template + long long scan(long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long scan(long long cursor, + Output output); + + template + long long scan(long long cursor, + const StringView &pattern, + Output output); + + template + long long scan(long long cursor, + long long count, + Output output); + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + long long wait(long long numslaves, long long timeout); + + long long wait(long long numslaves, const std::chrono::milliseconds &timeout); + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + // If *Input* is an iterator of a container of string, + // we use the default weight, i.e. 1, and send + // *ZINTERSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + // If *Input* is an iterator of a container of pair, i.e. key-weight pair, + // we send the command with the given weights: + // *ZINTERSTORE destination numkeys key [key ...] + // [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]* + // + // The following code use the default weight: + // + // vector keys = {"k1", "k2", "k3"}; + // redis.zinterstore(destination, keys.begin(), keys.end()); + // + // On the other hand, the following code use the given weights: + // + // vector> keys_with_weights = {{"k1", 1}, {"k2", 2}, {"k3", 3}}; + // redis.zinterstore(destination, keys_with_weights.begin(), keys_with_weights.end()); + // + // NOTE: `keys_with_weights` can also be of type `unordered_map`. + // However, it will be slower than vector>, since we use + // `distance(first, last)` to calculate the *numkeys* parameter. + // + // This also applies to *ZUNIONSTORE* command. + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + // See *zinterstore* comment for how to use this method. + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + void script_exists(Input first, Input last, Output output); + + template + void script_exists(std::initializer_list il, Output output) { + script_exists(il.begin(), il.end(), output); + } + + void script_flush(); + + void script_kill(); + + std::string script_load(const StringView &script); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Transaction commands. + void watch(const StringView &key); + + template + void watch(Input first, Input last); + + template + void watch(std::initializer_list il) { + watch(il.begin(), il.end()); + } + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first ,last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class ConnectionPoolGuard { + public: + ConnectionPoolGuard(ConnectionPool &pool, + Connection &connection) : _pool(pool), _connection(connection) {} + + ~ConnectionPoolGuard() { + _pool.release(std::move(_connection)); + } + + private: + ConnectionPool &_pool; + Connection &_connection; + }; + + template + friend class QueuedRedis; + + friend class RedisCluster; + + // For internal use. + explicit Redis(const ConnectionSPtr &connection); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + // Pool Mode. + // Public constructors create a *Redis* instance with a pool. + // In this case, *_connection* is a null pointer, and is never used. + ConnectionPool _pool; + + // Single Connection Mode. + // Private constructor creats a *Redis* instance with a single connection. + // This is used when we create Transaction, Pipeline and Subscriber. + // In this case, *_pool* is empty, and is never used. + ConnectionSPtr _connection; +}; + +} + +} + +#include "redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.hpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.hpp new file mode 100644 index 000000000..3a227a6f1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis.hpp @@ -0,0 +1,1365 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_HPP + +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +template +auto Redis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (_connection) { + // Single Connection Mode. + // TODO: In this case, should we reconnect? + if (_connection->broken()) { + throw Error("Connection is broken"); + } + + return _command(*_connection, cmd, std::forward(args)...); + } else { + // Pool Mode, i.e. get connection from pool. + auto connection = _pool.fetch(); + + assert(!connection.broken()); + + ConnectionPoolGuard guard(_pool, connection); + + return _command(connection, cmd, std::forward(args)...); + } +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, ReplyUPtr>::type { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +Result Redis::command(const StringView &cmd_name, Args &&...args) { + auto r = command(cmd_name, std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long Redis::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool Redis::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool Redis::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +template +void Redis::keys(const StringView &pattern, Output output) { + auto reply = command(cmd::keys, pattern); + + reply::to_array(*reply, output); +} + +inline bool Redis::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool Redis::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void Redis::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long Redis::scan(long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::scan, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::scan(long long cursor, + const StringView &pattern, + Output output) { + return scan(cursor, pattern, 10, output); +} + +template +inline long long Redis::scan(long long cursor, + long long count, + Output output) { + return scan(cursor, "*", count, output); +} + +template +inline long long Redis::scan(long long cursor, + Output output) { + return scan(cursor, "*", 10, output); +} + +template +long long Redis::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); +} + +// STRING commands. + +template +long long Redis::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = command(cmd::bitop_range, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool Redis::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void Redis::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void Redis::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair Redis::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair Redis::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long Redis::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long Redis::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long Redis::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void Redis::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long Redis::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void Redis::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long Redis::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void Redis::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto Redis::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto Redis::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long Redis::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long Redis::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long Redis::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void Redis::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long Redis::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool Redis::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void Redis::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long Redis::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::eval, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::eval, script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::evalsha, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::evalsha, script, keys, args); + + reply::to_array(*reply, output); +} + +template +void Redis::script_exists(Input first, Input last, Output output) { + if (first == last) { + throw Error("SCRIPT EXISTS: no key specified"); + } + + auto reply = command(cmd::script_exists_range, first, last); + + reply::to_array(*reply, output); +} + +// Transaction commands. + +template +void Redis::watch(Input first, Input last) { + auto reply = command(cmd::watch_range, first, last); + + reply::parse(*reply); +} + +// Stream commands. + +template +long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long Redis::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto Redis::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + auto reply = connection.recv(); + + return reply; +} + +template +inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.cpp new file mode 100644 index 000000000..6950f5730 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.cpp @@ -0,0 +1,769 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "redis_cluster.h" +#include +#include "command.h" +#include "errors.h" +#include "queued_redis.h" + +namespace sw { + +namespace redis { + +RedisCluster::RedisCluster(const std::string &uri) : RedisCluster(ConnectionOptions(uri)) {} + +Redis RedisCluster::redis(const StringView &hash_tag) { + auto opts = _pool.connection_options(hash_tag); + return Redis(std::make_shared(opts)); +} + +Pipeline RedisCluster::pipeline(const StringView &hash_tag) { + auto opts = _pool.connection_options(hash_tag); + return Pipeline(std::make_shared(opts)); +} + +Transaction RedisCluster::transaction(const StringView &hash_tag, bool piped) { + auto opts = _pool.connection_options(hash_tag); + return Transaction(std::make_shared(opts), piped); +} + +Subscriber RedisCluster::subscriber() { + auto opts = _pool.connection_options(); + return Subscriber(Connection(opts)); +} + +// KEY commands. + +long long RedisCluster::del(const StringView &key) { + auto reply = command(cmd::del, key); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::dump(const StringView &key) { + auto reply = command(cmd::dump, key); + + return reply::parse(*reply); +} + +long long RedisCluster::exists(const StringView &key) { + auto reply = command(cmd::exists, key); + + return reply::parse(*reply); +} + +bool RedisCluster::expire(const StringView &key, long long timeout) { + auto reply = command(cmd::expire, key, timeout); + + return reply::parse(*reply); +} + +bool RedisCluster::expireat(const StringView &key, long long timestamp) { + auto reply = command(cmd::expireat, key, timestamp); + + return reply::parse(*reply); +} + +bool RedisCluster::persist(const StringView &key) { + auto reply = command(cmd::persist, key); + + return reply::parse(*reply); +} + +bool RedisCluster::pexpire(const StringView &key, long long timeout) { + auto reply = command(cmd::pexpire, key, timeout); + + return reply::parse(*reply); +} + +bool RedisCluster::pexpireat(const StringView &key, long long timestamp) { + auto reply = command(cmd::pexpireat, key, timestamp); + + return reply::parse(*reply); +} + +long long RedisCluster::pttl(const StringView &key) { + auto reply = command(cmd::pttl, key); + + return reply::parse(*reply); +} + +void RedisCluster::rename(const StringView &key, const StringView &newkey) { + auto reply = command(cmd::rename, key, newkey); + + reply::parse(*reply); +} + +bool RedisCluster::renamenx(const StringView &key, const StringView &newkey) { + auto reply = command(cmd::renamenx, key, newkey); + + return reply::parse(*reply); +} + +void RedisCluster::restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace) { + auto reply = command(cmd::restore, key, val, ttl, replace); + + reply::parse(*reply); +} + +long long RedisCluster::touch(const StringView &key) { + auto reply = command(cmd::touch, key); + + return reply::parse(*reply); +} + +long long RedisCluster::ttl(const StringView &key) { + auto reply = command(cmd::ttl, key); + + return reply::parse(*reply); +} + +std::string RedisCluster::type(const StringView &key) { + auto reply = command(cmd::type, key); + + return reply::parse(*reply); +} + +long long RedisCluster::unlink(const StringView &key) { + auto reply = command(cmd::unlink, key); + + return reply::parse(*reply); +} + +// STRING commands. + +long long RedisCluster::append(const StringView &key, const StringView &val) { + auto reply = command(cmd::append, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::bitcount(const StringView &key, long long start, long long end) { + auto reply = command(cmd::bitcount, key, start, end); + + return reply::parse(*reply); +} + +long long RedisCluster::bitop(BitOp op, const StringView &destination, const StringView &key) { + auto reply = _command(cmd::bitop, destination, op, destination, key); + + return reply::parse(*reply); +} + +long long RedisCluster::bitpos(const StringView &key, + long long bit, + long long start, + long long end) { + auto reply = command(cmd::bitpos, key, bit, start, end); + + return reply::parse(*reply); +} + +long long RedisCluster::decr(const StringView &key) { + auto reply = command(cmd::decr, key); + + return reply::parse(*reply); +} + +long long RedisCluster::decrby(const StringView &key, long long decrement) { + auto reply = command(cmd::decrby, key, decrement); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::get(const StringView &key) { + auto reply = command(cmd::get, key); + + return reply::parse(*reply); +} + +long long RedisCluster::getbit(const StringView &key, long long offset) { + auto reply = command(cmd::getbit, key, offset); + + return reply::parse(*reply); +} + +std::string RedisCluster::getrange(const StringView &key, long long start, long long end) { + auto reply = command(cmd::getrange, key, start, end); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::getset(const StringView &key, const StringView &val) { + auto reply = command(cmd::getset, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::incr(const StringView &key) { + auto reply = command(cmd::incr, key); + + return reply::parse(*reply); +} + +long long RedisCluster::incrby(const StringView &key, long long increment) { + auto reply = command(cmd::incrby, key, increment); + + return reply::parse(*reply); +} + +double RedisCluster::incrbyfloat(const StringView &key, double increment) { + auto reply = command(cmd::incrbyfloat, key, increment); + + return reply::parse(*reply); +} + +void RedisCluster::psetex(const StringView &key, + long long ttl, + const StringView &val) { + auto reply = command(cmd::psetex, key, ttl, val); + + reply::parse(*reply); +} + +bool RedisCluster::set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + UpdateType type) { + auto reply = command(cmd::set, key, val, ttl.count(), type); + + reply::rewrite_set_reply(*reply); + + return reply::parse(*reply); +} + +void RedisCluster::setex(const StringView &key, + long long ttl, + const StringView &val) { + auto reply = command(cmd::setex, key, ttl, val); + + reply::parse(*reply); +} + +bool RedisCluster::setnx(const StringView &key, const StringView &val) { + auto reply = command(cmd::setnx, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::setrange(const StringView &key, long long offset, const StringView &val) { + auto reply = command(cmd::setrange, key, offset, val); + + return reply::parse(*reply); +} + +long long RedisCluster::strlen(const StringView &key) { + auto reply = command(cmd::strlen, key); + + return reply::parse(*reply); +} + +// LIST commands. + +OptionalStringPair RedisCluster::blpop(const StringView &key, long long timeout) { + auto reply = command(cmd::blpop, key, timeout); + + return reply::parse(*reply); +} + +OptionalStringPair RedisCluster::blpop(const StringView &key, const std::chrono::seconds &timeout) { + return blpop(key, timeout.count()); +} + +OptionalStringPair RedisCluster::brpop(const StringView &key, long long timeout) { + auto reply = command(cmd::brpop, key, timeout); + + return reply::parse(*reply); +} + +OptionalStringPair RedisCluster::brpop(const StringView &key, const std::chrono::seconds &timeout) { + return brpop(key, timeout.count()); +} + +OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + auto reply = command(cmd::brpoplpush, source, destination, timeout); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::lindex(const StringView &key, long long index) { + auto reply = command(cmd::lindex, key, index); + + return reply::parse(*reply); +} + +long long RedisCluster::linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + auto reply = command(cmd::linsert, key, position, pivot, val); + + return reply::parse(*reply); +} + +long long RedisCluster::llen(const StringView &key) { + auto reply = command(cmd::llen, key); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::lpop(const StringView &key) { + auto reply = command(cmd::lpop, key); + + return reply::parse(*reply); +} + +long long RedisCluster::lpush(const StringView &key, const StringView &val) { + auto reply = command(cmd::lpush, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::lpushx(const StringView &key, const StringView &val) { + auto reply = command(cmd::lpushx, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::lrem(const StringView &key, long long count, const StringView &val) { + auto reply = command(cmd::lrem, key, count, val); + + return reply::parse(*reply); +} + +void RedisCluster::lset(const StringView &key, long long index, const StringView &val) { + auto reply = command(cmd::lset, key, index, val); + + reply::parse(*reply); +} + +void RedisCluster::ltrim(const StringView &key, long long start, long long stop) { + auto reply = command(cmd::ltrim, key, start, stop); + + reply::parse(*reply); +} + +OptionalString RedisCluster::rpop(const StringView &key) { + auto reply = command(cmd::rpop, key); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::rpoplpush(const StringView &source, const StringView &destination) { + auto reply = command(cmd::rpoplpush, source, destination); + + return reply::parse(*reply); +} + +long long RedisCluster::rpush(const StringView &key, const StringView &val) { + auto reply = command(cmd::rpush, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::rpushx(const StringView &key, const StringView &val) { + auto reply = command(cmd::rpushx, key, val); + + return reply::parse(*reply); +} + +long long RedisCluster::hdel(const StringView &key, const StringView &field) { + auto reply = command(cmd::hdel, key, field); + + return reply::parse(*reply); +} + +bool RedisCluster::hexists(const StringView &key, const StringView &field) { + auto reply = command(cmd::hexists, key, field); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::hget(const StringView &key, const StringView &field) { + auto reply = command(cmd::hget, key, field); + + return reply::parse(*reply); +} + +long long RedisCluster::hincrby(const StringView &key, const StringView &field, long long increment) { + auto reply = command(cmd::hincrby, key, field, increment); + + return reply::parse(*reply); +} + +double RedisCluster::hincrbyfloat(const StringView &key, const StringView &field, double increment) { + auto reply = command(cmd::hincrbyfloat, key, field, increment); + + return reply::parse(*reply); +} + +long long RedisCluster::hlen(const StringView &key) { + auto reply = command(cmd::hlen, key); + + return reply::parse(*reply); +} + +bool RedisCluster::hset(const StringView &key, const StringView &field, const StringView &val) { + auto reply = command(cmd::hset, key, field, val); + + return reply::parse(*reply); +} + +bool RedisCluster::hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); +} + +bool RedisCluster::hsetnx(const StringView &key, const StringView &field, const StringView &val) { + auto reply = command(cmd::hsetnx, key, field, val); + + return reply::parse(*reply); +} + +bool RedisCluster::hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); +} + +long long RedisCluster::hstrlen(const StringView &key, const StringView &field) { + auto reply = command(cmd::hstrlen, key, field); + + return reply::parse(*reply); +} + +// SET commands. + +long long RedisCluster::sadd(const StringView &key, const StringView &member) { + auto reply = command(cmd::sadd, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::scard(const StringView &key) { + auto reply = command(cmd::scard, key); + + return reply::parse(*reply); +} + +long long RedisCluster::sdiffstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sdiffstore, destination, key); + + return reply::parse(*reply); +} + +long long RedisCluster::sinterstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sinterstore, destination, key); + + return reply::parse(*reply); +} + +bool RedisCluster::sismember(const StringView &key, const StringView &member) { + auto reply = command(cmd::sismember, key, member); + + return reply::parse(*reply); +} + +bool RedisCluster::smove(const StringView &source, + const StringView &destination, + const StringView &member) { + auto reply = command(cmd::smove, source, destination, member); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::spop(const StringView &key) { + auto reply = command(cmd::spop, key); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::srandmember(const StringView &key) { + auto reply = command(cmd::srandmember, key); + + return reply::parse(*reply); +} + +long long RedisCluster::srem(const StringView &key, const StringView &member) { + auto reply = command(cmd::srem, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::sunionstore(const StringView &destination, const StringView &key) { + auto reply = command(cmd::sunionstore, destination, key); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +auto RedisCluster::bzpopmax(const StringView &key, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax, key, timeout); + + return reply::parse>>(*reply); +} + +auto RedisCluster::bzpopmin(const StringView &key, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin, key, timeout); + + return reply::parse>>(*reply); +} + +long long RedisCluster::zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto reply = command(cmd::zadd, key, member, score, type, changed); + + return reply::parse(*reply); +} + +long long RedisCluster::zcard(const StringView &key) { + auto reply = command(cmd::zcard, key); + + return reply::parse(*reply); +} + +double RedisCluster::zincrby(const StringView &key, double increment, const StringView &member) { + auto reply = command(cmd::zincrby, key, increment, member); + + return reply::parse(*reply); +} + +long long RedisCluster::zinterstore(const StringView &destination, + const StringView &key, + double weight) { + auto reply = command(cmd::zinterstore, destination, key, weight); + + return reply::parse(*reply); +} + +Optional> RedisCluster::zpopmax(const StringView &key) { + auto reply = command(cmd::zpopmax, key, 1); + + return reply::parse>>(*reply); +} + +Optional> RedisCluster::zpopmin(const StringView &key) { + auto reply = command(cmd::zpopmin, key, 1); + + return reply::parse>>(*reply); +} + +OptionalLongLong RedisCluster::zrank(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrank, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::zrem(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrem, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::zremrangebyrank(const StringView &key, long long start, long long stop) { + auto reply = command(cmd::zremrangebyrank, key, start, stop); + + return reply::parse(*reply); +} + +OptionalLongLong RedisCluster::zrevrank(const StringView &key, const StringView &member) { + auto reply = command(cmd::zrevrank, key, member); + + return reply::parse(*reply); +} + +OptionalDouble RedisCluster::zscore(const StringView &key, const StringView &member) { + auto reply = command(cmd::zscore, key, member); + + return reply::parse(*reply); +} + +long long RedisCluster::zunionstore(const StringView &destination, + const StringView &key, + double weight) { + auto reply = command(cmd::zunionstore, destination, key, weight); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +bool RedisCluster::pfadd(const StringView &key, const StringView &element) { + auto reply = command(cmd::pfadd, key, element); + + return reply::parse(*reply); +} + +long long RedisCluster::pfcount(const StringView &key) { + auto reply = command(cmd::pfcount, key); + + return reply::parse(*reply); +} + +void RedisCluster::pfmerge(const StringView &destination, const StringView &key) { + auto reply = command(cmd::pfmerge, destination, key); + + reply::parse(*reply); +} + +// GEO commands. + +long long RedisCluster::geoadd(const StringView &key, + const std::tuple &member) { + auto reply = command(cmd::geoadd, key, member); + + return reply::parse(*reply); +} + +OptionalDouble RedisCluster::geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit) { + auto reply = command(cmd::geodist, key, member1, member2, unit); + + return reply::parse(*reply); +} + +OptionalLongLong RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + auto reply = command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + + reply::rewrite_georadius_reply(*reply); + + return reply::parse(*reply); +} + +OptionalLongLong RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + auto reply = command(cmd::georadiusbymember_store, + key, + member, + radius, + unit, + destination, + store_dist, + count); + + reply::rewrite_georadius_reply(*reply); + + return reply::parse(*reply); +} + +// PUBSUB commands. + +long long RedisCluster::publish(const StringView &channel, const StringView &message) { + auto reply = command(cmd::publish, channel, message); + + return reply::parse(*reply); +} + +// Stream commands. + +long long RedisCluster::xack(const StringView &key, const StringView &group, const StringView &id) { + auto reply = command(cmd::xack, key, group, id); + + return reply::parse(*reply); +} + +long long RedisCluster::xdel(const StringView &key, const StringView &id) { + auto reply = command(cmd::xdel, key, id); + + return reply::parse(*reply); +} + +void RedisCluster::xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + auto reply = command(cmd::xgroup_create, key, group, id, mkstream); + + reply::parse(*reply); +} + +void RedisCluster::xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + auto reply = command(cmd::xgroup_setid, key, group, id); + + reply::parse(*reply); +} + +long long RedisCluster::xgroup_destroy(const StringView &key, const StringView &group) { + auto reply = command(cmd::xgroup_destroy, key, group); + + return reply::parse(*reply); +} + +long long RedisCluster::xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + auto reply = command(cmd::xgroup_delconsumer, key, group, consumer); + + return reply::parse(*reply); +} + +long long RedisCluster::xlen(const StringView &key) { + auto reply = command(cmd::xlen, key); + + return reply::parse(*reply); +} + +long long RedisCluster::xtrim(const StringView &key, long long count, bool approx) { + auto reply = command(cmd::xtrim, key, count, approx); + + return reply::parse(*reply); +} + +void RedisCluster::_asking(Connection &connection) { + // Send ASKING command. + connection.send("ASKING"); + + auto reply = connection.recv(); + + assert(reply); + + reply::parse(*reply); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.h new file mode 100644 index 000000000..50a221367 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.h @@ -0,0 +1,1395 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H + +#include +#include +#include +#include +#include "shards_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class RedisCluster { +public: + RedisCluster(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(pool_opts, connection_opts) {} + + // Construct RedisCluster with URI: + // "tcp://127.0.0.1" or "tcp://127.0.0.1:6379" + // Only need to specify one URI. + explicit RedisCluster(const std::string &uri); + + RedisCluster(const RedisCluster &) = delete; + RedisCluster& operator=(const RedisCluster &) = delete; + + RedisCluster(RedisCluster &&) = default; + RedisCluster& operator=(RedisCluster &&) = default; + + Redis redis(const StringView &hash_tag); + + Pipeline pipeline(const StringView &hash_tag); + + Transaction transaction(const StringView &hash_tag, bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first, last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class Command { + public: + explicit Command(const StringView &cmd_name) : _cmd_name(cmd_name) {} + + template + void operator()(Connection &connection, Args &&...args) const { + CmdArgs cmd_args; + cmd_args.append(_cmd_name, std::forward(args)...); + connection.send(cmd_args); + } + + private: + StringView _cmd_name; + }; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + ReplyUPtr _command(Cmd cmd, Connection &connection, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::true_type, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::false_type, Input &&first, Args &&...args); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _range_command(Cmd cmd, std::true_type, Input input, Args &&...args); + + template + ReplyUPtr _range_command(Cmd cmd, std::false_type, Input input, Args &&...args); + + void _asking(Connection &connection); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + ShardsPool _pool; +}; + +} + +} + +#include "redis_cluster.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.hpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.hpp new file mode 100644 index 000000000..61da3f062 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/redis_cluster.hpp @@ -0,0 +1,1415 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP + +#include +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" +#include "shards_pool.h" + +namespace sw { + +namespace redis { + +template +auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + return _command(cmd, + std::is_convertible::type, StringView>(), + std::forward(key), + std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type { + auto cmd = Command(cmd_name); + + return _generic_command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type { + auto r = command(cmd_name, std::forward(key), std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(key), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last || std::next(first) == last) { + throw Error("command: invalid range"); + } + + const auto &key = *first; + ++first; + + auto cmd = [&key](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + cmd_args.append(key); + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long RedisCluster::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool RedisCluster::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool RedisCluster::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void RedisCluster::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long RedisCluster::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +// STRING commands. + +template +long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = _command(cmd::bitop_range, destination, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool RedisCluster::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void RedisCluster::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void RedisCluster::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void RedisCluster::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long RedisCluster::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void RedisCluster::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long RedisCluster::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long RedisCluster::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool RedisCluster::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long RedisCluster::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::evalsha, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = command(cmd::evalsha, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +// Stream commands. + +template +long long RedisCluster::xack(const StringView &key, + const StringView &group, + Input first, + Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_range, + first->first, + group, + consumer, + first, + last, + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup_block, + key, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_block_range, + first->first, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type { + return command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type { + auto k = std::to_string(std::forward(key)); + return command(cmd, k, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) { + return _command(cmd, key, key, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) { + return _range_command(cmd, + std::is_convertible< + typename std::decay< + decltype(*std::declval())>::type, StringView>(), + std::forward(first), + std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) { + return _command(cmd, *input, input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) { + return _command(cmd, std::get<0>(*input), input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + return connection.recv(); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) { + for (auto idx = 0; idx < 2; ++idx) { + try { + auto guarded_connection = _pool.fetch(key); + + return _command(cmd, guarded_connection.connection(), std::forward(args)...); + } catch (const IoError &err) { + // When master is down, one of its replicas will be promoted to be the new master. + // If we try to send command to the old master, we'll get an *IoError*. + // In this case, we need to update the slots mapping. + _pool.update(); + } catch (const ClosedError &err) { + // Node might be removed. + // 1. Get up-to-date slot mapping to check if the node still exists. + _pool.update(); + + // TODO: + // 2. If it's NOT exist, update slot mapping, and retry. + // 3. If it's still exist, that means the node is down, NOT removed, throw exception. + } catch (const MovedError &err) { + // Slot mapping has been changed, update it and try again. + _pool.update(); + } catch (const AskError &err) { + auto guarded_connection = _pool.fetch(err.node()); + auto &connection = guarded_connection.connection(); + + // 1. send ASKING command. + _asking(connection); + + // 2. resend last command. + try { + return _command(cmd, connection, std::forward(args)...); + } catch (const MovedError &err) { + throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state"); + } + } // For other exceptions, just throw it. + } + + // Possible failures: + // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx', + // while the destination node has NOT run it. + // In this case, client will be redirected by both nodes with MovedError. + // 2. Other failures... + throw Error("Failed to send command with key: " + std::string(key.data(), key.size())); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.cpp new file mode 100644 index 000000000..dc70d30cc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.cpp @@ -0,0 +1,150 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "reply.h" + +namespace sw { + +namespace redis { + +namespace reply { + +std::string to_status(redisReply &reply) { + if (!reply::is_status(reply)) { + throw ProtoError("Expect STATUS reply"); + } + + if (reply.str == nullptr) { + throw ProtoError("A null status reply"); + } + + // Old version hiredis' *redisReply::len* is of type int. + // So we CANNOT have something like: *return {reply.str, reply.len}*. + return std::string(reply.str, reply.len); +} + +std::string parse(ParseTag, redisReply &reply) { + if (!reply::is_string(reply) && !reply::is_status(reply)) { + throw ProtoError("Expect STRING reply"); + } + + if (reply.str == nullptr) { + throw ProtoError("A null string reply"); + } + + // Old version hiredis' *redisReply::len* is of type int. + // So we CANNOT have something like: *return {reply.str, reply.len}*. + return std::string(reply.str, reply.len); +} + +long long parse(ParseTag, redisReply &reply) { + if (!reply::is_integer(reply)) { + throw ProtoError("Expect INTEGER reply"); + } + + return reply.integer; +} + +double parse(ParseTag, redisReply &reply) { + return std::stod(parse(reply)); +} + +bool parse(ParseTag, redisReply &reply) { + auto ret = parse(reply); + + if (ret == 1) { + return true; + } else if (ret == 0) { + return false; + } else { + throw ProtoError("Invalid bool reply: " + std::to_string(ret)); + } +} + +void parse(ParseTag, redisReply &reply) { + if (!reply::is_status(reply)) { + throw ProtoError("Expect STATUS reply"); + } + + if (reply.str == nullptr) { + throw ProtoError("A null status reply"); + } + + static const std::string OK = "OK"; + + // Old version hiredis' *redisReply::len* is of type int. + // So we have to cast it to an unsigned int. + if (static_cast(reply.len) != OK.size() + || OK.compare(0, OK.size(), reply.str, reply.len) != 0) { + throw ProtoError("NOT ok status reply: " + reply::to_status(reply)); + } +} + +void rewrite_set_reply(redisReply &reply) { + if (is_nil(reply)) { + // Failed to set, and make it a FALSE reply. + reply.type = REDIS_REPLY_INTEGER; + reply.integer = 0; + + return; + } + + // Check if it's a "OK" status reply. + reply::parse(reply); + + assert(is_status(reply) && reply.str != nullptr); + + free(reply.str); + + // Make it a TRUE reply. + reply.type = REDIS_REPLY_INTEGER; + reply.integer = 1; +} + +void rewrite_georadius_reply(redisReply &reply) { + if (is_array(reply) && reply.element == nullptr) { + // Make it a nil reply. + reply.type = REDIS_REPLY_NIL; + } +} + +namespace detail { + +bool is_flat_array(redisReply &reply) { + assert(reply::is_array(reply)); + + // Empty array reply. + if (reply.element == nullptr || reply.elements == 0) { + return false; + } + + auto *sub_reply = reply.element[0]; + + // Null element. + if (sub_reply == nullptr) { + return false; + } + + return !reply::is_array(*sub_reply); +} + +} + +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.h new file mode 100644 index 000000000..b309de5bb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/reply.h @@ -0,0 +1,363 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H +#define SEWENEW_REDISPLUSPLUS_REPLY_H + +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +struct ReplyDeleter { + void operator()(redisReply *reply) const { + if (reply != nullptr) { + freeReplyObject(reply); + } + } +}; + +using ReplyUPtr = std::unique_ptr; + +namespace reply { + +template +struct ParseTag {}; + +template +inline T parse(redisReply &reply) { + return parse(ParseTag(), reply); +} + +void parse(ParseTag, redisReply &reply); + +std::string parse(ParseTag, redisReply &reply); + +long long parse(ParseTag, redisReply &reply); + +double parse(ParseTag, redisReply &reply); + +bool parse(ParseTag, redisReply &reply); + +template +Optional parse(ParseTag>, redisReply &reply); + +template +std::pair parse(ParseTag>, redisReply &reply); + +template +std::tuple parse(ParseTag>, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template +long long parse_scan_reply(redisReply &reply, Output output); + +inline bool is_error(redisReply &reply) { + return reply.type == REDIS_REPLY_ERROR; +} + +inline bool is_nil(redisReply &reply) { + return reply.type == REDIS_REPLY_NIL; +} + +inline bool is_string(redisReply &reply) { + return reply.type == REDIS_REPLY_STRING; +} + +inline bool is_status(redisReply &reply) { + return reply.type == REDIS_REPLY_STATUS; +} + +inline bool is_integer(redisReply &reply) { + return reply.type == REDIS_REPLY_INTEGER; +} + +inline bool is_array(redisReply &reply) { + return reply.type == REDIS_REPLY_ARRAY; +} + +std::string to_status(redisReply &reply); + +template +void to_array(redisReply &reply, Output output); + +// Rewrite set reply to bool type +void rewrite_set_reply(redisReply &reply); + +// Rewrite georadius reply to OptionalLongLong type +void rewrite_georadius_reply(redisReply &reply); + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple; + +} + +// Inline implementations. + +namespace reply { + +namespace detail { + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr) { + // Empty array. + return; + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null array element reply"); + } + + *output = parse::type>(*sub_reply); + + ++output; + } +} + +bool is_flat_array(redisReply &reply); + +template +void to_flat_array(redisReply &reply, Output output) { + if (reply.element == nullptr) { + // Empty array. + return; + } + + if (reply.elements % 2 != 0) { + throw ProtoError("Not string pair array reply"); + } + + for (std::size_t idx = 0; idx != reply.elements; idx += 2) { + auto *key_reply = reply.element[idx]; + auto *val_reply = reply.element[idx + 1]; + if (key_reply == nullptr || val_reply == nullptr) { + throw ProtoError("Null string array reply"); + } + + using Pair = typename IterType::type; + using FirstType = typename std::decay::type; + using SecondType = typename std::decay::type; + *output = std::make_pair(parse(*key_reply), + parse(*val_reply)); + + ++output; + } +} + +template +void to_array(std::true_type, redisReply &reply, Output output) { + if (is_flat_array(reply)) { + to_flat_array(reply, output); + } else { + to_array(reply, output); + } +} + +template +void to_array(std::false_type, redisReply &reply, Output output) { + to_array(reply, output); +} + +template +std::tuple parse_tuple(redisReply **reply, std::size_t idx) { + assert(reply != nullptr); + + auto *sub_reply = reply[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null reply"); + } + + return std::make_tuple(parse(*sub_reply)); +} + +template +auto parse_tuple(redisReply **reply, std::size_t idx) -> + typename std::enable_if>::type { + assert(reply != nullptr); + + return std::tuple_cat(parse_tuple(reply, idx), + parse_tuple(reply, idx + 1)); +} + +} + +template +Optional parse(ParseTag>, redisReply &reply) { + if (reply::is_nil(reply)) { + return {}; + } + + return Optional(parse(reply)); +} + +template +std::pair parse(ParseTag>, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != 2) { + throw ProtoError("NOT key-value PAIR reply"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null PAIR reply"); + } + + auto *first = reply.element[0]; + auto *second = reply.element[1]; + if (first == nullptr || second == nullptr) { + throw ProtoError("Null pair reply"); + } + + return std::make_pair(parse::type>(*first), + parse::type>(*second)); +} + +template +std::tuple parse(ParseTag>, redisReply &reply) { + constexpr auto size = sizeof...(Args); + + static_assert(size > 0, "DO NOT support parsing tuple with 0 element"); + + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != size) { + throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null TUPLE reply"); + } + + return detail::parse_tuple(reply.element, 0); +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::back_inserter(container)); + + return container; +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::inserter(container, container.end())); + + return container; +} + +template +long long parse_scan_reply(redisReply &reply, Output output) { + if (reply.elements != 2 || reply.element == nullptr) { + throw ProtoError("Invalid scan reply"); + } + + auto *cursor_reply = reply.element[0]; + auto *data_reply = reply.element[1]; + if (cursor_reply == nullptr || data_reply == nullptr) { + throw ProtoError("Invalid cursor reply or data reply"); + } + + auto cursor_str = reply::parse(*cursor_reply); + auto new_cursor = 0; + try { + new_cursor = std::stoll(cursor_str); + } catch (const std::exception &e) { + throw ProtoError("Invalid cursor reply: " + cursor_str); + } + + reply::to_array(*data_reply, output); + + return new_cursor; +} + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + detail::to_array(typename IsKvPairIter::type(), reply, output); +} + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple { + if (!is_array(reply) || reply.elements != 4) { + throw ProtoError("expect array reply with 4 elements"); + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + if (reply.element[idx] == nullptr) { + throw ProtoError("null array reply"); + } + } + + auto num = parse(*(reply.element[0])); + auto start = parse(*(reply.element[1])); + auto end = parse(*(reply.element[2])); + + auto &entry_reply = *(reply.element[3]); + if (!is_nil(entry_reply)) { + to_array(entry_reply, output); + } + + return std::make_tuple(num, std::move(start), std::move(end)); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.cpp new file mode 100644 index 000000000..a7d88a181 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.cpp @@ -0,0 +1,361 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "sentinel.h" +#include +#include +#include +#include +#include "redis.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class Sentinel::Iterator { +public: + Iterator(std::list &healthy_sentinels, + std::list &broken_sentinels) : + _healthy_sentinels(healthy_sentinels), + _broken_sentinels(broken_sentinels) { + reset(); + } + + Connection& next(); + + void reset(); + +private: + std::list &_healthy_sentinels; + + std::size_t _healthy_size = 0; + + std::list &_broken_sentinels; + + std::size_t _broken_size = 0; +}; + +Connection& Sentinel::Iterator::next() { + while (_healthy_size > 0) { + assert(_healthy_sentinels.size() >= _healthy_size); + + --_healthy_size; + + auto &connection = _healthy_sentinels.front(); + if (connection.broken()) { + _broken_sentinels.push_front(connection.options()); + ++_broken_size; + + _healthy_sentinels.pop_front(); + } else { + _healthy_sentinels.splice(_healthy_sentinels.end(), + _healthy_sentinels, + _healthy_sentinels.begin()); + + return _healthy_sentinels.back(); + } + } + + while (_broken_size > 0) { + assert(_broken_sentinels.size() >= _broken_size); + + --_broken_size; + + try { + const auto &opt = _broken_sentinels.front(); + Connection connection(opt); + _healthy_sentinels.push_back(std::move(connection)); + + _broken_sentinels.pop_front(); + + return _healthy_sentinels.back(); + } catch (const Error &e) { + // Failed to connect to sentinel. + _broken_sentinels.splice(_broken_sentinels.end(), + _broken_sentinels, + _broken_sentinels.begin()); + } + } + + throw StopIterError(); +} + +void Sentinel::Iterator::reset() { + _healthy_size = _healthy_sentinels.size(); + _broken_size = _broken_sentinels.size(); +} + +Sentinel::Sentinel(const SentinelOptions &sentinel_opts) : + _broken_sentinels(_parse_options(sentinel_opts)), + _sentinel_opts(sentinel_opts) { + if (_sentinel_opts.connect_timeout == std::chrono::milliseconds(0) + || _sentinel_opts.socket_timeout == std::chrono::milliseconds(0)) { + throw Error("With sentinel, connection timeout and socket timeout cannot be 0"); + } +} + +Connection Sentinel::master(const std::string &master_name, const ConnectionOptions &opts) { + std::lock_guard lock(_mutex); + + Iterator iter(_healthy_sentinels, _broken_sentinels); + std::size_t retries = 0; + while (true) { + try { + auto &sentinel = iter.next(); + + auto master = _get_master_addr_by_name(sentinel, master_name); + if (!master) { + // Try the next sentinel. + continue; + } + + auto connection = _connect_redis(*master, opts); + if (_get_role(connection) != Role::MASTER) { + // Retry the whole process at most SentinelOptions::max_retry times. + ++retries; + if (retries > _sentinel_opts.max_retry) { + throw Error("Failed to get master from sentinel"); + } + + std::this_thread::sleep_for(_sentinel_opts.retry_interval); + + // Restart the iteration. + iter.reset(); + continue; + } + + return connection; + } catch (const StopIterError &e) { + throw; + } catch (const Error &e) { + continue; + } + } +} + +Connection Sentinel::slave(const std::string &master_name, const ConnectionOptions &opts) { + std::lock_guard lock(_mutex); + + Iterator iter(_healthy_sentinels, _broken_sentinels); + std::size_t retries = 0; + while (true) { + try { + auto &sentinel = iter.next(); + + auto slaves = _get_slave_addr_by_name(sentinel, master_name); + if (slaves.empty()) { + // Try the next sentinel. + continue; + } + + // Normally slaves list is NOT very large, so there won't be a performance problem. + auto slave_iter = std::find(slaves.begin(), + slaves.end(), + Node{opts.host, opts.port}); + if (slave_iter != slaves.end() && slave_iter != slaves.begin()) { + // The given node is still a valid slave. Try it first. + std::swap(*(slaves.begin()), *slave_iter); + } + + for (const auto &slave : slaves) { + try { + auto connection = _connect_redis(slave, opts); + if (_get_role(connection) != Role::SLAVE) { + // Retry the whole process at most SentinelOptions::max_retry times. + ++retries; + if (retries > _sentinel_opts.max_retry) { + throw Error("Failed to get slave from sentinel"); + } + + std::this_thread::sleep_for(_sentinel_opts.retry_interval); + + // Restart the iteration. + iter.reset(); + break; + } + + return connection; + } catch (const Error &e) { + // Try the next slave. + continue; + } + } + } catch (const StopIterError &e) { + throw; + } catch (const Error &e) { + continue; + } + } +} + +Optional Sentinel::_get_master_addr_by_name(Connection &connection, const StringView &name) { + connection.send("SENTINEL GET-MASTER-ADDR-BY-NAME %b", name.data(), name.size()); + + auto reply = connection.recv(); + + assert(reply); + + auto master = reply::parse>>(*reply); + if (!master) { + return {}; + } + + int port = 0; + try { + port = std::stoi(master->second); + } catch (const std::exception &) { + throw ProtoError("Master port is invalid: " + master->second); + } + + return Optional{Node{master->first, port}}; +} + +std::vector Sentinel::_get_slave_addr_by_name(Connection &connection, + const StringView &name) { + try { + connection.send("SENTINEL SLAVES %b", name.data(), name.size()); + + auto reply = connection.recv(); + + assert(reply); + + auto slaves = _parse_slave_info(*reply); + + // Make slave list random. + std::mt19937 gen(std::random_device{}()); + std::shuffle(slaves.begin(), slaves.end(), gen); + + return slaves; + } catch (const ReplyError &e) { + // Unknown master name. + return {}; + } +} + +std::vector Sentinel::_parse_slave_info(redisReply &reply) const { + using SlaveInfo = std::unordered_map; + + auto slaves = reply::parse>(reply); + + std::vector nodes; + for (const auto &slave : slaves) { + auto flags_iter = slave.find("flags"); + auto ip_iter = slave.find("ip"); + auto port_iter = slave.find("port"); + if (flags_iter == slave.end() || ip_iter == slave.end() || port_iter == slave.end()) { + throw ProtoError("Invalid slave info"); + } + + // This slave is down, e.g. 's_down,slave,disconnected' + if (flags_iter->second != "slave") { + continue; + } + + int port = 0; + try { + port = std::stoi(port_iter->second); + } catch (const std::exception &) { + throw ProtoError("Slave port is invalid: " + port_iter->second); + } + + nodes.push_back(Node{ip_iter->second, port}); + } + + return nodes; +} + +Connection Sentinel::_connect_redis(const Node &node, ConnectionOptions opts) { + opts.host = node.host; + opts.port = node.port; + + return Connection(opts); +} + +Role Sentinel::_get_role(Connection &connection) { + connection.send("INFO REPLICATION"); + auto reply = connection.recv(); + + assert(reply); + auto info = reply::parse(*reply); + + auto start = info.find("role:"); + if (start == std::string::npos) { + throw ProtoError("Invalid INFO REPLICATION reply"); + } + start += 5; + auto stop = info.find("\r\n", start); + if (stop == std::string::npos) { + throw ProtoError("Invalid INFO REPLICATION reply"); + } + + auto role = info.substr(start, stop - start); + if (role == "master") { + return Role::MASTER; + } else if (role == "slave") { + return Role::SLAVE; + } else { + throw Error("Invalid role: " + role); + } +} + +std::list Sentinel::_parse_options(const SentinelOptions &opts) const { + std::list options; + for (const auto &node : opts.nodes) { + ConnectionOptions opt; + opt.host = node.first; + opt.port = node.second; + opt.password = opts.password; + opt.keep_alive = opts.keep_alive; + opt.connect_timeout = opts.connect_timeout; + opt.socket_timeout = opts.socket_timeout; + + options.push_back(opt); + } + + return options; +} + +SimpleSentinel::SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role) : + _sentinel(sentinel), + _master_name(master_name), + _role(role) { + if (!_sentinel) { + throw Error("Sentinel cannot be null"); + } + + if (_role != Role::MASTER && _role != Role::SLAVE) { + throw Error("Role must be Role::MASTER or Role::SLAVE"); + } +} + +Connection SimpleSentinel::create(const ConnectionOptions &opts) { + assert(_sentinel); + + if (_role == Role::MASTER) { + return _sentinel->master(_master_name, opts); + } + + assert(_role == Role::SLAVE); + + return _sentinel->slave(_master_name, opts); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.h new file mode 100644 index 000000000..e80d1e56a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/sentinel.h @@ -0,0 +1,138 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H +#define SEWENEW_REDISPLUSPLUS_SENTINEL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "shards.h" +#include "reply.h" + +namespace sw { + +namespace redis { + +struct SentinelOptions { + std::vector> nodes; + + std::string password; + + bool keep_alive = true; + + std::chrono::milliseconds connect_timeout{100}; + + std::chrono::milliseconds socket_timeout{100}; + + std::chrono::milliseconds retry_interval{100}; + + std::size_t max_retry = 2; +}; + +class Sentinel { +public: + explicit Sentinel(const SentinelOptions &sentinel_opts); + + Sentinel(const Sentinel &) = delete; + Sentinel& operator=(const Sentinel &) = delete; + + Sentinel(Sentinel &&) = delete; + Sentinel& operator=(Sentinel &&) = delete; + + ~Sentinel() = default; + +private: + Connection master(const std::string &master_name, const ConnectionOptions &opts); + + Connection slave(const std::string &master_name, const ConnectionOptions &opts); + + class Iterator; + + friend class SimpleSentinel; + + std::list _parse_options(const SentinelOptions &opts) const; + + Optional _get_master_addr_by_name(Connection &connection, const StringView &name); + + std::vector _get_slave_addr_by_name(Connection &connection, const StringView &name); + + Connection _connect_redis(const Node &node, ConnectionOptions opts); + + Role _get_role(Connection &connection); + + std::vector _parse_slave_info(redisReply &reply) const; + + std::list _healthy_sentinels; + + std::list _broken_sentinels; + + SentinelOptions _sentinel_opts; + + std::mutex _mutex; +}; + +class SimpleSentinel { +public: + SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role); + + SimpleSentinel() = default; + + SimpleSentinel(const SimpleSentinel &) = default; + SimpleSentinel& operator=(const SimpleSentinel &) = default; + + SimpleSentinel(SimpleSentinel &&) = default; + SimpleSentinel& operator=(SimpleSentinel &&) = default; + + ~SimpleSentinel() = default; + + explicit operator bool() const { + return bool(_sentinel); + } + + Connection create(const ConnectionOptions &opts); + +private: + std::shared_ptr _sentinel; + + std::string _master_name; + + Role _role = Role::MASTER; +}; + +class StopIterError : public Error { +public: + StopIterError() : Error("StopIterError") {} + + StopIterError(const StopIterError &) = default; + StopIterError& operator=(const StopIterError &) = default; + + StopIterError(StopIterError &&) = default; + StopIterError& operator=(StopIterError &&) = default; + + virtual ~StopIterError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.cpp new file mode 100644 index 000000000..e06d2a732 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.cpp @@ -0,0 +1,50 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "shards.h" + +namespace sw { + +namespace redis { + +RedirectionError::RedirectionError(const std::string &msg): ReplyError(msg) { + std::tie(_slot, _node) = _parse_error(msg); +} + +std::pair RedirectionError::_parse_error(const std::string &msg) const { + // "slot ip:port" + auto space_pos = msg.find(" "); + auto colon_pos = msg.find(":"); + if (space_pos == std::string::npos + || colon_pos == std::string::npos + || colon_pos < space_pos) { + throw ProtoError("Invalid ASK error message: " + msg); + } + + try { + auto slot = std::stoull(msg.substr(0, space_pos)); + auto host = msg.substr(space_pos + 1, colon_pos - space_pos - 1); + auto port = std::stoi(msg.substr(colon_pos + 1)); + + return {slot, {host, port}}; + } catch (const std::exception &e) { + throw ProtoError("Invalid ASK error message: " + msg); + } +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.h new file mode 100644 index 000000000..a0593acbc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_H + +#include +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +using Slot = std::size_t; + +struct SlotRange { + Slot min; + Slot max; +}; + +inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) { + return lhs.max < rhs.max; +} + +struct Node { + std::string host; + int port; +}; + +inline bool operator==(const Node &lhs, const Node &rhs) { + return lhs.host == rhs.host && lhs.port == rhs.port; +} + +struct NodeHash { + std::size_t operator()(const Node &node) const noexcept { + auto host_hash = std::hash{}(node.host); + auto port_hash = std::hash{}(node.port); + return host_hash ^ (port_hash << 1); + } +}; + +using Shards = std::map; + +class RedirectionError : public ReplyError { +public: + RedirectionError(const std::string &msg); + + RedirectionError(const RedirectionError &) = default; + RedirectionError& operator=(const RedirectionError &) = default; + + RedirectionError(RedirectionError &&) = default; + RedirectionError& operator=(RedirectionError &&) = default; + + virtual ~RedirectionError() = default; + + Slot slot() const { + return _slot; + } + + const Node& node() const { + return _node; + } + +private: + std::pair _parse_error(const std::string &msg) const; + + Slot _slot = 0; + Node _node; +}; + +class MovedError : public RedirectionError { +public: + explicit MovedError(const std::string &msg) : RedirectionError(msg) {} + + MovedError(const MovedError &) = default; + MovedError& operator=(const MovedError &) = default; + + MovedError(MovedError &&) = default; + MovedError& operator=(MovedError &&) = default; + + virtual ~MovedError() = default; +}; + +class AskError : public RedirectionError { +public: + explicit AskError(const std::string &msg) : RedirectionError(msg) {} + + AskError(const AskError &) = default; + AskError& operator=(const AskError &) = default; + + AskError(AskError &&) = default; + AskError& operator=(AskError &&) = default; + + virtual ~AskError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.cpp new file mode 100644 index 000000000..436cc265c --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.cpp @@ -0,0 +1,319 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "shards_pool.h" +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +const std::size_t ShardsPool::SHARDS; + +ShardsPool::ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts) : + _pool_opts(pool_opts), + _connection_opts(connection_opts) { + if (_connection_opts.type != ConnectionType::TCP) { + throw Error("Only support TCP connection for Redis Cluster"); + } + + Connection connection(_connection_opts); + + _shards = _cluster_slots(connection); + + _init_pool(_shards); +} + +ShardsPool::ShardsPool(ShardsPool &&that) { + std::lock_guard lock(that._mutex); + + _move(std::move(that)); +} + +ShardsPool& ShardsPool::operator=(ShardsPool &&that) { + if (this != &that) { + std::lock(_mutex, that._mutex); + std::lock_guard lock_this(_mutex, std::adopt_lock); + std::lock_guard lock_that(that._mutex, std::adopt_lock); + + _move(std::move(that)); + } + + return *this; +} + +GuardedConnection ShardsPool::fetch(const StringView &key) { + auto slot = _slot(key); + + return _fetch(slot); +} + +GuardedConnection ShardsPool::fetch() { + auto slot = _slot(); + + return _fetch(slot); +} + +GuardedConnection ShardsPool::fetch(const Node &node) { + std::lock_guard lock(_mutex); + + auto iter = _pools.find(node); + if (iter == _pools.end()) { + // Node doesn't exist, and it should be a newly created node. + // So add a new connection pool. + iter = _add_node(node); + } + + assert(iter != _pools.end()); + + return GuardedConnection(iter->second); +} + +void ShardsPool::update() { + // My might send command to a removed node. + // Try at most 3 times. + for (auto idx = 0; idx < 3; ++idx) { + try { + // Randomly pick a connection. + auto guarded_connection = fetch(); + auto shards = _cluster_slots(guarded_connection.connection()); + + std::unordered_set nodes; + for (const auto &shard : shards) { + nodes.insert(shard.second); + } + + std::lock_guard lock(_mutex); + + // TODO: If shards is unchanged, no need to update, and return immediately. + + _shards = std::move(shards); + + // Remove non-existent nodes. + for (auto iter = _pools.begin(); iter != _pools.end(); ) { + if (nodes.find(iter->first) == nodes.end()) { + // Node has been removed. + _pools.erase(iter++); + } else { + ++iter; + } + } + + // Add connection pool for new nodes. + // In fact, connections will be created lazily. + for (const auto &node : nodes) { + if (_pools.find(node) == _pools.end()) { + _add_node(node); + } + } + + // Update successfully. + return; + } catch (const Error &) { + // continue; + } + } + + throw Error("Failed to update shards info"); +} + +ConnectionOptions ShardsPool::connection_options(const StringView &key) { + auto slot = _slot(key); + + return _connection_options(slot); +} + +ConnectionOptions ShardsPool::connection_options() { + auto slot = _slot(); + + return _connection_options(slot); +} +void ShardsPool::_move(ShardsPool &&that) { + _pool_opts = that._pool_opts; + _connection_opts = that._connection_opts; + _shards = std::move(that._shards); + _pools = std::move(that._pools); +} + +void ShardsPool::_init_pool(const Shards &shards) { + for (const auto &shard : shards) { + _add_node(shard.second); + } +} + +Shards ShardsPool::_cluster_slots(Connection &connection) const { + auto reply = _cluster_slots_command(connection); + + assert(reply); + + return _parse_reply(*reply); +} + +ReplyUPtr ShardsPool::_cluster_slots_command(Connection &connection) const { + connection.send("CLUSTER SLOTS"); + + return connection.recv(); +} + +Shards ShardsPool::_parse_reply(redisReply &reply) const { + if (!reply::is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr || reply.elements == 0) { + throw Error("Empty slots"); + } + + Shards shards; + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null slot info"); + } + + shards.emplace(_parse_slot_info(*sub_reply)); + } + + return shards; +} + +std::pair ShardsPool::_parse_slot_info(redisReply &reply) const { + if (reply.elements < 3 || reply.element == nullptr) { + throw ProtoError("Invalid slot info"); + } + + // Min slot id + auto *min_slot_reply = reply.element[0]; + if (min_slot_reply == nullptr) { + throw ProtoError("Invalid min slot"); + } + std::size_t min_slot = reply::parse(*min_slot_reply); + + // Max slot id + auto *max_slot_reply = reply.element[1]; + if (max_slot_reply == nullptr) { + throw ProtoError("Invalid max slot"); + } + std::size_t max_slot = reply::parse(*max_slot_reply); + + if (min_slot > max_slot) { + throw ProtoError("Invalid slot range"); + } + + // Master node info + auto *node_reply = reply.element[2]; + if (node_reply == nullptr + || !reply::is_array(*node_reply) + || node_reply->element == nullptr + || node_reply->elements < 2) { + throw ProtoError("Invalid node info"); + } + + auto master_host = reply::parse(*(node_reply->element[0])); + int master_port = reply::parse(*(node_reply->element[1])); + + // By now, we ignore node id and other replicas' info. + + return {SlotRange{min_slot, max_slot}, Node{master_host, master_port}}; +} + +Slot ShardsPool::_slot(const StringView &key) const { + // The following code is copied from: https://redis.io/topics/cluster-spec + // And I did some minor changes. + + const auto *k = key.data(); + auto keylen = key.size(); + + // start-end indexes of { and }. + std::size_t s = 0; + std::size_t e = 0; + + // Search the first occurrence of '{'. + for (s = 0; s < keylen; s++) + if (k[s] == '{') break; + + // No '{' ? Hash the whole key. This is the base case. + if (s == keylen) return crc16(k, keylen) & SHARDS; + + // '{' found? Check if we have the corresponding '}'. + for (e = s + 1; e < keylen; e++) + if (k[e] == '}') break; + + // No '}' or nothing between {} ? Hash the whole key. + if (e == keylen || e == s + 1) return crc16(k, keylen) & SHARDS; + + // If we are here there is both a { and a } on its right. Hash + // what is in the middle between { and }. + return crc16(k + s + 1, e - s - 1) & SHARDS; +} + +Slot ShardsPool::_slot() const { + static thread_local std::default_random_engine engine; + + std::uniform_int_distribution uniform_dist(0, SHARDS); + + return uniform_dist(engine); +} + +ConnectionPoolSPtr& ShardsPool::_get_pool(Slot slot) { + auto shards_iter = _shards.lower_bound(SlotRange{slot, slot}); + if (shards_iter == _shards.end() || slot < shards_iter->first.min) { + throw Error("Slot is out of range: " + std::to_string(slot)); + } + + const auto &node = shards_iter->second; + + auto node_iter = _pools.find(node); + if (node_iter == _pools.end()) { + throw Error("Slot is NOT covered: " + std::to_string(slot)); + } + + return node_iter->second; +} + +GuardedConnection ShardsPool::_fetch(Slot slot) { + std::lock_guard lock(_mutex); + + auto &pool = _get_pool(slot); + + assert(pool); + + return GuardedConnection(pool); +} + +ConnectionOptions ShardsPool::_connection_options(Slot slot) { + std::lock_guard lock(_mutex); + + auto &pool = _get_pool(slot); + + assert(pool); + + return pool->connection_options(); +} + +auto ShardsPool::_add_node(const Node &node) -> NodeMap::iterator { + auto opts = _connection_opts; + opts.host = node.host; + opts.port = node.port; + + return _pools.emplace(node, std::make_shared(_pool_opts, opts)).first; +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.h new file mode 100644 index 000000000..1184806e9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/shards_pool.h @@ -0,0 +1,137 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H + +#include +#include +#include +#include +#include +#include "reply.h" +#include "connection_pool.h" +#include "shards.h" + +namespace sw { + +namespace redis { + +using ConnectionPoolSPtr = std::shared_ptr; + +class GuardedConnection { +public: + GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool), + _connection(_pool->fetch()) { + assert(!_connection.broken()); + } + + GuardedConnection(const GuardedConnection &) = delete; + GuardedConnection& operator=(const GuardedConnection &) = delete; + + GuardedConnection(GuardedConnection &&) = default; + GuardedConnection& operator=(GuardedConnection &&) = default; + + ~GuardedConnection() { + _pool->release(std::move(_connection)); + } + + Connection& connection() { + return _connection; + } + +private: + ConnectionPoolSPtr _pool; + Connection _connection; +}; + +class ShardsPool { +public: + ShardsPool() = default; + + ShardsPool(const ShardsPool &that) = delete; + ShardsPool& operator=(const ShardsPool &that) = delete; + + ShardsPool(ShardsPool &&that); + ShardsPool& operator=(ShardsPool &&that); + + ~ShardsPool() = default; + + ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + // Fetch a connection by key. + GuardedConnection fetch(const StringView &key); + + // Randomly pick a connection. + GuardedConnection fetch(); + + // Fetch a connection by node. + GuardedConnection fetch(const Node &node); + + void update(); + + ConnectionOptions connection_options(const StringView &key); + + ConnectionOptions connection_options(); + +private: + void _move(ShardsPool &&that); + + void _init_pool(const Shards &shards); + + Shards _cluster_slots(Connection &connection) const; + + ReplyUPtr _cluster_slots_command(Connection &connection) const; + + Shards _parse_reply(redisReply &reply) const; + + std::pair _parse_slot_info(redisReply &reply) const; + + // Get slot by key. + std::size_t _slot(const StringView &key) const; + + // Randomly pick a slot. + std::size_t _slot() const; + + ConnectionPoolSPtr& _get_pool(Slot slot); + + GuardedConnection _fetch(Slot slot); + + ConnectionOptions _connection_options(Slot slot); + + using NodeMap = std::unordered_map; + + NodeMap::iterator _add_node(const Node &node); + + ConnectionPoolOptions _pool_opts; + + ConnectionOptions _connection_opts; + + Shards _shards; + + NodeMap _pools; + + std::mutex _mutex; + + static const std::size_t SHARDS = 16383; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.cpp new file mode 100644 index 000000000..b699b02f0 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.cpp @@ -0,0 +1,222 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "subscriber.h" +#include + +namespace sw { + +namespace redis { + +const Subscriber::TypeIndex Subscriber::_msg_type_index = { + {"message", MsgType::MESSAGE}, + {"pmessage", MsgType::PMESSAGE}, + {"subscribe", MsgType::SUBSCRIBE}, + {"unsubscribe", MsgType::UNSUBSCRIBE}, + {"psubscribe", MsgType::PSUBSCRIBE}, + {"punsubscribe", MsgType::PUNSUBSCRIBE} +}; + +Subscriber::Subscriber(Connection connection) : _connection(std::move(connection)) {} + +void Subscriber::subscribe(const StringView &channel) { + _check_connection(); + + // TODO: cmd::subscribe DOES NOT send the subscribe message to Redis. + // In fact, it puts the command to network buffer. + // So we need a queue to record these sub or unsub commands, and + // ensure that before stopping the subscriber, all these commands + // have really been sent to Redis. + cmd::subscribe(_connection, channel); +} + +void Subscriber::unsubscribe() { + _check_connection(); + + cmd::unsubscribe(_connection); +} + +void Subscriber::unsubscribe(const StringView &channel) { + _check_connection(); + + cmd::unsubscribe(_connection, channel); +} + +void Subscriber::psubscribe(const StringView &pattern) { + _check_connection(); + + cmd::psubscribe(_connection, pattern); +} + +void Subscriber::punsubscribe() { + _check_connection(); + + cmd::punsubscribe(_connection); +} + +void Subscriber::punsubscribe(const StringView &pattern) { + _check_connection(); + + cmd::punsubscribe(_connection, pattern); +} + +void Subscriber::consume() { + _check_connection(); + + ReplyUPtr reply; + try { + reply = _connection.recv(); + } catch (const TimeoutError &) { + _connection.reset(); + throw; + } + + assert(reply); + + if (!reply::is_array(*reply) || reply->elements < 1 || reply->element == nullptr) { + throw ProtoError("Invalid subscribe message"); + } + + auto type = _msg_type(reply->element[0]); + switch (type) { + case MsgType::MESSAGE: + _handle_message(*reply); + break; + + case MsgType::PMESSAGE: + _handle_pmessage(*reply); + break; + + case MsgType::SUBSCRIBE: + case MsgType::UNSUBSCRIBE: + case MsgType::PSUBSCRIBE: + case MsgType::PUNSUBSCRIBE: + _handle_meta(type, *reply); + break; + + default: + assert(false); + } +} + +Subscriber::MsgType Subscriber::_msg_type(redisReply *reply) const { + if (reply == nullptr) { + throw ProtoError("Null type reply."); + } + + auto type = reply::parse(*reply); + + auto iter = _msg_type_index.find(type); + if (iter == _msg_type_index.end()) { + throw ProtoError("Invalid message type."); + } + + return iter->second; +} + +void Subscriber::_check_connection() { + if (_connection.broken()) { + throw Error("Connection is broken"); + } +} + +void Subscriber::_handle_message(redisReply &reply) { + if (_msg_callback == nullptr) { + return; + } + + if (reply.elements != 3) { + throw ProtoError("Expect 3 sub replies"); + } + + assert(reply.element != nullptr); + + auto *channel_reply = reply.element[1]; + if (channel_reply == nullptr) { + throw ProtoError("Null channel reply"); + } + auto channel = reply::parse(*channel_reply); + + auto *msg_reply = reply.element[2]; + if (msg_reply == nullptr) { + throw ProtoError("Null message reply"); + } + auto msg = reply::parse(*msg_reply); + + _msg_callback(std::move(channel), std::move(msg)); +} + +void Subscriber::_handle_pmessage(redisReply &reply) { + if (_pmsg_callback == nullptr) { + return; + } + + if (reply.elements != 4) { + throw ProtoError("Expect 4 sub replies"); + } + + assert(reply.element != nullptr); + + auto *pattern_reply = reply.element[1]; + if (pattern_reply == nullptr) { + throw ProtoError("Null pattern reply"); + } + auto pattern = reply::parse(*pattern_reply); + + auto *channel_reply = reply.element[2]; + if (channel_reply == nullptr) { + throw ProtoError("Null channel reply"); + } + auto channel = reply::parse(*channel_reply); + + auto *msg_reply = reply.element[3]; + if (msg_reply == nullptr) { + throw ProtoError("Null message reply"); + } + auto msg = reply::parse(*msg_reply); + + _pmsg_callback(std::move(pattern), std::move(channel), std::move(msg)); +} + +void Subscriber::_handle_meta(MsgType type, redisReply &reply) { + if (_meta_callback == nullptr) { + return; + } + + if (reply.elements != 3) { + throw ProtoError("Expect 3 sub replies"); + } + + assert(reply.element != nullptr); + + auto *channel_reply = reply.element[1]; + if (channel_reply == nullptr) { + throw ProtoError("Null channel reply"); + } + auto channel = reply::parse(*channel_reply); + + auto *num_reply = reply.element[2]; + if (num_reply == nullptr) { + throw ProtoError("Null num reply"); + } + auto num = reply::parse(*num_reply); + + _meta_callback(type, std::move(channel), num); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.h new file mode 100644 index 000000000..8b7c5cfb4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/subscriber.h @@ -0,0 +1,231 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H +#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H + +#include +#include +#include +#include "connection.h" +#include "reply.h" +#include "command.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +// @NOTE: Subscriber is NOT thread-safe. +// Subscriber uses callbacks to handle messages. There are 6 kinds of messages: +// 1) MESSAGE: message sent to a channel. +// 2) PMESSAGE: message sent to channels of a given pattern. +// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel. +// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel. +// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern. +// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern. +// +// Use Subscriber::on_message(MsgCallback) to set the callback function for message of +// *MESSAGE* type, and the callback interface is: +// void (std::string channel, std::string msg) +// +// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of +// *PMESSAGE* type, and the callback interface is: +// void (std::string pattern, std::string channel, std::string msg) +// +// Messages of other types are called *META MESSAGE*, they have the same callback interface. +// Use Subscriber::on_meta(MetaCallback) to set the callback function: +// void (Subscriber::MsgType type, OptionalString channel, long long num) +// +// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to +// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all +// channels/patterns, *channel* will be null. So the second parameter of meta callback +// is of type *OptionalString*. +// +// All these callback interfaces pass std::string by value, and you can take their ownership +// (i.e. std::move) safely. +// +// If you don't set callback for a specific kind of message, Subscriber::consume() will +// receive the message, and ignore it, i.e. no callback will be called. +class Subscriber { +public: + Subscriber(const Subscriber &) = delete; + Subscriber& operator=(const Subscriber &) = delete; + + Subscriber(Subscriber &&) = default; + Subscriber& operator=(Subscriber &&) = default; + + ~Subscriber() = default; + + enum class MsgType { + SUBSCRIBE, + UNSUBSCRIBE, + PSUBSCRIBE, + PUNSUBSCRIBE, + MESSAGE, + PMESSAGE + }; + + template + void on_message(MsgCb msg_callback); + + template + void on_pmessage(PMsgCb pmsg_callback); + + template + void on_meta(MetaCb meta_callback); + + void subscribe(const StringView &channel); + + template + void subscribe(Input first, Input last); + + template + void subscribe(std::initializer_list channels) { + subscribe(channels.begin(), channels.end()); + } + + void unsubscribe(); + + void unsubscribe(const StringView &channel); + + template + void unsubscribe(Input first, Input last); + + template + void unsubscribe(std::initializer_list channels) { + unsubscribe(channels.begin(), channels.end()); + } + + void psubscribe(const StringView &pattern); + + template + void psubscribe(Input first, Input last); + + template + void psubscribe(std::initializer_list channels) { + psubscribe(channels.begin(), channels.end()); + } + + void punsubscribe(); + + void punsubscribe(const StringView &channel); + + template + void punsubscribe(Input first, Input last); + + template + void punsubscribe(std::initializer_list channels) { + punsubscribe(channels.begin(), channels.end()); + } + + void consume(); + +private: + friend class Redis; + + friend class RedisCluster; + + explicit Subscriber(Connection connection); + + MsgType _msg_type(redisReply *reply) const; + + void _check_connection(); + + void _handle_message(redisReply &reply); + + void _handle_pmessage(redisReply &reply); + + void _handle_meta(MsgType type, redisReply &reply); + + using MsgCallback = std::function; + + using PatternMsgCallback = std::function; + + using MetaCallback = std::function; + + using TypeIndex = std::unordered_map; + static const TypeIndex _msg_type_index; + + Connection _connection; + + MsgCallback _msg_callback = nullptr; + + PatternMsgCallback _pmsg_callback = nullptr; + + MetaCallback _meta_callback = nullptr; +}; + +template +void Subscriber::on_message(MsgCb msg_callback) { + _msg_callback = msg_callback; +} + +template +void Subscriber::on_pmessage(PMsgCb pmsg_callback) { + _pmsg_callback = pmsg_callback; +} + +template +void Subscriber::on_meta(MetaCb meta_callback) { + _meta_callback = meta_callback; +} + +template +void Subscriber::subscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::subscribe_range(_connection, first, last); +} + +template +void Subscriber::unsubscribe(Input first, Input last) { + _check_connection(); + + cmd::unsubscribe_range(_connection, first, last); +} + +template +void Subscriber::psubscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::psubscribe_range(_connection, first, last); +} + +template +void Subscriber::punsubscribe(Input first, Input last) { + _check_connection(); + + cmd::punsubscribe_range(_connection, first, last); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.cpp b/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.cpp new file mode 100644 index 000000000..faa1bd178 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.cpp @@ -0,0 +1,123 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include "transaction.h" +#include "command.h" + +namespace sw { + +namespace redis { + +std::vector TransactionImpl::exec(Connection &connection, std::size_t cmd_num) { + _close_transaction(); + + _get_queued_replies(connection, cmd_num); + + return _exec(connection); +} + +void TransactionImpl::discard(Connection &connection, std::size_t cmd_num) { + _close_transaction(); + + _get_queued_replies(connection, cmd_num); + + _discard(connection); +} + +void TransactionImpl::_open_transaction(Connection &connection) { + assert(!_in_transaction); + + cmd::multi(connection); + auto reply = connection.recv(); + auto status = reply::to_status(*reply); + if (status != "OK") { + throw Error("Failed to open transaction: " + status); + } + + _in_transaction = true; +} + +void TransactionImpl::_close_transaction() { + if (!_in_transaction) { + throw Error("No command in transaction"); + } + + _in_transaction = false; +} + +void TransactionImpl::_get_queued_reply(Connection &connection) { + auto reply = connection.recv(); + auto status = reply::to_status(*reply); + if (status != "QUEUED") { + throw Error("Invalid QUEUED reply: " + status); + } +} + +void TransactionImpl::_get_queued_replies(Connection &connection, std::size_t cmd_num) { + if (_piped) { + // Get all QUEUED reply + while (cmd_num > 0) { + _get_queued_reply(connection); + + --cmd_num; + } + } +} + +std::vector TransactionImpl::_exec(Connection &connection) { + cmd::exec(connection); + + auto reply = connection.recv(); + + if (reply::is_nil(*reply)) { + // Execution has been aborted, i.e. watched key has been modified. + throw WatchError(); + } + + if (!reply::is_array(*reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply->element == nullptr || reply->elements == 0) { + // Since we don't allow EXEC without any command, this ARRAY reply + // should NOT be null or empty. + throw ProtoError("Null ARRAY reply"); + } + + std::vector replies; + for (std::size_t idx = 0; idx != reply->elements; ++idx) { + auto *sub_reply = reply->element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null sub reply"); + } + + auto r = ReplyUPtr(sub_reply); + reply->element[idx] = nullptr; + replies.push_back(std::move(r)); + } + + return replies; +} + +void TransactionImpl::_discard(Connection &connection) { + cmd::discard(connection); + auto reply = connection.recv(); + reply::parse(*reply); +} + +} + +} diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.h new file mode 100644 index 000000000..f19f24889 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/transaction.h @@ -0,0 +1,77 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H +#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H + +#include +#include +#include "connection.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class TransactionImpl { +public: + explicit TransactionImpl(bool piped) : _piped(piped) {} + + template + void command(Connection &connection, Cmd cmd, Args &&...args); + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t cmd_num); + +private: + void _open_transaction(Connection &connection); + + void _close_transaction(); + + void _get_queued_reply(Connection &connection); + + void _get_queued_replies(Connection &connection, std::size_t cmd_num); + + std::vector _exec(Connection &connection); + + void _discard(Connection &connection); + + bool _in_transaction = false; + + bool _piped; +}; + +template +void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + if (!_in_transaction) { + _open_transaction(connection); + } + + cmd(connection, std::forward(args)...); + + if (!_piped) { + _get_queued_reply(connection); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H diff --git a/ext/redis-plus-plus-1.1.1/src/sw/redis++/utils.h b/ext/redis-plus-plus-1.1.1/src/sw/redis++/utils.h new file mode 100644 index 000000000..e29e64e14 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/src/sw/redis++/utils.h @@ -0,0 +1,269 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H +#define SEWENEW_REDISPLUSPLUS_UTILS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +// By now, not all compilers support std::string_view, +// so we make our own implementation. +class StringView { +public: + constexpr StringView() noexcept = default; + + constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {} + + StringView(const char *data) : _data(data), _size(std::strlen(data)) {} + + StringView(const std::string &str) : _data(str.data()), _size(str.size()) {} + + constexpr StringView(const StringView &) noexcept = default; + + StringView& operator=(const StringView &) noexcept = default; + + constexpr const char* data() const noexcept { + return _data; + } + + constexpr std::size_t size() const noexcept { + return _size; + } + +private: + const char *_data = nullptr; + std::size_t _size = 0; +}; + +template +class Optional { +public: + Optional() = default; + + Optional(const Optional &) = default; + Optional& operator=(const Optional &) = default; + + Optional(Optional &&) = default; + Optional& operator=(Optional &&) = default; + + ~Optional() = default; + + template + explicit Optional(Args &&...args) : _value(true, T(std::forward(args)...)) {} + + explicit operator bool() const { + return _value.first; + } + + T& value() { + return _value.second; + } + + const T& value() const { + return _value.second; + } + + T* operator->() { + return &(_value.second); + } + + const T* operator->() const { + return &(_value.second); + } + + T& operator*() { + return _value.second; + } + + const T& operator*() const { + return _value.second; + } + +private: + std::pair _value; +}; + +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; + +template +struct IsKvPair : std::false_type {}; + +template +struct IsKvPair> : std::true_type {}; + +template +using Void = void; + +template > +struct IsInserter : std::false_type {}; + +template +//struct IsInserter> : std::true_type {}; +struct IsInserter::value>::type> + : std::true_type {}; + +template > +struct IterType { + using type = typename std::iterator_traits::value_type; +}; + +template +//struct IterType> { +struct IterType::value>::type> { + typename std::enable_if::value>::type> { + using type = typename std::decay::type; +}; + +template > +struct IsIter : std::false_type {}; + +template +struct IsIter::value>::type> : std::true_type {}; + +template +//struct IsIter::iterator_category>> +struct IsIter::value_type>::value>::type> + : std::integral_constant::value> {}; + +template +struct IsKvPairIter : IsKvPair::type> {}; + +template +struct TupleWithType : std::false_type {}; + +template +struct TupleWithType> : std::false_type {}; + +template +struct TupleWithType> : TupleWithType> {}; + +template +struct TupleWithType> : std::true_type {}; + +template +struct IndexSequence {}; + +template +struct MakeIndexSequence : MakeIndexSequence {}; + +template +struct MakeIndexSequence<0, Is...> : IndexSequence {}; + +// NthType and NthValue are taken from +// https://stackoverflow.com/questions/14261183 +template +struct NthType {}; + +template +struct NthType<0, Arg, Args...> { + using type = Arg; +}; + +template +struct NthType { + using type = typename NthType::type; +}; + +template +struct LastType { + using type = typename NthType::type; +}; + +struct InvalidLastType {}; + +template <> +struct LastType<> { + using type = InvalidLastType; +}; + +template +auto NthValue(Arg &&arg, Args &&...) + -> typename std::enable_if<(I == 0), decltype(std::forward(arg))>::type { + return std::forward(arg); +} + +template +auto NthValue(Arg &&, Args &&...args) + -> typename std::enable_if<(I > 0), + decltype(std::forward::type>( + std::declval::type>()))>::type { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template +auto LastValue(Args &&...args) + -> decltype(std::forward::type>( + std::declval::type>())) { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template > +struct HasPushBack : std::false_type {}; + +template +struct HasPushBack().push_back(std::declval()) + )>::value>::type> : std::true_type {}; + +template > +struct HasInsert : std::false_type {}; + +template +struct HasInsert().insert(std::declval(), + std::declval())), + typename T::iterator>::value>::type> : std::true_type {}; + +template +struct IsSequenceContainer + : std::integral_constant::value + && !std::is_same::type, std::string>::value> {}; + +template +struct IsAssociativeContainer + : std::integral_constant::value && !HasPushBack::value> {}; + +uint16_t crc16(const char *buf, int len); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H diff --git a/ext/redis-plus-plus-1.1.1/test/CMakeLists.txt b/ext/redis-plus-plus-1.1.1/test/CMakeLists.txt new file mode 100644 index 000000000..861e6ff88 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/CMakeLists.txt @@ -0,0 +1,33 @@ +project(test_redis++) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + cmake_minimum_required(VERSION 3.0.0) +else() + cmake_minimum_required(VERSION 2.8.0) +endif() + +set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src/sw/redis++) + +file(GLOB PROJECT_SOURCE_FILES "${PROJECT_SOURCE_DIR}/*.cpp") + +add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_FILES}) + +# hiredis dependency +find_path(HIREDIS_HEADER hiredis) +target_include_directories(${PROJECT_NAME} PUBLIC ${HIREDIS_HEADER}) + +find_library(HIREDIS_STATIC_LIB libhiredis.a) +target_link_libraries(${PROJECT_NAME} ${HIREDIS_STATIC_LIB}) + +# redis++ dependency +target_include_directories(${PROJECT_NAME} PUBLIC ../src) +set(REDIS_PLUS_PLUS_LIB ${CMAKE_CURRENT_BINARY_DIR}/../lib/libredis++.a) + +## solaris socket dependency +IF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)" ) + target_link_libraries(${PROJECT_NAME} -lsocket) +ENDIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)" ) + +find_package(Threads REQUIRED) + +target_link_libraries(${PROJECT_NAME} ${REDIS_PLUS_PLUS_LIB} ${CMAKE_THREAD_LIBS_INIT}) diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.h new file mode 100644 index 000000000..309bc6863 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.h @@ -0,0 +1,83 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +struct BenchmarkOptions { + std::size_t pool_size = 5; + std::size_t thread_num = 10; + std::size_t total_request_num = 100000; + std::size_t key_len = 10; + std::size_t val_len = 10; +}; + +template +class BenchmarkTest { +public: + BenchmarkTest(const BenchmarkOptions &opts, RedisInstance &instance); + + ~BenchmarkTest() { + _cleanup(); + } + + void run(); + +private: + template + void _run(const std::string &title, Func &&func); + + template + std::size_t _run(Func &&func, std::size_t request_num); + + void _test_get(); + + std::vector _gen_keys() const; + + std::string _gen_value() const; + + void _cleanup(); + + const std::string& _key(std::size_t idx) const { + return _keys[idx % _keys.size()]; + } + + BenchmarkOptions _opts; + + RedisInstance &_redis; + + std::vector _keys; + + std::string _value; +}; + +} + +} + +} + +#include "benchmark_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.hpp new file mode 100644 index 000000000..eaf50ec50 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/benchmark_test.hpp @@ -0,0 +1,178 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +BenchmarkTest::BenchmarkTest(const BenchmarkOptions &opts, + RedisInstance &instance) : _opts(opts), _redis(instance) { + REDIS_ASSERT(_opts.pool_size > 0 + && _opts.thread_num > 0 + && _opts.total_request_num > 0 + && _opts.key_len > 0 + && _opts.val_len > 0, + "Invalid benchmark test options."); + + _keys = _gen_keys(); + _value = _gen_value(); +} + +template +void BenchmarkTest::run() { + _cleanup(); + + _run("SET key value", [this](std::size_t idx) { this->_redis.set(this->_key(idx), _value); }); + + _run("GET key", [this](std::size_t idx) { + auto res = this->_redis.get(this->_key(idx)); + (void)res; + }); + + _cleanup(); + + _run("LPUSH key value", [this](std::size_t idx) { + this->_redis.lpush(this->_key(idx), _value); + }); + + _run("LRANGE key 0 10", [this](std::size_t idx) { + std::vector res; + res.reserve(10); + this->_redis.lrange(this->_key(idx), 0, 10, std::back_inserter(res)); + }); + + _run("LPOP key", [this](std::size_t idx) { + auto res = this->_redis.lpop(this->_key(idx)); + (void)res; + }); + + _cleanup(); + + _run("INCR key", [this](std::size_t idx) { + auto num = this->_redis.incr(this->_key(idx)); + (void)num; + }); + + _cleanup(); + + _run("SADD key member", [this](std::size_t idx) { + auto num = this->_redis.sadd(this->_key(idx), _value); + (void)num; + }); + + _run("SPOP key", [this](std::size_t idx) { + auto res = this->_redis.spop(this->_key(idx)); + (void)res; + }); + + _cleanup(); +} + +template +template +void BenchmarkTest::_run(const std::string &title, Func &&func) { + auto thread_num = _opts.thread_num; + auto requests_per_thread = _opts.total_request_num / thread_num; + auto total_request_num = requests_per_thread * thread_num; + std::vector> res; + res.reserve(thread_num); + res.push_back(std::async(std::launch::async, + [this](Func &&func, std::size_t request_num) { + return this->_run(std::forward(func), request_num); + }, + std::forward(func), + requests_per_thread)); + + auto total_in_msec = 0; + for (auto &fut : res) { + total_in_msec += fut.get(); + } + + auto total_in_sec = total_in_msec * 1.0 / 1000; + + auto avg = total_in_msec * 1.0 / total_request_num; + + auto ops = static_cast(1000 / avg); + + std::cout << "-----" << title << "-----" << std::endl; + std::cout << total_request_num << " requests cost " << total_in_sec << " seconds" << std::endl; + std::cout << ops << " requests per second" << std::endl; +} + +template +template +std::size_t BenchmarkTest::_run(Func &&func, std::size_t request_num) { + auto start = std::chrono::steady_clock::now(); + + for (auto idx = 0U; idx != request_num; ++idx) { + func(idx); + } + + auto stop = std::chrono::steady_clock::now(); + + return std::chrono::duration_cast(stop - start).count(); +} + +template +std::vector BenchmarkTest::_gen_keys() const { + const auto KEY_NUM = 100; + std::vector res; + res.reserve(KEY_NUM); + std::default_random_engine engine(std::random_device{}()); + std::uniform_int_distribution uniform_dist(0, 255); + for (auto i = 0; i != KEY_NUM; ++i) { + std::string str; + str.reserve(_opts.key_len); + for (std::size_t j = 0; j != _opts.key_len; ++j) { + str.push_back(static_cast(uniform_dist(engine))); + } + res.push_back(str); + } + + return res; +} + +template +std::string BenchmarkTest::_gen_value() const { + return std::string(_opts.val_len, 'x'); +} + +template +void BenchmarkTest::_cleanup() { + for (const auto &key : _keys) { + _redis.del(key); + } +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.h new file mode 100644 index 000000000..2127257c3 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ConnectionCmdTest { +public: + explicit ConnectionCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _run(Redis &redis); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "connection_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.hpp new file mode 100644 index 000000000..90e7c313b --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/connection_cmds_test.hpp @@ -0,0 +1,50 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP + +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ConnectionCmdTest::run() { + cluster_specializing_test(*this, &ConnectionCmdTest::_run, _redis); +} + +template +void ConnectionCmdTest::_run(Redis &instance) { + auto message = std::string("hello"); + + REDIS_ASSERT(instance.echo(message) == message, "failed to test echo"); + + REDIS_ASSERT(instance.ping() == "PONG", "failed to test ping"); + + REDIS_ASSERT(instance.ping(message) == message, "failed to test ping"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.h new file mode 100644 index 000000000..41b9795fa --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.h @@ -0,0 +1,47 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class GeoCmdTest { +public: + explicit GeoCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + RedisInstance &_redis; +}; + +} + +} + +} + +#include "geo_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.hpp new file mode 100644 index 000000000..866fafe10 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/geo_cmds_test.hpp @@ -0,0 +1,149 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void GeoCmdTest::run() { + auto key = test_key("geo"); + auto dest = test_key("dest"); + + KeyDeleter deleter(_redis, {key, dest}); + + auto members = { + std::make_tuple("m1", 10.0, 11.0), + std::make_tuple("m2", 10.1, 11.1), + std::make_tuple("m3", 10.2, 11.2) + }; + + REDIS_ASSERT(_redis.geoadd(key, std::make_tuple("m1", 10.0, 11.0)) == 1, + "failed to test geoadd"); + REDIS_ASSERT(_redis.geoadd(key, members) == 2, "failed to test geoadd"); + + auto dist = _redis.geodist(key, "m1", "m4", GeoUnit::KM); + REDIS_ASSERT(!dist, "failed to test geodist with nonexistent member"); + + std::vector hashes; + _redis.geohash(key, {"m1", "m4"}, std::back_inserter(hashes)); + REDIS_ASSERT(hashes.size() == 2, "failed to test geohash"); + REDIS_ASSERT(bool(hashes[0]) && *(hashes[0]) == "s1zned3z8u0" && !(hashes[1]), + "failed to test geohash"); + hashes.clear(); + _redis.geohash(key, {"m4"}, std::back_inserter(hashes)); + REDIS_ASSERT(hashes.size() == 1 && !(hashes[0]), "failed to test geohash"); + + std::vector>> pos; + _redis.geopos(key, {"m4"}, std::back_inserter(pos)); + REDIS_ASSERT(pos.size() == 1 && !(pos[0]), "failed to test geopos"); + + auto num = _redis.georadius(key, + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + dest, + false, + 10); + REDIS_ASSERT(bool(num) && *num == 3, "failed to test georadius with store option"); + + std::vector mems; + _redis.georadius(key, + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mems)); + REDIS_ASSERT(mems.size() == 3, "failed to test georadius with no option"); + + std::vector> with_dist; + _redis.georadius(key, + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(with_dist)); + REDIS_ASSERT(with_dist.size() == 3, "failed to test georadius with dist"); + + std::vector>> with_dist_coord; + _redis.georadius(key, + std::make_pair(10.1, 11.1), + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(with_dist_coord)); + REDIS_ASSERT(with_dist_coord.size() == 3, "failed to test georadius with dist and coord"); + + num = _redis.georadiusbymember(key, + "m1", + 100, + GeoUnit::KM, + dest, + false, + 10); + REDIS_ASSERT(bool(num) && *num == 3, "failed to test georadiusbymember with store option"); + + mems.clear(); + _redis.georadiusbymember(key, + "m1", + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(mems)); + REDIS_ASSERT(mems.size() == 3, "failed to test georadiusbymember with no option"); + + with_dist.clear(); + _redis.georadiusbymember(key, + "m1", + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(with_dist)); + REDIS_ASSERT(with_dist.size() == 3, "failed to test georadiusbymember with dist"); + + with_dist_coord.clear(); + _redis.georadiusbymember(key, + "m1", + 100, + GeoUnit::KM, + 10, + true, + std::back_inserter(with_dist_coord)); + REDIS_ASSERT(with_dist_coord.size() == 3, + "failed to test georadiusbymember with dist and coord"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.h new file mode 100644 index 000000000..60791c8b9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.h @@ -0,0 +1,55 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class HashCmdTest { +public: + explicit HashCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_hash(); + + void _test_hash_batch(); + + void _test_numeric(); + + void _test_hscan(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "hash_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.hpp new file mode 100644 index 000000000..42c309f4e --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hash_cmds_test.hpp @@ -0,0 +1,177 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void HashCmdTest::run() { + _test_hash(); + + _test_hash_batch(); + + _test_numeric(); + + _test_hscan(); +} + +template +void HashCmdTest::_test_hash() { + auto key = test_key("hash"); + + KeyDeleter deleter(_redis, key); + + auto f1 = std::string("f1"); + auto v1 = std::string("v1"); + auto f2 = std::string("f2"); + auto v2 = std::string("v2"); + auto f3 = std::string("f3"); + auto v3 = std::string("v3"); + + REDIS_ASSERT(_redis.hset(key, f1, v1), "failed to test hset"); + REDIS_ASSERT(!_redis.hset(key, f1, v2), "failed to test hset with exist field"); + + auto res = _redis.hget(key, f1); + REDIS_ASSERT(res && *res == v2, "failed to test hget"); + + REDIS_ASSERT(_redis.hsetnx(key, f2, v1), "failed to test hsetnx"); + REDIS_ASSERT(!_redis.hsetnx(key, f2, v2), "failed to test hsetnx with exist field"); + + res = _redis.hget(key, f2); + REDIS_ASSERT(res && *res == v1, "failed to test hget"); + + REDIS_ASSERT(!_redis.hexists(key, f3), "failed to test hexists"); + REDIS_ASSERT(_redis.hset(key, std::make_pair(f3, v3)), "failed to test hset"); + REDIS_ASSERT(_redis.hexists(key, f3), "failed to test hexists"); + + REDIS_ASSERT(_redis.hlen(key) == 3, "failed to test hlen"); + REDIS_ASSERT(_redis.hstrlen(key, f1) == static_cast(v1.size()), + "failed to test hstrlen"); + + REDIS_ASSERT(_redis.hdel(key, f1) == 1, "failed to test hdel"); + REDIS_ASSERT(_redis.hdel(key, {f1, f2, f3}) == 2, "failed to test hdel range"); +} + +template +void HashCmdTest::_test_hash_batch() { + auto key = test_key("hash"); + + KeyDeleter deleter(_redis, key); + + auto f1 = std::string("f1"); + auto v1 = std::string("v1"); + auto f2 = std::string("f2"); + auto v2 = std::string("v2"); + auto f3 = std::string("f3"); + + _redis.hmset(key, {std::make_pair(f1, v1), + std::make_pair(f2, v2)}); + + std::vector fields; + _redis.hkeys(key, std::back_inserter(fields)); + REDIS_ASSERT(fields.size() == 2, "failed to test hkeys"); + + std::vector vals; + _redis.hvals(key, std::back_inserter(vals)); + REDIS_ASSERT(vals.size() == 2, "failed to test hvals"); + + std::unordered_map items; + _redis.hgetall(key, std::inserter(items, items.end())); + REDIS_ASSERT(items.size() == 2 && items[f1] == v1 && items[f2] == v2, + "failed to test hgetall"); + + std::vector> item_vec; + _redis.hgetall(key, std::back_inserter(item_vec)); + REDIS_ASSERT(item_vec.size() == 2, "failed to test hgetall"); + + std::vector res; + _redis.hmget(key, {f1, f2, f3}, std::back_inserter(res)); + REDIS_ASSERT(res.size() == 3 + && bool(res[0]) && *(res[0]) == v1 + && bool(res[1]) && *(res[1]) == v2 + && !res[2], + "failed to test hmget"); +} + +template +void HashCmdTest::_test_numeric() { + auto key = test_key("numeric"); + + KeyDeleter deleter(_redis, key); + + auto field = "field"; + + REDIS_ASSERT(_redis.hincrby(key, field, 1) == 1, "failed to test hincrby"); + REDIS_ASSERT(_redis.hincrby(key, field, -1) == 0, "failed to test hincrby"); + REDIS_ASSERT(_redis.hincrbyfloat(key, field, 1.5) == 1.5, "failed to test hincrbyfloat"); +} + +template +void HashCmdTest::_test_hscan() { + auto key = test_key("hscan"); + + KeyDeleter deleter(_redis, key); + + auto items = std::unordered_map{ + std::make_pair("f1", "v1"), + std::make_pair("f2", "v2"), + std::make_pair("f3", "v3"), + }; + + _redis.hmset(key, items.begin(), items.end()); + + std::unordered_map item_map; + auto cursor = 0; + while (true) { + cursor = _redis.hscan(key, cursor, "f*", 2, std::inserter(item_map, item_map.end())); + if (cursor == 0) { + break; + } + } + + REDIS_ASSERT(item_map == items, "failed to test hscan with pattern and count"); + + std::vector> item_vec; + cursor = 0; + while (true) { + cursor = _redis.hscan(key, cursor, std::back_inserter(item_vec)); + if (cursor == 0) { + break; + } + } + + REDIS_ASSERT(item_vec.size() == items.size(), "failed to test hscan"); + for (const auto &ele : item_vec) { + REDIS_ASSERT(items.find(ele.first) != items.end(), "failed to test hscan"); + } +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.h new file mode 100644 index 000000000..50e454b23 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.h @@ -0,0 +1,47 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class HyperloglogCmdTest { +public: + explicit HyperloglogCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + RedisInstance &_redis; +}; + +} + +} + +} + +#include "hyperloglog_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.hpp new file mode 100644 index 000000000..09775255f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/hyperloglog_cmds_test.hpp @@ -0,0 +1,67 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP + +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void HyperloglogCmdTest::run() { + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + auto k3 = test_key("k3"); + + KeyDeleter deleter(_redis, {k1, k2, k3}); + + _redis.pfadd(k1, "a"); + auto members1 = {"b", "c", "d", "e", "f", "g"}; + _redis.pfadd(k1, members1); + + auto cnt = _redis.pfcount(k1); + auto err = cnt * 1.0 / (1 + members1.size()); + REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfadd and pfcount"); + + auto members2 = {"a", "b", "c", "h", "i", "j", "k"}; + _redis.pfadd(k2, members2); + auto total = 1 + members1.size() + members2.size() - 3; + + cnt = _redis.pfcount({k1, k2}); + err = cnt * 1.0 / total; + REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfcount"); + + _redis.pfmerge(k3, {k1, k2}); + cnt = _redis.pfcount(k3); + err = cnt * 1.0 / total; + REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfcount"); + + _redis.pfmerge(k3, k1); + REDIS_ASSERT(cnt == _redis.pfcount(k3), "failed to test pfmerge"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.h new file mode 100644 index 000000000..54d25ca28 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.h @@ -0,0 +1,55 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class KeysCmdTest { +public: + explicit KeysCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_key(); + + void _test_randomkey(Redis &instance); + + void _test_ttl(); + + void _test_scan(Redis &instance); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "keys_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.hpp new file mode 100644 index 000000000..de21f8d18 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/keys_cmds_test.hpp @@ -0,0 +1,166 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void KeysCmdTest::run() { + _test_key(); + + cluster_specializing_test(*this, &KeysCmdTest::_test_randomkey, _redis); + + _test_ttl(); + + cluster_specializing_test(*this, &KeysCmdTest::_test_scan, _redis); +} + +template +void KeysCmdTest::_test_key() { + auto key = test_key("key"); + auto dest = test_key("dest"); + auto new_key_name = test_key("new-key"); + auto not_exist_key = test_key("not-exist"); + + KeyDeleter deleter(_redis, {key, dest, new_key_name}); + + REDIS_ASSERT(_redis.exists(key) == 0, "failed to test exists"); + + auto val = std::string("val"); + _redis.set(key, val); + + REDIS_ASSERT(_redis.exists({key, not_exist_key}) == 1, "failed to test exists"); + + auto new_val = _redis.dump(key); + REDIS_ASSERT(bool(new_val), "failed to test dump"); + + _redis.restore(dest, *new_val, std::chrono::seconds(1000)); + + new_val = _redis.get(dest); + REDIS_ASSERT(bool(new_val) && *new_val == val, "failed to test dump and restore"); + + _redis.rename(dest, new_key_name); + + bool not_exist = false; + try { + _redis.rename(not_exist_key, new_key_name); + } catch (const Error &e) { + not_exist = true; + } + REDIS_ASSERT(not_exist, "failed to test rename with nonexistent key"); + + REDIS_ASSERT(_redis.renamenx(new_key_name, dest), "failed to test renamenx"); + + REDIS_ASSERT(_redis.touch(not_exist_key) == 0, "failed to test touch"); + REDIS_ASSERT(_redis.touch({key, dest, new_key_name}) == 2, "failed to test touch"); + + REDIS_ASSERT(_redis.type(key) == "string", "failed to test type"); + + REDIS_ASSERT(_redis.del({new_key_name, dest}) == 1, "failed to test del"); + REDIS_ASSERT(_redis.unlink({new_key_name, key}) == 1, "failed to test unlink"); +} + +template +void KeysCmdTest::_test_randomkey(Redis &instance) { + auto key = test_key("randomkey"); + + KeyDeleter deleter(instance, key); + + instance.set(key, "value"); + + auto rand_key = instance.randomkey(); + REDIS_ASSERT(bool(rand_key), "failed to test randomkey"); +} + +template +void KeysCmdTest::_test_ttl() { + using namespace std::chrono; + + auto key = test_key("ttl"); + + KeyDeleter deleter(_redis, key); + + _redis.set(key, "val", seconds(100)); + auto ttl = _redis.ttl(key); + REDIS_ASSERT(ttl > 0 && ttl <= 100, "failed to test ttl"); + + REDIS_ASSERT(_redis.persist(key), "failed to test persist"); + ttl = _redis.ttl(key); + REDIS_ASSERT(ttl == -1, "failed to test ttl"); + + REDIS_ASSERT(_redis.expire(key, seconds(100)), + "failed to test expire"); + + auto tp = time_point_cast(system_clock::now() + seconds(100)); + REDIS_ASSERT(_redis.expireat(key, tp), "failed to test expireat"); + ttl = _redis.ttl(key); + REDIS_ASSERT(ttl > 0, "failed to test expireat"); + + REDIS_ASSERT(_redis.pexpire(key, milliseconds(100000)), "failed to test expire"); + + auto pttl = _redis.pttl(key); + REDIS_ASSERT(pttl > 0 && pttl <= 100000, "failed to test pttl"); + + auto tp_milli = time_point_cast(system_clock::now() + milliseconds(100000)); + REDIS_ASSERT(_redis.pexpireat(key, tp_milli), "failed to test pexpireat"); + pttl = _redis.pttl(key); + REDIS_ASSERT(pttl > 0, "failed to test pexpireat"); +} + +template +void KeysCmdTest::_test_scan(Redis &instance) { + std::string key_pattern = "!@#$%^&()_+alseufoawhnlkszd"; + auto k1 = test_key(key_pattern + "k1"); + auto k2 = test_key(key_pattern + "k2"); + auto k3 = test_key(key_pattern + "k3"); + + auto keys = {k1, k2, k3}; + + KeyDeleter deleter(instance, keys); + + instance.set(k1, "v"); + instance.set(k2, "v"); + instance.set(k3, "v"); + + auto cursor = 0; + std::unordered_set res; + while (true) { + cursor = instance.scan(cursor, "*" + key_pattern + "*", 2, std::inserter(res, res.end())); + if (cursor == 0) { + break; + } + } + REDIS_ASSERT(res == std::unordered_set(keys), + "failed to test scan"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.h new file mode 100644 index 000000000..2092fe9e4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.h @@ -0,0 +1,55 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ListCmdTest { +public: + explicit ListCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_lpoppush(); + + void _test_rpoppush(); + + void _test_list(); + + void _test_blocking(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "list_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.hpp new file mode 100644 index 000000000..fd26d8fde --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/list_cmds_test.hpp @@ -0,0 +1,154 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP + +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ListCmdTest::run() { + _test_lpoppush(); + + _test_rpoppush(); + + _test_list(); + + _test_blocking(); +} + +template +void ListCmdTest::_test_lpoppush() { + auto key = test_key("lpoppush"); + + KeyDeleter deleter(_redis, key); + + auto item = _redis.lpop(key); + REDIS_ASSERT(!item, "failed to test lpop"); + + REDIS_ASSERT(_redis.lpushx(key, "1") == 0, "failed to test lpushx"); + REDIS_ASSERT(_redis.lpush(key, "1") == 1, "failed to test lpush"); + REDIS_ASSERT(_redis.lpushx(key, "2") == 2, "failed to test lpushx"); + REDIS_ASSERT(_redis.lpush(key, {"3", "4", "5"}) == 5, "failed to test lpush"); + + item = _redis.lpop(key); + REDIS_ASSERT(item && *item == "5", "failed to test lpop"); +} + +template +void ListCmdTest::_test_rpoppush() { + auto key = test_key("rpoppush"); + + KeyDeleter deleter(_redis, key); + + auto item = _redis.rpop(key); + REDIS_ASSERT(!item, "failed to test rpop"); + + REDIS_ASSERT(_redis.rpushx(key, "1") == 0, "failed to test rpushx"); + REDIS_ASSERT(_redis.rpush(key, "1") == 1, "failed to test rpush"); + REDIS_ASSERT(_redis.rpushx(key, "2") == 2, "failed to test rpushx"); + REDIS_ASSERT(_redis.rpush(key, {"3", "4", "5"}) == 5, "failed to test rpush"); + + item = _redis.rpop(key); + REDIS_ASSERT(item && *item == "5", "failed to test rpop"); +} + +template +void ListCmdTest::_test_list() { + auto key = test_key("list"); + + KeyDeleter deleter(_redis, key); + + auto item = _redis.lindex(key, 0); + REDIS_ASSERT(!item, "failed to test lindex"); + + _redis.lpush(key, {"1", "2", "3", "4", "5"}); + + REDIS_ASSERT(_redis.lrem(key, 0, "3") == 1, "failed to test lrem"); + + REDIS_ASSERT(_redis.linsert(key, InsertPosition::BEFORE, "2", "3") == 5, + "failed to test lindex"); + + REDIS_ASSERT(_redis.llen(key) == 5, "failed to test llen"); + + _redis.lset(key, 0, "6"); + item = _redis.lindex(key, 0); + REDIS_ASSERT(item && *item == "6", "failed to test lindex"); + + _redis.ltrim(key, 0, 2); + + std::vector res; + _redis.lrange(key, 0, -1, std::back_inserter(res)); + REDIS_ASSERT(res == std::vector({"6", "4", "3"}), "failed to test ltrim"); +} + +template +void ListCmdTest::_test_blocking() { + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + auto k3 = test_key("k3"); + + auto keys = {k1, k2, k3}; + + KeyDeleter deleter(_redis, keys); + + std::string val("value"); + _redis.lpush(k1, val); + + auto res = _redis.blpop(keys.begin(), keys.end()); + REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test blpop"); + + res = _redis.brpop(keys, std::chrono::seconds(1)); + REDIS_ASSERT(!res, "failed to test brpop with timeout"); + + _redis.lpush(k1, val); + res = _redis.blpop(k1); + REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test blpop"); + + res = _redis.blpop(k1, std::chrono::seconds(1)); + REDIS_ASSERT(!res, "failed to test blpop with timeout"); + + _redis.lpush(k1, val); + res = _redis.brpop(k1); + REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test brpop"); + + res = _redis.brpop(k1, std::chrono::seconds(1)); + REDIS_ASSERT(!res, "failed to test brpop with timeout"); + + auto str = _redis.brpoplpush(k2, k3, std::chrono::seconds(1)); + REDIS_ASSERT(!str, "failed to test brpoplpush with timeout"); + + _redis.lpush(k2, val); + str = _redis.brpoplpush(k2, k3); + REDIS_ASSERT(str && *str == val, "failed to test brpoplpush"); + + str = _redis.rpoplpush(k3, k2); + REDIS_ASSERT(str && *str == val, "failed to test rpoplpush"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.h new file mode 100644 index 000000000..fc6b80dde --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.h @@ -0,0 +1,57 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class PipelineTransactionTest { +public: + explicit PipelineTransactionTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + Pipeline _pipeline(const StringView &key); + + Transaction _transaction(const StringView &key, bool piped); + + void _test_pipeline(const StringView &key, Pipeline &pipe); + + void _test_transaction(const StringView &key, Transaction &tx); + + void _test_watch(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "pipeline_transaction_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.hpp new file mode 100644 index 000000000..4e6a745cb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pipeline_transaction_test.hpp @@ -0,0 +1,184 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void PipelineTransactionTest::run() { + { + auto key = test_key("pipeline"); + KeyDeleter deleter(_redis, key); + auto pipe = _pipeline(key); + _test_pipeline(key, pipe); + } + + { + auto key = test_key("transaction"); + KeyDeleter deleter(_redis, key); + auto tx = _transaction(key, true); + _test_transaction(key, tx); + } + + { + auto key = test_key("transaction"); + KeyDeleter deleter(_redis, key); + auto tx = _transaction(key, false); + _test_transaction(key, tx); + } + + _test_watch(); +} + +template +Pipeline PipelineTransactionTest::_pipeline(const StringView &) { + return _redis.pipeline(); +} + +template <> +inline Pipeline PipelineTransactionTest::_pipeline(const StringView &key) { + return _redis.pipeline(key); +} + +template +Transaction PipelineTransactionTest::_transaction(const StringView &, bool piped) { + return _redis.transaction(piped); +} + +template <> +inline Transaction PipelineTransactionTest::_transaction(const StringView &key, + bool piped) { + return _redis.transaction(key, piped); +} + +template +void PipelineTransactionTest::_test_pipeline(const StringView &key, + Pipeline &pipe) { + std::string val("value"); + auto replies = pipe.set(key, val) + .get(key) + .strlen(key) + .exec(); + + REDIS_ASSERT(replies.get(0), "failed to test pipeline with set operation"); + + auto new_val = replies.get(1); + std::size_t len = replies.get(2); + REDIS_ASSERT(bool(new_val) && *new_val == val && len == val.size(), + "failed to test pipeline with string operations"); + + REDIS_ASSERT(reply::parse(replies.get(0)), "failed to test pipeline with set operation"); + + new_val = reply::parse(replies.get(1)); + len = reply::parse(replies.get(2)); + REDIS_ASSERT(bool(new_val) && *new_val == val && len == val.size(), + "failed to test pipeline with string operations"); +} + +template +void PipelineTransactionTest::_test_transaction(const StringView &key, + Transaction &tx) { + std::unordered_map m = { + std::make_pair("f1", "v1"), + std::make_pair("f2", "v2"), + std::make_pair("f3", "v3") + }; + auto replies = tx.hmset(key, m.begin(), m.end()) + .hgetall(key) + .hdel(key, "f1") + .exec(); + + replies.get(0); + + decltype(m) mm; + replies.get(1, std::inserter(mm, mm.end())); + REDIS_ASSERT(mm == m, "failed to test transaction"); + + REDIS_ASSERT(replies.get(2) == 1, "failed to test transaction"); + + tx.set(key, "value") + .get(key) + .incr(key); + + tx.discard(); + + replies = tx.del(key) + .set(key, "value") + .exec(); + + REDIS_ASSERT(replies.get(0) == 1, "failed to test transaction"); + + REDIS_ASSERT(replies.get(1), "failed to test transaction"); +} + +template +void PipelineTransactionTest::_test_watch() { + auto key = test_key("watch"); + + KeyDeleter deleter(_redis, key); + + { + auto tx = _transaction(key, false); + + auto redis = tx.redis(); + + redis.watch(key); + + auto replies = tx.set(key, "1").get(key).exec(); + + REDIS_ASSERT(replies.size() == 2 + && replies.template get(0) == true, "failed to test watch"); + + auto val = replies.template get(1); + + REDIS_ASSERT(val && *val == "1", "failed to test watch"); + } + + try { + auto tx = _transaction(key, false); + + auto redis = tx.redis(); + + redis.watch(key); + + // Key has been modified by other client. + _redis.set(key, "val"); + + // Transaction should fail, and throw WatchError + tx.set(key, "1").exec(); + + REDIS_ASSERT(false, "failed to test watch"); + } catch (const sw::redis::WatchError &err) { + // Catch the error. + } +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.h new file mode 100644 index 000000000..f816eaecc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.h @@ -0,0 +1,53 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class PubSubTest { +public: + explicit PubSubTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_sub_channel(); + + void _test_sub_pattern(); + + void _test_unsubscribe(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "pubsub_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.hpp new file mode 100644 index 000000000..4530c0788 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/pubsub_test.hpp @@ -0,0 +1,244 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void PubSubTest::run() { + _test_sub_channel(); + + _test_sub_pattern(); + + _test_unsubscribe(); +} + +template +void PubSubTest::_test_sub_channel() { + auto sub = _redis.subscriber(); + + auto msgs = {"msg1", "msg2"}; + auto channel1 = test_key("c1"); + sub.on_message([&msgs, &channel1](std::string channel, std::string msg) { + static std::size_t idx = 0; + REDIS_ASSERT(channel == channel1 && msg == *(msgs.begin() + idx), + "failed to test subscribe"); + ++idx; + }); + + sub.subscribe(channel1); + + // Consume the SUBSCRIBE message. + sub.consume(); + + for (const auto &msg : msgs) { + _redis.publish(channel1, msg); + sub.consume(); + } + + sub.unsubscribe(channel1); + + // Consume the UNSUBSCRIBE message. + sub.consume(); + + auto channel2 = test_key("c2"); + auto channel3 = test_key("c3"); + auto channel4 = test_key("c4"); + std::unordered_set channels; + sub.on_meta([&channels](Subscriber::MsgType type, + OptionalString channel, + long long num) { + REDIS_ASSERT(bool(channel), "failed to test subscribe"); + + if (type == Subscriber::MsgType::SUBSCRIBE) { + auto iter = channels.find(*channel); + REDIS_ASSERT(iter == channels.end(), "failed to test subscribe"); + channels.insert(*channel); + REDIS_ASSERT(static_cast(num) == channels.size(), + "failed to test subscribe"); + } else if (type == Subscriber::MsgType::UNSUBSCRIBE) { + auto iter = channels.find(*channel); + REDIS_ASSERT(iter != channels.end(), "failed to test subscribe"); + channels.erase(*channel); + REDIS_ASSERT(static_cast(num) == channels.size(), + "failed to test subscribe"); + } else { + REDIS_ASSERT(false, "Unknown message type"); + } + }); + + std::unordered_map messages = { + {channel2, "msg2"}, + {channel3, "msg3"}, + {channel4, "msg4"}, + }; + sub.on_message([&messages](std::string channel, std::string msg) { + REDIS_ASSERT(messages.find(channel) != messages.end(), + "failed to test subscribe"); + REDIS_ASSERT(messages[channel] == msg, "failed to test subscribe"); + }); + + sub.subscribe({channel2, channel3, channel4}); + + for (std::size_t idx = 0; idx != channels.size(); ++idx) { + sub.consume(); + } + + for (const auto &ele : messages) { + _redis.publish(ele.first, ele.second); + sub.consume(); + } + + auto tmp = {channel2, channel3, channel4}; + sub.unsubscribe(tmp); + + for (std::size_t idx = 0; idx != tmp.size(); ++idx) { + sub.consume(); + } +} + +template +void PubSubTest::_test_sub_pattern() { + auto sub = _redis.subscriber(); + + auto msgs = {"msg1", "msg2"}; + auto pattern1 = test_key("pattern*"); + std::string channel1 = test_key("pattern1"); + sub.on_pmessage([&msgs, &pattern1, &channel1](std::string pattern, + std::string channel, + std::string msg) { + static std::size_t idx = 0; + REDIS_ASSERT(pattern == pattern1 + && channel == channel1 + && msg == *(msgs.begin() + idx), + "failed to test psubscribe"); + ++idx; + }); + + sub.psubscribe(pattern1); + + // Consume the PSUBSCRIBE message. + sub.consume(); + + for (const auto &msg : msgs) { + _redis.publish(channel1, msg); + sub.consume(); + } + + sub.punsubscribe(pattern1); + + // Consume the PUNSUBSCRIBE message. + sub.consume(); + + auto channel2 = test_key("pattern22"); + auto channel3 = test_key("pattern33"); + auto channel4 = test_key("pattern44"); + std::unordered_set channels; + sub.on_meta([&channels](Subscriber::MsgType type, + OptionalString channel, + long long num) { + REDIS_ASSERT(bool(channel), "failed to test psubscribe"); + + if (type == Subscriber::MsgType::PSUBSCRIBE) { + auto iter = channels.find(*channel); + REDIS_ASSERT(iter == channels.end(), "failed to test psubscribe"); + channels.insert(*channel); + REDIS_ASSERT(static_cast(num) == channels.size(), + "failed to test psubscribe"); + } else if (type == Subscriber::MsgType::PUNSUBSCRIBE) { + auto iter = channels.find(*channel); + REDIS_ASSERT(iter != channels.end(), "failed to test psubscribe"); + channels.erase(*channel); + REDIS_ASSERT(static_cast(num) == channels.size(), + "failed to test psubscribe"); + } else { + REDIS_ASSERT(false, "Unknown message type"); + } + }); + + auto pattern2 = test_key("pattern2*"); + auto pattern3 = test_key("pattern3*"); + auto pattern4 = test_key("pattern4*"); + std::unordered_set patterns = {pattern2, pattern3, pattern4}; + + std::unordered_map messages = { + {channel2, "msg2"}, + {channel3, "msg3"}, + {channel4, "msg4"}, + }; + sub.on_pmessage([&patterns, &messages](std::string pattern, + std::string channel, + std::string msg) { + REDIS_ASSERT(patterns.find(pattern) != patterns.end(), + "failed to test psubscribe"); + REDIS_ASSERT(messages[channel] == msg, "failed to test psubscribe"); + }); + + sub.psubscribe({pattern2, pattern3, pattern4}); + + for (std::size_t idx = 0; idx != channels.size(); ++idx) { + sub.consume(); + } + + for (const auto &ele : messages) { + _redis.publish(ele.first, ele.second); + sub.consume(); + } + + auto tmp = {pattern2, pattern3, pattern4}; + sub.punsubscribe(tmp); + + for (std::size_t idx = 0; idx != tmp.size(); ++idx) { + sub.consume(); + } +} + +template +void PubSubTest::_test_unsubscribe() { + auto sub = _redis.subscriber(); + + sub.on_meta([](Subscriber::MsgType type, + OptionalString channel, + long long num) { + REDIS_ASSERT(type == Subscriber::MsgType::UNSUBSCRIBE, + "failed to test unsub"); + + REDIS_ASSERT(!channel, "failed to test unsub"); + + REDIS_ASSERT(num == 0, "failed to test unsub"); + }); + + sub.unsubscribe(); + sub.consume(); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SUBPUB_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.h new file mode 100644 index 000000000..e41fdef9d --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.h @@ -0,0 +1,76 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class SanityTest { +public: + SanityTest(const ConnectionOptions &opts, RedisInstance &instance) + : _opts(opts), _redis(instance) {} + + void run(); + +private: + void _test_uri_ctor(); + + void _ping(Redis &instance); + + void _test_move_ctor(); + + void _test_cmdargs(); + + void _test_generic_command(); + + void _test_hash_tag(); + + void _test_hash_tag(std::initializer_list keys); + + std::string _test_key(const std::string &key); + + void _test_ping(Redis &instance); + + void _test_pipeline(const StringView &key, Pipeline &pipeline); + + void _test_transaction(const StringView &key, Transaction &transaction); + + Pipeline _pipeline(const StringView &key); + + Transaction _transaction(const StringView &key); + + ConnectionOptions _opts; + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "sanity_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.hpp new file mode 100644 index 000000000..cff992ecb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/sanity_test.hpp @@ -0,0 +1,299 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_HPP + +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void SanityTest::run() { + _test_uri_ctor(); + + _test_move_ctor(); + + cluster_specializing_test(*this, &SanityTest::_test_ping, _redis); + + auto pipe_key = test_key("pipeline"); + auto tx_key = test_key("transaction"); + + KeyDeleter deleter(_redis, {pipe_key, tx_key}); + + auto pipeline = _pipeline(pipe_key); + _test_pipeline(pipe_key, pipeline); + + auto transaction = _transaction(tx_key); + _test_transaction(tx_key, transaction); + + _test_cmdargs(); + + _test_generic_command(); +} + +template +void SanityTest::_test_uri_ctor() { + std::string uri; + switch (_opts.type) { + case sw::redis::ConnectionType::TCP: + uri = "tcp://" + _opts.host + ":" + std::to_string(_opts.port); + break; + + case sw::redis::ConnectionType::UNIX: + REDIS_ASSERT(false, "NO test for UNIX Domain Socket"); + break; + + default: + REDIS_ASSERT(false, "Unknown connection type"); + } + + auto instance = RedisInstance(uri); + + cluster_specializing_test(*this, &SanityTest::_ping, instance); +} + +template +void SanityTest::_ping(Redis &instance) { + try { + auto pong = instance.ping(); + REDIS_ASSERT(pong == "PONG", "Failed to test constructing Redis with uri"); + } catch (const sw::redis::ReplyError &e) { + REDIS_ASSERT(e.what() == std::string("NOAUTH Authentication required."), + "Failed to test constructing Redis with uri"); + } +} + +template +void SanityTest::_test_move_ctor() { + auto test_move_ctor = std::move(_redis); + + _redis = std::move(test_move_ctor); +} + +template +void SanityTest::_test_cmdargs() { + auto lpush_num = [](Connection &connection, const StringView &key, long long num) { + connection.send("LPUSH %b %lld", + key.data(), key.size(), + num); + }; + + auto lpush_nums = [](Connection &connection, + const StringView &key, + const std::vector &nums) { + CmdArgs args; + args.append("LPUSH").append(key); + for (auto num : nums) { + args.append(std::to_string(num)); + } + + connection.send(args); + }; + + auto key = test_key("lpush_num"); + + KeyDeleter deleter(_redis, key); + + auto reply = _redis.command(lpush_num, key, 1); + REDIS_ASSERT(reply::parse(*reply) == 1, "failed to test cmdargs"); + + std::vector nums = {2, 3, 4, 5}; + reply = _redis.command(lpush_nums, key, nums); + REDIS_ASSERT(reply::parse(*reply) == 5, "failed to test cmdargs"); + + std::vector res; + _redis.lrange(key, 0, -1, std::back_inserter(res)); + REDIS_ASSERT((res == std::vector{"5", "4", "3", "2", "1"}), + "failed to test cmdargs"); +} + +template +void SanityTest::_test_generic_command() { + auto key = test_key("key"); + auto not_exist_key = test_key("not_exist_key"); + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + + KeyDeleter deleter(_redis, {key, not_exist_key, k1, k2}); + + std::string cmd("set"); + _redis.command(cmd, key, 123); + auto reply = _redis.command("get", key); + auto val = reply::parse(*reply); + REDIS_ASSERT(val && *val == "123", "failed to test generic command"); + + val = _redis.template command("get", key); + REDIS_ASSERT(val && *val == "123", "failed to test generic command"); + + std::vector res; + _redis.command("mget", key, not_exist_key, std::back_inserter(res)); + REDIS_ASSERT(res.size() == 2 && res[0] && *res[0] == "123" && !res[1], + "failed to test generic command"); + + reply = _redis.command("incr", key); + REDIS_ASSERT(reply::parse(*reply) == 124, "failed to test generic command"); + + _redis.command("mset", k1.c_str(), "v", k2.c_str(), "v"); + reply = _redis.command("mget", k1, k2); + res.clear(); + reply::to_array(*reply, std::back_inserter(res)); + REDIS_ASSERT(res.size() == 2 && res[0] && *(res[0]) == "v" && res[1] && *(res[1]) == "v", + "failed to test generic command"); + + res = _redis.template command>("mget", k1, k2); + REDIS_ASSERT(res.size() == 2 && res[0] && *(res[0]) == "v" && res[1] && *(res[1]) == "v", + "failed to test generic command"); + + res.clear(); + _redis.command("mget", k1, k2, std::back_inserter(res)); + REDIS_ASSERT(res.size() == 2 && res[0] && *(res[0]) == "v" && res[1] && *(res[1]) == "v", + "failed to test generic command"); + + auto set_cmd_str = {"set", key.c_str(), "new_value"}; + _redis.command(set_cmd_str.begin(), set_cmd_str.end()); + + auto get_cmd_str = {"get", key.c_str()}; + reply = _redis.command(get_cmd_str.begin(), get_cmd_str.end()); + val = reply::parse(*reply); + REDIS_ASSERT(val && *val == "new_value", "failed to test generic command"); + + val = _redis.template command(get_cmd_str.begin(), get_cmd_str.end()); + REDIS_ASSERT(val && *val == "new_value", "failed to test generic command"); + + auto mget_cmd_str = {"mget", key.c_str(), not_exist_key.c_str()}; + res.clear(); + _redis.command(mget_cmd_str.begin(), mget_cmd_str.end(), std::back_inserter(res)); + REDIS_ASSERT(res.size() == 2 && res[0] && *res[0] == "new_value" && !res[1], + "failed to test generic command"); +} + +template +void SanityTest::_test_hash_tag() { + _test_hash_tag({_test_key("{tag}postfix1"), + _test_key("{tag}postfix2"), + _test_key("{tag}postfix3")}); + + _test_hash_tag({_test_key("prefix1{tag}postfix1"), + _test_key("prefix2{tag}postfix2"), + _test_key("prefix3{tag}postfix3")}); + + _test_hash_tag({_test_key("prefix1{tag}"), + _test_key("prefix2{tag}"), + _test_key("prefix3{tag}")}); + + _test_hash_tag({_test_key("prefix{}postfix"), + _test_key("prefix{}postfix"), + _test_key("prefix{}postfix")}); + + _test_hash_tag({_test_key("prefix1{tag}post}fix1"), + _test_key("prefix2{tag}pos}tfix2"), + _test_key("prefix3{tag}postfi}x3")}); + + _test_hash_tag({_test_key("prefix1{t{ag}postfix1"), + _test_key("prefix2{t{ag}postfix2"), + _test_key("prefix3{t{ag}postfix3")}); + + _test_hash_tag({_test_key("prefix1{t{ag}postfi}x1"), + _test_key("prefix2{t{ag}post}fix2"), + _test_key("prefix3{t{ag}po}stfix3")}); +} + +template +void SanityTest::_test_hash_tag(std::initializer_list keys) { + KeyDeleter deleter(_redis, keys.begin(), keys.end()); + + std::string value = "value"; + std::vector> kvs; + for (const auto &key : keys) { + kvs.emplace_back(key, value); + } + + _redis.mset(kvs.begin(), kvs.end()); + + std::vector res; + res.reserve(keys.size()); + _redis.mget(keys.begin(), keys.end(), std::back_inserter(res)); + + REDIS_ASSERT(res.size() == keys.size(), "failed to test hash tag"); + + for (const auto &ele : res) { + REDIS_ASSERT(ele && *ele == value, "failed to test hash tag"); + } +} + +template +std::string SanityTest::_test_key(const std::string &key) { + REDIS_ASSERT(key.size() > 1, "failed to generate key"); + + // Ensure that key prefix has NO hash tag. Also see the implementation of test_key. + return key.substr(1); +} + +template +void SanityTest::_test_ping(Redis &instance) { + auto reply = instance.command("ping"); + REDIS_ASSERT(reply && reply::parse(*reply) == "PONG", + "failed to test generic command"); + + auto pong = instance.command("ping"); + REDIS_ASSERT(pong == "PONG", "failed to test generic command"); +} + +template +void SanityTest::_test_pipeline(const StringView &key, Pipeline &pipeline) { + auto pipe_replies = pipeline.command("set", key, "value").command("get", key).exec(); + auto val = pipe_replies.get(1); + REDIS_ASSERT(val && *val == "value", "failed to test generic command"); +} + +template +void SanityTest::_test_transaction(const StringView &key, Transaction &transaction) { + auto tx_replies = transaction.command("set", key, 456).command("incr", key).exec(); + REDIS_ASSERT(tx_replies.get(1) == 457, "failed to test generic command"); +} + +template +Pipeline SanityTest::_pipeline(const StringView &) { + return _redis.pipeline(); +} + +template <> +inline Pipeline SanityTest::_pipeline(const StringView &key) { + return _redis.pipeline(key); +} + +template +Transaction SanityTest::_transaction(const StringView &) { + return _redis.transaction(); +} + +template <> +inline Transaction SanityTest::_transaction(const StringView &key) { + return _redis.transaction(key); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SANITY_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.h new file mode 100644 index 000000000..f7adb11b6 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ScriptCmdTest { +public: + explicit ScriptCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _run(Redis &instance); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "script_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.hpp new file mode 100644 index 000000000..76e07fda2 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/script_cmds_test.hpp @@ -0,0 +1,97 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ScriptCmdTest::run() { + cluster_specializing_test(*this, + &ScriptCmdTest::_run, + _redis); +} + +template +void ScriptCmdTest::_run(Redis &instance) { + auto key1 = test_key("k1"); + auto key2 = test_key("k2"); + + KeyDeleter deleter(instance, {key1, key2}); + + std::string script = "redis.call('set', KEYS[1], 1);" + "redis.call('set', KEYS[2], 2);" + "local first = redis.call('get', KEYS[1]);" + "local second = redis.call('get', KEYS[2]);" + "return first + second"; + + auto num = instance.eval(script, {key1, key2}, {}); + REDIS_ASSERT(num == 3, "failed to test scripting for cluster"); + + script = "return 1"; + num = instance.eval(script, {}, {}); + REDIS_ASSERT(num == 1, "failed to test eval"); + + auto script_with_args = "return {ARGV[1] + 1, ARGV[2] + 2, ARGV[3] + 3}"; + std::vector res; + instance.eval(script_with_args, + {"k"}, + {"1", "2", "3"}, + std::back_inserter(res)); + REDIS_ASSERT(res == std::vector({2, 4, 6}), + "failed to test eval with array reply"); + + auto sha1 = instance.script_load(script); + num = instance.evalsha(sha1, {}, {}); + REDIS_ASSERT(num == 1, "failed to test evalsha"); + + auto sha2 = instance.script_load(script_with_args); + res.clear(); + instance.evalsha(sha2, + {"k"}, + {"1", "2", "3"}, + std::back_inserter(res)); + REDIS_ASSERT(res == std::vector({2, 4, 6}), + "failed to test evalsha with array reply"); + + std::list exist_res; + instance.script_exists({sha1, sha2, std::string("not exist")}, std::back_inserter(exist_res)); + REDIS_ASSERT(exist_res == std::list({true, true, false}), + "failed to test script exists"); + + instance.script_flush(); + exist_res.clear(); + instance.script_exists({sha1, sha2, std::string("not exist")}, std::back_inserter(exist_res)); + REDIS_ASSERT(exist_res == std::list({false, false, false}), + "failed to test script flush"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SCRIPT_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.h new file mode 100644 index 000000000..c5320d793 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.h @@ -0,0 +1,53 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class SetCmdTest { +public: + explicit SetCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_set(); + + void _test_multi_set(); + + void _test_sscan(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "set_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.hpp new file mode 100644 index 000000000..1a4c24bfd --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/set_cmds_test.hpp @@ -0,0 +1,184 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_HPP + +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void SetCmdTest::run() { + _test_set(); + + _test_multi_set(); + + _test_sscan(); +} + +template +void SetCmdTest::_test_set() { + auto key = test_key("set"); + + KeyDeleter deleter(_redis, key); + + std::string m1("m1"); + std::string m2("m2"); + std::string m3("m3"); + + REDIS_ASSERT(_redis.sadd(key, m1) == 1, "failed to test sadd"); + + auto members = {m1, m2, m3}; + REDIS_ASSERT(_redis.sadd(key, members) == 2, "failed to test sadd with multiple members"); + + REDIS_ASSERT(_redis.scard(key) == 3, "failed to test scard"); + + REDIS_ASSERT(_redis.sismember(key, m1), "failed to test sismember"); + + std::unordered_set res; + _redis.smembers(key, std::inserter(res, res.end())); + REDIS_ASSERT(res.find(m1) != res.end() + && res.find(m2) != res.end() + && res.find(m3) != res.end(), + "failed to test smembers"); + + auto ele = _redis.srandmember(key); + REDIS_ASSERT(bool(ele) && res.find(*ele) != res.end(), "failed to test srandmember"); + + std::vector rand_members; + _redis.srandmember(key, 2, std::back_inserter(rand_members)); + REDIS_ASSERT(rand_members.size() == 2, "failed to test srandmember"); + + ele = _redis.spop(key); + REDIS_ASSERT(bool(ele) && res.find(*ele) != res.end(), "failed to test spop"); + + rand_members.clear(); + _redis.spop(key, 3, std::back_inserter(rand_members)); + REDIS_ASSERT(rand_members.size() == 2, "failed to test srandmember"); + + rand_members.clear(); + _redis.srandmember(key, 2, std::back_inserter(rand_members)); + REDIS_ASSERT(rand_members.empty(), "failed to test srandmember with empty set"); + + _redis.spop(key, 2, std::back_inserter(rand_members)); + REDIS_ASSERT(rand_members.empty(), "failed to test spop with empty set"); + + _redis.sadd(key, members); + REDIS_ASSERT(_redis.srem(key, m1) == 1, "failed to test srem"); + REDIS_ASSERT(_redis.srem(key, members) == 2, "failed to test srem with mulitple members"); + REDIS_ASSERT(_redis.srem(key, members) == 0, "failed to test srem with mulitple members"); +} + +template +void SetCmdTest::_test_multi_set() { + auto k1 = test_key("s1"); + auto k2 = test_key("s2"); + auto k3 = test_key("s3"); + auto k4 = test_key("s4"); + auto k5 = test_key("s5"); + auto k6 = test_key("s6"); + + KeyDeleter keys(_redis, {k1, k2, k3, k4, k5, k6}); + + _redis.sadd(k1, {"a", "c"}); + _redis.sadd(k2, {"a", "b"}); + std::vector sdiff; + _redis.sdiff({k1, k1}, std::back_inserter(sdiff)); + REDIS_ASSERT(sdiff.empty(), "failed to test sdiff"); + + _redis.sdiff({k1, k2}, std::back_inserter(sdiff)); + REDIS_ASSERT(sdiff == std::vector({"c"}), "failed to test sdiff"); + + _redis.sdiffstore(k3, {k1, k2}); + sdiff.clear(); + _redis.smembers(k3, std::back_inserter(sdiff)); + REDIS_ASSERT(sdiff == std::vector({"c"}), "failed to test sdiffstore"); + + REDIS_ASSERT(_redis.sdiffstore(k3, k1) == 2, "failed to test sdiffstore"); + + REDIS_ASSERT(_redis.sinterstore(k3, k1) == 2, "failed to test sinterstore"); + + REDIS_ASSERT(_redis.sunionstore(k3, k1) == 2, "failed to test sunionstore"); + + std::vector sinter; + _redis.sinter({k1, k2}, std::back_inserter(sinter)); + REDIS_ASSERT(sinter == std::vector({"a"}), "failed to test sinter"); + + _redis.sinterstore(k4, {k1, k2}); + sinter.clear(); + _redis.smembers(k4, std::back_inserter(sinter)); + REDIS_ASSERT(sinter == std::vector({"a"}), "failed to test sinterstore"); + + std::unordered_set sunion; + _redis.sunion({k1, k2}, std::inserter(sunion, sunion.end())); + REDIS_ASSERT(sunion == std::unordered_set({"a", "b", "c"}), + "failed to test sunion"); + + _redis.sunionstore(k5, {k1, k2}); + sunion.clear(); + _redis.smembers(k5, std::inserter(sunion, sunion.end())); + REDIS_ASSERT(sunion == std::unordered_set({"a", "b", "c"}), + "failed to test sunionstore"); + + REDIS_ASSERT(_redis.smove(k5, k6, "a"), "failed to test smove"); +} + +template +void SetCmdTest::_test_sscan() { + auto key = test_key("sscan"); + + KeyDeleter deleter(_redis, key); + + std::unordered_set members = {"m1", "m2", "m3"}; + _redis.sadd(key, members.begin(), members.end()); + + std::unordered_set res; + long long cursor = 0; + while (true) { + cursor = _redis.sscan(key, cursor, "m*", 1, std::inserter(res, res.end())); + if (cursor == 0) { + break; + } + } + + REDIS_ASSERT(res == members, "failed to test sscan"); + + res.clear(); + cursor = 0; + while (true) { + cursor = _redis.sscan(key, cursor, std::inserter(res, res.end())); + if (cursor == 0) { + break; + } + } + + REDIS_ASSERT(res == members, "failed to test sscan"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_SET_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.h new file mode 100644 index 000000000..24873a8b4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.h @@ -0,0 +1,54 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class StreamCmdsTest { +public: + explicit StreamCmdsTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + using Item = std::pair>; + using Result = std::unordered_map>; + + void _test_stream_cmds(); + + void _test_group_cmds(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "stream_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.hpp new file mode 100644 index 000000000..df6f57690 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/stream_cmds_test.hpp @@ -0,0 +1,225 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_HPP + +#include +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void StreamCmdsTest::run() { + _test_stream_cmds(); + + _test_group_cmds(); +} + +template +void StreamCmdsTest::_test_stream_cmds() { + auto key = test_key("stream"); + + KeyDeleter deleter(_redis, key); + + std::vector> attrs = { + {"f1", "v1"}, + {"f2", "v2"} + }; + auto id = "1565427842-0"; + REDIS_ASSERT(_redis.xadd(key, id, attrs.begin(), attrs.end()) == id, + "failed to test xadd"); + + std::vector> keys = {std::make_pair(key, "0-0")}; + Result result; + _redis.xread(keys.begin(), keys.end(), 1, std::inserter(result, result.end())); + + REDIS_ASSERT(result.size() == 1 + && result.find(key) != result.end() + && result[key].size() == 1 + && result[key][0].first == id + && result[key][0].second.size() == 2, + "failed to test xread"); + + result.clear(); + _redis.xread(key, std::string("0-0"), 1, std::inserter(result, result.end())); + + REDIS_ASSERT(result.size() == 1 + && result.find(key) != result.end() + && result[key].size() == 1 + && result[key][0].first == id + && result[key][0].second.size() == 2, + "failed to test xread"); + + result.clear(); + keys = {std::make_pair(key, id)}; + _redis.xread(keys.begin(), + keys.end(), + std::chrono::seconds(1), + 2, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xread"); + + _redis.xread(key, + id, + std::chrono::seconds(1), + 2, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xread"); + + id = "1565427842-1"; + REDIS_ASSERT(_redis.xadd(key, id, attrs.begin(), attrs.end()) == id, + "failed to test xadd"); + + REDIS_ASSERT(_redis.xlen(key) == 2, "failed to test xlen"); + + REDIS_ASSERT(_redis.xtrim(key, 1, false) == 1, "failed to test xtrim"); + + std::vector items; + _redis.xrange(key, "-", "+", std::back_inserter(items)); + REDIS_ASSERT(items.size() == 1 && items[0].first == id, "failed to test xrange"); + + items.clear(); + _redis.xrevrange(key, "+", "-", std::back_inserter(items)); + REDIS_ASSERT(items.size() == 1 && items[0].first == id, "failed to test xrevrange"); + + REDIS_ASSERT(_redis.xdel(key, {id, "111-111"}) == 1, "failed to test xdel"); +} + +template +void StreamCmdsTest::_test_group_cmds() { + auto key = test_key("stream"); + + KeyDeleter deleter(_redis, key); + + auto group = "group"; + auto consumer1 = "consumer1"; + + _redis.xgroup_create(key, group, "$", true); + + std::vector> attrs = { + {"f1", "v1"}, + {"f2", "v2"} + }; + auto id = _redis.xadd(key, "*", attrs.begin(), attrs.end()); + auto keys = {std::make_pair(key, ">")}; + + Result result; + _redis.xreadgroup(group, + consumer1, + keys.begin(), + keys.end(), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 1 + && result.find(key) != result.end() + && result[key].size() == 1 + && result[key][0].first == id, + "failed to test xreadgroup"); + + result.clear(); + _redis.xreadgroup(group, + consumer1, + key, + std::string(">"), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xreadgroup"); + + result.clear(); + _redis.xreadgroup(group, + "not-exist-consumer", + keys.begin(), + keys.end(), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xreadgroup"); + + result.clear(); + _redis.xreadgroup(group, + consumer1, + keys.begin(), + keys.end(), + std::chrono::seconds(1), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xreadgroup"); + + result.clear(); + _redis.xreadgroup(group, + consumer1, + key, + ">", + std::chrono::seconds(1), + 1, + std::inserter(result, result.end())); + REDIS_ASSERT(result.size() == 0, "failed to test xreadgroup"); + + using PendingResult = std::vector>; + PendingResult pending_result; + _redis.xpending(key, group, "-", "+", 1, consumer1, std::back_inserter(pending_result)); + + REDIS_ASSERT(pending_result.size() == 1 + && std::get<0>(pending_result[0]) == id + && std::get<1>(pending_result[0]) == consumer1, + "failed to test xpending"); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + auto consumer2 = "consumer2"; + std::vector items; + auto ids = {id}; + _redis.xclaim(key, + group, + consumer2, + std::chrono::milliseconds(10), + ids, + std::back_inserter(items)); + REDIS_ASSERT(items.size() == 1 && items[0].first == id, "failed to test xclaim"); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + items.clear(); + _redis.xclaim(key, group, consumer1, std::chrono::milliseconds(10), id, std::back_inserter(items)); + REDIS_ASSERT(items.size() == 1 && items[0].first == id, "failed to test xclaim: " + std::to_string(items.size())); + + _redis.xack(key, group, id); + + REDIS_ASSERT(_redis.xgroup_delconsumer(key, group, consumer1) == 0, + "failed to test xgroup_delconsumer"); + + REDIS_ASSERT(_redis.xgroup_delconsumer(key, group, consumer2) == 0, + "failed to test xgroup_delconsumer"); + + REDIS_ASSERT(_redis.xgroup_destroy(key, group) == 1, + "failed to test xgroup_destroy"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_STREAM_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.h new file mode 100644 index 000000000..86788b2bc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.h @@ -0,0 +1,57 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class StringCmdTest { +public: + explicit StringCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_str(); + + void _test_bit(); + + void _test_numeric(); + + void _test_getset(); + + void _test_mgetset(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "string_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.hpp new file mode 100644 index 000000000..b8a0aa3c1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/string_cmds_test.hpp @@ -0,0 +1,247 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_HPP + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void StringCmdTest::run() { + _test_str(); + + _test_bit(); + + _test_numeric(); + + _test_getset(); + + _test_mgetset(); +} + +template +void StringCmdTest::_test_str() { + auto key = test_key("str"); + + KeyDeleter deleter(_redis, key); + + std::string val("value"); + + long long val_size = val.size(); + + auto len1 = _redis.append(key, val); + REDIS_ASSERT(len1 == val_size, "failed to append to non-existent key"); + + auto len2 = _redis.append(key, val); + REDIS_ASSERT(len2 == len1 + val_size, "failed to append to non-empty string"); + + auto len3 = _redis.append(key, {}); + REDIS_ASSERT(len3 == len2, "failed to append empty string"); + + auto len4 = _redis.strlen(key); + REDIS_ASSERT(len4 == len3, "failed to test strlen"); + + REDIS_ASSERT(_redis.del(key) == 1, "failed to remove key"); + + auto len5 = _redis.append(key, {}); + REDIS_ASSERT(len5 == 0, "failed to append empty string to non-existent key"); + + _redis.del(key); + + REDIS_ASSERT(_redis.getrange(key, 0, 2) == "", "failed to test getrange on non-existent key"); + + _redis.set(key, val); + + REDIS_ASSERT(_redis.getrange(key, 1, 2) == val.substr(1, 2), "failed to test getrange"); + + long long new_size = val.size() * 2; + REDIS_ASSERT(_redis.setrange(key, val.size(), val) == new_size, "failed to test setrange"); + REDIS_ASSERT(_redis.getrange(key, 0, -1) == val + val, "failed to test setrange"); +} + +template +void StringCmdTest::_test_bit() { + auto key = test_key("bit"); + + KeyDeleter deleter(_redis, key); + + REDIS_ASSERT(_redis.bitcount(key) == 0, "failed to test bitcount on non-existent key"); + + REDIS_ASSERT(_redis.getbit(key, 5) == 0, "failed to test getbit"); + + REDIS_ASSERT(_redis.template command("SETBIT", key, 1, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 3, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 7, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 10, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 10, 0) == 1, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 11, 1) == 0, + "failed to test setbit"); + REDIS_ASSERT(_redis.template command("SETBIT", key, 21, 1) == 0, + "failed to test setbit"); + + // key -> 01010001, 00010000, 00000100 + + REDIS_ASSERT(_redis.getbit(key, 1) == 1, "failed to test getbit"); + REDIS_ASSERT(_redis.getbit(key, 2) == 0, "failed to test getbit"); + REDIS_ASSERT(_redis.getbit(key, 7) == 1, "failed to test getbit"); + REDIS_ASSERT(_redis.getbit(key, 10) == 0, "failed to test getbit"); + REDIS_ASSERT(_redis.getbit(key, 100) == 0, "failed to test getbit"); + + REDIS_ASSERT(_redis.bitcount(key) == 5, "failed to test bitcount"); + REDIS_ASSERT(_redis.bitcount(key, 0, 0) == 3, "failed to test bitcount"); + REDIS_ASSERT(_redis.bitcount(key, 0, 1) == 4, "failed to test bitcount"); + REDIS_ASSERT(_redis.bitcount(key, -2, -1) == 2, "failed to test bitcount"); + + REDIS_ASSERT(_redis.bitpos(key, 1) == 1, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 0) == 0, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 1, 1, 1) == 11, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 0, 1, 1) == 8, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 1, -1, -1) == 21, "failed to test bitpos"); + REDIS_ASSERT(_redis.bitpos(key, 0, -1, -1) == 16, "failed to test bitpos"); + + auto dest_key = test_key("bitop_dest"); + auto src_key1 = test_key("bitop_src1"); + auto src_key2 = test_key("bitop_src2"); + + KeyDeleter deleters(_redis, {dest_key, src_key1, src_key2}); + + // src_key1 -> 00010000 + _redis.template command("SETBIT", src_key1, 3, 1); + + // src_key2 -> 00000000, 00001000 + _redis.template command("SETBIT", src_key2, 12, 1); + + REDIS_ASSERT(_redis.bitop(BitOp::AND, dest_key, {src_key1, src_key2}) == 2, + "failed to test bitop"); + + // dest_key -> 00000000, 00000000 + auto v = _redis.get(dest_key); + REDIS_ASSERT(v && *v == std::string(2, 0), "failed to test bitop"); + + REDIS_ASSERT(_redis.bitop(BitOp::NOT, dest_key, src_key1) == 1, + "failed to test bitop"); + + // dest_key -> 11101111 + v = _redis.get(dest_key); + REDIS_ASSERT(v && *v == std::string(1, 0xEF), "failed to test bitop"); +} + +template +void StringCmdTest::_test_numeric() { + auto key = test_key("numeric"); + + KeyDeleter deleter(_redis, key); + + REDIS_ASSERT(_redis.incr(key) == 1, "failed to test incr"); + REDIS_ASSERT(_redis.decr(key) == 0, "failed to test decr"); + REDIS_ASSERT(_redis.incrby(key, 3) == 3, "failed to test incrby"); + REDIS_ASSERT(_redis.decrby(key, 3) == 0, "failed to test decrby"); + REDIS_ASSERT(_redis.incrby(key, -3) == -3, "failed to test incrby"); + REDIS_ASSERT(_redis.decrby(key, -3) == 0, "failed to test incrby"); + REDIS_ASSERT(_redis.incrbyfloat(key, 1.5) == 1.5, "failed to test incrbyfloat"); +} + +template +void StringCmdTest::_test_getset() { + auto key = test_key("getset"); + auto non_exist_key = test_key("non-existent"); + + KeyDeleter deleter(_redis, {key, non_exist_key}); + + std::string val("value"); + REDIS_ASSERT(_redis.set(key, val), "failed to test set"); + + auto v = _redis.get(key); + REDIS_ASSERT(v && *v == val, "failed to test get"); + + v = _redis.getset(key, val + val); + REDIS_ASSERT(v && *v == val, "failed to test get"); + + REDIS_ASSERT(!_redis.set(key, val, std::chrono::milliseconds(0), UpdateType::NOT_EXIST), + "failed to test set with NOT_EXIST type"); + REDIS_ASSERT(!_redis.set(non_exist_key, val, std::chrono::milliseconds(0), UpdateType::EXIST), + "failed to test set with EXIST type"); + + REDIS_ASSERT(!_redis.setnx(key, val), "failed to test setnx"); + REDIS_ASSERT(_redis.setnx(non_exist_key, val), "failed to test setnx"); + + auto ttl = std::chrono::seconds(10); + + _redis.set(key, val, ttl); + REDIS_ASSERT(_redis.ttl(key) <= ttl.count(), "failed to test set key with ttl"); + + _redis.setex(key, ttl, val); + REDIS_ASSERT(_redis.ttl(key) <= ttl.count(), "failed to test setex"); + + auto pttl = std::chrono::milliseconds(10000); + + _redis.psetex(key, ttl, val); + REDIS_ASSERT(_redis.pttl(key) <= pttl.count(), "failed to test psetex"); +} + +template +void StringCmdTest::_test_mgetset() { + auto kvs = {std::make_pair(test_key("k1"), "v1"), + std::make_pair(test_key("k2"), "v2"), + std::make_pair(test_key("k3"), "v3")}; + + std::vector keys; + std::vector vals; + for (const auto &kv : kvs) { + keys.push_back(kv.first); + vals.push_back(kv.second); + } + + KeyDeleter deleter(_redis, keys.begin(), keys.end()); + + _redis.mset(kvs); + + std::vector res; + _redis.mget(keys.begin(), keys.end(), std::back_inserter(res)); + + REDIS_ASSERT(res.size() == kvs.size(), "failed to test mget"); + + std::vector res_vals; + for (const auto &ele : res) { + REDIS_ASSERT(bool(ele), "failed to test mget"); + + res_vals.push_back(*ele); + } + + REDIS_ASSERT(vals == res_vals, "failed to test mget"); + + REDIS_ASSERT(!_redis.msetnx(kvs), "failed to test msetnx"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_STRING_CMDS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/test_main.cpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/test_main.cpp new file mode 100644 index 000000000..dd2880d1a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/test_main.cpp @@ -0,0 +1,303 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#include +#include +#include +#include +#include +#include "sanity_test.h" +#include "connection_cmds_test.h" +#include "keys_cmds_test.h" +#include "string_cmds_test.h" +#include "list_cmds_test.h" +#include "hash_cmds_test.h" +#include "set_cmds_test.h" +#include "zset_cmds_test.h" +#include "hyperloglog_cmds_test.h" +#include "geo_cmds_test.h" +#include "script_cmds_test.h" +#include "pubsub_test.h" +#include "pipeline_transaction_test.h" +#include "threads_test.h" +#include "stream_cmds_test.h" +#include "benchmark_test.h" + +namespace { + +void print_help(); + +auto parse_options(int argc, char **argv) + -> std::tuple, + sw::redis::Optional, + sw::redis::Optional>; + +template +void run_test(const sw::redis::ConnectionOptions &opts); + +template +void run_benchmark(const sw::redis::ConnectionOptions &opts, + const sw::redis::test::BenchmarkOptions &benchmark_opts); + +} + +int main(int argc, char **argv) { + try { + sw::redis::Optional opts; + sw::redis::Optional cluster_node_opts; + sw::redis::Optional benchmark_opts; + std::tie(opts, cluster_node_opts, benchmark_opts) = parse_options(argc, argv); + + if (opts) { + std::cout << "Testing Redis..." << std::endl; + + if (benchmark_opts) { + run_benchmark(*opts, *benchmark_opts); + } else { + run_test(*opts); + } + } + + if (cluster_node_opts) { + std::cout << "Testing RedisCluster..." << std::endl; + + if (benchmark_opts) { + run_benchmark(*cluster_node_opts, *benchmark_opts); + } else { + run_test(*cluster_node_opts); + } + } + + std::cout << "Pass all tests" << std::endl; + } catch (const sw::redis::Error &e) { + std::cerr << "Test failed: " << e.what() << std::endl; + return -1; + } + + return 0; +} + +namespace { + +void print_help() { + std::cerr << "Usage: test_redis++ -h host -p port" + << " -n cluster_node -c cluster_port [-a auth] [-b]\n\n"; + std::cerr << "See https://github.com/sewenew/redis-plus-plus#run-tests-optional" + << " for details on how to run test" << std::endl; +} + +auto parse_options(int argc, char **argv) + -> std::tuple, + sw::redis::Optional, + sw::redis::Optional> { + std::string host; + int port = 0; + std::string auth; + std::string cluster_node; + int cluster_port = 0; + bool benchmark = false; + sw::redis::test::BenchmarkOptions tmp_benchmark_opts; + + int opt = 0; + while ((opt = getopt(argc, argv, "h:p:a:n:c:k:v:r:t:bs:")) != -1) { + try { + switch (opt) { + case 'h': + host = optarg; + break; + + case 'p': + port = std::stoi(optarg); + break; + + case 'a': + auth = optarg; + break; + + case 'n': + cluster_node = optarg; + break; + + case 'c': + cluster_port = std::stoi(optarg); + break; + + case 'b': + benchmark = true; + break; + + case 'k': + tmp_benchmark_opts.key_len = std::stoi(optarg); + break; + + case 'v': + tmp_benchmark_opts.val_len = std::stoi(optarg); + break; + + case 'r': + tmp_benchmark_opts.total_request_num = std::stoi(optarg); + break; + + case 't': + tmp_benchmark_opts.thread_num = std::stoi(optarg); + break; + + case 's': + tmp_benchmark_opts.pool_size = std::stoi(optarg); + break; + + default: + throw sw::redis::Error("Unknow command line option"); + break; + } + } catch (const sw::redis::Error &e) { + print_help(); + throw; + } catch (const std::exception &e) { + print_help(); + throw sw::redis::Error("Invalid command line option"); + } + } + + sw::redis::Optional opts; + if (!host.empty() && port > 0) { + sw::redis::ConnectionOptions tmp; + tmp.host = host; + tmp.port = port; + tmp.password = auth; + + opts = sw::redis::Optional(tmp); + } + + sw::redis::Optional cluster_opts; + if (!cluster_node.empty() && cluster_port > 0) { + sw::redis::ConnectionOptions tmp; + tmp.host = cluster_node; + tmp.port = cluster_port; + tmp.password = auth; + + cluster_opts = sw::redis::Optional(tmp); + } + + if (!opts && !cluster_opts) { + print_help(); + throw sw::redis::Error("Invalid connection options"); + } + + sw::redis::Optional benchmark_opts; + if (benchmark) { + benchmark_opts = sw::redis::Optional(tmp_benchmark_opts); + } + + return std::make_tuple(std::move(opts), std::move(cluster_opts), std::move(benchmark_opts)); +} + +template +void run_test(const sw::redis::ConnectionOptions &opts) { + auto instance = RedisInstance(opts); + + sw::redis::test::SanityTest sanity_test(opts, instance); + sanity_test.run(); + + std::cout << "Pass sanity tests" << std::endl; + + sw::redis::test::ConnectionCmdTest connection_test(instance); + connection_test.run(); + + std::cout << "Pass connection commands tests" << std::endl; + + sw::redis::test::KeysCmdTest keys_test(instance); + keys_test.run(); + + std::cout << "Pass keys commands tests" << std::endl; + + sw::redis::test::StringCmdTest string_test(instance); + string_test.run(); + + std::cout << "Pass string commands tests" << std::endl; + + sw::redis::test::ListCmdTest list_test(instance); + list_test.run(); + + std::cout << "Pass list commands tests" << std::endl; + + sw::redis::test::HashCmdTest hash_test(instance); + hash_test.run(); + + std::cout << "Pass hash commands tests" << std::endl; + + sw::redis::test::SetCmdTest set_test(instance); + set_test.run(); + + std::cout << "Pass set commands tests" << std::endl; + + sw::redis::test::ZSetCmdTest zset_test(instance); + zset_test.run(); + + std::cout << "Pass zset commands tests" << std::endl; + + sw::redis::test::HyperloglogCmdTest hll_test(instance); + hll_test.run(); + + std::cout << "Pass hyperloglog commands tests" << std::endl; + + sw::redis::test::GeoCmdTest geo_test(instance); + geo_test.run(); + + std::cout << "Pass geo commands tests" << std::endl; + + sw::redis::test::ScriptCmdTest script_test(instance); + script_test.run(); + + std::cout << "Pass script commands tests" << std::endl; + + sw::redis::test::PubSubTest pubsub_test(instance); + pubsub_test.run(); + + std::cout << "Pass pubsub tests" << std::endl; + + sw::redis::test::PipelineTransactionTest pipe_tx_test(instance); + pipe_tx_test.run(); + + std::cout << "Pass pipeline and transaction tests" << std::endl; + + sw::redis::test::ThreadsTest threads_test(opts); + threads_test.run(); + + std::cout << "Pass threads tests" << std::endl; + + sw::redis::test::StreamCmdsTest stream_test(instance); + stream_test.run(); + + std::cout << "Pass stream commands tests" << std::endl; +} + +template +void run_benchmark(const sw::redis::ConnectionOptions &opts, + const sw::redis::test::BenchmarkOptions &benchmark_opts) { + std::cout << "Benchmark test options:" << std::endl; + std::cout << " Thread number: " << benchmark_opts.thread_num << std::endl; + std::cout << " Connection pool size: " << benchmark_opts.pool_size << std::endl; + std::cout << " Length of key: " << benchmark_opts.key_len << std::endl; + std::cout << " Length of value: " << benchmark_opts.val_len << std::endl; + + auto instance = RedisInstance(opts); + + sw::redis::test::BenchmarkTest benchmark_test(benchmark_opts, instance); + benchmark_test.run(); +} + +} diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.h new file mode 100644 index 000000000..aee307ec2 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.h @@ -0,0 +1,51 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ThreadsTest { +public: + explicit ThreadsTest(const ConnectionOptions &opts) : _opts(opts) {} + + void run(); + +private: + void _test_multithreads(RedisInstance redis, int threads_num, int times); + + void _test_timeout(); + + ConnectionOptions _opts; +}; + +} + +} + +} + +#include "threads_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.hpp new file mode 100644 index 000000000..24bee9454 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/threads_test.hpp @@ -0,0 +1,147 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_HPP + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ThreadsTest::run() { + // 100 * 10000 = 1 million writes + auto thread_num = 100; + auto times = 10000; + + // Default pool options: single connection and wait forever. + _test_multithreads(RedisInstance(_opts), thread_num, times); + + // Pool with 10 connections. + ConnectionPoolOptions pool_opts; + pool_opts.size = 10; + _test_multithreads(RedisInstance(_opts, pool_opts), thread_num, times); + + _test_timeout(); +} + +template +void ThreadsTest::_test_multithreads(RedisInstance redis, + int thread_num, + int times) { + std::vector keys; + keys.reserve(thread_num); + for (auto idx = 0; idx != thread_num; ++idx) { + auto key = test_key("multi-threads::" + std::to_string(idx)); + keys.push_back(key); + } + + using DeleterUPtr = std::unique_ptr>; + std::vector deleters; + for (const auto &key : keys) { + deleters.emplace_back(new KeyDeleter(redis, key)); + } + + std::vector workers; + workers.reserve(thread_num); + for (const auto &key : keys) { + workers.emplace_back([&redis, key, times]() { + try { + for (auto i = 0; i != times; ++i) { + redis.incr(key); + } + } catch (...) { + // Something bad happens. + return; + } + }); + } + + for (auto &worker : workers) { + worker.join(); + } + + for (const auto &key : keys) { + auto val = redis.get(key); + REDIS_ASSERT(bool(val), "failed to test multithreads, cannot get value of " + key); + + auto num = std::stoi(*val); + REDIS_ASSERT(num == times, "failed to test multithreads, num: " + + *val + ", times: " + std::to_string(times)); + } +} + +template +void ThreadsTest::_test_timeout() { + using namespace std::chrono; + + ConnectionPoolOptions pool_opts; + pool_opts.size = 1; + pool_opts.wait_timeout = milliseconds(100); + + auto redis = RedisInstance(_opts, pool_opts); + + auto key = test_key("key"); + + std::atomic slow_get_is_running{false}; + auto slow_get = [&slow_get_is_running](Connection &connection, const StringView &key) { + slow_get_is_running = true; + + // Sleep a while to simulate a slow get. + std::this_thread::sleep_for(seconds(5)); + + connection.send("GET %b", key.data(), key.size()); + }; + auto slow_get_thread = std::thread([&redis, slow_get, &key]() { + redis.command(slow_get, key); + }); + + auto get_thread = std::thread([&redis, &slow_get_is_running, &key]() { + try { + while (!slow_get_is_running) { + std::this_thread::sleep_for(milliseconds(10)); + } + + redis.get(key); + + // Slow get is running, this thread should + // timeout before obtaining the connection. + // So it never reaches here. + REDIS_ASSERT(false, "failed to test pool timeout"); + } catch (const Error &err) { + // This thread timeout. + } + }); + + slow_get_thread.join(); + get_thread.join(); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_THREADS_TEST_HPP diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/utils.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/utils.h new file mode 100644 index 000000000..7c430e2d4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/utils.h @@ -0,0 +1,96 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_UTILS_H +#define SEWENEW_REDISPLUSPLUS_TEST_UTILS_H + +#include +#include +#include + +#define REDIS_ASSERT(condition, msg) \ + sw::redis::test::redis_assert((condition), (msg), __FILE__, __LINE__) + +namespace sw { + +namespace redis { + +namespace test { + +inline void redis_assert(bool condition, + const std::string &msg, + const std::string &file, + int line) { + if (!condition) { + auto err_msg = "ASSERT: " + msg + ". " + file + ":" + std::to_string(line); + throw Error(err_msg); + } +} + +inline std::string test_key(const std::string &k) { + // Key prefix with hash tag, + // so that we can call multiple-key commands on RedisCluster. + return "{sw::redis::test}::" + k; +} + +template +void cluster_specializing_test(Test &test, void (Test::*func)(Redis &instance), Redis &instance) { + (test.*func)(instance); +} + +template +void cluster_specializing_test(Test &test, + void (Test::*func)(Redis &instance), + RedisCluster &cluster) { + auto instance = cluster.redis("hash-tag"); + (test.*func)(instance); +} + +template +class KeyDeleter { +public: + template + KeyDeleter(RedisInstance &redis, Input first, Input last) : _redis(redis), _keys(first, last) { + _delete(); + } + + KeyDeleter(RedisInstance &redis, std::initializer_list il) : + KeyDeleter(redis, il.begin(), il.end()) {} + + KeyDeleter(RedisInstance &redis, const std::string &key) : KeyDeleter(redis, {key}) {} + + ~KeyDeleter() { + _delete(); + } + +private: + void _delete() { + if (!_keys.empty()) { + _redis.del(_keys.begin(), _keys.end()); + } + } + + RedisInstance &_redis; + std::vector _keys; +}; + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_UTILS_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.h b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.h new file mode 100644 index 000000000..56fd0b96f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.h @@ -0,0 +1,61 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_H +#define SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_H + +#include + +namespace sw { + +namespace redis { + +namespace test { + +template +class ZSetCmdTest { +public: + explicit ZSetCmdTest(RedisInstance &instance) : _redis(instance) {} + + void run(); + +private: + void _test_zset(); + + void _test_zscan(); + + void _test_range(); + + void _test_lex(); + + void _test_multi_zset(); + + void _test_zpop(); + + void _test_bzpop(); + + RedisInstance &_redis; +}; + +} + +} + +} + +#include "zset_cmds_test.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_H diff --git a/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.hpp b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.hpp new file mode 100644 index 000000000..fb331bb0a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/test/src/sw/redis++/zset_cmds_test.hpp @@ -0,0 +1,350 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_HPP +#define SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_HPP + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace test { + +template +void ZSetCmdTest::run() { + _test_zset(); + + _test_zscan(); + + _test_range(); + + _test_lex(); + + _test_multi_zset(); + + _test_zpop(); + + _test_bzpop(); +} + +template +void ZSetCmdTest::_test_zset() { + auto key = test_key("zset"); + + KeyDeleter deleter(_redis, key); + + std::map s = { + std::make_pair("m1", 1.2), + std::make_pair("m2", 2), + std::make_pair("m3", 3), + }; + + const auto &ele = *(s.begin()); + REDIS_ASSERT(_redis.zadd(key, ele.first, ele.second, UpdateType::EXIST) == 0, + "failed to test zadd with noexistent member"); + + REDIS_ASSERT(_redis.zadd(key, s.begin(), s.end()) == 3, "failed to test zadd"); + + REDIS_ASSERT(_redis.zadd(key, ele.first, ele.second, UpdateType::NOT_EXIST) == 0, + "failed to test zadd with exist member"); + + REDIS_ASSERT(_redis.zadd(key, s.begin(), s.end(), UpdateType::ALWAYS, true) == 0, + "failed to test zadd"); + + REDIS_ASSERT(_redis.zcard(key) == 3, "failed to test zcard"); + + auto rank = _redis.zrank(key, "m2"); + REDIS_ASSERT(bool(rank) && *rank == 1, "failed to test zrank"); + rank = _redis.zrevrank(key, "m4"); + REDIS_ASSERT(!rank, "failed to test zrevrank with nonexistent member"); + + auto score = _redis.zscore(key, "m4"); + REDIS_ASSERT(!score, "failed to test zscore with nonexistent member"); + + REDIS_ASSERT(_redis.zincrby(key, 1, "m3") == 4, "failed to test zincrby"); + + score = _redis.zscore(key, "m3"); + REDIS_ASSERT(score && *score == 4, "failed to test zscore"); + + REDIS_ASSERT(_redis.zrem(key, "m1") == 1, "failed to test zrem"); + REDIS_ASSERT(_redis.zrem(key, {"m1", "m2", "m3", "m4"}) == 2, "failed to test zrem"); +} + +template +void ZSetCmdTest::_test_zscan() { + auto key = test_key("zscan"); + + KeyDeleter deleter(_redis, key); + + std::map s = { + std::make_pair("m1", 1.2), + std::make_pair("m2", 2), + std::make_pair("m3", 3), + }; + _redis.zadd(key, s.begin(), s.end()); + + std::map res; + auto cursor = 0; + while (true) { + cursor = _redis.zscan(key, cursor, "m*", 2, std::inserter(res, res.end())); + if (cursor == 0) { + break; + } + } + REDIS_ASSERT(res == s, "failed to test zscan"); +} + +template +void ZSetCmdTest::_test_range() { + auto key = test_key("range"); + + KeyDeleter deleter(_redis, key); + + std::map s = { + std::make_pair("m1", 1), + std::make_pair("m2", 2), + std::make_pair("m3", 3), + }; + _redis.zadd(key, s.begin(), s.end()); + + REDIS_ASSERT(_redis.zcount(key, UnboundedInterval{}) == 3, "failed to test zcount"); + + std::vector members; + _redis.zrange(key, 0, -1, std::back_inserter(members)); + REDIS_ASSERT(members.size() == s.size(), "failed to test zrange"); + for (const auto &mem : {"m1", "m2", "m3"}) { + REDIS_ASSERT(std::find(members.begin(), members.end(), mem) != members.end(), + "failed to test zrange"); + } + + std::map res; + _redis.zrange(key, 0, -1, std::inserter(res, res.end())); + REDIS_ASSERT(s == res, "failed to test zrange with score"); + + members.clear(); + _redis.zrevrange(key, 0, 0, std::back_inserter(members)); + REDIS_ASSERT(members.size() == 1 && members[0] == "m3", "failed to test zrevrange"); + + res.clear(); + _redis.zrevrange(key, 0, 0, std::inserter(res, res.end())); + REDIS_ASSERT(res.size() == 1 && res.find("m3") != res.end() && res["m3"] == 3, + "failed to test zrevrange with score"); + + members.clear(); + _redis.zrangebyscore(key, UnboundedInterval{}, std::back_inserter(members)); + REDIS_ASSERT(members.size() == s.size(), "failed to test zrangebyscore"); + for (const auto &mem : {"m1", "m2", "m3"}) { + REDIS_ASSERT(std::find(members.begin(), members.end(), mem) != members.end(), + "failed to test zrangebyscore"); + } + + members.clear(); + _redis.zrangebyscore(key, + BoundedInterval(1, 2, BoundType::RIGHT_OPEN), + std::back_inserter(members)); + REDIS_ASSERT(members.size() == 1 && members[0] == "m1", "failed to test zrangebyscore"); + + res.clear(); + _redis.zrangebyscore(key, + LeftBoundedInterval(2, BoundType::OPEN), + std::inserter(res, res.end())); + REDIS_ASSERT(res.size() == 1 && res.find("m3") != res.end() && res["m3"] == 3, + "failed to test zrangebyscore"); + + members.clear(); + _redis.zrevrangebyscore(key, + BoundedInterval(1, 3, BoundType::CLOSED), + std::back_inserter(members)); + REDIS_ASSERT(members == std::vector({"m3", "m2", "m1"}), + "failed to test zrevrangebyscore"); + + res.clear(); + _redis.zrevrangebyscore(key, + RightBoundedInterval(1, BoundType::LEFT_OPEN), + std::inserter(res, res.end())); + REDIS_ASSERT(res.size() == 1 && res.find("m1") != res.end() && res["m1"] == 1, + "failed to test zrevrangebyscore"); + + REDIS_ASSERT(_redis.zremrangebyrank(key, 0, 0) == 1, "failed to test zremrangebyrank"); + + REDIS_ASSERT(_redis.zremrangebyscore(key, + BoundedInterval(2, 3, BoundType::LEFT_OPEN)) == 1, + "failed to test zremrangebyscore"); +} + +template +void ZSetCmdTest::_test_lex() { + auto key = test_key("lex"); + + KeyDeleter deleter(_redis, key); + + auto s = { + std::make_pair("m1", 0), + std::make_pair("m2", 0), + std::make_pair("m3", 0), + }; + _redis.zadd(key, s.begin(), s.end()); + + REDIS_ASSERT(_redis.zlexcount(key, UnboundedInterval{}) == 3, + "failed to test zlexcount"); + + std::vector members; + _redis.zrangebylex(key, + LeftBoundedInterval("m2", BoundType::OPEN), + std::back_inserter(members)); + REDIS_ASSERT(members.size() == 1 && members[0] == "m3", + "failed to test zrangebylex"); + + members.clear(); + _redis.zrevrangebylex(key, + RightBoundedInterval("m1", BoundType::LEFT_OPEN), + std::back_inserter(members)); + REDIS_ASSERT(members.size() == 1 && members[0] == "m1", + "failed to test zrevrangebylex"); + + REDIS_ASSERT(_redis.zremrangebylex(key, + BoundedInterval("m1", "m3", BoundType::OPEN)) == 1, + "failed to test zremrangebylex"); +} + +template +void ZSetCmdTest::_test_multi_zset() { + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + auto k3 = test_key("k3"); + + KeyDeleter deleter(_redis, {k1, k2, k3}); + + _redis.zadd(k1, {std::make_pair("a", 1), std::make_pair("b", 2)}); + _redis.zadd(k2, {std::make_pair("a", 2), std::make_pair("c", 3)}); + + REDIS_ASSERT(_redis.zinterstore(k3, {k1, k2}) == 1, "failed to test zinterstore"); + auto score = _redis.zscore(k3, "a"); + REDIS_ASSERT(bool(score) && *score == 3, "failed to test zinterstore"); + + REDIS_ASSERT(_redis.zinterstore(k3, k1, 2) == 2, "failed to test zinterstore"); + + _redis.del(k3); + + REDIS_ASSERT(_redis.zinterstore(k3, {k1, k2}, Aggregation::MAX) == 1, + "failed to test zinterstore"); + score = _redis.zscore(k3, "a"); + REDIS_ASSERT(bool(score) && *score == 2, "failed to test zinterstore"); + + _redis.del(k3); + + REDIS_ASSERT(_redis.zunionstore(k3, + {std::make_pair(k1, 1), std::make_pair(k2, 2)}, + Aggregation::MIN) == 3, + "failed to test zunionstore"); + std::vector> res; + _redis.zrange(k3, 0, -1, std::back_inserter(res)); + for (const auto &ele : res) { + if (ele.first == "a") { + REDIS_ASSERT(ele.second == 1, "failed to test zunionstore"); + } else if (ele.first == "b") { + REDIS_ASSERT(ele.second == 2, "failed to test zunionstore"); + } else if (ele.first == "c") { + REDIS_ASSERT(ele.second == 6, "failed to test zunionstore"); + } else { + REDIS_ASSERT(false, "failed to test zuionstore"); + } + } + + REDIS_ASSERT(_redis.zunionstore(k3, k1, 2) == 2, "failed to test zunionstore"); +} + +template +void ZSetCmdTest::_test_zpop() { + auto key = test_key("zpop"); + + KeyDeleter deleter(_redis, key); + + _redis.zadd(key, {std::make_pair("m1", 1.1), + std::make_pair("m2", 2.2), + std::make_pair("m3", 3.3), + std::make_pair("m4", 4.4), + std::make_pair("m5", 5.5), + std::make_pair("m6", 6.6)}); + + auto item = _redis.zpopmax(key); + REDIS_ASSERT(item && item->first == "m6", "failed to test zpopmax"); + + item = _redis.zpopmin(key); + REDIS_ASSERT(item && item->first == "m1", "failed to test zpopmin"); + + std::vector> vec; + _redis.zpopmax(key, 2, std::back_inserter(vec)); + REDIS_ASSERT(vec.size() == 2 && vec[0].first == "m5" && vec[1].first == "m4", + "failed to test zpopmax"); + + std::unordered_map m; + _redis.zpopmin(key, 2, std::inserter(m, m.end())); + REDIS_ASSERT(m.size() == 2 && m.find("m3") != m.end() && m.find("m2") != m.end(), + "failed to test zpopmin"); +} + +template +void ZSetCmdTest::_test_bzpop() { + auto key1 = test_key("bzpop1"); + auto key2 = test_key("bzpop2"); + + KeyDeleter deleter(_redis, {key1, key2}); + + _redis.zadd(key1, {std::make_pair("m1", 1.1), + std::make_pair("m2", 2.2), + std::make_pair("m3", 3.3), + std::make_pair("m4", 4.4), + std::make_pair("m5", 5.5), + std::make_pair("m6", 6.6)}); + + _redis.zadd(key2, {std::make_pair("m1", 1.1), + std::make_pair("m2", 2.2), + std::make_pair("m3", 3.3), + std::make_pair("m4", 4.4), + std::make_pair("m5", 5.5), + std::make_pair("m6", 6.6)}); + + auto item = _redis.bzpopmax(key1); + REDIS_ASSERT(item && std::get<0>(*item) == key1 && std::get<1>(*item) == "m6", + "failed to test bzpopmax"); + + item = _redis.bzpopmin(key1, std::chrono::seconds(1)); + REDIS_ASSERT(item && std::get<0>(*item) == key1 && std::get<1>(*item) == "m1", + "failed to test zpopmin"); + + item = _redis.bzpopmax({key1, key2}, std::chrono::seconds(1)); + REDIS_ASSERT(item && std::get<0>(*item) == key1 && std::get<1>(*item) == "m5", + "failed to test zpopmax"); + + item = _redis.bzpopmin({key2, key1}); + REDIS_ASSERT(item && std::get<0>(*item) == key2 && std::get<1>(*item) == "m1", + "failed to test zpopmin"); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TEST_ZSET_CMDS_TEST_HPP From d699116795b5e1db30798b8abb59b4fa3e70ea13 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 16:02:49 -0700 Subject: [PATCH 04/13] mac deps --- controller/PostgreSQL.cpp | 22 +- controller/PostgreSQL.hpp | 4 + controller/Redis.hpp | 15 + ext/hiredis-0.14.1/.gitignore | 1 - ext/hiredis-0.14.1/include/hiredis/alloc.h | 53 + .../include/hiredis}/async.h | 2 +- .../include/hiredis}/dict.h | 0 ext/hiredis-0.14.1/include/hiredis/fmacros.h | 12 + .../include/hiredis}/hiredis.h | 35 +- .../include/hiredis}/net.h | 4 - .../include/hiredis}/read.h | 26 +- ext/hiredis-0.14.1/include/hiredis/sds.h | 273 + ext/hiredis-0.14.1/include/hiredis/sdsalloc.h | 42 + .../include/hiredis}/win32.h | 0 ext/hiredis-vip-0.3.0/.gitignore | 7 - ext/hiredis-vip-0.3.0/.travis.yml | 16 - ext/hiredis-vip-0.3.0/CHANGELOG.md | 16 - ext/hiredis-vip-0.3.0/COPYING | 29 - ext/hiredis-vip-0.3.0/Makefile | 205 - ext/hiredis-vip-0.3.0/README.md | 255 - ext/hiredis-vip-0.3.0/adapters/ae.h | 154 - ext/hiredis-vip-0.3.0/adapters/glib.h | 153 - ext/hiredis-vip-0.3.0/adapters/libev.h | 147 - ext/hiredis-vip-0.3.0/adapters/libevent.h | 135 - ext/hiredis-vip-0.3.0/adapters/libuv.h | 122 - ext/hiredis-vip-0.3.0/adlist.c | 341 -- ext/hiredis-vip-0.3.0/adlist.h | 93 - ext/hiredis-vip-0.3.0/async.c | 691 --- ext/hiredis-vip-0.3.0/command.c | 1700 ------ ext/hiredis-vip-0.3.0/command.h | 179 - ext/hiredis-vip-0.3.0/crc16.c | 87 - ext/hiredis-vip-0.3.0/dict.c | 338 -- ext/hiredis-vip-0.3.0/examples/example-ae.c | 62 - ext/hiredis-vip-0.3.0/examples/example-glib.c | 73 - .../examples/example-libev.c | 52 - .../examples/example-libevent.c | 53 - .../examples/example-libuv.c | 53 - ext/hiredis-vip-0.3.0/examples/example.c | 78 - ext/hiredis-vip-0.3.0/fmacros.h | 23 - ext/hiredis-vip-0.3.0/hiarray.c | 188 - ext/hiredis-vip-0.3.0/hiarray.h | 56 - ext/hiredis-vip-0.3.0/hircluster.c | 4747 ----------------- ext/hiredis-vip-0.3.0/hircluster.h | 178 - ext/hiredis-vip-0.3.0/hiredis.c | 1021 ---- ext/hiredis-vip-0.3.0/hiutil.c | 554 -- ext/hiredis-vip-0.3.0/hiutil.h | 265 - ext/hiredis-vip-0.3.0/net.c | 458 -- ext/hiredis-vip-0.3.0/read.c | 525 -- ext/hiredis-vip-0.3.0/sds.c | 1095 ---- ext/hiredis-vip-0.3.0/sds.h | 105 - ext/hiredis-vip-0.3.0/test.c | 806 --- make-mac.mk | 5 +- objects.mk | 1 - 53 files changed, 434 insertions(+), 15121 deletions(-) create mode 100644 controller/Redis.hpp create mode 100644 ext/hiredis-0.14.1/include/hiredis/alloc.h rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/async.h (98%) rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/dict.h (100%) create mode 100644 ext/hiredis-0.14.1/include/hiredis/fmacros.h rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/hiredis.h (83%) rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/net.h (97%) rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/read.h (80%) create mode 100644 ext/hiredis-0.14.1/include/hiredis/sds.h create mode 100644 ext/hiredis-0.14.1/include/hiredis/sdsalloc.h rename ext/{hiredis-vip-0.3.0 => hiredis-0.14.1/include/hiredis}/win32.h (100%) delete mode 100644 ext/hiredis-vip-0.3.0/.gitignore delete mode 100644 ext/hiredis-vip-0.3.0/.travis.yml delete mode 100644 ext/hiredis-vip-0.3.0/CHANGELOG.md delete mode 100644 ext/hiredis-vip-0.3.0/COPYING delete mode 100644 ext/hiredis-vip-0.3.0/Makefile delete mode 100644 ext/hiredis-vip-0.3.0/README.md delete mode 100644 ext/hiredis-vip-0.3.0/adapters/ae.h delete mode 100644 ext/hiredis-vip-0.3.0/adapters/glib.h delete mode 100644 ext/hiredis-vip-0.3.0/adapters/libev.h delete mode 100644 ext/hiredis-vip-0.3.0/adapters/libevent.h delete mode 100644 ext/hiredis-vip-0.3.0/adapters/libuv.h delete mode 100644 ext/hiredis-vip-0.3.0/adlist.c delete mode 100644 ext/hiredis-vip-0.3.0/adlist.h delete mode 100644 ext/hiredis-vip-0.3.0/async.c delete mode 100644 ext/hiredis-vip-0.3.0/command.c delete mode 100644 ext/hiredis-vip-0.3.0/command.h delete mode 100644 ext/hiredis-vip-0.3.0/crc16.c delete mode 100644 ext/hiredis-vip-0.3.0/dict.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-ae.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-glib.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-libev.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-libevent.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example-libuv.c delete mode 100644 ext/hiredis-vip-0.3.0/examples/example.c delete mode 100644 ext/hiredis-vip-0.3.0/fmacros.h delete mode 100644 ext/hiredis-vip-0.3.0/hiarray.c delete mode 100644 ext/hiredis-vip-0.3.0/hiarray.h delete mode 100644 ext/hiredis-vip-0.3.0/hircluster.c delete mode 100644 ext/hiredis-vip-0.3.0/hircluster.h delete mode 100644 ext/hiredis-vip-0.3.0/hiredis.c delete mode 100644 ext/hiredis-vip-0.3.0/hiutil.c delete mode 100644 ext/hiredis-vip-0.3.0/hiutil.h delete mode 100644 ext/hiredis-vip-0.3.0/net.c delete mode 100644 ext/hiredis-vip-0.3.0/read.c delete mode 100644 ext/hiredis-vip-0.3.0/sds.c delete mode 100644 ext/hiredis-vip-0.3.0/sds.h delete mode 100644 ext/hiredis-vip-0.3.0/test.c diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 91cbdb789..0640cb8ee 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -78,6 +78,8 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R , _waitNoticePrinted(false) , _listenPort(listenPort) , _rc(rc) + , _redis(NULL) + , _cluster(NULL) { char myAddress[64]; _myAddressStr = myId.address().toString(myAddress); @@ -113,6 +115,21 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R PQfinish(conn); conn = NULL; + if (_rc != NULL) { + sw::redis::ConnectionOptions opts; + sw::redis::ConnectionPoolOptions poolOpts; + opts.host = _rc->hostname; + opts.port = _rc->port; + opts.password = _rc->password; + opts.db = 0; + poolOpts.size = 10; + if (_rc->clusterMode) { + _cluster = new sw::redis::RedisCluster(opts, poolOpts); + } else { + _redis = new sw::redis::Redis(opts, poolOpts); + } + } + _readyLock.lock(); _heartbeatThread = std::thread(&PostgreSQL::heartbeat, this); _membersDbWatcher = std::thread(&PostgreSQL::membersDbWatcher, this); @@ -128,6 +145,8 @@ PostgreSQL::~PostgreSQL() _run = 0; std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + _heartbeatThread.join(); _membersDbWatcher.join(); _networksDbWatcher.join(); @@ -135,7 +154,8 @@ PostgreSQL::~PostgreSQL() _commitThread[i].join(); } _onlineNotificationThread.join(); - + delete _redis; + delete _cluster; } diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 5d14e2ff6..986559acf 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -20,6 +20,8 @@ #define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4 +#include + extern "C" { typedef struct pg_conn PGconn; } @@ -98,6 +100,8 @@ private: int _listenPort; RedisConfig *_rc; + sw::redis::Redis *_redis; + sw::redis::RedisCluster *_cluster; }; } // namespace ZeroTier diff --git a/controller/Redis.hpp b/controller/Redis.hpp new file mode 100644 index 000000000..095419b01 --- /dev/null +++ b/controller/Redis.hpp @@ -0,0 +1,15 @@ +#ifndef ZT_CONTROLLER_REDIS_HPP +#define ZT_CONTROLLER_REDIS_HPP + +#include + +namespace ZeroTier { +struct RedisConfig { + std::string hostname; + int port; + std::string password; + bool clusterMode; +}; +} + +#endif \ No newline at end of file diff --git a/ext/hiredis-0.14.1/.gitignore b/ext/hiredis-0.14.1/.gitignore index c44b5c537..db2ad032a 100644 --- a/ext/hiredis-0.14.1/.gitignore +++ b/ext/hiredis-0.14.1/.gitignore @@ -3,5 +3,4 @@ /*.o /*.so /*.dylib -/*.a /*.pc diff --git a/ext/hiredis-0.14.1/include/hiredis/alloc.h b/ext/hiredis-0.14.1/include/hiredis/alloc.h new file mode 100644 index 000000000..2c9b04e35 --- /dev/null +++ b/ext/hiredis-0.14.1/include/hiredis/alloc.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Michael Grunder + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HIREDIS_ALLOC_H +#define HIREDIS_ALLOC_H + +#include /* for size_t */ + +#ifndef HIREDIS_OOM_HANDLER +#define HIREDIS_OOM_HANDLER abort() +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void *hi_malloc(size_t size); +void *hi_calloc(size_t nmemb, size_t size); +void *hi_realloc(void *ptr, size_t size); +char *hi_strdup(const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* HIREDIS_ALLOC_H */ diff --git a/ext/hiredis-vip-0.3.0/async.h b/ext/hiredis-0.14.1/include/hiredis/async.h similarity index 98% rename from ext/hiredis-vip-0.3.0/async.h rename to ext/hiredis-0.14.1/include/hiredis/async.h index 2ba7142b8..e69d84090 100644 --- a/ext/hiredis-vip-0.3.0/async.h +++ b/ext/hiredis-0.14.1/include/hiredis/async.h @@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); typedef struct redisCallback { struct redisCallback *next; /* simple singly linked list */ redisCallbackFn *fn; + int pending_subs; void *privdata; } redisCallback; @@ -68,7 +69,6 @@ typedef struct redisAsyncContext { /* Not used by hiredis */ void *data; - void (*dataHandler)(struct redisAsyncContext* ac); /* Event library data and hooks */ struct { diff --git a/ext/hiredis-vip-0.3.0/dict.h b/ext/hiredis-0.14.1/include/hiredis/dict.h similarity index 100% rename from ext/hiredis-vip-0.3.0/dict.h rename to ext/hiredis-0.14.1/include/hiredis/dict.h diff --git a/ext/hiredis-0.14.1/include/hiredis/fmacros.h b/ext/hiredis-0.14.1/include/hiredis/fmacros.h new file mode 100644 index 000000000..3227faafd --- /dev/null +++ b/ext/hiredis-0.14.1/include/hiredis/fmacros.h @@ -0,0 +1,12 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#define _XOPEN_SOURCE 600 +#define _POSIX_C_SOURCE 200112L + +#if defined(__APPLE__) && defined(__MACH__) +/* Enable TCP_KEEPALIVE */ +#define _DARWIN_C_SOURCE +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/hiredis.h b/ext/hiredis-0.14.1/include/hiredis/hiredis.h similarity index 83% rename from ext/hiredis-vip-0.3.0/hiredis.h rename to ext/hiredis-0.14.1/include/hiredis/hiredis.h index 87f7366f8..d945bf204 100644 --- a/ext/hiredis-vip-0.3.0/hiredis.h +++ b/ext/hiredis-0.14.1/include/hiredis/hiredis.h @@ -38,10 +38,12 @@ #include /* for struct timeval */ #include /* uintXX_t, etc */ #include "sds.h" /* for sds */ +#include "alloc.h" /* for allocation wrappers */ #define HIREDIS_MAJOR 0 -#define HIREDIS_MINOR 13 +#define HIREDIS_MINOR 14 #define HIREDIS_PATCH 1 +#define HIREDIS_SONAME 0.14 /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ @@ -79,30 +81,6 @@ * SO_REUSEADDR is being used. */ #define REDIS_CONNECT_RETRIES 10 -/* strerror_r has two completely different prototypes and behaviors - * depending on system issues, so we need to operate on the error buffer - * differently depending on which strerror_r we're using. */ -#ifndef _GNU_SOURCE -/* "regular" POSIX strerror_r that does the right thing. */ -#define __redis_strerror_r(errno, buf, len) \ - do { \ - strerror_r((errno), (buf), (len)); \ - } while (0) -#else -/* "bad" GNU strerror_r we need to clean up after. */ -#define __redis_strerror_r(errno, buf, len) \ - do { \ - char *err_str = strerror_r((errno), (buf), (len)); \ - /* If return value _isn't_ the start of the buffer we passed in, \ - * then GNU strerror_r returned an internal static buffer and we \ - * need to copy the result into our private buffer. */ \ - if (err_str != (buf)) { \ - buf[(len)] = '\0'; \ - strncat((buf), err_str, ((len) - 1)); \ - } \ - } while (0) -#endif - #ifdef __cplusplus extern "C" { #endif @@ -111,7 +89,7 @@ extern "C" { typedef struct redisReply { int type; /* REDIS_REPLY_* */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ - int len; /* Length of string */ + size_t len; /* Length of string */ char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ @@ -132,7 +110,7 @@ void redisFreeSdsCommand(sds cmd); enum redisConnectionType { REDIS_CONN_TCP, - REDIS_CONN_UNIX, + REDIS_CONN_UNIX }; /* Context for a connection to Redis */ @@ -156,6 +134,7 @@ typedef struct redisContext { struct { char *path; } unix_sock; + } redisContext; redisContext *redisConnect(const char *ip, int port); @@ -177,7 +156,7 @@ redisContext *redisConnectFd(int fd); * host, ip (or path), timeout and bind address are reused, * flags are used unmodified from the existing context. * - * Returns REDIS_OK on successfull connect or REDIS_ERR otherwise. + * Returns REDIS_OK on successful connect or REDIS_ERR otherwise. */ int redisReconnect(redisContext *c); diff --git a/ext/hiredis-vip-0.3.0/net.h b/ext/hiredis-0.14.1/include/hiredis/net.h similarity index 97% rename from ext/hiredis-vip-0.3.0/net.h rename to ext/hiredis-0.14.1/include/hiredis/net.h index 2f1a0bf85..d9dc36257 100644 --- a/ext/hiredis-vip-0.3.0/net.h +++ b/ext/hiredis-0.14.1/include/hiredis/net.h @@ -37,10 +37,6 @@ #include "hiredis.h" -#if defined(__sun) -#define AF_LOCAL AF_UNIX -#endif - int redisCheckSocketError(redisContext *c); int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); diff --git a/ext/hiredis-vip-0.3.0/read.h b/ext/hiredis-0.14.1/include/hiredis/read.h similarity index 80% rename from ext/hiredis-vip-0.3.0/read.h rename to ext/hiredis-0.14.1/include/hiredis/read.h index 088c97903..2988aa453 100644 --- a/ext/hiredis-vip-0.3.0/read.h +++ b/ext/hiredis-0.14.1/include/hiredis/read.h @@ -38,7 +38,7 @@ #define REDIS_OK 0 /* When an error occurs, the err flag in a context is set to hold the type of - * error that occured. REDIS_ERR_IO means there was an I/O error and you + * error that occurred. REDIS_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */ #define REDIS_ERR_IO 1 /* Error in read or write */ @@ -46,9 +46,6 @@ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */ #define REDIS_ERR_OOM 5 /* Out of memory */ #define REDIS_ERR_OTHER 2 /* Everything else... */ -#if 1 //shenzheng 2015-8-10 redis cluster -#define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 6 -#endif //shenzheng 2015-8-10 redis cluster #define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 @@ -59,16 +56,6 @@ #define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ -#if 1 //shenzheng 2015-8-22 redis cluster -#define REDIS_ERROR_MOVED "MOVED" -#define REDIS_ERROR_ASK "ASK" -#define REDIS_ERROR_TRYAGAIN "TRYAGAIN" -#define REDIS_ERROR_CROSSSLOT "CROSSSLOT" -#define REDIS_ERROR_CLUSTERDOWN "CLUSTERDOWN" - -#define REDIS_STATUS_OK "OK" -#endif //shenzheng 2015-9-24 redis cluster - #ifdef __cplusplus extern "C" { #endif @@ -113,14 +100,9 @@ void redisReaderFree(redisReader *r); int redisReaderFeed(redisReader *r, const char *buf, size_t len); int redisReaderGetReply(redisReader *r, void **reply); -/* Backwards compatibility, can be removed on big version bump. */ -#define redisReplyReaderCreate redisReaderCreate -#define redisReplyReaderFree redisReaderFree -#define redisReplyReaderFeed redisReaderFeed -#define redisReplyReaderGetReply redisReaderGetReply -#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) -#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) -#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) +#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) #ifdef __cplusplus } diff --git a/ext/hiredis-0.14.1/include/hiredis/sds.h b/ext/hiredis-0.14.1/include/hiredis/sds.h new file mode 100644 index 000000000..13be75a9f --- /dev/null +++ b/ext/hiredis-0.14.1/include/hiredis/sds.h @@ -0,0 +1,273 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SDS_H +#define __SDS_H + +#define SDS_MAX_PREALLOC (1024*1024) + +#include +#include +#include + +typedef char *sds; + +/* Note: sdshdr5 is never used, we just access the flags byte directly. + * However is here to document the layout of type 5 SDS strings. */ +struct __attribute__ ((__packed__)) sdshdr5 { + unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr8 { + uint8_t len; /* used */ + uint8_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr16 { + uint16_t len; /* used */ + uint16_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr32 { + uint32_t len; /* used */ + uint32_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr64 { + uint64_t len; /* used */ + uint64_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; + +#define SDS_TYPE_5 0 +#define SDS_TYPE_8 1 +#define SDS_TYPE_16 2 +#define SDS_TYPE_32 3 +#define SDS_TYPE_64 4 +#define SDS_TYPE_MASK 7 +#define SDS_TYPE_BITS 3 +#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); +#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) +#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) + +static inline size_t sdslen(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->len; + case SDS_TYPE_16: + return SDS_HDR(16,s)->len; + case SDS_TYPE_32: + return SDS_HDR(32,s)->len; + case SDS_TYPE_64: + return SDS_HDR(64,s)->len; + } + return 0; +} + +static inline size_t sdsavail(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + return 0; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + return sh->alloc - sh->len; + } + } + return 0; +} + +static inline void sdssetlen(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len = newlen; + break; + } +} + +static inline void sdsinclen(sds s, size_t inc) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len += inc; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len += inc; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len += inc; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len += inc; + break; + } +} + +/* sdsalloc() = sdsavail() + sdslen() */ +static inline size_t sdsalloc(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->alloc; + case SDS_TYPE_16: + return SDS_HDR(16,s)->alloc; + case SDS_TYPE_32: + return SDS_HDR(32,s)->alloc; + case SDS_TYPE_64: + return SDS_HDR(64,s)->alloc; + } + return 0; +} + +static inline void sdssetalloc(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + /* Nothing to do, this type has no total allocation info. */ + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->alloc = newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->alloc = newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->alloc = newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->alloc = newlen; + break; + } +} + +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +sds sdsdup(const sds s); +void sdsfree(sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); + +sds sdscatvprintf(sds s, const char *fmt, va_list ap); +#ifdef __GNUC__ +sds sdscatprintf(sds s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +sds sdscatprintf(sds s, const char *fmt, ...); +#endif + +sds sdscatfmt(sds s, char const *fmt, ...); +sds sdstrim(sds s, const char *cset); +void sdsrange(sds s, int start, int end); +void sdsupdatelen(sds s); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); + +/* Low level functions exposed to the user API */ +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); +void *sdsAllocPtr(sds s); + +/* Export the allocator used by SDS to the program using SDS. + * Sometimes the program SDS is linked to, may use a different set of + * allocators, but may want to allocate or free things that SDS will + * respectively free or allocate. */ +void *sds_malloc(size_t size); +void *sds_realloc(void *ptr, size_t size); +void sds_free(void *ptr); + +#ifdef REDIS_TEST +int sdsTest(int argc, char *argv[]); +#endif + +#endif diff --git a/ext/hiredis-0.14.1/include/hiredis/sdsalloc.h b/ext/hiredis-0.14.1/include/hiredis/sdsalloc.h new file mode 100644 index 000000000..f43023c48 --- /dev/null +++ b/ext/hiredis-0.14.1/include/hiredis/sdsalloc.h @@ -0,0 +1,42 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* SDS allocator selection. + * + * This file is used in order to change the SDS allocator at compile time. + * Just define the following defines to what you want to use. Also add + * the include of your alternate allocator if needed (not needed in order + * to use the default libc allocator). */ + +#define s_malloc malloc +#define s_realloc realloc +#define s_free free diff --git a/ext/hiredis-vip-0.3.0/win32.h b/ext/hiredis-0.14.1/include/hiredis/win32.h similarity index 100% rename from ext/hiredis-vip-0.3.0/win32.h rename to ext/hiredis-0.14.1/include/hiredis/win32.h diff --git a/ext/hiredis-vip-0.3.0/.gitignore b/ext/hiredis-vip-0.3.0/.gitignore deleted file mode 100644 index c44b5c537..000000000 --- a/ext/hiredis-vip-0.3.0/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/hiredis-test -/examples/hiredis-example* -/*.o -/*.so -/*.dylib -/*.a -/*.pc diff --git a/ext/hiredis-vip-0.3.0/.travis.yml b/ext/hiredis-vip-0.3.0/.travis.yml deleted file mode 100644 index 1df63b0b5..000000000 --- a/ext/hiredis-vip-0.3.0/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: c -compiler: - - gcc - - clang - -env: - - CFLAGS="-Werror" - - PRE="valgrind --track-origins=yes --leak-check=full" - - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror" - - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" - -install: - - sudo apt-get update -qq - - sudo apt-get install libc6-dbg libc6-dev libc6-i686:i386 libc6-dev-i386 libc6-dbg:i386 valgrind -y - -script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example diff --git a/ext/hiredis-vip-0.3.0/CHANGELOG.md b/ext/hiredis-vip-0.3.0/CHANGELOG.md deleted file mode 100644 index db304b6a7..000000000 --- a/ext/hiredis-vip-0.3.0/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -### 0.3.0 - Dec 07, 2016 - -* Support redisClustervCommand, redisClustervAppendCommand and redisClustervAsyncCommand api. (deep011) -* Add flags HIRCLUSTER_FLAG_ADD_OPENSLOT and HIRCLUSTER_FLAG_ROUTE_USE_SLOTS. (deep011) -* Support redisClusterCommandArgv related api. (deep011) -* Fix some serious bugs. (deep011) - -### 0.2.1 - Nov 24, 2015 - -This release support redis cluster api. - -* Add hiredis 0.3.1. (deep011) -* Support cluster synchronous API. (deep011) -* Support multi-key command(mget/mset/del) for redis cluster. (deep011) -* Support cluster pipelining. (deep011) -* Support cluster asynchronous API. (deep011) diff --git a/ext/hiredis-vip-0.3.0/COPYING b/ext/hiredis-vip-0.3.0/COPYING deleted file mode 100644 index a5fc97395..000000000 --- a/ext/hiredis-vip-0.3.0/COPYING +++ /dev/null @@ -1,29 +0,0 @@ -Copyright (c) 2009-2011, Salvatore Sanfilippo -Copyright (c) 2010-2011, Pieter Noordhuis - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of Redis nor the names of its contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ext/hiredis-vip-0.3.0/Makefile b/ext/hiredis-vip-0.3.0/Makefile deleted file mode 100644 index 58494bfc3..000000000 --- a/ext/hiredis-vip-0.3.0/Makefile +++ /dev/null @@ -1,205 +0,0 @@ -# Hiredis Makefile -# Copyright (C) 2010-2011 Salvatore Sanfilippo -# Copyright (C) 2010-2011 Pieter Noordhuis -# This file is released under the BSD license, see the COPYING file - -OBJ=net.o hiredis.o sds.o async.o read.o hiarray.o hiutil.o command.o crc16.o adlist.o hircluster.o -EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib -TESTS=hiredis-test -LIBNAME=libhiredis_vip -PKGCONFNAME=hiredis.pc - -HIREDIS_VIP_MAJOR=$(shell grep HIREDIS_VIP_MAJOR hircluster.h | awk '{print $$3}') -HIREDIS_VIP_MINOR=$(shell grep HIREDIS_VIP_MINOR hircluster.h | awk '{print $$3}') -HIREDIS_VIP_PATCH=$(shell grep HIREDIS_VIP_PATCH hircluster.h | awk '{print $$3}') - -# Installation related variables and target -PREFIX?=/usr/local -INCLUDE_PATH?=include/hiredis-vip -LIBRARY_PATH?=lib -PKGCONF_PATH?=pkgconfig -INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) -INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) -INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) - -# redis-server configuration used for testing -REDIS_PORT=56379 -REDIS_SERVER=redis-server -define REDIS_TEST_CONFIG - daemonize yes - pidfile /tmp/hiredis-test-redis.pid - port $(REDIS_PORT) - bind 127.0.0.1 - unixsocket /tmp/hiredis-test-redis.sock -endef -export REDIS_TEST_CONFIG - -# Fallback to gcc when $CC is not in $PATH. -CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc') -OPTIMIZATION?=-O3 -WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -DEBUG?= -g -ggdb -REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH) -REAL_LDFLAGS=$(LDFLAGS) $(ARCH) - -DYLIBSUFFIX=so -STLIBSUFFIX=a -DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR) -DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR) -DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) -DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) -STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) -STLIB_MAKE_CMD=ar rcs $(STLIBNAME) - -# Platform-specific overrides -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -ifeq ($(uname_S),SunOS) - REAL_LDFLAGS+= -ldl -lnsl -lsocket - DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) - INSTALL= cp -r -endif -ifeq ($(uname_S),Darwin) - DYLIBSUFFIX=dylib - DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(DYLIBSUFFIX) - DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(DYLIBSUFFIX) - DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) -endif - -all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) - -# Deps (use make dep to generate this) - -adlist.o: adlist.c adlist.h hiutil.h -async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h -command.o: command.c command.h hiredis.h read.h sds.h adlist.h hiutil.h hiarray.h -crc16.o: crc16.c hiutil.h -dict.o: dict.c fmacros.h dict.h -hiarray.o: hiarray.c hiarray.h hiutil.h -hircluster.o: hircluster.c fmacros.h hircluster.h hiredis.h read.h sds.h adlist.h hiarray.h hiutil.h async.h command.h dict.c dict.h -hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h -hiutil.o: hiutil.c hiutil.h -net.o: net.c fmacros.h net.h hiredis.h read.h sds.h -read.o: read.c fmacros.h read.h sds.h -sds.o: sds.c sds.h -test.o: test.c fmacros.h hiredis.h read.h sds.h net.h - -$(DYLIBNAME): $(OBJ) - $(DYLIB_MAKE_CMD) $(OBJ) - -$(STLIBNAME): $(OBJ) - $(STLIB_MAKE_CMD) $(OBJ) - -dynamic: $(DYLIBNAME) -static: $(STLIBNAME) - -# Binaries: -hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) - -hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) - -hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME) - -ifndef AE_DIR -hiredis-example-ae: - @echo "Please specify AE_DIR (e.g. /src)" - @false -else -hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) -endif - -ifndef LIBUV_DIR -hiredis-example-libuv: - @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" - @false -else -hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME) -endif - -hiredis-example: examples/example.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) - -examples: $(EXAMPLES) - -hiredis-test: test.o $(STLIBNAME) - -hiredis-%: %.o $(STLIBNAME) - $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) - -test: hiredis-test - ./hiredis-test - -check: hiredis-test - @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - - $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \ - ( kill `cat /tmp/hiredis-test-redis.pid` && false ) - kill `cat /tmp/hiredis-test-redis.pid` - -.c.o: - $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< - -clean: - rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov - -dep: - $(CC) -MM *.c - -ifeq ($(uname_S),SunOS) - INSTALL?= cp -r -endif - -INSTALL?= cp -a - -$(PKGCONFNAME): hiredis.h - @echo "Generating $@ for pkgconfig..." - @echo prefix=$(PREFIX) > $@ - @echo exec_prefix=\$${prefix} >> $@ - @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ - @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ - @echo >> $@ - @echo Name: hiredis >> $@ - @echo Description: Minimalistic C client library for Redis. >> $@ - @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ - @echo Libs: -L\$${libdir} -lhiredis >> $@ - @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ - -install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) - mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) - $(INSTALL) hiredis.h async.h read.h sds.h hiutil.h hiarray.h dict.h dict.c adlist.h fmacros.h hircluster.h adapters $(INSTALL_INCLUDE_PATH) - $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) - cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) - cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME) - $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) - mkdir -p $(INSTALL_PKGCONF_PATH) - $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) - -32bit: - @echo "" - @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" - @echo "" - $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" - -32bit-vars: - $(eval CFLAGS=-m32) - $(eval LDFLAGS=-m32) - -gprof: - $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" - -gcov: - $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" - -coverage: gcov - make check - mkdir -p tmp/lcov - lcov -d . -c -o tmp/lcov/hiredis.info - genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info - -noopt: - $(MAKE) OPTIMIZATION="" - -.PHONY: all test check clean dep install 32bit gprof gcov noopt diff --git a/ext/hiredis-vip-0.3.0/README.md b/ext/hiredis-vip-0.3.0/README.md deleted file mode 100644 index 897419390..000000000 --- a/ext/hiredis-vip-0.3.0/README.md +++ /dev/null @@ -1,255 +0,0 @@ - -# HIREDIS-VIP - -Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. - -Hiredis-vip supported redis cluster. - -Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . - -## CLUSTER SUPPORT - -### FEATURES: - -* **`SUPPORT REDIS CLUSTER`**: - * Connect to redis cluster and run commands. - -* **`SUPPORT MULTI-KEY COMMAND`**: - * Support `MSET`, `MGET` and `DEL`. - -* **`SUPPORT PIPELING`**: - * Support redis pipeline and can contain multi-key command like above. - -* **`SUPPORT Asynchronous API`**: - * User can run commands with asynchronous mode. - -### CLUSTER API: - -```c -redisClusterContext *redisClusterConnect(const char *addrs, int flags); -redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags); -redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); -void redisClusterFree(redisClusterContext *cc); -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); -void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); -void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); -void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); -redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); -int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); -int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); -int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); -int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); -int redisClusterGetReply(redisClusterContext *cc, void **reply); -void redisClusterReset(redisClusterContext *cc); - -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); -int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); -int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); -int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); -int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); -int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); - -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); -void redisClusterAsyncFree(redisClusterAsyncContext *acc); -``` - -## Quick usage - -If you want used but not read the follow, please reference the examples: -https://github.com/vipshop/hiredis-vip/wiki - -## Cluster synchronous API - -To consume the synchronous API, there are only a few function calls that need to be introduced: - -```c -redisClusterContext *redisClusterConnect(const char *addrs, int flags); -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); -void redisClusterFree(redisClusterContext *cc); -``` - -### Cluster connecting - -The function `redisClusterConnect` is used to create a so-called `redisClusterContext`. The -context is where Hiredis-vip Cluster holds state for connections. The `redisClusterContext` -struct has an integer `err` field that is non-zero when the connection is in -an error state. The field `errstr` will contain a string with a description of -the error. -After trying to connect to Redis using `redisClusterContext` you should -check the `err` field to see if establishing the connection was successful: -```c -redisClusterContext *cc = redisClusterConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); -if (cc != NULL && cc->err) { - printf("Error: %s\n", cc->errstr); - // handle error -} -``` - -### Cluster sending commands - -The next that will be introduced is `redisClusterCommand`. -This function takes a format similar to printf. In the simplest form, -it is used like this: -```c -reply = redisClusterCommand(clustercontext, "SET foo bar"); -``` - -The specifier `%s` interpolates a string in the command, and uses `strlen` to -determine the length of the string: -```c -reply = redisClusterCommand(clustercontext, "SET foo %s", value); -``` -Internally, Hiredis-vip splits the command in different arguments and will -convert it to the protocol used to communicate with Redis. -One or more spaces separates arguments, so you can use the specifiers -anywhere in an argument: -```c -reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value); -``` - -### Cluster multi-key commands - -Hiredis-vip supports mget/mset/del multi-key commands. -Those multi-key commands is highly effective. -Millions of keys in one mget command just used several seconds. - -Example: -```c -reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); -``` - -### Cluster cleaning up - -To disconnect and free the context the following function can be used: -```c -void redisClusterFree(redisClusterContext *cc); -``` -This function immediately closes the socket and then frees the allocations done in -creating the context. - -### Cluster pipelining - -The function `redisClusterGetReply` is exported as part of the Hiredis API and can be used -when a reply is expected on the socket. To pipeline commands, the only things that needs -to be done is filling up the output buffer. For this cause, two commands can be used that -are identical to the `redisClusterCommand` family, apart from not returning a reply: -```c -int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); -int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); -``` -After calling either function one or more times, `redisClusterGetReply` can be used to receive the -subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where -the latter means an error occurred while reading a reply. Just as with the other commands, -the `err` field in the context can be used to find out what the cause of this error is. -```c -void redisClusterReset(redisClusterContext *cc); -``` -Warning: You must call `redisClusterReset` function after one pipelining anyway. - -The following examples shows a simple cluster pipeline: -```c -redisReply *reply; -redisClusterAppendCommand(clusterContext,"SET foo bar"); -redisClusterAppendCommand(clusterContext,"GET foo"); -redisClusterGetReply(clusterContext,&reply); // reply for SET -freeReplyObject(reply); -redisClusterGetReply(clusterContext,&reply); // reply for GET -freeReplyObject(reply); -redisClusterReset(clusterContext); -``` - -## Cluster asynchronous API - -Hiredis-vip comes with an cluster asynchronous API that works easily with any event library. -Now we just support and test for libevent and redis ae, if you need for other event libraries, -please contact with us, and we will support it quickly. - -### Connecting - -The function `redisAsyncConnect` can be used to establish a non-blocking connection to -Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field -should be checked after creation to see if there were errors creating the connection. -Because the connection that will be created is non-blocking, the kernel is not able to -instantly return if the specified host and port is able to accept a connection. -```c -redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); -if (acc->err) { - printf("Error: %s\n", acc->errstr); - // handle error -} -``` - -The cluster asynchronous context can hold a disconnect callback function that is called when the -connection is disconnected (either because of an error or per user request). This function should -have the following prototype: -```c -void(const redisAsyncContext *c, int status); -``` -On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the -user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` -field in the context can be accessed to find out the cause of the error. - -You not need to reconnect in the disconnect callback, hiredis-vip will reconnect this connection itself -when commands come to this redis node. - -Setting the disconnect callback can only be done once per context. For subsequent calls it will -return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: -```c -int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); -``` -### Sending commands and their callbacks - -In an cluster asynchronous context, commands are automatically pipelined due to the nature of an event loop. -Therefore, unlike the cluster synchronous API, there is only a single way to send commands. -Because commands are sent to Redis cluster asynchronously, issuing a command requires a callback function -that is called when the reply is received. Reply callbacks should have the following prototype: -```c -void(redisClusterAsyncContext *acc, void *reply, void *privdata); -``` -The `privdata` argument can be used to curry arbitrary data to the callback from the point where -the command is initially queued for execution. - -The functions that can be used to issue commands in an asynchronous context are: -```c -int redisClusterAsyncCommand( - redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, - void *privdata, const char *format, ...); -``` -This function work like their blocking counterparts. The return value is `REDIS_OK` when the command -was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection -is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is -returned on calls to the `redisClusterAsyncCommand` family. - -If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback -for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only -valid for the duration of the callback. - -All pending callbacks are called with a `NULL` reply when the context encountered an error. - -### Disconnecting - -An cluster asynchronous connection can be terminated using: -```c -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); -``` -When this function is called, the connection is **not** immediately terminated. Instead, new -commands are no longer accepted and the connection is only terminated when all pending commands -have been written to the socket, their respective replies have been read and their respective -callbacks have been executed. After this, the disconnection callback is executed with the -`REDIS_OK` status and the context object is freed. - -### Hooking it up to event library *X* - -There are a few hooks that need to be set on the cluster context object after it is created. -See the `adapters/` directory for bindings to *ae* and *libevent*. - -## AUTHORS - -Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). -The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis). -The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011). -Hiredis-vip is released under the BSD license. diff --git a/ext/hiredis-vip-0.3.0/adapters/ae.h b/ext/hiredis-vip-0.3.0/adapters/ae.h deleted file mode 100644 index f861cf287..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/ae.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __HIREDIS_AE_H__ -#define __HIREDIS_AE_H__ -#include -#include -#include "../hiredis.h" -#include "../async.h" - -#if 1 //shenzheng 2015-11-5 redis cluster -#include "../hircluster.h" -#endif //shenzheng 2015-11-5 redis cluster - -typedef struct redisAeEvents { - redisAsyncContext *context; - aeEventLoop *loop; - int fd; - int reading, writing; -} redisAeEvents; - -static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { - ((void)el); ((void)fd); ((void)mask); - - redisAeEvents *e = (redisAeEvents*)privdata; - redisAsyncHandleRead(e->context); -} - -static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { - ((void)el); ((void)fd); ((void)mask); - - redisAeEvents *e = (redisAeEvents*)privdata; - redisAsyncHandleWrite(e->context); -} - -static void redisAeAddRead(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - aeEventLoop *loop = e->loop; - if (!e->reading) { - e->reading = 1; - aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); - } -} - -static void redisAeDelRead(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - aeEventLoop *loop = e->loop; - if (e->reading) { - e->reading = 0; - aeDeleteFileEvent(loop,e->fd,AE_READABLE); - } -} - -static void redisAeAddWrite(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - aeEventLoop *loop = e->loop; - if (!e->writing) { - e->writing = 1; - aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); - } -} - -static void redisAeDelWrite(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - aeEventLoop *loop = e->loop; - if (e->writing) { - e->writing = 0; - aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); - } -} - -static void redisAeCleanup(void *privdata) { - redisAeEvents *e = (redisAeEvents*)privdata; - redisAeDelRead(privdata); - redisAeDelWrite(privdata); - free(e); -} - -static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisAeEvents *e; - - /* Nothing should be attached when something is already attached */ - if (ac->ev.data != NULL) - return REDIS_ERR; - - /* Create container for context and r/w events */ - e = (redisAeEvents*)malloc(sizeof(*e)); - e->context = ac; - e->loop = loop; - e->fd = c->fd; - e->reading = e->writing = 0; - - /* Register functions to start/stop listening for events */ - ac->ev.addRead = redisAeAddRead; - ac->ev.delRead = redisAeDelRead; - ac->ev.addWrite = redisAeAddWrite; - ac->ev.delWrite = redisAeDelWrite; - ac->ev.cleanup = redisAeCleanup; - ac->ev.data = e; - - return REDIS_OK; -} - -#if 1 //shenzheng 2015-11-5 redis cluster - -static int redisAeAttach_link(redisAsyncContext *ac, void *base) -{ - redisAeAttach((aeEventLoop *)base, ac); -} - -static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) { - - if(acc == NULL || loop == NULL) - { - return REDIS_ERR; - } - - acc->adapter = loop; - acc->attach_fn = redisAeAttach_link; - - return REDIS_OK; -} - -#endif //shenzheng 2015-11-5 redis cluster - -#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/glib.h b/ext/hiredis-vip-0.3.0/adapters/glib.h deleted file mode 100644 index e13eee73b..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/glib.h +++ /dev/null @@ -1,153 +0,0 @@ -#ifndef __HIREDIS_GLIB_H__ -#define __HIREDIS_GLIB_H__ - -#include - -#include "../hiredis.h" -#include "../async.h" - -typedef struct -{ - GSource source; - redisAsyncContext *ac; - GPollFD poll_fd; -} RedisSource; - -static void -redis_source_add_read (gpointer data) -{ - RedisSource *source = data; - g_return_if_fail(source); - source->poll_fd.events |= G_IO_IN; - g_main_context_wakeup(g_source_get_context(data)); -} - -static void -redis_source_del_read (gpointer data) -{ - RedisSource *source = data; - g_return_if_fail(source); - source->poll_fd.events &= ~G_IO_IN; - g_main_context_wakeup(g_source_get_context(data)); -} - -static void -redis_source_add_write (gpointer data) -{ - RedisSource *source = data; - g_return_if_fail(source); - source->poll_fd.events |= G_IO_OUT; - g_main_context_wakeup(g_source_get_context(data)); -} - -static void -redis_source_del_write (gpointer data) -{ - RedisSource *source = data; - g_return_if_fail(source); - source->poll_fd.events &= ~G_IO_OUT; - g_main_context_wakeup(g_source_get_context(data)); -} - -static void -redis_source_cleanup (gpointer data) -{ - RedisSource *source = data; - - g_return_if_fail(source); - - redis_source_del_read(source); - redis_source_del_write(source); - /* - * It is not our responsibility to remove ourself from the - * current main loop. However, we will remove the GPollFD. - */ - if (source->poll_fd.fd >= 0) { - g_source_remove_poll(data, &source->poll_fd); - source->poll_fd.fd = -1; - } -} - -static gboolean -redis_source_prepare (GSource *source, - gint *timeout_) -{ - RedisSource *redis = (RedisSource *)source; - *timeout_ = -1; - return !!(redis->poll_fd.events & redis->poll_fd.revents); -} - -static gboolean -redis_source_check (GSource *source) -{ - RedisSource *redis = (RedisSource *)source; - return !!(redis->poll_fd.events & redis->poll_fd.revents); -} - -static gboolean -redis_source_dispatch (GSource *source, - GSourceFunc callback, - gpointer user_data) -{ - RedisSource *redis = (RedisSource *)source; - - if ((redis->poll_fd.revents & G_IO_OUT)) { - redisAsyncHandleWrite(redis->ac); - redis->poll_fd.revents &= ~G_IO_OUT; - } - - if ((redis->poll_fd.revents & G_IO_IN)) { - redisAsyncHandleRead(redis->ac); - redis->poll_fd.revents &= ~G_IO_IN; - } - - if (callback) { - return callback(user_data); - } - - return TRUE; -} - -static void -redis_source_finalize (GSource *source) -{ - RedisSource *redis = (RedisSource *)source; - - if (redis->poll_fd.fd >= 0) { - g_source_remove_poll(source, &redis->poll_fd); - redis->poll_fd.fd = -1; - } -} - -static GSource * -redis_source_new (redisAsyncContext *ac) -{ - static GSourceFuncs source_funcs = { - .prepare = redis_source_prepare, - .check = redis_source_check, - .dispatch = redis_source_dispatch, - .finalize = redis_source_finalize, - }; - redisContext *c = &ac->c; - RedisSource *source; - - g_return_val_if_fail(ac != NULL, NULL); - - source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); - source->ac = ac; - source->poll_fd.fd = c->fd; - source->poll_fd.events = 0; - source->poll_fd.revents = 0; - g_source_add_poll((GSource *)source, &source->poll_fd); - - ac->ev.addRead = redis_source_add_read; - ac->ev.delRead = redis_source_del_read; - ac->ev.addWrite = redis_source_add_write; - ac->ev.delWrite = redis_source_del_write; - ac->ev.cleanup = redis_source_cleanup; - ac->ev.data = source; - - return (GSource *)source; -} - -#endif /* __HIREDIS_GLIB_H__ */ diff --git a/ext/hiredis-vip-0.3.0/adapters/libev.h b/ext/hiredis-vip-0.3.0/adapters/libev.h deleted file mode 100644 index 2bf8d521f..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/libev.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __HIREDIS_LIBEV_H__ -#define __HIREDIS_LIBEV_H__ -#include -#include -#include -#include "../hiredis.h" -#include "../async.h" - -typedef struct redisLibevEvents { - redisAsyncContext *context; - struct ev_loop *loop; - int reading, writing; - ev_io rev, wev; -} redisLibevEvents; - -static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { -#if EV_MULTIPLICITY - ((void)loop); -#endif - ((void)revents); - - redisLibevEvents *e = (redisLibevEvents*)watcher->data; - redisAsyncHandleRead(e->context); -} - -static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { -#if EV_MULTIPLICITY - ((void)loop); -#endif - ((void)revents); - - redisLibevEvents *e = (redisLibevEvents*)watcher->data; - redisAsyncHandleWrite(e->context); -} - -static void redisLibevAddRead(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - struct ev_loop *loop = e->loop; - ((void)loop); - if (!e->reading) { - e->reading = 1; - ev_io_start(EV_A_ &e->rev); - } -} - -static void redisLibevDelRead(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - struct ev_loop *loop = e->loop; - ((void)loop); - if (e->reading) { - e->reading = 0; - ev_io_stop(EV_A_ &e->rev); - } -} - -static void redisLibevAddWrite(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - struct ev_loop *loop = e->loop; - ((void)loop); - if (!e->writing) { - e->writing = 1; - ev_io_start(EV_A_ &e->wev); - } -} - -static void redisLibevDelWrite(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - struct ev_loop *loop = e->loop; - ((void)loop); - if (e->writing) { - e->writing = 0; - ev_io_stop(EV_A_ &e->wev); - } -} - -static void redisLibevCleanup(void *privdata) { - redisLibevEvents *e = (redisLibevEvents*)privdata; - redisLibevDelRead(privdata); - redisLibevDelWrite(privdata); - free(e); -} - -static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisLibevEvents *e; - - /* Nothing should be attached when something is already attached */ - if (ac->ev.data != NULL) - return REDIS_ERR; - - /* Create container for context and r/w events */ - e = (redisLibevEvents*)malloc(sizeof(*e)); - e->context = ac; -#if EV_MULTIPLICITY - e->loop = loop; -#else - e->loop = NULL; -#endif - e->reading = e->writing = 0; - e->rev.data = e; - e->wev.data = e; - - /* Register functions to start/stop listening for events */ - ac->ev.addRead = redisLibevAddRead; - ac->ev.delRead = redisLibevDelRead; - ac->ev.addWrite = redisLibevAddWrite; - ac->ev.delWrite = redisLibevDelWrite; - ac->ev.cleanup = redisLibevCleanup; - ac->ev.data = e; - - /* Initialize read/write events */ - ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); - ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); - return REDIS_OK; -} - -#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/libevent.h b/ext/hiredis-vip-0.3.0/adapters/libevent.h deleted file mode 100644 index 6bc911c77..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/libevent.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __HIREDIS_LIBEVENT_H__ -#define __HIREDIS_LIBEVENT_H__ -#include -#include "../hiredis.h" -#include "../async.h" - -#if 1 //shenzheng 2015-9-21 redis cluster -#include "../hircluster.h" -#endif //shenzheng 2015-9-21 redis cluster - -typedef struct redisLibeventEvents { - redisAsyncContext *context; - struct event rev, wev; -} redisLibeventEvents; - -static void redisLibeventReadEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); - redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleRead(e->context); -} - -static void redisLibeventWriteEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); - redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleWrite(e->context); -} - -static void redisLibeventAddRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(&e->rev,NULL); -} - -static void redisLibeventDelRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->rev); -} - -static void redisLibeventAddWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(&e->wev,NULL); -} - -static void redisLibeventDelWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->wev); -} - -static void redisLibeventCleanup(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->rev); - event_del(&e->wev); - free(e); -} - -static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { - redisContext *c = &(ac->c); - redisLibeventEvents *e; - - /* Nothing should be attached when something is already attached */ - if (ac->ev.data != NULL) - return REDIS_ERR; - - /* Create container for context and r/w events */ - e = (redisLibeventEvents*)malloc(sizeof(*e)); - e->context = ac; - - /* Register functions to start/stop listening for events */ - ac->ev.addRead = redisLibeventAddRead; - ac->ev.delRead = redisLibeventDelRead; - ac->ev.addWrite = redisLibeventAddWrite; - ac->ev.delWrite = redisLibeventDelWrite; - ac->ev.cleanup = redisLibeventCleanup; - ac->ev.data = e; - - /* Initialize and install read/write events */ - event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e); - event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e); - event_base_set(base,&e->rev); - event_base_set(base,&e->wev); - return REDIS_OK; -} - -#if 1 //shenzheng 2015-9-21 redis cluster - -static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) -{ - redisLibeventAttach(ac, (struct event_base *)base); -} - -static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) { - - if(acc == NULL || base == NULL) - { - return REDIS_ERR; - } - - acc->adapter = base; - acc->attach_fn = redisLibeventAttach_link; - - return REDIS_OK; -} - -#endif //shenzheng 2015-9-21 redis cluster - -#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/libuv.h b/ext/hiredis-vip-0.3.0/adapters/libuv.h deleted file mode 100644 index 3c9a49f53..000000000 --- a/ext/hiredis-vip-0.3.0/adapters/libuv.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef __HIREDIS_LIBUV_H__ -#define __HIREDIS_LIBUV_H__ -#include -#include -#include "../hiredis.h" -#include "../async.h" -#include - -typedef struct redisLibuvEvents { - redisAsyncContext* context; - uv_poll_t handle; - int events; -} redisLibuvEvents; - - -static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { - redisLibuvEvents* p = (redisLibuvEvents*)handle->data; - - if (status != 0) { - return; - } - - if (events & UV_READABLE) { - redisAsyncHandleRead(p->context); - } - if (events & UV_WRITABLE) { - redisAsyncHandleWrite(p->context); - } -} - - -static void redisLibuvAddRead(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - p->events |= UV_READABLE; - - uv_poll_start(&p->handle, p->events, redisLibuvPoll); -} - - -static void redisLibuvDelRead(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - p->events &= ~UV_READABLE; - - if (p->events) { - uv_poll_start(&p->handle, p->events, redisLibuvPoll); - } else { - uv_poll_stop(&p->handle); - } -} - - -static void redisLibuvAddWrite(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - p->events |= UV_WRITABLE; - - uv_poll_start(&p->handle, p->events, redisLibuvPoll); -} - - -static void redisLibuvDelWrite(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - p->events &= ~UV_WRITABLE; - - if (p->events) { - uv_poll_start(&p->handle, p->events, redisLibuvPoll); - } else { - uv_poll_stop(&p->handle); - } -} - - -static void on_close(uv_handle_t* handle) { - redisLibuvEvents* p = (redisLibuvEvents*)handle->data; - - free(p); -} - - -static void redisLibuvCleanup(void *privdata) { - redisLibuvEvents* p = (redisLibuvEvents*)privdata; - - uv_close((uv_handle_t*)&p->handle, on_close); -} - - -static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { - redisContext *c = &(ac->c); - - if (ac->ev.data != NULL) { - return REDIS_ERR; - } - - ac->ev.addRead = redisLibuvAddRead; - ac->ev.delRead = redisLibuvDelRead; - ac->ev.addWrite = redisLibuvAddWrite; - ac->ev.delWrite = redisLibuvDelWrite; - ac->ev.cleanup = redisLibuvCleanup; - - redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); - - if (!p) { - return REDIS_ERR; - } - - memset(p, 0, sizeof(*p)); - - if (uv_poll_init(loop, &p->handle, c->fd) != 0) { - return REDIS_ERR; - } - - ac->ev.data = p; - p->handle.data = p; - p->context = ac; - - return REDIS_OK; -} - -#endif diff --git a/ext/hiredis-vip-0.3.0/adlist.c b/ext/hiredis-vip-0.3.0/adlist.c deleted file mode 100644 index b490a6bd1..000000000 --- a/ext/hiredis-vip-0.3.0/adlist.c +++ /dev/null @@ -1,341 +0,0 @@ -/* adlist.c - A generic doubly linked list implementation - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - - -#include -#include "adlist.h" -#include "hiutil.h" - -/* Create a new list. The created list can be freed with - * AlFreeList(), but private value of every node need to be freed - * by the user before to call AlFreeList(). - * - * On error, NULL is returned. Otherwise the pointer to the new list. */ -hilist *listCreate(void) -{ - struct hilist *list; - - if ((list = hi_alloc(sizeof(*list))) == NULL) - return NULL; - list->head = list->tail = NULL; - list->len = 0; - list->dup = NULL; - list->free = NULL; - list->match = NULL; - return list; -} - -/* Free the whole list. - * - * This function can't fail. */ -void listRelease(hilist *list) -{ - unsigned long len; - listNode *current, *next; - - current = list->head; - len = list->len; - while(len--) { - next = current->next; - if (list->free) list->free(current->value); - hi_free(current); - current = next; - } - hi_free(list); -} - -/* Add a new node to the list, to head, containing the specified 'value' - * pointer as value. - * - * On error, NULL is returned and no operation is performed (i.e. the - * list remains unaltered). - * On success the 'list' pointer you pass to the function is returned. */ -hilist *listAddNodeHead(hilist *list, void *value) -{ - listNode *node; - - if ((node = hi_alloc(sizeof(*node))) == NULL) - return NULL; - node->value = value; - if (list->len == 0) { - list->head = list->tail = node; - node->prev = node->next = NULL; - } else { - node->prev = NULL; - node->next = list->head; - list->head->prev = node; - list->head = node; - } - list->len++; - return list; -} - -/* Add a new node to the list, to tail, containing the specified 'value' - * pointer as value. - * - * On error, NULL is returned and no operation is performed (i.e. the - * list remains unaltered). - * On success the 'list' pointer you pass to the function is returned. */ -hilist *listAddNodeTail(hilist *list, void *value) -{ - listNode *node; - - if ((node = hi_alloc(sizeof(*node))) == NULL) - return NULL; - node->value = value; - if (list->len == 0) { - list->head = list->tail = node; - node->prev = node->next = NULL; - } else { - node->prev = list->tail; - node->next = NULL; - list->tail->next = node; - list->tail = node; - } - list->len++; - return list; -} - -hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) { - listNode *node; - - if ((node = hi_alloc(sizeof(*node))) == NULL) - return NULL; - node->value = value; - if (after) { - node->prev = old_node; - node->next = old_node->next; - if (list->tail == old_node) { - list->tail = node; - } - } else { - node->next = old_node; - node->prev = old_node->prev; - if (list->head == old_node) { - list->head = node; - } - } - if (node->prev != NULL) { - node->prev->next = node; - } - if (node->next != NULL) { - node->next->prev = node; - } - list->len++; - return list; -} - -/* Remove the specified node from the specified list. - * It's up to the caller to free the private value of the node. - * - * This function can't fail. */ -void listDelNode(hilist *list, listNode *node) -{ - if (node->prev) - node->prev->next = node->next; - else - list->head = node->next; - if (node->next) - node->next->prev = node->prev; - else - list->tail = node->prev; - if (list->free) list->free(node->value); - hi_free(node); - list->len--; -} - -/* Returns a list iterator 'iter'. After the initialization every - * call to listNext() will return the next element of the list. - * - * This function can't fail. */ -listIter *listGetIterator(hilist *list, int direction) -{ - listIter *iter; - - if ((iter = hi_alloc(sizeof(*iter))) == NULL) return NULL; - if (direction == AL_START_HEAD) - iter->next = list->head; - else - iter->next = list->tail; - iter->direction = direction; - return iter; -} - -/* Release the iterator memory */ -void listReleaseIterator(listIter *iter) { - hi_free(iter); -} - -/* Create an iterator in the list private iterator structure */ -void listRewind(hilist *list, listIter *li) { - li->next = list->head; - li->direction = AL_START_HEAD; -} - -void listRewindTail(hilist *list, listIter *li) { - li->next = list->tail; - li->direction = AL_START_TAIL; -} - -/* Return the next element of an iterator. - * It's valid to remove the currently returned element using - * listDelNode(), but not to remove other elements. - * - * The function returns a pointer to the next element of the list, - * or NULL if there are no more elements, so the classical usage patter - * is: - * - * iter = listGetIterator(list,); - * while ((node = listNext(iter)) != NULL) { - * doSomethingWith(listNodeValue(node)); - * } - * - * */ -listNode *listNext(listIter *iter) -{ - listNode *current = iter->next; - - if (current != NULL) { - if (iter->direction == AL_START_HEAD) - iter->next = current->next; - else - iter->next = current->prev; - } - return current; -} - -/* Duplicate the whole list. On out of memory NULL is returned. - * On success a copy of the original list is returned. - * - * The 'Dup' method set with listSetDupMethod() function is used - * to copy the node value. Otherwise the same pointer value of - * the original node is used as value of the copied node. - * - * The original list both on success or error is never modified. */ -hilist *listDup(hilist *orig) -{ - hilist *copy; - listIter *iter; - listNode *node; - - if ((copy = listCreate()) == NULL) - return NULL; - copy->dup = orig->dup; - copy->free = orig->free; - copy->match = orig->match; - iter = listGetIterator(orig, AL_START_HEAD); - while((node = listNext(iter)) != NULL) { - void *value; - - if (copy->dup) { - value = copy->dup(node->value); - if (value == NULL) { - listRelease(copy); - listReleaseIterator(iter); - return NULL; - } - } else - value = node->value; - if (listAddNodeTail(copy, value) == NULL) { - listRelease(copy); - listReleaseIterator(iter); - return NULL; - } - } - listReleaseIterator(iter); - return copy; -} - -/* Search the list for a node matching a given key. - * The match is performed using the 'match' method - * set with listSetMatchMethod(). If no 'match' method - * is set, the 'value' pointer of every node is directly - * compared with the 'key' pointer. - * - * On success the first matching node pointer is returned - * (search starts from head). If no matching node exists - * NULL is returned. */ -listNode *listSearchKey(hilist *list, void *key) -{ - listIter *iter; - listNode *node; - - iter = listGetIterator(list, AL_START_HEAD); - while((node = listNext(iter)) != NULL) { - if (list->match) { - if (list->match(node->value, key)) { - listReleaseIterator(iter); - return node; - } - } else { - if (key == node->value) { - listReleaseIterator(iter); - return node; - } - } - } - listReleaseIterator(iter); - return NULL; -} - -/* Return the element at the specified zero-based index - * where 0 is the head, 1 is the element next to head - * and so on. Negative integers are used in order to count - * from the tail, -1 is the last element, -2 the penultimate - * and so on. If the index is out of range NULL is returned. */ -listNode *listIndex(hilist *list, long index) { - listNode *n; - - if (index < 0) { - index = (-index)-1; - n = list->tail; - while(index-- && n) n = n->prev; - } else { - n = list->head; - while(index-- && n) n = n->next; - } - return n; -} - -/* Rotate the list removing the tail node and inserting it to the head. */ -void listRotate(hilist *list) { - listNode *tail = list->tail; - - if (listLength(list) <= 1) return; - - /* Detach current tail */ - list->tail = tail->prev; - list->tail->next = NULL; - /* Move it as head */ - list->head->prev = tail; - tail->prev = NULL; - tail->next = list->head; - list->head = tail; -} diff --git a/ext/hiredis-vip-0.3.0/adlist.h b/ext/hiredis-vip-0.3.0/adlist.h deleted file mode 100644 index 5b9a53ea5..000000000 --- a/ext/hiredis-vip-0.3.0/adlist.h +++ /dev/null @@ -1,93 +0,0 @@ -/* adlist.h - A generic doubly linked list implementation - * - * Copyright (c) 2006-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __ADLIST_H__ -#define __ADLIST_H__ - -/* Node, List, and Iterator are the only data structures used currently. */ - -typedef struct listNode { - struct listNode *prev; - struct listNode *next; - void *value; -} listNode; - -typedef struct listIter { - listNode *next; - int direction; -} listIter; - -typedef struct hilist { - listNode *head; - listNode *tail; - void *(*dup)(void *ptr); - void (*free)(void *ptr); - int (*match)(void *ptr, void *key); - unsigned long len; -} hilist; - -/* Functions implemented as macros */ -#define listLength(l) ((l)->len) -#define listFirst(l) ((l)->head) -#define listLast(l) ((l)->tail) -#define listPrevNode(n) ((n)->prev) -#define listNextNode(n) ((n)->next) -#define listNodeValue(n) ((n)->value) - -#define listSetDupMethod(l,m) ((l)->dup = (m)) -#define listSetFreeMethod(l,m) ((l)->free = (m)) -#define listSetMatchMethod(l,m) ((l)->match = (m)) - -#define listGetDupMethod(l) ((l)->dup) -#define listGetFree(l) ((l)->free) -#define listGetMatchMethod(l) ((l)->match) - -/* Prototypes */ -hilist *listCreate(void); -void listRelease(hilist *list); -hilist *listAddNodeHead(hilist *list, void *value); -hilist *listAddNodeTail(hilist *list, void *value); -hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after); -void listDelNode(hilist *list, listNode *node); -listIter *listGetIterator(hilist *list, int direction); -listNode *listNext(listIter *iter); -void listReleaseIterator(listIter *iter); -hilist *listDup(hilist *orig); -listNode *listSearchKey(hilist *list, void *key); -listNode *listIndex(hilist *list, long index); -void listRewind(hilist *list, listIter *li); -void listRewindTail(hilist *list, listIter *li); -void listRotate(hilist *list); - -/* Directions for iterators */ -#define AL_START_HEAD 0 -#define AL_START_TAIL 1 - -#endif /* __ADLIST_H__ */ diff --git a/ext/hiredis-vip-0.3.0/async.c b/ext/hiredis-vip-0.3.0/async.c deleted file mode 100644 index 75a3575de..000000000 --- a/ext/hiredis-vip-0.3.0/async.c +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include "async.h" -#include "net.h" -#include "dict.c" -#include "sds.h" - -#define _EL_ADD_READ(ctx) do { \ - if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ - } while(0) -#define _EL_DEL_READ(ctx) do { \ - if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ - } while(0) -#define _EL_ADD_WRITE(ctx) do { \ - if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ - } while(0) -#define _EL_DEL_WRITE(ctx) do { \ - if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ - } while(0) -#define _EL_CLEANUP(ctx) do { \ - if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ - } while(0); - -/* Forward declaration of function in hiredis.c */ -int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); - -/* Functions managing dictionary of callbacks for pub/sub. */ -static unsigned int callbackHash(const void *key) { - return dictGenHashFunction((const unsigned char *)key, - sdslen((const sds)key)); -} - -static void *callbackValDup(void *privdata, const void *src) { - ((void) privdata); - redisCallback *dup = malloc(sizeof(*dup)); - memcpy(dup,src,sizeof(*dup)); - return dup; -} - -static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { - int l1, l2; - ((void) privdata); - - l1 = sdslen((const sds)key1); - l2 = sdslen((const sds)key2); - if (l1 != l2) return 0; - return memcmp(key1,key2,l1) == 0; -} - -static void callbackKeyDestructor(void *privdata, void *key) { - ((void) privdata); - sdsfree((sds)key); -} - -static void callbackValDestructor(void *privdata, void *val) { - ((void) privdata); - free(val); -} - -static dictType callbackDict = { - callbackHash, - NULL, - callbackValDup, - callbackKeyCompare, - callbackKeyDestructor, - callbackValDestructor -}; - -static redisAsyncContext *redisAsyncInitialize(redisContext *c) { - redisAsyncContext *ac; - - ac = realloc(c,sizeof(redisAsyncContext)); - if (ac == NULL) - return NULL; - - c = &(ac->c); - - /* The regular connect functions will always set the flag REDIS_CONNECTED. - * For the async API, we want to wait until the first write event is - * received up before setting this flag, so reset it here. */ - c->flags &= ~REDIS_CONNECTED; - - ac->err = 0; - ac->errstr = NULL; - ac->data = NULL; - ac->dataHandler = NULL; - - ac->ev.data = NULL; - ac->ev.addRead = NULL; - ac->ev.delRead = NULL; - ac->ev.addWrite = NULL; - ac->ev.delWrite = NULL; - ac->ev.cleanup = NULL; - - ac->onConnect = NULL; - ac->onDisconnect = NULL; - - ac->replies.head = NULL; - ac->replies.tail = NULL; - ac->sub.invalid.head = NULL; - ac->sub.invalid.tail = NULL; - ac->sub.channels = dictCreate(&callbackDict,NULL); - ac->sub.patterns = dictCreate(&callbackDict,NULL); - return ac; -} - -/* We want the error field to be accessible directly instead of requiring - * an indirection to the redisContext struct. */ -static void __redisAsyncCopyError(redisAsyncContext *ac) { - if (!ac) - return; - - redisContext *c = &(ac->c); - ac->err = c->err; - ac->errstr = c->errstr; -} - -redisAsyncContext *redisAsyncConnect(const char *ip, int port) { - redisContext *c; - redisAsyncContext *ac; - - c = redisConnectNonBlock(ip,port); - if (c == NULL) - return NULL; - - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, - const char *source_addr) { - redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); - redisAsyncContext *ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, - const char *source_addr) { - redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); - redisAsyncContext *ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnectUnix(const char *path) { - redisContext *c; - redisAsyncContext *ac; - - c = redisConnectUnixNonBlock(path); - if (c == NULL) - return NULL; - - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - - __redisAsyncCopyError(ac); - return ac; -} - -int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { - if (ac->onConnect == NULL) { - ac->onConnect = fn; - - /* The common way to detect an established connection is to wait for - * the first write event to be fired. This assumes the related event - * library functions are already set. */ - _EL_ADD_WRITE(ac); - return REDIS_OK; - } - return REDIS_ERR; -} - -int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { - if (ac->onDisconnect == NULL) { - ac->onDisconnect = fn; - return REDIS_OK; - } - return REDIS_ERR; -} - -/* Helper functions to push/shift callbacks */ -static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { - redisCallback *cb; - - /* Copy callback from stack to heap */ - cb = malloc(sizeof(*cb)); - if (cb == NULL) - return REDIS_ERR_OOM; - - if (source != NULL) { - memcpy(cb,source,sizeof(*cb)); - cb->next = NULL; - } - - /* Store callback in list */ - if (list->head == NULL) - list->head = cb; - if (list->tail != NULL) - list->tail->next = cb; - list->tail = cb; - return REDIS_OK; -} - -static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { - redisCallback *cb = list->head; - if (cb != NULL) { - list->head = cb->next; - if (cb == list->tail) - list->tail = NULL; - - /* Copy callback from heap to stack */ - if (target != NULL) - memcpy(target,cb,sizeof(*cb)); - free(cb); - return REDIS_OK; - } - return REDIS_ERR; -} - -static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { - redisContext *c = &(ac->c); - if (cb->fn != NULL) { - c->flags |= REDIS_IN_CALLBACK; - cb->fn(ac,reply,cb->privdata); - c->flags &= ~REDIS_IN_CALLBACK; - } -} - -/* Helper function to free the context. */ -static void __redisAsyncFree(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb; - dictIterator *it; - dictEntry *de; - - /* Execute pending callbacks with NULL reply. */ - while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) - __redisRunCallback(ac,&cb,NULL); - - /* Execute callbacks for invalid commands */ - while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) - __redisRunCallback(ac,&cb,NULL); - - /* Run subscription callbacks callbacks with NULL reply */ - it = dictGetIterator(ac->sub.channels); - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - dictRelease(ac->sub.channels); - - it = dictGetIterator(ac->sub.patterns); - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - dictRelease(ac->sub.patterns); - - /* Signal event lib to clean up */ - _EL_CLEANUP(ac); - - /* Execute disconnect callback. When redisAsyncFree() initiated destroying - * this context, the status will always be REDIS_OK. */ - if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { - if (c->flags & REDIS_FREEING) { - ac->onDisconnect(ac,REDIS_OK); - } else { - ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); - } - } - - if (ac->dataHandler) { - ac->dataHandler(ac); - } - - /* Cleanup self */ - redisFree(c); -} - -/* Free the async context. When this function is called from a callback, - * control needs to be returned to redisProcessCallbacks() before actual - * free'ing. To do so, a flag is set on the context which is picked up by - * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ -void redisAsyncFree(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - c->flags |= REDIS_FREEING; - if (!(c->flags & REDIS_IN_CALLBACK)) - __redisAsyncFree(ac); -} - -/* Helper function to make the disconnect happen and clean up. */ -static void __redisAsyncDisconnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - /* Make sure error is accessible if there is any */ - __redisAsyncCopyError(ac); - - if (ac->err == 0) { - /* For clean disconnects, there should be no pending callbacks. */ - assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); - } else { - /* Disconnection is caused by an error, make sure that pending - * callbacks cannot call new commands. */ - c->flags |= REDIS_DISCONNECTING; - } - - /* For non-clean disconnects, __redisAsyncFree() will execute pending - * callbacks with a NULL-reply. */ - __redisAsyncFree(ac); -} - -/* Tries to do a clean disconnect from Redis, meaning it stops new commands - * from being issued, but tries to flush the output buffer and execute - * callbacks for all remaining replies. When this function is called from a - * callback, there might be more replies and we can safely defer disconnecting - * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately - * when there are no pending callbacks. */ -void redisAsyncDisconnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - c->flags |= REDIS_DISCONNECTING; - if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) - __redisAsyncDisconnect(ac); -} - -static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { - redisContext *c = &(ac->c); - dict *callbacks; - dictEntry *de; - int pvariant; - char *stype; - sds sname; - - /* Custom reply functions are not supported for pub/sub. This will fail - * very hard when they are used... */ - if (reply->type == REDIS_REPLY_ARRAY) { - assert(reply->elements >= 2); - assert(reply->element[0]->type == REDIS_REPLY_STRING); - stype = reply->element[0]->str; - pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; - - if (pvariant) - callbacks = ac->sub.patterns; - else - callbacks = ac->sub.channels; - - /* Locate the right callback */ - assert(reply->element[1]->type == REDIS_REPLY_STRING); - sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); - de = dictFind(callbacks,sname); - if (de != NULL) { - memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); - - /* If this is an unsubscribe message, remove it. */ - if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { - dictDelete(callbacks,sname); - - /* If this was the last unsubscribe message, revert to - * non-subscribe mode. */ - assert(reply->element[2]->type == REDIS_REPLY_INTEGER); - if (reply->element[2]->integer == 0) - c->flags &= ~REDIS_SUBSCRIBED; - } - } - sdsfree(sname); - } else { - /* Shift callback for invalid commands. */ - __redisShiftCallback(&ac->sub.invalid,dstcb); - } - return REDIS_OK; -} - -void redisProcessCallbacks(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb = {NULL, NULL, NULL}; - void *reply = NULL; - int status; - - while((status = redisGetReply(c,&reply)) == REDIS_OK) { - if (reply == NULL) { - /* When the connection is being disconnected and there are - * no more replies, this is the cue to really disconnect. */ - if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { - __redisAsyncDisconnect(ac); - return; - } - - /* If monitor mode, repush callback */ - if(c->flags & REDIS_MONITORING) { - __redisPushCallback(&ac->replies,&cb); - } - - /* When the connection is not being disconnected, simply stop - * trying to get replies and wait for the next loop tick. */ - break; - } - - /* Even if the context is subscribed, pending regular callbacks will - * get a reply before pub/sub messages arrive. */ - if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { - /* - * A spontaneous reply in a not-subscribed context can be the error - * reply that is sent when a new connection exceeds the maximum - * number of allowed connections on the server side. - * - * This is seen as an error instead of a regular reply because the - * server closes the connection after sending it. - * - * To prevent the error from being overwritten by an EOF error the - * connection is closed here. See issue #43. - * - * Another possibility is that the server is loading its dataset. - * In this case we also want to close the connection, and have the - * user wait until the server is ready to take our request. - */ - if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { - c->err = REDIS_ERR_OTHER; - snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); - c->reader->fn->freeObject(reply); - __redisAsyncDisconnect(ac); - return; - } - /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ - assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); - if(c->flags & REDIS_SUBSCRIBED) - __redisGetSubscribeCallback(ac,reply,&cb); - } - - if (cb.fn != NULL) { - __redisRunCallback(ac,&cb,reply); - c->reader->fn->freeObject(reply); - - /* Proceed with free'ing when redisAsyncFree() was called. */ - if (c->flags & REDIS_FREEING) { - __redisAsyncFree(ac); - return; - } - } else { - /* No callback for this reply. This can either be a NULL callback, - * or there were no callbacks to begin with. Either way, don't - * abort with an error, but simply ignore it because the client - * doesn't know what the server will spit out over the wire. */ - c->reader->fn->freeObject(reply); - } - } - - /* Disconnect when there was an error reading the reply */ - if (status != REDIS_OK) - __redisAsyncDisconnect(ac); -} - -/* Internal helper function to detect socket status the first time a read or - * write event fires. When connecting was not succesful, the connect callback - * is called with a REDIS_ERR status and the context is free'd. */ -static int __redisAsyncHandleConnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (redisCheckSocketError(c) == REDIS_ERR) { - /* Try again later when connect(2) is still in progress. */ - if (errno == EINPROGRESS) - return REDIS_OK; - - if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); - __redisAsyncDisconnect(ac); - return REDIS_ERR; - } - - /* Mark context as connected. */ - c->flags |= REDIS_CONNECTED; - if (ac->onConnect) ac->onConnect(ac,REDIS_OK); - return REDIS_OK; -} - -/* This function should be called when the socket is readable. - * It processes all replies that can be read and executes their callbacks. - */ -void redisAsyncHandleRead(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (!(c->flags & REDIS_CONNECTED)) { - /* Abort connect was not successful. */ - if (__redisAsyncHandleConnect(ac) != REDIS_OK) - return; - /* Try again later when the context is still not connected. */ - if (!(c->flags & REDIS_CONNECTED)) - return; - } - - if (redisBufferRead(c) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Always re-schedule reads */ - _EL_ADD_READ(ac); - redisProcessCallbacks(ac); - } -} - -void redisAsyncHandleWrite(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - int done = 0; - - if (!(c->flags & REDIS_CONNECTED)) { - /* Abort connect was not successful. */ - if (__redisAsyncHandleConnect(ac) != REDIS_OK) - return; - /* Try again later when the context is still not connected. */ - if (!(c->flags & REDIS_CONNECTED)) - return; - } - - if (redisBufferWrite(c,&done) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Continue writing when not done, stop writing otherwise */ - if (!done) - _EL_ADD_WRITE(ac); - else - _EL_DEL_WRITE(ac); - - /* Always schedule reads after writes */ - _EL_ADD_READ(ac); - } -} - -/* Sets a pointer to the first argument and its length starting at p. Returns - * the number of bytes to skip to get to the following argument. */ -static const char *nextArgument(const char *start, const char **str, size_t *len) { - const char *p = start; - if (p[0] != '$') { - p = strchr(p,'$'); - if (p == NULL) return NULL; - } - - *len = (int)strtol(p+1,NULL,10); - p = strchr(p,'\r'); - assert(p); - *str = p+2; - return p+2+(*len)+2; -} - -/* Helper function for the redisAsyncCommand* family of functions. Writes a - * formatted command to the output buffer and registers the provided callback - * function with the context. */ -static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { - redisContext *c = &(ac->c); - redisCallback cb; - int pvariant, hasnext; - const char *cstr, *astr; - size_t clen, alen; - const char *p; - sds sname; - int ret; - - /* Don't accept new commands when the connection is about to be closed. */ - if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; - - /* Setup callback */ - cb.fn = fn; - cb.privdata = privdata; - - /* Find out which command will be appended. */ - p = nextArgument(cmd,&cstr,&clen); - assert(p != NULL); - hasnext = (p[0] == '$'); - pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; - cstr += pvariant; - clen -= pvariant; - - if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { - c->flags |= REDIS_SUBSCRIBED; - - /* Add every channel/pattern to the list of subscription callbacks. */ - while ((p = nextArgument(p,&astr,&alen)) != NULL) { - sname = sdsnewlen(astr,alen); - if (pvariant) - ret = dictReplace(ac->sub.patterns,sname,&cb); - else - ret = dictReplace(ac->sub.channels,sname,&cb); - - if (ret == 0) sdsfree(sname); - } - } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { - /* It is only useful to call (P)UNSUBSCRIBE when the context is - * subscribed to one or more channels or patterns. */ - if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; - - /* (P)UNSUBSCRIBE does not have its own response: every channel or - * pattern that is unsubscribed will receive a message. This means we - * should not append a callback function for this command. */ - } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { - /* Set monitor flag and push callback */ - c->flags |= REDIS_MONITORING; - __redisPushCallback(&ac->replies,&cb); - } else { - if (c->flags & REDIS_SUBSCRIBED) - /* This will likely result in an error reply, but it needs to be - * received and passed to the callback. */ - __redisPushCallback(&ac->sub.invalid,&cb); - else - __redisPushCallback(&ac->replies,&cb); - } - - __redisAppendCommand(c,cmd,len); - - /* Always schedule a write when the write buffer is non-empty */ - _EL_ADD_WRITE(ac); - - return REDIS_OK; -} - -int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { - char *cmd; - int len; - int status; - len = redisvFormatCommand(&cmd,format,ap); - - /* We don't want to pass -1 or -2 to future functions as a length. */ - if (len < 0) - return REDIS_ERR; - - status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - free(cmd); - return status; -} - -int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { - va_list ap; - int status; - va_start(ap,format); - status = redisvAsyncCommand(ac,fn,privdata,format,ap); - va_end(ap); - return status; -} - -int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - sds cmd; - int len; - int status; - len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); - status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - sdsfree(cmd); - return status; -} - -int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { - int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - return status; -} diff --git a/ext/hiredis-vip-0.3.0/command.c b/ext/hiredis-vip-0.3.0/command.c deleted file mode 100644 index e32091b40..000000000 --- a/ext/hiredis-vip-0.3.0/command.c +++ /dev/null @@ -1,1700 +0,0 @@ -#include -#include - -#include "command.h" -#include "hiutil.h" -#include "hiarray.h" - - -static uint64_t cmd_id = 0; /* command id counter */ - - -/* - * Return true, if the redis command take no key, otherwise - * return false - */ -static int -redis_argz(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_PING: - case CMD_REQ_REDIS_QUIT: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts no arguments, otherwise - * return false - */ -static int -redis_arg0(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_EXISTS: - case CMD_REQ_REDIS_PERSIST: - case CMD_REQ_REDIS_PTTL: - case CMD_REQ_REDIS_SORT: - case CMD_REQ_REDIS_TTL: - case CMD_REQ_REDIS_TYPE: - case CMD_REQ_REDIS_DUMP: - - case CMD_REQ_REDIS_DECR: - case CMD_REQ_REDIS_GET: - case CMD_REQ_REDIS_INCR: - case CMD_REQ_REDIS_STRLEN: - - case CMD_REQ_REDIS_HGETALL: - case CMD_REQ_REDIS_HKEYS: - case CMD_REQ_REDIS_HLEN: - case CMD_REQ_REDIS_HVALS: - - case CMD_REQ_REDIS_LLEN: - case CMD_REQ_REDIS_LPOP: - case CMD_REQ_REDIS_RPOP: - - case CMD_REQ_REDIS_SCARD: - case CMD_REQ_REDIS_SMEMBERS: - case CMD_REQ_REDIS_SPOP: - - case CMD_REQ_REDIS_ZCARD: - case CMD_REQ_REDIS_PFCOUNT: - case CMD_REQ_REDIS_AUTH: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts exactly 1 argument, otherwise - * return false - */ -static int -redis_arg1(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_EXPIRE: - case CMD_REQ_REDIS_EXPIREAT: - case CMD_REQ_REDIS_PEXPIRE: - case CMD_REQ_REDIS_PEXPIREAT: - - case CMD_REQ_REDIS_APPEND: - case CMD_REQ_REDIS_DECRBY: - case CMD_REQ_REDIS_GETBIT: - case CMD_REQ_REDIS_GETSET: - case CMD_REQ_REDIS_INCRBY: - case CMD_REQ_REDIS_INCRBYFLOAT: - case CMD_REQ_REDIS_SETNX: - - case CMD_REQ_REDIS_HEXISTS: - case CMD_REQ_REDIS_HGET: - - case CMD_REQ_REDIS_LINDEX: - case CMD_REQ_REDIS_LPUSHX: - case CMD_REQ_REDIS_RPOPLPUSH: - case CMD_REQ_REDIS_RPUSHX: - - case CMD_REQ_REDIS_SISMEMBER: - - case CMD_REQ_REDIS_ZRANK: - case CMD_REQ_REDIS_ZREVRANK: - case CMD_REQ_REDIS_ZSCORE: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts exactly 2 arguments, otherwise - * return false - */ -static int -redis_arg2(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_GETRANGE: - case CMD_REQ_REDIS_PSETEX: - case CMD_REQ_REDIS_SETBIT: - case CMD_REQ_REDIS_SETEX: - case CMD_REQ_REDIS_SETRANGE: - - case CMD_REQ_REDIS_HINCRBY: - case CMD_REQ_REDIS_HINCRBYFLOAT: - case CMD_REQ_REDIS_HSET: - case CMD_REQ_REDIS_HSETNX: - - case CMD_REQ_REDIS_LRANGE: - case CMD_REQ_REDIS_LREM: - case CMD_REQ_REDIS_LSET: - case CMD_REQ_REDIS_LTRIM: - - case CMD_REQ_REDIS_SMOVE: - - case CMD_REQ_REDIS_ZCOUNT: - case CMD_REQ_REDIS_ZLEXCOUNT: - case CMD_REQ_REDIS_ZINCRBY: - case CMD_REQ_REDIS_ZREMRANGEBYLEX: - case CMD_REQ_REDIS_ZREMRANGEBYRANK: - case CMD_REQ_REDIS_ZREMRANGEBYSCORE: - - case CMD_REQ_REDIS_RESTORE: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts exactly 3 arguments, otherwise - * return false - */ -static int -redis_arg3(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_LINSERT: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command accepts 0 or more arguments, otherwise - * return false - */ -static int -redis_argn(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_BITCOUNT: - - case CMD_REQ_REDIS_SET: - case CMD_REQ_REDIS_HDEL: - case CMD_REQ_REDIS_HMGET: - case CMD_REQ_REDIS_HMSET: - case CMD_REQ_REDIS_HSCAN: - - case CMD_REQ_REDIS_LPUSH: - case CMD_REQ_REDIS_RPUSH: - - case CMD_REQ_REDIS_SADD: - case CMD_REQ_REDIS_SDIFF: - case CMD_REQ_REDIS_SDIFFSTORE: - case CMD_REQ_REDIS_SINTER: - case CMD_REQ_REDIS_SINTERSTORE: - case CMD_REQ_REDIS_SREM: - case CMD_REQ_REDIS_SUNION: - case CMD_REQ_REDIS_SUNIONSTORE: - case CMD_REQ_REDIS_SRANDMEMBER: - case CMD_REQ_REDIS_SSCAN: - - case CMD_REQ_REDIS_PFADD: - case CMD_REQ_REDIS_PFMERGE: - - case CMD_REQ_REDIS_ZADD: - case CMD_REQ_REDIS_ZINTERSTORE: - case CMD_REQ_REDIS_ZRANGE: - case CMD_REQ_REDIS_ZRANGEBYSCORE: - case CMD_REQ_REDIS_ZREM: - case CMD_REQ_REDIS_ZREVRANGE: - case CMD_REQ_REDIS_ZRANGEBYLEX: - case CMD_REQ_REDIS_ZREVRANGEBYSCORE: - case CMD_REQ_REDIS_ZUNIONSTORE: - case CMD_REQ_REDIS_ZSCAN: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command is a vector command accepting one or - * more keys, otherwise return false - */ -static int -redis_argx(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_MGET: - case CMD_REQ_REDIS_DEL: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command is a vector command accepting one or - * more key-value pairs, otherwise return false - */ -static int -redis_argkvx(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_MSET: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Return true, if the redis command is either EVAL or EVALSHA. These commands - * have a special format with exactly 2 arguments, followed by one or more keys, - * followed by zero or more arguments (the documentation online seems to suggest - * that at least one argument is required, but that shouldn't be the case). - */ -static int -redis_argeval(struct cmd *r) -{ - switch (r->type) { - case CMD_REQ_REDIS_EVAL: - case CMD_REQ_REDIS_EVALSHA: - return 1; - - default: - break; - } - - return 0; -} - -/* - * Reference: http://redis.io/topics/protocol - * - * Redis >= 1.2 uses the unified protocol to send requests to the Redis - * server. In the unified protocol all the arguments sent to the server - * are binary safe and every request has the following general form: - * - * * CR LF - * $ CR LF - * CR LF - * ... - * $ CR LF - * CR LF - * - * Before the unified request protocol, redis protocol for requests supported - * the following commands - * 1). Inline commands: simple commands where arguments are just space - * separated strings. No binary safeness is possible. - * 2). Bulk commands: bulk commands are exactly like inline commands, but - * the last argument is handled in a special way in order to allow for - * a binary-safe last argument. - * - * only supports the Redis unified protocol for requests. - */ -void -redis_parse_cmd(struct cmd *r) -{ - int len; - char *p, *m, *token = NULL; - char *cmd_end; - char ch; - uint32_t rlen = 0; /* running length in parsing fsa */ - uint32_t rnarg = 0; /* running # arg used by parsing fsa */ - enum { - SW_START, - SW_NARG, - SW_NARG_LF, - SW_REQ_TYPE_LEN, - SW_REQ_TYPE_LEN_LF, - SW_REQ_TYPE, - SW_REQ_TYPE_LF, - SW_KEY_LEN, - SW_KEY_LEN_LF, - SW_KEY, - SW_KEY_LF, - SW_ARG1_LEN, - SW_ARG1_LEN_LF, - SW_ARG1, - SW_ARG1_LF, - SW_ARG2_LEN, - SW_ARG2_LEN_LF, - SW_ARG2, - SW_ARG2_LF, - SW_ARG3_LEN, - SW_ARG3_LEN_LF, - SW_ARG3, - SW_ARG3_LF, - SW_ARGN_LEN, - SW_ARGN_LEN_LF, - SW_ARGN, - SW_ARGN_LF, - SW_SENTINEL - } state; - - state = SW_START; - cmd_end = r->cmd + r->clen; - - ASSERT(state >= SW_START && state < SW_SENTINEL); - ASSERT(r->cmd != NULL && r->clen > 0); - - for (p = r->cmd; p < cmd_end; p++) { - ch = *p; - - switch (state) { - - case SW_START: - case SW_NARG: - if (token == NULL) { - if (ch != '*') { - goto error; - } - token = p; - /* req_start <- p */ - r->narg_start = p; - rnarg = 0; - state = SW_NARG; - } else if (isdigit(ch)) { - rnarg = rnarg * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if (rnarg == 0) { - goto error; - } - r->narg = rnarg; - r->narg_end = p; - token = NULL; - state = SW_NARG_LF; - } else { - goto error; - } - - break; - - case SW_NARG_LF: - switch (ch) { - case LF: - state = SW_REQ_TYPE_LEN; - break; - - default: - goto error; - } - - break; - - case SW_REQ_TYPE_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - token = p; - rlen = 0; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if (rlen == 0 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_REQ_TYPE_LEN_LF; - } else { - goto error; - } - - break; - - case SW_REQ_TYPE_LEN_LF: - switch (ch) { - case LF: - state = SW_REQ_TYPE; - break; - - default: - goto error; - } - - break; - - case SW_REQ_TYPE: - if (token == NULL) { - token = p; - } - - m = token + rlen; - if (m >= cmd_end) { - //m = cmd_end - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - m = token; - token = NULL; - r->type = CMD_UNKNOWN; - - switch (p - m) { - - case 3: - if (str3icmp(m, 'g', 'e', 't')) { - r->type = CMD_REQ_REDIS_GET; - break; - } - - if (str3icmp(m, 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_SET; - break; - } - - if (str3icmp(m, 't', 't', 'l')) { - r->type = CMD_REQ_REDIS_TTL; - break; - } - - if (str3icmp(m, 'd', 'e', 'l')) { - r->type = CMD_REQ_REDIS_DEL; - break; - } - - break; - - case 4: - if (str4icmp(m, 'p', 't', 't', 'l')) { - r->type = CMD_REQ_REDIS_PTTL; - break; - } - - if (str4icmp(m, 'd', 'e', 'c', 'r')) { - r->type = CMD_REQ_REDIS_DECR; - break; - } - - if (str4icmp(m, 'd', 'u', 'm', 'p')) { - r->type = CMD_REQ_REDIS_DUMP; - break; - } - - if (str4icmp(m, 'h', 'd', 'e', 'l')) { - r->type = CMD_REQ_REDIS_HDEL; - break; - } - - if (str4icmp(m, 'h', 'g', 'e', 't')) { - r->type = CMD_REQ_REDIS_HGET; - break; - } - - if (str4icmp(m, 'h', 'l', 'e', 'n')) { - r->type = CMD_REQ_REDIS_HLEN; - break; - } - - if (str4icmp(m, 'h', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_HSET; - break; - } - - if (str4icmp(m, 'i', 'n', 'c', 'r')) { - r->type = CMD_REQ_REDIS_INCR; - break; - } - - if (str4icmp(m, 'l', 'l', 'e', 'n')) { - r->type = CMD_REQ_REDIS_LLEN; - break; - } - - if (str4icmp(m, 'l', 'p', 'o', 'p')) { - r->type = CMD_REQ_REDIS_LPOP; - break; - } - - if (str4icmp(m, 'l', 'r', 'e', 'm')) { - r->type = CMD_REQ_REDIS_LREM; - break; - } - - if (str4icmp(m, 'l', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_LSET; - break; - } - - if (str4icmp(m, 'r', 'p', 'o', 'p')) { - r->type = CMD_REQ_REDIS_RPOP; - break; - } - - if (str4icmp(m, 's', 'a', 'd', 'd')) { - r->type = CMD_REQ_REDIS_SADD; - break; - } - - if (str4icmp(m, 's', 'p', 'o', 'p')) { - r->type = CMD_REQ_REDIS_SPOP; - break; - } - - if (str4icmp(m, 's', 'r', 'e', 'm')) { - r->type = CMD_REQ_REDIS_SREM; - break; - } - - if (str4icmp(m, 't', 'y', 'p', 'e')) { - r->type = CMD_REQ_REDIS_TYPE; - break; - } - - if (str4icmp(m, 'm', 'g', 'e', 't')) { - r->type = CMD_REQ_REDIS_MGET; - break; - } - if (str4icmp(m, 'm', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_MSET; - break; - } - - if (str4icmp(m, 'z', 'a', 'd', 'd')) { - r->type = CMD_REQ_REDIS_ZADD; - break; - } - - if (str4icmp(m, 'z', 'r', 'e', 'm')) { - r->type = CMD_REQ_REDIS_ZREM; - break; - } - - if (str4icmp(m, 'e', 'v', 'a', 'l')) { - r->type = CMD_REQ_REDIS_EVAL; - break; - } - - if (str4icmp(m, 's', 'o', 'r', 't')) { - r->type = CMD_REQ_REDIS_SORT; - break; - } - - if (str4icmp(m, 'p', 'i', 'n', 'g')) { - r->type = CMD_REQ_REDIS_PING; - r->noforward = 1; - break; - } - - if (str4icmp(m, 'q', 'u', 'i', 't')) { - r->type = CMD_REQ_REDIS_QUIT; - r->quit = 1; - break; - } - - if (str4icmp(m, 'a', 'u', 't', 'h')) { - r->type = CMD_REQ_REDIS_AUTH; - r->noforward = 1; - break; - } - - break; - - case 5: - if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) { - r->type = CMD_REQ_REDIS_HKEYS; - break; - } - - if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) { - r->type = CMD_REQ_REDIS_HMGET; - break; - } - - if (str5icmp(m, 'h', 'm', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_HMSET; - break; - } - - if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) { - r->type = CMD_REQ_REDIS_HVALS; - break; - } - - if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) { - r->type = CMD_REQ_REDIS_HSCAN; - break; - } - - if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) { - r->type = CMD_REQ_REDIS_LPUSH; - break; - } - - if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) { - r->type = CMD_REQ_REDIS_LTRIM; - break; - } - - if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) { - r->type = CMD_REQ_REDIS_RPUSH; - break; - } - - if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) { - r->type = CMD_REQ_REDIS_SCARD; - break; - } - - if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) { - r->type = CMD_REQ_REDIS_SDIFF; - break; - } - - if (str5icmp(m, 's', 'e', 't', 'e', 'x')) { - r->type = CMD_REQ_REDIS_SETEX; - break; - } - - if (str5icmp(m, 's', 'e', 't', 'n', 'x')) { - r->type = CMD_REQ_REDIS_SETNX; - break; - } - - if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) { - r->type = CMD_REQ_REDIS_SMOVE; - break; - } - - if (str5icmp(m, 's', 's', 'c', 'a', 'n')) { - r->type = CMD_REQ_REDIS_SSCAN; - break; - } - - if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) { - r->type = CMD_REQ_REDIS_ZCARD; - break; - } - - if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) { - r->type = CMD_REQ_REDIS_ZRANK; - break; - } - - if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) { - r->type = CMD_REQ_REDIS_ZSCAN; - break; - } - - if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) { - r->type = CMD_REQ_REDIS_PFADD; - break; - } - - break; - - case 6: - if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) { - r->type = CMD_REQ_REDIS_APPEND; - break; - } - - if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) { - r->type = CMD_REQ_REDIS_DECRBY; - break; - } - - if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) { - r->type = CMD_REQ_REDIS_EXISTS; - break; - } - - if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) { - r->type = CMD_REQ_REDIS_EXPIRE; - break; - } - - if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) { - r->type = CMD_REQ_REDIS_GETBIT; - break; - } - - if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) { - r->type = CMD_REQ_REDIS_GETSET; - break; - } - - if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) { - r->type = CMD_REQ_REDIS_PSETEX; - break; - } - - if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) { - r->type = CMD_REQ_REDIS_HSETNX; - break; - } - - if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) { - r->type = CMD_REQ_REDIS_INCRBY; - break; - } - - if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) { - r->type = CMD_REQ_REDIS_LINDEX; - break; - } - - if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) { - r->type = CMD_REQ_REDIS_LPUSHX; - break; - } - - if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_LRANGE; - break; - } - - if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) { - r->type = CMD_REQ_REDIS_RPUSHX; - break; - } - - if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) { - r->type = CMD_REQ_REDIS_SETBIT; - break; - } - - if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) { - r->type = CMD_REQ_REDIS_SINTER; - break; - } - - if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) { - r->type = CMD_REQ_REDIS_STRLEN; - break; - } - - if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) { - r->type = CMD_REQ_REDIS_SUNION; - break; - } - - if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) { - r->type = CMD_REQ_REDIS_ZCOUNT; - break; - } - - if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_ZRANGE; - break; - } - - if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZSCORE; - break; - } - - break; - - case 7: - if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) { - r->type = CMD_REQ_REDIS_PERSIST; - break; - } - - if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) { - r->type = CMD_REQ_REDIS_PEXPIRE; - break; - } - - if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) { - r->type = CMD_REQ_REDIS_HEXISTS; - break; - } - - if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) { - r->type = CMD_REQ_REDIS_HGETALL; - break; - } - - if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) { - r->type = CMD_REQ_REDIS_HINCRBY; - break; - } - - if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) { - r->type = CMD_REQ_REDIS_LINSERT; - break; - } - - if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) { - r->type = CMD_REQ_REDIS_ZINCRBY; - break; - } - - if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) { - r->type = CMD_REQ_REDIS_EVALSHA; - break; - } - - if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_RESTORE; - break; - } - - if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) { - r->type = CMD_REQ_REDIS_PFCOUNT; - break; - } - - if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) { - r->type = CMD_REQ_REDIS_PFMERGE; - break; - } - - break; - - case 8: - if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { - r->type = CMD_REQ_REDIS_EXPIREAT; - break; - } - - if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) { - r->type = CMD_REQ_REDIS_BITCOUNT; - break; - } - - if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_GETRANGE; - break; - } - - if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_SETRANGE; - break; - } - - if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) { - r->type = CMD_REQ_REDIS_SMEMBERS; - break; - } - - if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) { - r->type = CMD_REQ_REDIS_ZREVRANK; - break; - } - - break; - - case 9: - if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { - r->type = CMD_REQ_REDIS_PEXPIREAT; - break; - } - - if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) { - r->type = CMD_REQ_REDIS_RPOPLPUSH; - break; - } - - if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) { - r->type = CMD_REQ_REDIS_SISMEMBER; - break; - } - - if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) { - r->type = CMD_REQ_REDIS_ZREVRANGE; - break; - } - - if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) { - r->type = CMD_REQ_REDIS_ZLEXCOUNT; - break; - } - - break; - - case 10: - if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_SDIFFSTORE; - break; - } - - case 11: - if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { - r->type = CMD_REQ_REDIS_INCRBYFLOAT; - break; - } - - if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_SINTERSTORE; - break; - } - - if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) { - r->type = CMD_REQ_REDIS_SRANDMEMBER; - break; - } - - if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_SUNIONSTORE; - break; - } - - if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZINTERSTORE; - break; - } - - if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZUNIONSTORE; - break; - } - - if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { - r->type = CMD_REQ_REDIS_ZRANGEBYLEX; - break; - } - - break; - - case 12: - if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { - r->type = CMD_REQ_REDIS_HINCRBYFLOAT; - break; - } - - - break; - - case 13: - if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZRANGEBYSCORE; - break; - } - - break; - - case 14: - if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { - r->type = CMD_REQ_REDIS_ZREMRANGEBYLEX; - break; - } - - break; - - case 15: - if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) { - r->type = CMD_REQ_REDIS_ZREMRANGEBYRANK; - break; - } - - break; - - case 16: - if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZREMRANGEBYSCORE; - break; - } - - if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { - r->type = CMD_REQ_REDIS_ZREVRANGEBYSCORE; - break; - } - - break; - - default: - break; - } - - if (r->type == CMD_UNKNOWN) { - goto error; - } - - state = SW_REQ_TYPE_LF; - break; - - case SW_REQ_TYPE_LF: - switch (ch) { - case LF: - if (redis_argz(r)) { - goto done; - } else if (redis_argeval(r)) { - state = SW_ARG1_LEN; - } else { - state = SW_KEY_LEN; - } - break; - - default: - goto error; - } - - break; - - case SW_KEY_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - token = p; - rlen = 0; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - - if (rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_KEY_LEN_LF; - } else { - goto error; - } - - break; - - case SW_KEY_LEN_LF: - switch (ch) { - case LF: - state = SW_KEY; - break; - - default: - goto error; - } - - break; - - case SW_KEY: - if (token == NULL) { - token = p; - } - - m = token + rlen; - if (m >= cmd_end) { - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } else { /* got a key */ - struct keypos *kpos; - - p = m; /* move forward by rlen bytes */ - rlen = 0; - m = token; - token = NULL; - - kpos = hiarray_push(r->keys); - if (kpos == NULL) { - goto enomem; - } - kpos->start = m; - kpos->end = p; - //kpos->v_len = 0; - - state = SW_KEY_LF; - } - - break; - - case SW_KEY_LF: - switch (ch) { - case LF: - if (redis_arg0(r)) { - if (rnarg != 0) { - goto error; - } - goto done; - } else if (redis_arg1(r)) { - if (rnarg != 1) { - goto error; - } - state = SW_ARG1_LEN; - } else if (redis_arg2(r)) { - if (rnarg != 2) { - goto error; - } - state = SW_ARG1_LEN; - } else if (redis_arg3(r)) { - if (rnarg != 3) { - goto error; - } - state = SW_ARG1_LEN; - } else if (redis_argn(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARG1_LEN; - } else if (redis_argx(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_KEY_LEN; - } else if (redis_argkvx(r)) { - if (rnarg == 0) { - goto done; - } - if (r->narg % 2 == 0) { - goto error; - } - state = SW_ARG1_LEN; - } else if (redis_argeval(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_ARG1_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - rlen = 0; - token = p; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if ((p - token) <= 1 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - - /* - //for mset value length - if(redis_argkvx(r)) - { - struct keypos *kpos; - uint32_t array_len = array_n(r->keys); - if(array_len == 0) - { - goto error; - } - - kpos = array_n(r->keys, array_len-1); - if (kpos == NULL || kpos->v_len != 0) { - goto error; - } - - kpos->v_len = rlen; - } - */ - state = SW_ARG1_LEN_LF; - } else { - goto error; - } - - break; - - case SW_ARG1_LEN_LF: - switch (ch) { - case LF: - state = SW_ARG1; - break; - - default: - goto error; - } - - break; - - case SW_ARG1: - m = p + rlen; - if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - - state = SW_ARG1_LF; - - break; - - case SW_ARG1_LF: - switch (ch) { - case LF: - if (redis_arg1(r)) { - if (rnarg != 0) { - goto error; - } - goto done; - } else if (redis_arg2(r)) { - if (rnarg != 1) { - goto error; - } - state = SW_ARG2_LEN; - } else if (redis_arg3(r)) { - if (rnarg != 2) { - goto error; - } - state = SW_ARG2_LEN; - } else if (redis_argn(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else if (redis_argeval(r)) { - if (rnarg < 2) { - goto error; - } - state = SW_ARG2_LEN; - } else if (redis_argkvx(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_KEY_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_ARG2_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - rlen = 0; - token = p; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if ((p - token) <= 1 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_ARG2_LEN_LF; - } else { - goto error; - } - - break; - - case SW_ARG2_LEN_LF: - switch (ch) { - case LF: - state = SW_ARG2; - break; - - default: - goto error; - } - - break; - - case SW_ARG2: - if (token == NULL && redis_argeval(r)) { - /* - * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must - * be tokenized and stored in contiguous memory. - */ - token = p; - } - - m = p + rlen; - if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - - if (redis_argeval(r)) { - uint32_t nkey; - char *chp; - - /* - * For EVAL/EVALSHA, we need to find the integer value of this - * argument. It tells us the number of keys in the script, and - * we need to error out if number of keys is 0. At this point, - * both p and m point to the end of the argument and r->token - * points to the start. - */ - if (p - token < 1) { - goto error; - } - - for (nkey = 0, chp = token; chp < p; chp++) { - if (isdigit(*chp)) { - nkey = nkey * 10 + (uint32_t)(*chp - '0'); - } else { - goto error; - } - } - if (nkey == 0) { - goto error; - } - - token = NULL; - } - - state = SW_ARG2_LF; - - break; - - case SW_ARG2_LF: - switch (ch) { - case LF: - if (redis_arg2(r)) { - if (rnarg != 0) { - goto error; - } - goto done; - } else if (redis_arg3(r)) { - if (rnarg != 1) { - goto error; - } - state = SW_ARG3_LEN; - } else if (redis_argn(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else if (redis_argeval(r)) { - if (rnarg < 1) { - goto error; - } - state = SW_KEY_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_ARG3_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - rlen = 0; - token = p; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if ((p - token) <= 1 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_ARG3_LEN_LF; - } else { - goto error; - } - - break; - - case SW_ARG3_LEN_LF: - switch (ch) { - case LF: - state = SW_ARG3; - break; - - default: - goto error; - } - - break; - - case SW_ARG3: - m = p + rlen; - if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - state = SW_ARG3_LF; - - break; - - case SW_ARG3_LF: - switch (ch) { - case LF: - if (redis_arg3(r)) { - if (rnarg != 0) { - goto error; - } - goto done; - } else if (redis_argn(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_ARGN_LEN: - if (token == NULL) { - if (ch != '$') { - goto error; - } - rlen = 0; - token = p; - } else if (isdigit(ch)) { - rlen = rlen * 10 + (uint32_t)(ch - '0'); - } else if (ch == CR) { - if ((p - token) <= 1 || rnarg == 0) { - goto error; - } - rnarg--; - token = NULL; - state = SW_ARGN_LEN_LF; - } else { - goto error; - } - - break; - - case SW_ARGN_LEN_LF: - switch (ch) { - case LF: - state = SW_ARGN; - break; - - default: - goto error; - } - - break; - - case SW_ARGN: - m = p + rlen; - if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; - goto error; - } - - if (*m != CR) { - goto error; - } - - p = m; /* move forward by rlen bytes */ - rlen = 0; - state = SW_ARGN_LF; - - break; - - case SW_ARGN_LF: - switch (ch) { - case LF: - if (redis_argn(r) || redis_argeval(r)) { - if (rnarg == 0) { - goto done; - } - state = SW_ARGN_LEN; - } else { - goto error; - } - - break; - - default: - goto error; - } - - break; - - case SW_SENTINEL: - default: - NOT_REACHED(); - break; - } - } - - ASSERT(p == cmd_end); - - return; - -done: - - ASSERT(r->type > CMD_UNKNOWN && r->type < CMD_SENTINEL); - - r->result = CMD_PARSE_OK; - - return; - -enomem: - - r->result = CMD_PARSE_ENOMEM; - - return; - -error: - - r->result = CMD_PARSE_ERROR; - errno = EINVAL; - if(r->errstr == NULL){ - r->errstr = hi_alloc(100*sizeof(*r->errstr)); - } - - len = _scnprintf(r->errstr, 100, "Parse command error. Cmd type: %d, state: %d, break position: %d.", - r->type, state, (int)(p - r->cmd)); - r->errstr[len] = '\0'; -} - -struct cmd *command_get() -{ - struct cmd *command; - command = hi_alloc(sizeof(struct cmd)); - if(command == NULL) - { - return NULL; - } - - command->id = ++cmd_id; - command->result = CMD_PARSE_OK; - command->errstr = NULL; - command->type = CMD_UNKNOWN; - command->cmd = NULL; - command->clen = 0; - command->keys = NULL; - command->narg_start = NULL; - command->narg_end = NULL; - command->narg = 0; - command->quit = 0; - command->noforward = 0; - command->slot_num = -1; - command->frag_seq = NULL; - command->reply = NULL; - command->sub_commands = NULL; - - command->keys = hiarray_create(1, sizeof(struct keypos)); - if (command->keys == NULL) - { - hi_free(command); - return NULL; - } - - return command; -} - -void command_destroy(struct cmd *command) -{ - if(command == NULL) - { - return; - } - - if(command->cmd != NULL) - { - free(command->cmd); - } - - if(command->errstr != NULL){ - hi_free(command->errstr); - } - - if(command->keys != NULL) - { - command->keys->nelem = 0; - hiarray_destroy(command->keys); - } - - if(command->frag_seq != NULL) - { - hi_free(command->frag_seq); - command->frag_seq = NULL; - } - - if(command->reply != NULL) - { - freeReplyObject(command->reply); - } - - if(command->sub_commands != NULL) - { - listRelease(command->sub_commands); - } - - hi_free(command); -} - - diff --git a/ext/hiredis-vip-0.3.0/command.h b/ext/hiredis-vip-0.3.0/command.h deleted file mode 100644 index b7c388a69..000000000 --- a/ext/hiredis-vip-0.3.0/command.h +++ /dev/null @@ -1,179 +0,0 @@ -#ifndef __COMMAND_H_ -#define __COMMAND_H_ - -#include - -#include "hiredis.h" -#include "adlist.h" - -typedef enum cmd_parse_result { - CMD_PARSE_OK, /* parsing ok */ - CMD_PARSE_ENOMEM, /* out of memory */ - CMD_PARSE_ERROR, /* parsing error */ - CMD_PARSE_REPAIR, /* more to parse -> repair parsed & unparsed data */ - CMD_PARSE_AGAIN, /* incomplete -> parse again */ -} cmd_parse_result_t; - -#define CMD_TYPE_CODEC(ACTION) \ - ACTION( UNKNOWN ) \ - ACTION( REQ_REDIS_DEL ) /* redis commands - keys */ \ - ACTION( REQ_REDIS_EXISTS ) \ - ACTION( REQ_REDIS_EXPIRE ) \ - ACTION( REQ_REDIS_EXPIREAT ) \ - ACTION( REQ_REDIS_PEXPIRE ) \ - ACTION( REQ_REDIS_PEXPIREAT ) \ - ACTION( REQ_REDIS_PERSIST ) \ - ACTION( REQ_REDIS_PTTL ) \ - ACTION( REQ_REDIS_SORT ) \ - ACTION( REQ_REDIS_TTL ) \ - ACTION( REQ_REDIS_TYPE ) \ - ACTION( REQ_REDIS_APPEND ) /* redis requests - string */ \ - ACTION( REQ_REDIS_BITCOUNT ) \ - ACTION( REQ_REDIS_DECR ) \ - ACTION( REQ_REDIS_DECRBY ) \ - ACTION( REQ_REDIS_DUMP ) \ - ACTION( REQ_REDIS_GET ) \ - ACTION( REQ_REDIS_GETBIT ) \ - ACTION( REQ_REDIS_GETRANGE ) \ - ACTION( REQ_REDIS_GETSET ) \ - ACTION( REQ_REDIS_INCR ) \ - ACTION( REQ_REDIS_INCRBY ) \ - ACTION( REQ_REDIS_INCRBYFLOAT ) \ - ACTION( REQ_REDIS_MGET ) \ - ACTION( REQ_REDIS_MSET ) \ - ACTION( REQ_REDIS_PSETEX ) \ - ACTION( REQ_REDIS_RESTORE ) \ - ACTION( REQ_REDIS_SET ) \ - ACTION( REQ_REDIS_SETBIT ) \ - ACTION( REQ_REDIS_SETEX ) \ - ACTION( REQ_REDIS_SETNX ) \ - ACTION( REQ_REDIS_SETRANGE ) \ - ACTION( REQ_REDIS_STRLEN ) \ - ACTION( REQ_REDIS_HDEL ) /* redis requests - hashes */ \ - ACTION( REQ_REDIS_HEXISTS ) \ - ACTION( REQ_REDIS_HGET ) \ - ACTION( REQ_REDIS_HGETALL ) \ - ACTION( REQ_REDIS_HINCRBY ) \ - ACTION( REQ_REDIS_HINCRBYFLOAT ) \ - ACTION( REQ_REDIS_HKEYS ) \ - ACTION( REQ_REDIS_HLEN ) \ - ACTION( REQ_REDIS_HMGET ) \ - ACTION( REQ_REDIS_HMSET ) \ - ACTION( REQ_REDIS_HSET ) \ - ACTION( REQ_REDIS_HSETNX ) \ - ACTION( REQ_REDIS_HSCAN) \ - ACTION( REQ_REDIS_HVALS ) \ - ACTION( REQ_REDIS_LINDEX ) /* redis requests - lists */ \ - ACTION( REQ_REDIS_LINSERT ) \ - ACTION( REQ_REDIS_LLEN ) \ - ACTION( REQ_REDIS_LPOP ) \ - ACTION( REQ_REDIS_LPUSH ) \ - ACTION( REQ_REDIS_LPUSHX ) \ - ACTION( REQ_REDIS_LRANGE ) \ - ACTION( REQ_REDIS_LREM ) \ - ACTION( REQ_REDIS_LSET ) \ - ACTION( REQ_REDIS_LTRIM ) \ - ACTION( REQ_REDIS_PFADD ) /* redis requests - hyperloglog */ \ - ACTION( REQ_REDIS_PFCOUNT ) \ - ACTION( REQ_REDIS_PFMERGE ) \ - ACTION( REQ_REDIS_RPOP ) \ - ACTION( REQ_REDIS_RPOPLPUSH ) \ - ACTION( REQ_REDIS_RPUSH ) \ - ACTION( REQ_REDIS_RPUSHX ) \ - ACTION( REQ_REDIS_SADD ) /* redis requests - sets */ \ - ACTION( REQ_REDIS_SCARD ) \ - ACTION( REQ_REDIS_SDIFF ) \ - ACTION( REQ_REDIS_SDIFFSTORE ) \ - ACTION( REQ_REDIS_SINTER ) \ - ACTION( REQ_REDIS_SINTERSTORE ) \ - ACTION( REQ_REDIS_SISMEMBER ) \ - ACTION( REQ_REDIS_SMEMBERS ) \ - ACTION( REQ_REDIS_SMOVE ) \ - ACTION( REQ_REDIS_SPOP ) \ - ACTION( REQ_REDIS_SRANDMEMBER ) \ - ACTION( REQ_REDIS_SREM ) \ - ACTION( REQ_REDIS_SUNION ) \ - ACTION( REQ_REDIS_SUNIONSTORE ) \ - ACTION( REQ_REDIS_SSCAN) \ - ACTION( REQ_REDIS_ZADD ) /* redis requests - sorted sets */ \ - ACTION( REQ_REDIS_ZCARD ) \ - ACTION( REQ_REDIS_ZCOUNT ) \ - ACTION( REQ_REDIS_ZINCRBY ) \ - ACTION( REQ_REDIS_ZINTERSTORE ) \ - ACTION( REQ_REDIS_ZLEXCOUNT ) \ - ACTION( REQ_REDIS_ZRANGE ) \ - ACTION( REQ_REDIS_ZRANGEBYLEX ) \ - ACTION( REQ_REDIS_ZRANGEBYSCORE ) \ - ACTION( REQ_REDIS_ZRANK ) \ - ACTION( REQ_REDIS_ZREM ) \ - ACTION( REQ_REDIS_ZREMRANGEBYRANK ) \ - ACTION( REQ_REDIS_ZREMRANGEBYLEX ) \ - ACTION( REQ_REDIS_ZREMRANGEBYSCORE ) \ - ACTION( REQ_REDIS_ZREVRANGE ) \ - ACTION( REQ_REDIS_ZREVRANGEBYSCORE ) \ - ACTION( REQ_REDIS_ZREVRANK ) \ - ACTION( REQ_REDIS_ZSCORE ) \ - ACTION( REQ_REDIS_ZUNIONSTORE ) \ - ACTION( REQ_REDIS_ZSCAN) \ - ACTION( REQ_REDIS_EVAL ) /* redis requests - eval */ \ - ACTION( REQ_REDIS_EVALSHA ) \ - ACTION( REQ_REDIS_PING ) /* redis requests - ping/quit */ \ - ACTION( REQ_REDIS_QUIT) \ - ACTION( REQ_REDIS_AUTH) \ - ACTION( RSP_REDIS_STATUS ) /* redis response */ \ - ACTION( RSP_REDIS_ERROR ) \ - ACTION( RSP_REDIS_INTEGER ) \ - ACTION( RSP_REDIS_BULK ) \ - ACTION( RSP_REDIS_MULTIBULK ) \ - ACTION( SENTINEL ) \ - - -#define DEFINE_ACTION(_name) CMD_##_name, -typedef enum cmd_type { - CMD_TYPE_CODEC(DEFINE_ACTION) -} cmd_type_t; -#undef DEFINE_ACTION - - -struct keypos { - char *start; /* key start pos */ - char *end; /* key end pos */ - uint32_t remain_len; /* remain length after keypos->end for more key-value pairs in command, like mset */ -}; - -struct cmd { - - uint64_t id; /* command id */ - - cmd_parse_result_t result; /* command parsing result */ - char *errstr; /* error info when the command parse failed */ - - cmd_type_t type; /* command type */ - - char *cmd; - uint32_t clen; /* command length */ - - struct hiarray *keys; /* array of keypos, for req */ - - char *narg_start; /* narg start (redis) */ - char *narg_end; /* narg end (redis) */ - uint32_t narg; /* # arguments (redis) */ - - unsigned quit:1; /* quit request? */ - unsigned noforward:1; /* not need forward (example: ping) */ - - int slot_num; /* this command should send to witch slot? - * -1:the keys in this command cross different slots*/ - struct cmd **frag_seq; /* sequence of fragment command, map from keys to fragments*/ - - redisReply *reply; - - hilist *sub_commands; /* just for pipeline and multi-key commands */ -}; - -void redis_parse_cmd(struct cmd *r); - -struct cmd *command_get(void); -void command_destroy(struct cmd *command); - -#endif diff --git a/ext/hiredis-vip-0.3.0/crc16.c b/ext/hiredis-vip-0.3.0/crc16.c deleted file mode 100644 index 0f304f6e4..000000000 --- a/ext/hiredis-vip-0.3.0/crc16.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2001-2010 Georges Menie (www.menie.org) - * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the University of California, Berkeley nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* CRC16 implementation according to CCITT standards. - * - * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the - * following parameters: - * - * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" - * Width : 16 bit - * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) - * Initialization : 0000 - * Reflect Input byte : False - * Reflect Output CRC : False - * Xor constant to output CRC : 0000 - * Output for "123456789" : 31C3 - */ -#include "hiutil.h" - -static const uint16_t crc16tab[256]= { - 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, - 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, - 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, - 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, - 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, - 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, - 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, - 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, - 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, - 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, - 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, - 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, - 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, - 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, - 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, - 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, - 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, - 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, - 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, - 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, - 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, - 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, - 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, - 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, - 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, - 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, - 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, - 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, - 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, - 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, - 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, - 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 -}; - -uint16_t crc16(const char *buf, int len) { - int counter; - uint16_t crc = 0; - for (counter = 0; counter < len; counter++) - crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; - return crc; -} diff --git a/ext/hiredis-vip-0.3.0/dict.c b/ext/hiredis-vip-0.3.0/dict.c deleted file mode 100644 index 79b1041ca..000000000 --- a/ext/hiredis-vip-0.3.0/dict.c +++ /dev/null @@ -1,338 +0,0 @@ -/* Hash table implementation. - * - * This file implements in memory hash tables with insert/del/replace/find/ - * get-random-element operations. Hash tables will auto resize if needed - * tables of power of two in size are used, collisions are handled by - * chaining. See the source code for more information... :) - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include "dict.h" - -/* -------------------------- private prototypes ---------------------------- */ - -static int _dictExpandIfNeeded(dict *ht); -static unsigned long _dictNextPower(unsigned long size); -static int _dictKeyIndex(dict *ht, const void *key); -static int _dictInit(dict *ht, dictType *type, void *privDataPtr); - -/* -------------------------- hash functions -------------------------------- */ - -/* Generic hash function (a popular one from Bernstein). - * I tested a few and this was the best. */ -static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { - unsigned int hash = 5381; - - while (len--) - hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ - return hash; -} - -/* ----------------------------- API implementation ------------------------- */ - -/* Reset an hashtable already initialized with ht_init(). - * NOTE: This function should only called by ht_destroy(). */ -static void _dictReset(dict *ht) { - ht->table = NULL; - ht->size = 0; - ht->sizemask = 0; - ht->used = 0; -} - -/* Create a new hash table */ -static dict *dictCreate(dictType *type, void *privDataPtr) { - dict *ht = malloc(sizeof(*ht)); - _dictInit(ht,type,privDataPtr); - return ht; -} - -/* Initialize the hash table */ -static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { - _dictReset(ht); - ht->type = type; - ht->privdata = privDataPtr; - return DICT_OK; -} - -/* Expand or create the hashtable */ -static int dictExpand(dict *ht, unsigned long size) { - dict n; /* the new hashtable */ - unsigned long realsize = _dictNextPower(size), i; - - /* the size is invalid if it is smaller than the number of - * elements already inside the hashtable */ - if (ht->used > size) - return DICT_ERR; - - _dictInit(&n, ht->type, ht->privdata); - n.size = realsize; - n.sizemask = realsize-1; - n.table = calloc(realsize,sizeof(dictEntry*)); - - /* Copy all the elements from the old to the new table: - * note that if the old hash table is empty ht->size is zero, - * so dictExpand just creates an hash table. */ - n.used = ht->used; - for (i = 0; i < ht->size && ht->used > 0; i++) { - dictEntry *he, *nextHe; - - if (ht->table[i] == NULL) continue; - - /* For each hash entry on this slot... */ - he = ht->table[i]; - while(he) { - unsigned int h; - - nextHe = he->next; - /* Get the new element index */ - h = dictHashKey(ht, he->key) & n.sizemask; - he->next = n.table[h]; - n.table[h] = he; - ht->used--; - /* Pass to the next element */ - he = nextHe; - } - } - assert(ht->used == 0); - free(ht->table); - - /* Remap the new hashtable in the old */ - *ht = n; - return DICT_OK; -} - -/* Add an element to the target hash table */ -static int dictAdd(dict *ht, void *key, void *val) { - int index; - dictEntry *entry; - - /* Get the index of the new element, or -1 if - * the element already exists. */ - if ((index = _dictKeyIndex(ht, key)) == -1) - return DICT_ERR; - - /* Allocates the memory and stores key */ - entry = malloc(sizeof(*entry)); - entry->next = ht->table[index]; - ht->table[index] = entry; - - /* Set the hash entry fields. */ - dictSetHashKey(ht, entry, key); - dictSetHashVal(ht, entry, val); - ht->used++; - return DICT_OK; -} - -/* Add an element, discarding the old if the key already exists. - * Return 1 if the key was added from scratch, 0 if there was already an - * element with such key and dictReplace() just performed a value update - * operation. */ -static int dictReplace(dict *ht, void *key, void *val) { - dictEntry *entry, auxentry; - - /* Try to add the element. If the key - * does not exists dictAdd will suceed. */ - if (dictAdd(ht, key, val) == DICT_OK) - return 1; - /* It already exists, get the entry */ - entry = dictFind(ht, key); - /* Free the old value and set the new one */ - /* Set the new value and free the old one. Note that it is important - * to do that in this order, as the value may just be exactly the same - * as the previous one. In this context, think to reference counting, - * you want to increment (set), and then decrement (free), and not the - * reverse. */ - auxentry = *entry; - dictSetHashVal(ht, entry, val); - dictFreeEntryVal(ht, &auxentry); - return 0; -} - -/* Search and remove an element */ -static int dictDelete(dict *ht, const void *key) { - unsigned int h; - dictEntry *de, *prevde; - - if (ht->size == 0) - return DICT_ERR; - h = dictHashKey(ht, key) & ht->sizemask; - de = ht->table[h]; - - prevde = NULL; - while(de) { - if (dictCompareHashKeys(ht,key,de->key)) { - /* Unlink the element from the list */ - if (prevde) - prevde->next = de->next; - else - ht->table[h] = de->next; - - dictFreeEntryKey(ht,de); - dictFreeEntryVal(ht,de); - free(de); - ht->used--; - return DICT_OK; - } - prevde = de; - de = de->next; - } - return DICT_ERR; /* not found */ -} - -/* Destroy an entire hash table */ -static int _dictClear(dict *ht) { - unsigned long i; - - /* Free all the elements */ - for (i = 0; i < ht->size && ht->used > 0; i++) { - dictEntry *he, *nextHe; - - if ((he = ht->table[i]) == NULL) continue; - while(he) { - nextHe = he->next; - dictFreeEntryKey(ht, he); - dictFreeEntryVal(ht, he); - free(he); - ht->used--; - he = nextHe; - } - } - /* Free the table and the allocated cache structure */ - free(ht->table); - /* Re-initialize the table */ - _dictReset(ht); - return DICT_OK; /* never fails */ -} - -/* Clear & Release the hash table */ -static void dictRelease(dict *ht) { - _dictClear(ht); - free(ht); -} - -static dictEntry *dictFind(dict *ht, const void *key) { - dictEntry *he; - unsigned int h; - - if (ht->size == 0) return NULL; - h = dictHashKey(ht, key) & ht->sizemask; - he = ht->table[h]; - while(he) { - if (dictCompareHashKeys(ht, key, he->key)) - return he; - he = he->next; - } - return NULL; -} - -static dictIterator *dictGetIterator(dict *ht) { - dictIterator *iter = malloc(sizeof(*iter)); - - iter->ht = ht; - iter->index = -1; - iter->entry = NULL; - iter->nextEntry = NULL; - return iter; -} - -static dictEntry *dictNext(dictIterator *iter) { - while (1) { - if (iter->entry == NULL) { - iter->index++; - if (iter->index >= - (signed)iter->ht->size) break; - iter->entry = iter->ht->table[iter->index]; - } else { - iter->entry = iter->nextEntry; - } - if (iter->entry) { - /* We need to save the 'next' here, the iterator user - * may delete the entry we are returning. */ - iter->nextEntry = iter->entry->next; - return iter->entry; - } - } - return NULL; -} - -static void dictReleaseIterator(dictIterator *iter) { - free(iter); -} - -/* ------------------------- private functions ------------------------------ */ - -/* Expand the hash table if needed */ -static int _dictExpandIfNeeded(dict *ht) { - /* If the hash table is empty expand it to the intial size, - * if the table is "full" dobule its size. */ - if (ht->size == 0) - return dictExpand(ht, DICT_HT_INITIAL_SIZE); - if (ht->used == ht->size) - return dictExpand(ht, ht->size*2); - return DICT_OK; -} - -/* Our hash table capability is a power of two */ -static unsigned long _dictNextPower(unsigned long size) { - unsigned long i = DICT_HT_INITIAL_SIZE; - - if (size >= LONG_MAX) return LONG_MAX; - while(1) { - if (i >= size) - return i; - i *= 2; - } -} - -/* Returns the index of a free slot that can be populated with - * an hash entry for the given 'key'. - * If the key already exists, -1 is returned. */ -static int _dictKeyIndex(dict *ht, const void *key) { - unsigned int h; - dictEntry *he; - - /* Expand the hashtable if needed */ - if (_dictExpandIfNeeded(ht) == DICT_ERR) - return -1; - /* Compute the key hash value */ - h = dictHashKey(ht, key) & ht->sizemask; - /* Search if this slot does not already contain the given key */ - he = ht->table[h]; - while(he) { - if (dictCompareHashKeys(ht, key, he->key)) - return -1; - he = he->next; - } - return h; -} - diff --git a/ext/hiredis-vip-0.3.0/examples/example-ae.c b/ext/hiredis-vip-0.3.0/examples/example-ae.c deleted file mode 100644 index 8efa7306a..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-ae.c +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -/* Put event loop in the global scope, so it can be explicitly stopped */ -static aeEventLoop *loop; - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); - - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); -} - -void connectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - aeStop(loop); - return; - } - - printf("Connected...\n"); -} - -void disconnectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - aeStop(loop); - return; - } - - printf("Disconnected...\n"); - aeStop(loop); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - loop = aeCreateEventLoop(64); - redisAeAttach(loop, c); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - aeMain(loop); - return 0; -} - diff --git a/ext/hiredis-vip-0.3.0/examples/example-glib.c b/ext/hiredis-vip-0.3.0/examples/example-glib.c deleted file mode 100644 index d6e10f8e8..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-glib.c +++ /dev/null @@ -1,73 +0,0 @@ -#include - -#include -#include -#include - -static GMainLoop *mainloop; - -static void -connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, - int status) -{ - if (status != REDIS_OK) { - g_printerr("Failed to connect: %s\n", ac->errstr); - g_main_loop_quit(mainloop); - } else { - g_printerr("Connected...\n"); - } -} - -static void -disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, - int status) -{ - if (status != REDIS_OK) { - g_error("Failed to disconnect: %s", ac->errstr); - } else { - g_printerr("Disconnected...\n"); - g_main_loop_quit(mainloop); - } -} - -static void -command_cb(redisAsyncContext *ac, - gpointer r, - gpointer user_data G_GNUC_UNUSED) -{ - redisReply *reply = r; - - if (reply) { - g_print("REPLY: %s\n", reply->str); - } - - redisAsyncDisconnect(ac); -} - -gint -main (gint argc G_GNUC_UNUSED, - gchar *argv[] G_GNUC_UNUSED) -{ - redisAsyncContext *ac; - GMainContext *context = NULL; - GSource *source; - - ac = redisAsyncConnect("127.0.0.1", 6379); - if (ac->err) { - g_printerr("%s\n", ac->errstr); - exit(EXIT_FAILURE); - } - - source = redis_source_new(ac); - mainloop = g_main_loop_new(context, FALSE); - g_source_attach(source, context); - - redisAsyncSetConnectCallback(ac, connect_cb); - redisAsyncSetDisconnectCallback(ac, disconnect_cb); - redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); - redisAsyncCommand(ac, command_cb, NULL, "GET key"); - - g_main_loop_run(mainloop); - - return EXIT_SUCCESS; -} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libev.c b/ext/hiredis-vip-0.3.0/examples/example-libev.c deleted file mode 100644 index cc8b166ec..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-libev.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); - - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); -} - -void connectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Connected...\n"); -} - -void disconnectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Disconnected...\n"); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - redisLibevAttach(EV_DEFAULT_ c); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - ev_loop(EV_DEFAULT_ 0); - return 0; -} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libevent.c b/ext/hiredis-vip-0.3.0/examples/example-libevent.c deleted file mode 100644 index d333c22b7..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-libevent.c +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); - - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); -} - -void connectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Connected...\n"); -} - -void disconnectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Disconnected...\n"); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - struct event_base *base = event_base_new(); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - redisLibeventAttach(c,base); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - event_base_dispatch(base); - return 0; -} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libuv.c b/ext/hiredis-vip-0.3.0/examples/example-libuv.c deleted file mode 100644 index a5462d410..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example-libuv.c +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) return; - printf("argv[%s]: %s\n", (char*)privdata, reply->str); - - /* Disconnect after receiving the reply to GET */ - redisAsyncDisconnect(c); -} - -void connectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Connected...\n"); -} - -void disconnectCallback(const redisAsyncContext *c, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", c->errstr); - return; - } - printf("Disconnected...\n"); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - uv_loop_t* loop = uv_default_loop(); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - redisLibuvAttach(c,loop); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - uv_run(loop, UV_RUN_DEFAULT); - return 0; -} diff --git a/ext/hiredis-vip-0.3.0/examples/example.c b/ext/hiredis-vip-0.3.0/examples/example.c deleted file mode 100644 index 25226a807..000000000 --- a/ext/hiredis-vip-0.3.0/examples/example.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -#include - -int main(int argc, char **argv) { - unsigned int j; - redisContext *c; - redisReply *reply; - const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; - int port = (argc > 2) ? atoi(argv[2]) : 6379; - - struct timeval timeout = { 1, 500000 }; // 1.5 seconds - c = redisConnectWithTimeout(hostname, port, timeout); - if (c == NULL || c->err) { - if (c) { - printf("Connection error: %s\n", c->errstr); - redisFree(c); - } else { - printf("Connection error: can't allocate redis context\n"); - } - exit(1); - } - - /* PING server */ - reply = redisCommand(c,"PING"); - printf("PING: %s\n", reply->str); - freeReplyObject(reply); - - /* Set a key */ - reply = redisCommand(c,"SET %s %s", "foo", "hello world"); - printf("SET: %s\n", reply->str); - freeReplyObject(reply); - - /* Set a key using binary safe API */ - reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); - printf("SET (binary API): %s\n", reply->str); - freeReplyObject(reply); - - /* Try a GET and two INCR */ - reply = redisCommand(c,"GET foo"); - printf("GET foo: %s\n", reply->str); - freeReplyObject(reply); - - reply = redisCommand(c,"INCR counter"); - printf("INCR counter: %lld\n", reply->integer); - freeReplyObject(reply); - /* again ... */ - reply = redisCommand(c,"INCR counter"); - printf("INCR counter: %lld\n", reply->integer); - freeReplyObject(reply); - - /* Create a list of numbers, from 0 to 9 */ - reply = redisCommand(c,"DEL mylist"); - freeReplyObject(reply); - for (j = 0; j < 10; j++) { - char buf[64]; - - snprintf(buf,64,"%d",j); - reply = redisCommand(c,"LPUSH mylist element-%s", buf); - freeReplyObject(reply); - } - - /* Let's check what we have inside the list */ - reply = redisCommand(c,"LRANGE mylist 0 -1"); - if (reply->type == REDIS_REPLY_ARRAY) { - for (j = 0; j < reply->elements; j++) { - printf("%u) %s\n", j, reply->element[j]->str); - } - } - freeReplyObject(reply); - - /* Disconnects and frees the context */ - redisFree(c); - - return 0; -} diff --git a/ext/hiredis-vip-0.3.0/fmacros.h b/ext/hiredis-vip-0.3.0/fmacros.h deleted file mode 100644 index a3b1df034..000000000 --- a/ext/hiredis-vip-0.3.0/fmacros.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef __HIREDIS_FMACRO_H -#define __HIREDIS_FMACRO_H - -#if defined(__linux__) -#ifndef _BSD_SOURCE -#define _BSD_SOURCE -#endif -#define _DEFAULT_SOURCE -#endif - -#if defined(__sun__) -#define _POSIX_C_SOURCE 200112L -#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) -#define _XOPEN_SOURCE 600 -#else -#define _XOPEN_SOURCE -#endif - -#if __APPLE__ && __MACH__ -#define _OSX -#endif - -#endif diff --git a/ext/hiredis-vip-0.3.0/hiarray.c b/ext/hiredis-vip-0.3.0/hiarray.c deleted file mode 100644 index cf742ecf6..000000000 --- a/ext/hiredis-vip-0.3.0/hiarray.c +++ /dev/null @@ -1,188 +0,0 @@ -#include - -#include "hiutil.h" -#include "hiarray.h" - -struct hiarray * -hiarray_create(uint32_t n, size_t size) -{ - struct hiarray *a; - - ASSERT(n != 0 && size != 0); - - a = hi_alloc(sizeof(*a)); - if (a == NULL) { - return NULL; - } - - a->elem = hi_alloc(n * size); - if (a->elem == NULL) { - hi_free(a); - return NULL; - } - - a->nelem = 0; - a->size = size; - a->nalloc = n; - - return a; -} - -void -hiarray_destroy(struct hiarray *a) -{ - hiarray_deinit(a); - hi_free(a); -} - -int -hiarray_init(struct hiarray *a, uint32_t n, size_t size) -{ - ASSERT(n != 0 && size != 0); - - a->elem = hi_alloc(n * size); - if (a->elem == NULL) { - return HI_ENOMEM; - } - - a->nelem = 0; - a->size = size; - a->nalloc = n; - - return HI_OK; -} - -void -hiarray_deinit(struct hiarray *a) -{ - ASSERT(a->nelem == 0); - - if (a->elem != NULL) { - hi_free(a->elem); - } -} - -uint32_t -hiarray_idx(struct hiarray *a, void *elem) -{ - uint8_t *p, *q; - uint32_t off, idx; - - ASSERT(elem >= a->elem); - - p = a->elem; - q = elem; - off = (uint32_t)(q - p); - - ASSERT(off % (uint32_t)a->size == 0); - - idx = off / (uint32_t)a->size; - - return idx; -} - -void * -hiarray_push(struct hiarray *a) -{ - void *elem, *new; - size_t size; - - if (a->nelem == a->nalloc) { - - /* the array is full; allocate new array */ - size = a->size * a->nalloc; - new = hi_realloc(a->elem, 2 * size); - if (new == NULL) { - return NULL; - } - - a->elem = new; - a->nalloc *= 2; - } - - elem = (uint8_t *)a->elem + a->size * a->nelem; - a->nelem++; - - return elem; -} - -void * -hiarray_pop(struct hiarray *a) -{ - void *elem; - - ASSERT(a->nelem != 0); - - a->nelem--; - elem = (uint8_t *)a->elem + a->size * a->nelem; - - return elem; -} - -void * -hiarray_get(struct hiarray *a, uint32_t idx) -{ - void *elem; - - ASSERT(a->nelem != 0); - ASSERT(idx < a->nelem); - - elem = (uint8_t *)a->elem + (a->size * idx); - - return elem; -} - -void * -hiarray_top(struct hiarray *a) -{ - ASSERT(a->nelem != 0); - - return hiarray_get(a, a->nelem - 1); -} - -void -hiarray_swap(struct hiarray *a, struct hiarray *b) -{ - struct hiarray tmp; - - tmp = *a; - *a = *b; - *b = tmp; -} - -/* - * Sort nelem elements of the array in ascending order based on the - * compare comparator. - */ -void -hiarray_sort(struct hiarray *a, hiarray_compare_t compare) -{ - ASSERT(a->nelem != 0); - - qsort(a->elem, a->nelem, a->size, compare); -} - -/* - * Calls the func once for each element in the array as long as func returns - * success. On failure short-circuits and returns the error status. - */ -int -hiarray_each(struct hiarray *a, hiarray_each_t func, void *data) -{ - uint32_t i, nelem; - - ASSERT(array_n(a) != 0); - ASSERT(func != NULL); - - for (i = 0, nelem = hiarray_n(a); i < nelem; i++) { - void *elem = hiarray_get(a, i); - rstatus_t status; - - status = func(elem, data); - if (status != HI_OK) { - return status; - } - } - - return HI_OK; -} diff --git a/ext/hiredis-vip-0.3.0/hiarray.h b/ext/hiredis-vip-0.3.0/hiarray.h deleted file mode 100644 index fda3a4b8b..000000000 --- a/ext/hiredis-vip-0.3.0/hiarray.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef __HIARRAY_H_ -#define __HIARRAY_H_ - -#include - -typedef int (*hiarray_compare_t)(const void *, const void *); -typedef int (*hiarray_each_t)(void *, void *); - -struct hiarray { - uint32_t nelem; /* # element */ - void *elem; /* element */ - size_t size; /* element size */ - uint32_t nalloc; /* # allocated element */ -}; - -#define null_hiarray { 0, NULL, 0, 0 } - -static inline void -hiarray_null(struct hiarray *a) -{ - a->nelem = 0; - a->elem = NULL; - a->size = 0; - a->nalloc = 0; -} - -static inline void -hiarray_set(struct hiarray *a, void *elem, size_t size, uint32_t nalloc) -{ - a->nelem = 0; - a->elem = elem; - a->size = size; - a->nalloc = nalloc; -} - -static inline uint32_t -hiarray_n(const struct hiarray *a) -{ - return a->nelem; -} - -struct hiarray *hiarray_create(uint32_t n, size_t size); -void hiarray_destroy(struct hiarray *a); -int hiarray_init(struct hiarray *a, uint32_t n, size_t size); -void hiarray_deinit(struct hiarray *a); - -uint32_t hiarray_idx(struct hiarray *a, void *elem); -void *hiarray_push(struct hiarray *a); -void *hiarray_pop(struct hiarray *a); -void *hiarray_get(struct hiarray *a, uint32_t idx); -void *hiarray_top(struct hiarray *a); -void hiarray_swap(struct hiarray *a, struct hiarray *b); -void hiarray_sort(struct hiarray *a, hiarray_compare_t compare); -int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data); - -#endif diff --git a/ext/hiredis-vip-0.3.0/hircluster.c b/ext/hiredis-vip-0.3.0/hircluster.c deleted file mode 100644 index edf9cb2f9..000000000 --- a/ext/hiredis-vip-0.3.0/hircluster.c +++ /dev/null @@ -1,4747 +0,0 @@ - -#include "fmacros.h" -#include -#include -#include -#include -#include - -#include "hircluster.h" -#include "hiutil.h" -#include "adlist.h" -#include "hiarray.h" -#include "command.h" -#include "dict.c" - -#define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" -#define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" - -#define REDIS_COMMAND_ASKING "ASKING" -#define REDIS_COMMAND_PING "PING" - -#define REDIS_PROTOCOL_ASKING "*1\r\n$6\r\nASKING\r\n" - -#define IP_PORT_SEPARATOR ":" - -#define CLUSTER_ADDRESS_SEPARATOR "," - -#define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5 - -typedef struct cluster_async_data -{ - redisClusterAsyncContext *acc; - struct cmd *command; - redisClusterCallbackFn *callback; - int retry_count; - void *privdata; -}cluster_async_data; - -typedef enum CLUSTER_ERR_TYPE{ - CLUSTER_NOT_ERR = 0, - CLUSTER_ERR_MOVED, - CLUSTER_ERR_ASK, - CLUSTER_ERR_TRYAGAIN, - CLUSTER_ERR_CROSSSLOT, - CLUSTER_ERR_CLUSTERDOWN, - CLUSTER_ERR_SENTINEL -}CLUSTER_ERR_TYPE; - -static void cluster_node_deinit(cluster_node *node); -static void cluster_slot_destroy(cluster_slot *slot); -static void cluster_open_slot_destroy(copen_slot *oslot); - -void listClusterNodeDestructor(void *val) -{ - cluster_node_deinit(val); - - hi_free(val); -} - -void listClusterSlotDestructor(void *val) -{ - cluster_slot_destroy(val); -} - -unsigned int dictSdsHash(const void *key) { - return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); -} - -int dictSdsKeyCompare(void *privdata, const void *key1, - const void *key2) -{ - int l1,l2; - DICT_NOTUSED(privdata); - - l1 = sdslen((sds)key1); - l2 = sdslen((sds)key2); - if (l1 != l2) return 0; - return memcmp(key1, key2, l1) == 0; -} - -void dictSdsDestructor(void *privdata, void *val) -{ - DICT_NOTUSED(privdata); - - sdsfree(val); -} - -void dictClusterNodeDestructor(void *privdata, void *val) -{ - DICT_NOTUSED(privdata); - - cluster_node_deinit(val); - - hi_free(val); -} - -/* Cluster nodes hash table, mapping nodes - * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) - * or addresses(1.2.3.4:6379) to clusterNode structures. - * Those nodes need destroy. - */ -dictType clusterNodesDictType = { - dictSdsHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCompare, /* key compare */ - dictSdsDestructor, /* key destructor */ - dictClusterNodeDestructor /* val destructor */ -}; - -/* Cluster nodes hash table, mapping nodes - * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) - * or addresses(1.2.3.4:6379) to clusterNode structures. - * Those nodes do not need destroy. - */ -dictType clusterNodesRefDictType = { - dictSdsHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCompare, /* key compare */ - dictSdsDestructor, /* key destructor */ - NULL /* val destructor */ -}; - - -void listCommandFree(void *command) -{ - struct cmd *cmd = command; - command_destroy(cmd); -} - -/* Defined in hiredis.c */ -void __redisSetError(redisContext *c, int type, const char *str); - -/* Forward declaration of function in hiredis.c */ -int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); - -/* Helper function for the redisClusterCommand* family of functions. - * - * Write a formatted command to the output buffer. If the given context is - * blocking, immediately read the reply into the "reply" pointer. When the - * context is non-blocking, the "reply" pointer will not be used and the - * command is simply appended to the write buffer. - * - * Returns the reply when a reply was succesfully retrieved. Returns NULL - * otherwise. When NULL is returned in a blocking context, the error field - * in the context will be set. - */ -static void *__redisBlockForReply(redisContext *c) { - void *reply; - - if (c->flags & REDIS_BLOCK) { - if (redisGetReply(c,&reply) != REDIS_OK) - return NULL; - return reply; - } - return NULL; -} - - -/* ----------------------------------------------------------------------------- - * Key space handling - * -------------------------------------------------------------------------- */ - -/* We have 16384 hash slots. The hash slot of a given key is obtained - * as the least significant 14 bits of the crc16 of the key. - * - * However if the key contains the {...} pattern, only the part between - * { and } is hashed. This may be useful in the future to force certain - * keys to be in the same node (assuming no resharding is in progress). */ -static unsigned int keyHashSlot(char *key, int keylen) { - int s, e; /* start-end indexes of { and } */ - - for (s = 0; s < keylen; s++) - if (key[s] == '{') break; - - /* No '{' ? Hash the whole key. This is the base case. */ - if (s == keylen) return crc16(key,keylen) & 0x3FFF; - - /* '{' found? Check if we have the corresponding '}'. */ - for (e = s+1; e < keylen; e++) - if (key[e] == '}') break; - - /* No '}' or nothing betweeen {} ? Hash the whole key. */ - if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF; - - /* If we are here there is both a { and a } on its right. Hash - * what is in the middle between { and }. */ - return crc16(key+s+1,e-s-1) & 0x3FFF; -} - -static void __redisClusterSetError(redisClusterContext *cc, int type, const char *str) { - size_t len; - - if(cc == NULL){ - return; - } - - cc->err = type; - if (str != NULL) { - len = strlen(str); - len = len < (sizeof(cc->errstr)-1) ? len : (sizeof(cc->errstr)-1); - memcpy(cc->errstr,str,len); - cc->errstr[len] = '\0'; - } else { - /* Only REDIS_ERR_IO may lack a description! */ - assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, cc->errstr, sizeof(cc->errstr)); - } -} - -static int cluster_reply_error_type(redisReply *reply) -{ - - if(reply == NULL) - { - return REDIS_ERR; - } - - if(reply->type == REDIS_REPLY_ERROR) - { - if((int)strlen(REDIS_ERROR_MOVED) < reply->len && - strncmp(reply->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0) - { - return CLUSTER_ERR_MOVED; - } - else if((int)strlen(REDIS_ERROR_ASK) < reply->len && - strncmp(reply->str, REDIS_ERROR_ASK, strlen(REDIS_ERROR_ASK)) == 0) - { - return CLUSTER_ERR_ASK; - } - else if((int)strlen(REDIS_ERROR_TRYAGAIN) < reply->len && - strncmp(reply->str, REDIS_ERROR_TRYAGAIN, strlen(REDIS_ERROR_TRYAGAIN)) == 0) - { - return CLUSTER_ERR_TRYAGAIN; - } - else if((int)strlen(REDIS_ERROR_CROSSSLOT) < reply->len && - strncmp(reply->str, REDIS_ERROR_CROSSSLOT, strlen(REDIS_ERROR_CROSSSLOT)) == 0) - { - return CLUSTER_ERR_CROSSSLOT; - } - else if((int)strlen(REDIS_ERROR_CLUSTERDOWN) < reply->len && - strncmp(reply->str, REDIS_ERROR_CLUSTERDOWN, strlen(REDIS_ERROR_CLUSTERDOWN)) == 0) - { - return CLUSTER_ERR_CLUSTERDOWN; - } - else - { - return CLUSTER_ERR_SENTINEL; - } - } - - return CLUSTER_NOT_ERR; -} - -static int cluster_node_init(cluster_node *node) -{ - if(node == NULL){ - return REDIS_ERR; - } - - node->name = NULL; - node->addr = NULL; - node->host = NULL; - node->port = 0; - node->role = REDIS_ROLE_NULL; - node->myself = 0; - node->slaves = NULL; - node->con = NULL; - node->acon = NULL; - node->slots = NULL; - node->failure_count = 0; - node->data = NULL; - node->migrating = NULL; - node->importing = NULL; - - return REDIS_OK; -} - -static void cluster_node_deinit(cluster_node *node) -{ - copen_slot **oslot; - - if(node == NULL) - { - return; - } - - sdsfree(node->name); - sdsfree(node->addr); - sdsfree(node->host); - node->port = 0; - node->role = REDIS_ROLE_NULL; - node->myself = 0; - - if(node->con != NULL) - { - redisFree(node->con); - } - - if(node->acon != NULL) - { - redisAsyncFree(node->acon); - } - - if(node->slots != NULL) - { - listRelease(node->slots); - } - - if(node->slaves != NULL) - { - listRelease(node->slaves); - } - - if(node->migrating) - { - while(hiarray_n(node->migrating)) - { - oslot = hiarray_pop(node->migrating); - cluster_open_slot_destroy(*oslot); - } - - hiarray_destroy(node->migrating); - node->migrating = NULL; - } - - if(node->importing) - { - while(hiarray_n(node->importing)) - { - oslot = hiarray_pop(node->importing); - cluster_open_slot_destroy(*oslot); - } - - hiarray_destroy(node->importing); - node->importing = NULL; - } -} - -static int cluster_slot_init(cluster_slot *slot, cluster_node *node) -{ - slot->start = 0; - slot->end = 0; - slot->node = node; - - return REDIS_OK; -} - -static cluster_slot *cluster_slot_create(cluster_node *node) -{ - cluster_slot *slot; - - slot = hi_alloc(sizeof(*slot)); - if(slot == NULL){ - return NULL; - } - - cluster_slot_init(slot, node); - - if(node != NULL){ - ASSERT(node->role == REDIS_ROLE_MASTER); - if(node->slots == NULL){ - node->slots = listCreate(); - if(node->slots == NULL) - { - cluster_slot_destroy(slot); - return NULL; - } - - node->slots->free = listClusterSlotDestructor; - } - - listAddNodeTail(node->slots, slot); - } - - return slot; -} - -static int cluster_slot_ref_node(cluster_slot * slot, cluster_node *node) -{ - if(slot == NULL || node == NULL){ - return REDIS_ERR; - } - - - if(node->role != REDIS_ROLE_MASTER){ - return REDIS_ERR; - } - - if(node->slots == NULL){ - node->slots = listCreate(); - if(node->slots == NULL) - { - return REDIS_ERR; - } - - node->slots->free = listClusterSlotDestructor; - } - - listAddNodeTail(node->slots, slot); - slot->node = node; - - return REDIS_OK; -} - -static void cluster_slot_destroy(cluster_slot *slot) -{ - slot->start = 0; - slot->end = 0; - slot->node = NULL; - - hi_free(slot); -} - -static copen_slot *cluster_open_slot_create(uint32_t slot_num, int migrate, - sds remote_name, cluster_node *node) -{ - copen_slot *oslot; - - oslot = hi_alloc(sizeof(*oslot)); - if(oslot == NULL){ - return NULL; - } - - oslot->slot_num = 0; - oslot->migrate = 0; - oslot->node = NULL; - oslot->remote_name = NULL; - - oslot->slot_num = slot_num; - oslot->migrate = migrate; - oslot->node = node; - oslot->remote_name = sdsdup(remote_name); - - return oslot; -} - -static void cluster_open_slot_destroy(copen_slot *oslot) -{ - oslot->slot_num = 0; - oslot->migrate = 0; - oslot->node = NULL; - - if(oslot->remote_name != NULL){ - sdsfree(oslot->remote_name); - oslot->remote_name = NULL; - } - - hi_free(oslot); -} - -/** - * Return a new node with the "cluster slots" command reply. - */ -static cluster_node *node_get_with_slots( - redisClusterContext *cc, redisReply *host_elem, - redisReply *port_elem, uint8_t role) -{ - cluster_node *node = NULL; - - if(host_elem == NULL || port_elem == NULL){ - return NULL; - } - - if(host_elem->type != REDIS_REPLY_STRING || - host_elem->len <= 0){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "node ip is not string."); - goto error; - } - - if(port_elem->type != REDIS_REPLY_INTEGER || - port_elem->integer <= 0){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "node port is not integer."); - goto error; - } - - if(!hi_valid_port((int)port_elem->integer)){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "node port is not valid."); - goto error; - } - - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL){ - __redisClusterSetError(cc, - REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - cluster_node_init(node); - - if(role == REDIS_ROLE_MASTER){ - node->slots = listCreate(); - if(node->slots == NULL){ - hi_free(node); - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "slots for node listCreate error"); - goto error; - } - - node->slots->free = listClusterSlotDestructor; - } - - node->name = NULL; - node->addr = sdsnewlen(host_elem->str, host_elem->len); - node->addr = sdscatfmt(node->addr, ":%i", port_elem->integer); - - node->host = sdsnewlen(host_elem->str, host_elem->len); - node->port = (int)port_elem->integer; - node->role = role; - - return node; - -error: - - if(node != NULL){ - hi_free(node); - } - - return NULL; -} - -/** - * Return a new node with the "cluster nodes" command reply. - */ -static cluster_node *node_get_with_nodes( - redisClusterContext *cc, - sds *node_infos, int info_count, uint8_t role) -{ - sds *ip_port = NULL; - int count_ip_port = 0; - cluster_node *node; - - if(info_count < 8) - { - return NULL; - } - - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - cluster_node_init(node); - - if(role == REDIS_ROLE_MASTER) - { - node->slots = listCreate(); - if(node->slots == NULL) - { - hi_free(node); - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "slots for node listCreate error"); - goto error; - } - - node->slots->free = listClusterSlotDestructor; - } - - node->name = node_infos[0]; - node->addr = node_infos[1]; - - ip_port = sdssplitlen(node_infos[1], sdslen(node_infos[1]), - IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &count_ip_port); - if(ip_port == NULL || count_ip_port != 2) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split ip port error"); - goto error; - } - node->host = ip_port[0]; - node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - node->role = role; - - sdsfree(ip_port[1]); - free(ip_port); - - node_infos[0] = NULL; - node_infos[1] = NULL; - - return node; - -error: - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, count_ip_port); - } - - if(node != NULL) - { - hi_free(node); - } - - return NULL; -} - -static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) -{ - dictIterator *di; - dictEntry *de_f, *de_t; - cluster_node *node_f, *node_t; - redisContext *c; - redisAsyncContext *ac; - - if(nodes_f == NULL || nodes_t == NULL){ - return; - } - - di = dictGetIterator(nodes_t); - while((de_t = dictNext(di)) != NULL){ - node_t = dictGetEntryVal(de_t); - if(node_t == NULL){ - continue; - } - - de_f = dictFind(nodes_f, node_t->addr); - if(de_f == NULL){ - continue; - } - - node_f = dictGetEntryVal(de_f); - if(node_f->con != NULL){ - c = node_f->con; - node_f->con = node_t->con; - node_t->con = c; - } - - if(node_f->acon != NULL){ - ac = node_f->acon; - node_f->acon = node_t->acon; - node_t->acon = ac; - - node_t->acon->data = node_t; - if (node_f->acon) - node_f->acon->data = node_f; - } - } - - dictReleaseIterator(di); - -} - -static int -cluster_slot_start_cmp(const void *t1, const void *t2) -{ - const cluster_slot **s1 = t1, **s2 = t2; - - return (*s1)->start > (*s2)->start?1:-1; -} - -static int -cluster_master_slave_mapping_with_name(redisClusterContext *cc, - dict **nodes, cluster_node *node, sds master_name) -{ - int ret; - dictEntry *di; - cluster_node *node_old; - listNode *lnode; - - if(node == NULL || master_name == NULL) - { - return REDIS_ERR; - } - - if(*nodes == NULL) - { - *nodes = dictCreate( - &clusterNodesRefDictType, NULL); - } - - di = dictFind(*nodes, master_name); - if(di == NULL) - { - ret = dictAdd(*nodes, - sdsnewlen(master_name, sdslen(master_name)), node); - if(ret != DICT_OK) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "the address already exists in the nodes"); - return REDIS_ERR; - } - - } - else - { - node_old = dictGetEntryVal(di); - if(node_old == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "dict get value null"); - return REDIS_ERR; - } - - if(node->role == REDIS_ROLE_MASTER && - node_old->role == REDIS_ROLE_MASTER) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "two masters have the same name"); - return REDIS_ERR; - } - else if(node->role == REDIS_ROLE_MASTER - && node_old->role == REDIS_ROLE_SLAVE) - { - if(node->slaves == NULL) - { - node->slaves = listCreate(); - if(node->slaves == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - return REDIS_ERR; - } - - node->slaves->free = - listClusterNodeDestructor; - } - - if(node_old->slaves != NULL) - { - node_old->slaves->free = NULL; - while(listLength(node_old->slaves) > 0) - { - lnode = listFirst(node_old->slaves); - listAddNodeHead(node->slaves, lnode->value); - listDelNode(node_old->slaves, lnode); - } - listRelease(node_old->slaves); - node_old->slaves = NULL; - } - - listAddNodeHead(node->slaves, node_old); - - dictSetHashVal(*nodes, di, node); - } - else if(node->role == REDIS_ROLE_SLAVE) - { - if(node_old->slaves == NULL) - { - node_old->slaves = listCreate(); - if(node_old->slaves == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - return REDIS_ERR; - } - - node_old->slaves->free = - listClusterNodeDestructor; - } - - listAddNodeTail(node_old->slaves, node); - } - else - { - NOT_REACHED(); - } - } - - return REDIS_OK; -} - -/** - * Parse the "cluster slots" command reply to nodes dict. - */ -dict * -parse_cluster_slots(redisClusterContext *cc, - redisReply *reply, int flags) -{ - int ret; - cluster_slot *slot = NULL; - dict *nodes = NULL; - dictEntry *den; - redisReply *elem_slots; - redisReply *elem_slots_begin, *elem_slots_end; - redisReply *elem_nodes; - redisReply *elem_ip, *elem_port; - cluster_node *master = NULL, *slave; - sds address; - uint32_t i, idx; - - if(reply == NULL){ - return NULL; - } - - nodes = dictCreate(&clusterNodesDictType, NULL); - if(nodes == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "out of memory"); - goto error; - } - - if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "reply is not an array."); - goto error; - } - - for(i = 0; i < reply->elements; i ++){ - elem_slots = reply->element[i]; - if(elem_slots->type != REDIS_REPLY_ARRAY || - elem_slots->elements < 3){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "first sub_reply is not an array."); - goto error; - } - - slot = cluster_slot_create(NULL); - if(slot == NULL){ - __redisClusterSetError(cc, REDIS_ERR_OOM, - "Slot create failed: out of memory."); - goto error; - } - - //one slots region - for(idx = 0; idx < elem_slots->elements; idx ++){ - if(idx == 0){ - elem_slots_begin = elem_slots->element[idx]; - if(elem_slots_begin->type != REDIS_REPLY_INTEGER){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "slot begin is not an integer."); - goto error; - } - slot->start = (int)(elem_slots_begin->integer); - }else if(idx == 1){ - elem_slots_end = elem_slots->element[idx]; - if(elem_slots_end->type != REDIS_REPLY_INTEGER){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "slot end is not an integer."); - goto error; - } - - slot->end = (int)(elem_slots_end->integer); - - if(slot->start > slot->end){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "slot begin is bigger than slot end."); - goto error; - } - }else{ - elem_nodes = elem_slots->element[idx]; - if(elem_nodes->type != REDIS_REPLY_ARRAY || - elem_nodes->elements != 3){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "nodes sub_reply is not an correct array."); - goto error; - } - - elem_ip = elem_nodes->element[0]; - elem_port = elem_nodes->element[1]; - - if(elem_ip == NULL || elem_port == NULL || - elem_ip->type != REDIS_REPLY_STRING || - elem_port->type != REDIS_REPLY_INTEGER){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Command(cluster slots) reply error: " - "master ip or port is not correct."); - goto error; - } - - //this is master. - if(idx == 2){ - address = sdsnewlen(elem_ip->str, elem_ip->len); - address = sdscatfmt(address, ":%i", elem_port->integer); - - den = dictFind(nodes, address); - //master already exits, break to the next slots region. - if(den != NULL){ - sdsfree(address); - - master = dictGetEntryVal(den); - ret = cluster_slot_ref_node(slot, master); - if(ret != REDIS_OK){ - __redisClusterSetError(cc, REDIS_ERR_OOM, - "Slot ref node failed: out of memory."); - goto error; - } - - slot = NULL; - break; - } - - sdsfree(address); - master = node_get_with_slots(cc, elem_ip, - elem_port, REDIS_ROLE_MASTER); - if(master == NULL){ - goto error; - } - - ret = dictAdd(nodes, - sdsnewlen(master->addr, sdslen(master->addr)), master); - if(ret != DICT_OK){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "The address already exists in the nodes"); - cluster_node_deinit(master); - hi_free(master); - goto error; - } - - ret = cluster_slot_ref_node(slot, master); - if(ret != REDIS_OK){ - __redisClusterSetError(cc, REDIS_ERR_OOM, - "Slot ref node failed: out of memory."); - goto error; - } - - slot = NULL; - }else if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){ - slave = node_get_with_slots(cc, elem_ip, - elem_port, REDIS_ROLE_SLAVE); - if(slave == NULL){ - goto error; - } - - if(master->slaves == NULL){ - master->slaves = listCreate(); - if(master->slaves == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - cluster_node_deinit(slave); - goto error; - } - - master->slaves->free = - listClusterNodeDestructor; - } - - listAddNodeTail(master->slaves, slave); - } - } - } - } - - return nodes; - -error: - - if(nodes != NULL){ - dictRelease(nodes); - } - - if(slot != NULL){ - cluster_slot_destroy(slot); - } - - return NULL; -} - -/** - * Parse the "cluster nodes" command reply to nodes dict. - */ -dict * -parse_cluster_nodes(redisClusterContext *cc, - char *str, int str_len, int flags) -{ - int ret; - dict *nodes = NULL; - dict *nodes_name = NULL; - cluster_node *master, *slave; - cluster_slot *slot; - char *pos, *start, *end, *line_start, *line_end; - char *role; - int role_len; - uint8_t myself = 0; - int slot_start, slot_end; - sds *part = NULL, *slot_start_end = NULL; - int count_part = 0, count_slot_start_end = 0; - int k; - int len; - - nodes = dictCreate(&clusterNodesDictType, NULL); - if(nodes == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "out of memory"); - goto error; - } - - start = str; - end = start + str_len; - - line_start = start; - - for(pos = start; pos < end; pos ++){ - if(*pos == '\n'){ - line_end = pos - 1; - len = line_end - line_start; - - part = sdssplitlen(line_start, len + 1, " ", 1, &count_part); - - if(part == NULL || count_part < 8){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split cluster nodes error"); - goto error; - } - - //the address string is ":0", skip this node. - if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0){ - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - - start = pos + 1; - line_start = start; - pos = start; - - continue; - } - - if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0){ - role_len = sdslen(part[2]) - 7; - role = part[2] + 7; - myself = 1; - }else{ - role_len = sdslen(part[2]); - role = part[2]; - } - - //add master node - if(role_len >= 6 && memcmp(role, "master", 6) == 0){ - if(count_part < 8){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Master node parts number error: less than 8."); - goto error; - } - - master = node_get_with_nodes(cc, - part, count_part, REDIS_ROLE_MASTER); - if(master == NULL){ - goto error; - } - - ret = dictAdd(nodes, - sdsnewlen(master->addr, sdslen(master->addr)), master); - if(ret != DICT_OK){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "The address already exists in the nodes"); - cluster_node_deinit(master); - hi_free(master); - goto error; - } - - if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){ - ret = cluster_master_slave_mapping_with_name(cc, - &nodes_name, master, master->name); - if(ret != REDIS_OK){ - cluster_node_deinit(master); - hi_free(master); - goto error; - } - } - - if(myself) master->myself = 1; - - for(k = 8; k < count_part; k ++){ - slot_start_end = sdssplitlen(part[k], - sdslen(part[k]), "-", 1, &count_slot_start_end); - - if(slot_start_end == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split slot start end error(NULL)"); - goto error; - }else if(count_slot_start_end == 1){ - slot_start = - hi_atoi(slot_start_end[0], sdslen(slot_start_end[0])); - slot_end = slot_start; - }else if(count_slot_start_end == 2){ - slot_start = - hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));; - slot_end = - hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));; - }else{ - //add open slot for master - if(flags & HIRCLUSTER_FLAG_ADD_OPENSLOT && - count_slot_start_end == 3 && - sdslen(slot_start_end[0]) > 1 && - sdslen(slot_start_end[1]) == 1 && - sdslen(slot_start_end[2]) > 1 && - slot_start_end[0][0] == '[' && - slot_start_end[2][sdslen(slot_start_end[2])-1] == ']'){ - - copen_slot *oslot, **oslot_elem; - - sdsrange(slot_start_end[0], 1, -1); - sdsrange(slot_start_end[2], 0, -2); - - if(slot_start_end[1][0] == '>'){ - oslot = cluster_open_slot_create( - hi_atoi(slot_start_end[0], - sdslen(slot_start_end[0])), - 1, slot_start_end[2], master); - if(oslot == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "create open slot error"); - goto error; - } - - if(master->migrating == NULL){ - master->migrating = hiarray_create(1, sizeof(oslot)); - if(master->migrating == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "create migrating array error"); - cluster_open_slot_destroy(oslot); - goto error; - } - } - - oslot_elem = hiarray_push(master->migrating); - if(oslot_elem == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Push migrating array error: out of memory"); - cluster_open_slot_destroy(oslot); - goto error; - } - - *oslot_elem = oslot; - }else if(slot_start_end[1][0] == '<'){ - oslot = cluster_open_slot_create(hi_atoi(slot_start_end[0], - sdslen(slot_start_end[0])), 0, slot_start_end[2], - master); - if(oslot == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "create open slot error"); - goto error; - } - - if(master->importing == NULL){ - master->importing = hiarray_create(1, sizeof(oslot)); - if(master->importing == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "create migrating array error"); - cluster_open_slot_destroy(oslot); - goto error; - } - } - - oslot_elem = hiarray_push(master->importing); - if(oslot_elem == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "push migrating array error: out of memory"); - cluster_open_slot_destroy(oslot); - goto error; - } - - *oslot_elem = oslot; - } - } - - slot_start = -1; - slot_end = -1; - } - - sdsfreesplitres(slot_start_end, count_slot_start_end); - count_slot_start_end = 0; - slot_start_end = NULL; - - if(slot_start < 0 || slot_end < 0 || - slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS){ - continue; - } - - slot = cluster_slot_create(master); - if(slot == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - goto error; - } - - slot->start = (uint32_t)slot_start; - slot->end = (uint32_t)slot_end; - } - - } - //add slave node - else if((flags & HIRCLUSTER_FLAG_ADD_SLAVE) && - (role_len >= 5 && memcmp(role, "slave", 5) == 0)){ - slave = node_get_with_nodes(cc, part, - count_part, REDIS_ROLE_SLAVE); - if(slave == NULL){ - goto error; - } - - ret = cluster_master_slave_mapping_with_name(cc, - &nodes_name, slave, part[3]); - if(ret != REDIS_OK){ - cluster_node_deinit(slave); - hi_free(slave); - goto error; - } - - if(myself) slave->myself = 1; - } - - if(myself == 1){ - myself = 0; - } - - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - - start = pos + 1; - line_start = start; - pos = start; - } - } - - if(nodes_name != NULL){ - dictRelease(nodes_name); - } - - return nodes; - -error: - - if(part != NULL){ - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - } - - if(slot_start_end != NULL){ - sdsfreesplitres(slot_start_end, count_slot_start_end); - count_slot_start_end = 0; - slot_start_end = NULL; - } - - if(nodes != NULL){ - dictRelease(nodes); - } - - if(nodes_name != NULL){ - dictRelease(nodes_name); - } - - return NULL; -} - -/** - * Update route with the "cluster nodes" or "cluster slots" command reply. - */ -static int -cluster_update_route_by_addr(redisClusterContext *cc, - const char *ip, int port) -{ - redisContext *c = NULL; - redisReply *reply = NULL; - dict *nodes = NULL; - struct hiarray *slots = NULL; - cluster_node *master; - cluster_slot *slot, **slot_elem; - dictIterator *dit = NULL; - dictEntry *den; - listIter *lit = NULL; - listNode *lnode; - cluster_node *table[REDIS_CLUSTER_SLOTS]; - uint32_t j, k; - - if(cc == NULL){ - return REDIS_ERR; - } - - if(ip == NULL || port <= 0){ - __redisClusterSetError(cc, - REDIS_ERR_OTHER,"Ip or port error!"); - goto error; - } - - if(cc->timeout){ - c = redisConnectWithTimeout(ip, port, *cc->timeout); - }else{ - c = redisConnect(ip, port); - } - - if (c == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Init redis context error(return NULL)"); - goto error; - }else if(c->err){ - __redisClusterSetError(cc,c->err,c->errstr); - goto error; - } - - if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){ - reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); - if(reply == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster slots) reply error(NULL)."); - goto error; - }else if(reply->type != REDIS_REPLY_ARRAY){ - if(reply->type == REDIS_REPLY_ERROR){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - reply->str); - }else{ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster slots) reply error: type is not array."); - } - - goto error; - } - - nodes = parse_cluster_slots(cc, reply, cc->flags); - }else{ - reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); - if(reply == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster nodes) reply error(NULL)."); - goto error; - }else if(reply->type != REDIS_REPLY_STRING){ - if(reply->type == REDIS_REPLY_ERROR){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - reply->str); - }else{ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster nodes) reply error: type is not string."); - } - - goto error; - } - - nodes = parse_cluster_nodes(cc, reply->str, reply->len, cc->flags); - } - - if(nodes == NULL){ - goto error; - } - - memset(table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - - slots = hiarray_create(dictSize(nodes), sizeof(cluster_slot*)); - if(slots == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Slots array create failed: out of memory"); - goto error; - } - - dit = dictGetIterator(nodes); - if(dit == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Dict get iterator failed: out of memory"); - goto error; - } - - while((den = dictNext(dit))){ - master = dictGetEntryVal(den); - if(master->role != REDIS_ROLE_MASTER){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Node role must be master"); - goto error; - } - - if(master->slots == NULL){ - continue; - } - - lit = listGetIterator(master->slots, AL_START_HEAD); - if(lit == NULL){ - __redisClusterSetError(cc, REDIS_ERR_OOM, - "List get iterator failed: out of memory"); - goto error; - } - - while((lnode = listNext(lit))){ - slot = listNodeValue(lnode); - if(slot->start > slot->end || - slot->end >= REDIS_CLUSTER_SLOTS){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Slot region for node is error"); - goto error; - } - - slot_elem = hiarray_push(slots); - *slot_elem = slot; - } - - listReleaseIterator(lit); - } - - dictReleaseIterator(dit); - - hiarray_sort(slots, cluster_slot_start_cmp); - for(j = 0; j < hiarray_n(slots); j ++){ - slot_elem = hiarray_get(slots, j); - - for(k = (*slot_elem)->start; k <= (*slot_elem)->end; k ++){ - if(table[k] != NULL){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Diffent node hold a same slot"); - goto error; - } - - table[k] = (*slot_elem)->node; - } - } - - cluster_nodes_swap_ctx(cc->nodes, nodes); - if(cc->nodes != NULL){ - dictRelease(cc->nodes); - cc->nodes = NULL; - } - cc->nodes = nodes; - - if(cc->slots != NULL) - { - cc->slots->nelem = 0; - hiarray_destroy(cc->slots); - cc->slots = NULL; - } - cc->slots = slots; - - memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - cc->route_version ++; - - freeReplyObject(reply); - - if(c != NULL){ - redisFree(c); - } - - return REDIS_OK; - -error: - - if(dit != NULL){ - dictReleaseIterator(dit); - } - - if(lit != NULL){ - listReleaseIterator(lit); - } - - if(slots != NULL) - { - if(slots == cc->slots) - { - cc->slots = NULL; - } - - slots->nelem = 0; - hiarray_destroy(slots); - } - - if(nodes != NULL){ - if(nodes == cc->nodes){ - cc->nodes = NULL; - } - - dictRelease(nodes); - } - - if(reply != NULL){ - freeReplyObject(reply); - reply = NULL; - } - - if(c != NULL){ - redisFree(c); - } - - return REDIS_ERR; -} - - -/** - * Update route with the "cluster nodes" command reply. - */ -static int -cluster_update_route_with_nodes_old(redisClusterContext *cc, - const char *ip, int port) -{ - int ret; - redisContext *c = NULL; - redisReply *reply = NULL; - struct hiarray *slots = NULL; - dict *nodes = NULL; - dict *nodes_name = NULL; - cluster_node *master, *slave; - cluster_slot **slot; - char *pos, *start, *end, *line_start, *line_end; - char *role; - int role_len; - uint8_t myself = 0; - int slot_start, slot_end; - sds *part = NULL, *slot_start_end = NULL; - int count_part = 0, count_slot_start_end = 0; - int j, k; - int len; - cluster_node *table[REDIS_CLUSTER_SLOTS] = {NULL}; - - if(cc == NULL) - { - return REDIS_ERR; - } - - if(ip == NULL || port <= 0) - { - __redisClusterSetError(cc, - REDIS_ERR_OTHER,"ip or port error!"); - goto error; - } - - if(cc->timeout) - { - c = redisConnectWithTimeout(ip, port, *cc->timeout); - } - else - { - c = redisConnect(ip, port); - } - - if (c == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "init redis context error(return NULL)"); - goto error; - } - else if(c->err) - { - __redisClusterSetError(cc,c->err,c->errstr); - goto error; - } - - reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); - - if(reply == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "command(cluster nodes) reply error(NULL)"); - goto error; - } - else if(reply->type != REDIS_REPLY_STRING) - { - if(reply->type == REDIS_REPLY_ERROR) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - reply->str); - } - else - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "command(cluster nodes) reply error(type is not string)"); - } - - goto error; - } - - nodes = dictCreate(&clusterNodesDictType, NULL); - - slots = hiarray_create(10, sizeof(cluster_slot*)); - if(slots == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "array create error"); - goto error; - } - - start = reply->str; - end = start + reply->len; - - line_start = start; - - for(pos = start; pos < end; pos ++) - { - if(*pos == '\n') - { - line_end = pos - 1; - len = line_end - line_start; - - part = sdssplitlen(line_start, len + 1, " ", 1, &count_part); - - if(part == NULL || count_part < 8) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split cluster nodes error"); - goto error; - } - - //the address string is ":0", skip this node. - if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0) - { - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - - start = pos + 1; - line_start = start; - pos = start; - - continue; - } - - if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0) - { - role_len = sdslen(part[2]) - 7; - role = part[2] + 7; - myself = 1; - } - else - { - role_len = sdslen(part[2]); - role = part[2]; - } - - //add master node - if(role_len >= 6 && memcmp(role, "master", 6) == 0) - { - if(count_part < 8) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "master node part number error"); - goto error; - } - - master = node_get_with_nodes(cc, - part, count_part, REDIS_ROLE_MASTER); - if(master == NULL) - { - goto error; - } - - ret = dictAdd(nodes, - sdsnewlen(master->addr, sdslen(master->addr)), master); - if(ret != DICT_OK) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "the address already exists in the nodes"); - cluster_node_deinit(master); - hi_free(master); - goto error; - } - - if(cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) - { - ret = cluster_master_slave_mapping_with_name(cc, - &nodes_name, master, master->name); - if(ret != REDIS_OK) - { - cluster_node_deinit(master); - hi_free(master); - goto error; - } - } - - if(myself == 1) - { - master->con = c; - c = NULL; - } - - for(k = 8; k < count_part; k ++) - { - slot_start_end = sdssplitlen(part[k], - sdslen(part[k]), "-", 1, &count_slot_start_end); - - if(slot_start_end == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "split slot start end error(NULL)"); - goto error; - } - else if(count_slot_start_end == 1) - { - slot_start = - hi_atoi(slot_start_end[0], sdslen(slot_start_end[0])); - slot_end = slot_start; - } - else if(count_slot_start_end == 2) - { - slot_start = - hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));; - slot_end = - hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));; - } - else - { - slot_start = -1; - slot_end = -1; - } - - sdsfreesplitres(slot_start_end, count_slot_start_end); - count_slot_start_end = 0; - slot_start_end = NULL; - - if(slot_start < 0 || slot_end < 0 || - slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS) - { - continue; - } - - for(j = slot_start; j <= slot_end; j ++) - { - if(table[j] != NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "diffent node hold a same slot"); - goto error; - } - table[j] = master; - } - - slot = hiarray_push(slots); - if(slot == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "slot push in array error"); - goto error; - } - - *slot = cluster_slot_create(master); - if(*slot == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - goto error; - } - - (*slot)->start = (uint32_t)slot_start; - (*slot)->end = (uint32_t)slot_end; - } - - } - //add slave node - else if((cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) && - (role_len >= 5 && memcmp(role, "slave", 5) == 0)) - { - slave = node_get_with_nodes(cc, part, - count_part, REDIS_ROLE_SLAVE); - if(slave == NULL) - { - goto error; - } - - ret = cluster_master_slave_mapping_with_name(cc, - &nodes_name, slave, part[3]); - if(ret != REDIS_OK) - { - cluster_node_deinit(slave); - hi_free(slave); - goto error; - } - - if(myself == 1) - { - slave->con = c; - c = NULL; - } - } - - if(myself == 1) - { - myself = 0; - } - - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - - start = pos + 1; - line_start = start; - pos = start; - } - } - - if(cc->slots != NULL) - { - cc->slots->nelem = 0; - hiarray_destroy(cc->slots); - cc->slots = NULL; - } - cc->slots = slots; - - cluster_nodes_swap_ctx(cc->nodes, nodes); - - if(cc->nodes != NULL) - { - dictRelease(cc->nodes); - cc->nodes = NULL; - } - cc->nodes = nodes; - - hiarray_sort(cc->slots, cluster_slot_start_cmp); - - memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - cc->route_version ++; - - freeReplyObject(reply); - - if(c != NULL) - { - redisFree(c); - } - - if(nodes_name != NULL) - { - dictRelease(nodes_name); - } - - return REDIS_OK; - -error: - - if(part != NULL) - { - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; - } - - if(slot_start_end != NULL) - { - sdsfreesplitres(slot_start_end, count_slot_start_end); - count_slot_start_end = 0; - slot_start_end = NULL; - } - - if(slots != NULL) - { - if(slots == cc->slots) - { - cc->slots = NULL; - } - - slots->nelem = 0; - hiarray_destroy(slots); - } - - if(nodes != NULL) - { - if(nodes == cc->nodes) - { - cc->nodes = NULL; - } - - dictRelease(nodes); - } - - if(nodes_name != NULL) - { - dictRelease(nodes_name); - } - - if(reply != NULL) - { - freeReplyObject(reply); - reply = NULL; - } - - if(c != NULL) - { - redisFree(c); - } - - return REDIS_ERR; -} - -int -cluster_update_route(redisClusterContext *cc) -{ - int ret; - int flag_err_not_set = 1; - cluster_node *node; - dictIterator *it; - dictEntry *de; - - if(cc == NULL) - { - return REDIS_ERR; - } - - if(cc->ip != NULL && cc->port > 0) - { - ret = cluster_update_route_by_addr(cc, cc->ip, cc->port); - if(ret == REDIS_OK) - { - return REDIS_OK; - } - - flag_err_not_set = 0; - } - - if(cc->nodes == NULL) - { - if(flag_err_not_set) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no server address"); - } - - return REDIS_ERR; - } - - it = dictGetIterator(cc->nodes); - while ((de = dictNext(it)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL || node->host == NULL || node->port < 0) - { - continue; - } - - ret = cluster_update_route_by_addr(cc, node->host, node->port); - if(ret == REDIS_OK) - { - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - dictReleaseIterator(it); - return REDIS_OK; - } - - flag_err_not_set = 0; - } - - dictReleaseIterator(it); - - if(flag_err_not_set) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no valid server address"); - } - - return REDIS_ERR; -} - -static void print_cluster_node_list(redisClusterContext *cc) -{ - dictIterator *di = NULL; - dictEntry *de; - listIter *it; - listNode *ln; - cluster_node *master, *slave; - hilist *slaves; - - if(cc == NULL) - { - return; - } - - di = dictGetIterator(cc->nodes); - - printf("name\taddress\trole\tslaves\n"); - - while((de = dictNext(di)) != NULL) { - master = dictGetEntryVal(de); - - printf("%s\t%s\t%d\t%s\n",master->name, master->addr, - master->role, master->slaves?"hava":"null"); - - slaves = master->slaves; - if(slaves == NULL) - { - continue; - } - - it = listGetIterator(slaves, AL_START_HEAD); - while((ln = listNext(it)) != NULL) - { - slave = listNodeValue(ln); - printf("%s\t%s\t%d\t%s\n",slave->name, slave->addr, - slave->role, slave->slaves?"hava":"null"); - } - - listReleaseIterator(it); - - printf("\n"); - } -} - - -int test_cluster_update_route(redisClusterContext *cc) -{ - int ret; - - ret = cluster_update_route(cc); - - //print_cluster_node_list(cc); - - return ret; -} - -static redisClusterContext *redisClusterContextInit(void) { - redisClusterContext *cc; - - cc = calloc(1,sizeof(redisClusterContext)); - if (cc == NULL) - return NULL; - - cc->err = 0; - cc->errstr[0] = '\0'; - cc->ip = NULL; - cc->port = 0; - cc->flags = 0; - cc->timeout = NULL; - cc->nodes = NULL; - cc->slots = NULL; - cc->max_redirect_count = CLUSTER_DEFAULT_MAX_REDIRECT_COUNT; - cc->retry_count = 0; - cc->requests = NULL; - cc->need_update_route = 0; - cc->update_route_time = 0LL; - - cc->route_version = 0LL; - - memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - - return cc; -} - -void redisClusterFree(redisClusterContext *cc) { - - if (cc == NULL) - return; - - if(cc->ip) - { - sdsfree(cc->ip); - cc->ip = NULL; - } - - if (cc->timeout) - { - free(cc->timeout); - } - - memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); - - if(cc->slots != NULL) - { - cc->slots->nelem = 0; - hiarray_destroy(cc->slots); - cc->slots = NULL; - } - - if(cc->nodes != NULL) - { - dictRelease(cc->nodes); - } - - if(cc->requests != NULL) - { - listRelease(cc->requests); - } - - free(cc); -} - -static int redisClusterAddNode(redisClusterContext *cc, const char *addr) -{ - dictEntry *node_entry; - cluster_node *node; - sds *ip_port = NULL; - int ip_port_count = 0; - sds ip; - int port; - - if(cc == NULL) - { - return REDIS_ERR; - } - - if(cc->nodes == NULL) - { - cc->nodes = dictCreate(&clusterNodesDictType, NULL); - if(cc->nodes == NULL) - { - return REDIS_ERR; - } - } - - node_entry = dictFind(cc->nodes, addr); - if(node_entry == NULL) - { - ip_port = sdssplitlen(addr, strlen(addr), - IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &ip_port_count); - if(ip_port == NULL || ip_port_count != 2 || - sdslen(ip_port[0]) <= 0 || sdslen(ip_port[1]) <= 0) - { - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, ip_port_count); - } - __redisClusterSetError(cc,REDIS_ERR_OTHER,"server address is error(correct is like: 127.0.0.1:1234)"); - return REDIS_ERR; - } - - ip = ip_port[0]; - port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - - if(port <= 0) - { - sdsfreesplitres(ip_port, ip_port_count); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"server port is error"); - return REDIS_ERR; - } - - sdsfree(ip_port[1]); - free(ip_port); - ip_port = NULL; - - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - sdsfree(ip); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"alloc cluster node error"); - return REDIS_ERR; - } - - cluster_node_init(node); - - node->addr = sdsnew(addr); - if(node->addr == NULL) - { - sdsfree(ip); - hi_free(node); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"new node address error"); - return REDIS_ERR; - } - - node->host = ip; - node->port = port; - - dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); - } - - return REDIS_OK; -} - - -/* Connect to a Redis cluster. On error the field error in the returned - * context will be set to the return value of the error function. - * When no set of reply functions is given, the default set will be used. */ -static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, const char *addrs) { - - int ret; - sds *address = NULL; - int address_count = 0; - int i; - - if(cc == NULL) - { - return NULL; - } - - - address = sdssplitlen(addrs, strlen(addrs), CLUSTER_ADDRESS_SEPARATOR, - strlen(CLUSTER_ADDRESS_SEPARATOR), &address_count); - if(address == NULL || address_count <= 0) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"servers address is error(correct is like: 127.0.0.1:1234,127.0.0.2:5678)"); - return cc; - } - - for(i = 0; i < address_count; i ++) - { - ret = redisClusterAddNode(cc, address[i]); - if(ret != REDIS_OK) - { - sdsfreesplitres(address, address_count); - return cc; - } - } - - sdsfreesplitres(address, address_count); - - cluster_update_route(cc); - - return cc; -} - -redisClusterContext *redisClusterConnect(const char *addrs, int flags) -{ - redisClusterContext *cc; - - cc = redisClusterContextInit(); - - if(cc == NULL) - { - return NULL; - } - - cc->flags |= REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - return _redisClusterConnect(cc, addrs); -} - -redisClusterContext *redisClusterConnectWithTimeout( - const char *addrs, const struct timeval tv, int flags) -{ - redisClusterContext *cc; - - cc = redisClusterContextInit(); - - if(cc == NULL) - { - return NULL; - } - - cc->flags |= REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - if (cc->timeout == NULL) - { - cc->timeout = malloc(sizeof(struct timeval)); - } - - memcpy(cc->timeout, &tv, sizeof(struct timeval)); - - return _redisClusterConnect(cc, addrs); -} - -redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) { - - redisClusterContext *cc; - - cc = redisClusterContextInit(); - - if(cc == NULL) - { - return NULL; - } - - cc->flags &= ~REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - return _redisClusterConnect(cc, addrs); -} - -redisContext *ctx_get_by_node(cluster_node *node, - const struct timeval *timeout, int flags) -{ - redisContext *c = NULL; - if(node == NULL) - { - return NULL; - } - - c = node->con; - if(c != NULL) - { - if(c->err) - { - redisReconnect(c); - } - - return c; - } - - if(node->host == NULL || node->port <= 0) - { - return NULL; - } - - if(flags & REDIS_BLOCK) - { - if(timeout) - { - c = redisConnectWithTimeout(node->host, node->port, *timeout); - } - else - { - c = redisConnect(node->host, node->port); - } - } - else - { - c = redisConnectNonBlock(node->host, node->port); - } - - node->con = c; - - return c; -} - -static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num) -{ - struct hiarray *slots; - uint32_t slot_count; - cluster_slot **slot; - uint32_t middle, start, end; - uint8_t stop = 0; - - if(cc == NULL) - { - return NULL; - } - - if(slot_num >= REDIS_CLUSTER_SLOTS) - { - return NULL; - } - - slots = cc->slots; - if(slots == NULL) - { - return NULL; - } - slot_count = hiarray_n(slots); - - start = 0; - end = slot_count - 1; - middle = 0; - - do{ - if(start >= end) - { - stop = 1; - middle = end; - } - else - { - middle = start + (end - start)/2; - } - - ASSERT(middle < slot_count); - - slot = hiarray_get(slots, middle); - if((*slot)->start > slot_num) - { - end = middle - 1; - } - else if((*slot)->end < slot_num) - { - start = middle + 1; - } - else - { - return (*slot)->node; - } - - - }while(!stop); - - printf("slot_num : %d\n", slot_num); - printf("slot_count : %d\n", slot_count); - printf("start : %d\n", start); - printf("end : %d\n", end); - printf("middle : %d\n", middle); - - return NULL; -} - - -static cluster_node *node_get_by_table(redisClusterContext *cc, uint32_t slot_num) -{ - if(cc == NULL) - { - return NULL; - } - - if(slot_num >= REDIS_CLUSTER_SLOTS) - { - return NULL; - } - - return cc->table[slot_num]; - -} - -static cluster_node *node_get_witch_connected(redisClusterContext *cc) -{ - dictIterator *di; - dictEntry *de; - struct cluster_node *node; - redisContext *c = NULL; - redisReply *reply = NULL; - - if(cc == NULL || cc->nodes == NULL) - { - return NULL; - } - - di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL) - { - continue; - } - - c = ctx_get_by_node(node, cc->timeout, REDIS_BLOCK); - if(c == NULL || c->err) - { - continue; - } - - reply = redisCommand(c, REDIS_COMMAND_PING); - if(reply != NULL && reply->type == REDIS_REPLY_STATUS && - reply->str != NULL && strcmp(reply->str, "PONG") == 0) - { - freeReplyObject(reply); - reply = NULL; - - dictReleaseIterator(di); - - return node; - } - else if(reply != NULL) - { - freeReplyObject(reply); - reply = NULL; - } - } - - dictReleaseIterator(di); - - return NULL; -} - -static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len) -{ - struct cmd *command = NULL; - struct keypos *kp; - int key_count; - uint32_t i; - int slot_num = -1; - - if(cc == NULL || cmd == NULL || len <= 0) - { - goto done; - } - - command = command_get(); - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto done; - } - - command->cmd = cmd; - command->clen = len; - redis_parse_cmd(command); - if(command->result != CMD_PARSE_OK) - { - __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); - goto done; - } - - key_count = hiarray_n(command->keys); - - if(key_count <= 0) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no keys in command(must have keys for redis cluster mode)"); - goto done; - } - else if(key_count == 1) - { - kp = hiarray_get(command->keys, 0); - slot_num = keyHashSlot(kp->start, kp->end - kp->start); - - goto done; - } - - for(i = 0; i < hiarray_n(command->keys); i ++) - { - kp = hiarray_get(command->keys, i); - - slot_num = keyHashSlot(kp->start, kp->end - kp->start); - } - -done: - - if(command != NULL) - { - command->cmd = NULL; - command_destroy(command); - } - - return slot_num; -} - -/* Get the cluster config from one node. - * Return value: config_value string must free by usr. - */ -static char * cluster_config_get(redisClusterContext *cc, - const char *config_name, int *config_value_len) -{ - redisContext *c; - cluster_node *node; - redisReply *reply = NULL, *sub_reply; - char *config_value = NULL; - - if(cc == NULL || config_name == NULL - || config_value_len == NULL) - { - return NULL; - } - - node = node_get_witch_connected(cc); - if(node == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OTHER, "no reachable node in cluster"); - goto error; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - - reply = redisCommand(c, "config get %s", config_name); - if(reply == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OTHER, "reply for config get is null"); - goto error; - } - - if(reply->type != REDIS_REPLY_ARRAY) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get type is not array"); - goto error; - } - - if(reply->elements != 2) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get elements number is not 2"); - goto error; - } - - sub_reply = reply->element[0]; - if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get config name is not string"); - goto error; - } - - if(strcmp(sub_reply->str, config_name)) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get config name is not we want"); - goto error; - } - - sub_reply = reply->element[1]; - if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply for config get config value type is not string"); - goto error; - } - - config_value = sub_reply->str; - *config_value_len = sub_reply->len; - sub_reply->str= NULL; - - if(reply != NULL) - { - freeReplyObject(reply); - } - - return config_value; - -error: - - if(reply != NULL) - { - freeReplyObject(reply); - } - - return NULL; -} - -/* Helper function for the redisClusterAppendCommand* family of functions. - * - * Write a formatted command to the output buffer. When this family - * is used, you need to call redisGetReply yourself to retrieve - * the reply (or replies in pub/sub). - */ -static int __redisClusterAppendCommand(redisClusterContext *cc, - struct cmd *command) { - - cluster_node *node; - redisContext *c = NULL; - - if(cc == NULL || command == NULL) - { - return REDIS_ERR; - } - - node = node_get_by_table(cc, (uint32_t)command->slot_num); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by slot error"); - return REDIS_ERR; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); - return REDIS_ERR; - } - else if(c->err) - { - __redisClusterSetError(cc, c->err, c->errstr); - return REDIS_ERR; - } - - if (__redisAppendCommand(c, command->cmd, command->clen) != REDIS_OK) - { - __redisClusterSetError(cc, c->err, c->errstr); - return REDIS_ERR; - } - - return REDIS_OK; -} - -/* Helper function for the redisClusterGetReply* family of functions. - */ -static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) -{ - cluster_node *node; - redisContext *c; - - if(cc == NULL || slot_num < 0 || reply == NULL) - { - return REDIS_ERR; - } - - node = node_get_by_table(cc, (uint32_t)slot_num); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table is null"); - return REDIS_ERR; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - else if(c->err) - { - if(cc->need_update_route == 0) - { - cc->retry_count ++; - if(cc->retry_count > cc->max_redirect_count) - { - cc->need_update_route = 1; - cc->retry_count = 0; - } - } - __redisClusterSetError(cc, c->err, c->errstr); - return REDIS_ERR; - } - - if(redisGetReply(c, reply) != REDIS_OK) - { - __redisClusterSetError(cc, c->err, c->errstr); - return REDIS_ERR; - } - - if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED) - { - cc->need_update_route = 1; - } - - return REDIS_OK; -} - -static cluster_node *node_get_by_ask_error_reply( - redisClusterContext *cc, redisReply *reply) -{ - sds *part = NULL, *ip_port = NULL; - int part_len = 0, ip_port_len; - dictEntry *de; - cluster_node *node = NULL; - - if(cc == NULL || reply == NULL) - { - return NULL; - } - - if(cluster_reply_error_type(reply) != CLUSTER_ERR_ASK) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "reply is not ask error!"); - return NULL; - } - - part = sdssplitlen(reply->str, reply->len, " ", 1, &part_len); - - if(part != NULL && part_len == 3) - { - ip_port = sdssplitlen(part[2], sdslen(part[2]), - ":", 1, &ip_port_len); - - if(ip_port != NULL && ip_port_len == 2) - { - de = dictFind(cc->nodes, part[2]); - if(de == NULL) - { - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OOM, "Out of memory"); - - goto done; - } - - cluster_node_init(node); - node->addr = part[1]; - node->host = ip_port[0]; - node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - node->role = REDIS_ROLE_MASTER; - - dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); - - part = NULL; - ip_port = NULL; - } - else - { - node = de->val; - - goto done; - } - } - else - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "ask error reply address part parse error!"); - - goto done; - } - - } - else - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "ask error reply parse error!"); - - goto done; - } - -done: - - if(part != NULL) - { - sdsfreesplitres(part, part_len); - part = NULL; - } - - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, ip_port_len); - ip_port = NULL; - } - - return node; -} - -static void *redis_cluster_command_execute(redisClusterContext *cc, - struct cmd *command) -{ - int ret; - void *reply = NULL; - cluster_node *node; - redisContext *c = NULL; - int error_type; - -retry: - - node = node_get_by_table(cc, (uint32_t)command->slot_num); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table error"); - return NULL; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); - return NULL; - } - else if(c->err) - { - node = node_get_witch_connected(cc); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no reachable node in cluster"); - return NULL; - } - - cc->retry_count ++; - if(cc->retry_count > cc->max_redirect_count) - { - __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, - "too many cluster redirect"); - return NULL; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); - return NULL; - } - else if(c->err) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - } - -ask_retry: - - if (__redisAppendCommand(c,command->cmd, command->clen) != REDIS_OK) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - - reply = __redisBlockForReply(c); - if(reply == NULL) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - - error_type = cluster_reply_error_type(reply); - if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) - { - cc->retry_count ++; - if(cc->retry_count > cc->max_redirect_count) - { - __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, - "too many cluster redirect"); - freeReplyObject(reply); - return NULL; - } - - switch(error_type) - { - case CLUSTER_ERR_MOVED: - freeReplyObject(reply); - reply = NULL; - ret = cluster_update_route(cc); - if(ret != REDIS_OK) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - return NULL; - } - - goto retry; - - break; - case CLUSTER_ERR_ASK: - node = node_get_by_ask_error_reply(cc, reply); - if(node == NULL) - { - freeReplyObject(reply); - return NULL; - } - - freeReplyObject(reply); - reply = NULL; - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); - return NULL; - } - else if(c->err) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - - reply = redisCommand(c, REDIS_COMMAND_ASKING); - if(reply == NULL) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - - freeReplyObject(reply); - reply = NULL; - - goto ask_retry; - - break; - case CLUSTER_ERR_TRYAGAIN: - case CLUSTER_ERR_CROSSSLOT: - case CLUSTER_ERR_CLUSTERDOWN: - freeReplyObject(reply); - reply = NULL; - goto retry; - - break; - default: - - break; - } - } - - return reply; -} - -static int command_pre_fragment(redisClusterContext *cc, - struct cmd *command, hilist *commands) -{ - - struct keypos *kp, *sub_kp; - uint32_t key_count; - uint32_t i, j; - uint32_t idx; - uint32_t key_len; - int slot_num = -1; - struct cmd *sub_command; - struct cmd **sub_commands = NULL; - char num_str[12]; - uint8_t num_str_len; - - - if(command == NULL || commands == NULL) - { - goto done; - } - - key_count = hiarray_n(command->keys); - - sub_commands = hi_zalloc(REDIS_CLUSTER_SLOTS * sizeof(*sub_commands)); - if (sub_commands == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto done; - } - - command->frag_seq = hi_alloc(key_count * sizeof(*command->frag_seq)); - if(command->frag_seq == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto done; - } - - - for(i = 0; i < key_count; i ++) - { - kp = hiarray_get(command->keys, i); - - slot_num = keyHashSlot(kp->start, kp->end - kp->start); - - if(slot_num < 0 || slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"keyHashSlot return error"); - goto done; - } - - if (sub_commands[slot_num] == NULL) { - sub_commands[slot_num] = command_get(); - if (sub_commands[slot_num] == NULL) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - } - - command->frag_seq[i] = sub_command = sub_commands[slot_num]; - - sub_command->narg++; - - sub_kp = hiarray_push(sub_command->keys); - if (sub_kp == NULL) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - - sub_kp->start = kp->start; - sub_kp->end = kp->end; - - key_len = (uint32_t)(kp->end - kp->start); - - sub_command->clen += key_len + uint_len(key_len); - - sub_command->slot_num = slot_num; - - if (command->type == CMD_REQ_REDIS_MSET) { - uint32_t len = 0; - char *p; - - for (p = sub_kp->end + 1; !isdigit(*p); p++){} - - p = sub_kp->end + 1; - while(!isdigit(*p)) - { - p ++; - } - - for (; isdigit(*p); p++) { - len = len * 10 + (uint32_t)(*p - '0'); - } - - len += CRLF_LEN * 2; - len += (p - sub_kp->end); - sub_kp->remain_len = len; - sub_command->clen += len; - } - } - - for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { /* prepend command header */ - sub_command = sub_commands[i]; - if (sub_command == NULL) { - continue; - } - - idx = 0; - if (command->type == CMD_REQ_REDIS_MGET) { - //"*%d\r\n$4\r\nmget\r\n" - - sub_command->clen += 5*sub_command->narg; - - sub_command->narg ++; - - hi_itoa(num_str, sub_command->narg); - num_str_len = (uint8_t)(strlen(num_str)); - - sub_command->clen += 13 + num_str_len; - - sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); - if(sub_command->cmd == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - - sub_command->cmd[idx++] = '*'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, "\r\n$4\r\nmget\r\n", 12); - idx += 12; - - for(j = 0; j < hiarray_n(sub_command->keys); j ++) - { - kp = hiarray_get(sub_command->keys, j); - key_len = (uint32_t)(kp->end - kp->start); - hi_itoa(num_str, key_len); - num_str_len = strlen(num_str); - - sub_command->cmd[idx++] = '$'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - memcpy(sub_command->cmd + idx, kp->start, key_len); - idx += key_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - } - } else if (command->type == CMD_REQ_REDIS_DEL) { - //"*%d\r\n$3\r\ndel\r\n" - - sub_command->clen += 5*sub_command->narg; - - sub_command->narg ++; - - hi_itoa(num_str, sub_command->narg); - num_str_len = (uint8_t)strlen(num_str); - - sub_command->clen += 12 + num_str_len; - - sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); - if(sub_command->cmd == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - - sub_command->cmd[idx++] = '*'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, "\r\n$3\r\ndel\r\n", 11); - idx += 11; - - for(j = 0; j < hiarray_n(sub_command->keys); j ++) - { - kp = hiarray_get(sub_command->keys, j); - key_len = (uint32_t)(kp->end - kp->start); - hi_itoa(num_str, key_len); - num_str_len = strlen(num_str); - - sub_command->cmd[idx++] = '$'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - memcpy(sub_command->cmd + idx, kp->start, key_len); - idx += key_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - } - } else if (command->type == CMD_REQ_REDIS_MSET) { - //"*%d\r\n$4\r\nmset\r\n" - - sub_command->clen += 3*sub_command->narg; - - sub_command->narg *= 2; - - sub_command->narg ++; - - hi_itoa(num_str, sub_command->narg); - num_str_len = (uint8_t)strlen(num_str); - - sub_command->clen += 13 + num_str_len; - - sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); - if(sub_command->cmd == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; - } - - sub_command->cmd[idx++] = '*'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, "\r\n$4\r\nmset\r\n", 12); - idx += 12; - - for(j = 0; j < hiarray_n(sub_command->keys); j ++) - { - kp = hiarray_get(sub_command->keys, j); - key_len = (uint32_t)(kp->end - kp->start); - hi_itoa(num_str, key_len); - num_str_len = strlen(num_str); - - sub_command->cmd[idx++] = '$'; - memcpy(sub_command->cmd + idx, num_str, num_str_len); - idx += num_str_len; - memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); - idx += CRLF_LEN; - memcpy(sub_command->cmd + idx, kp->start, key_len + kp->remain_len); - idx += key_len + kp->remain_len; - - } - } else { - NOT_REACHED(); - } - - //printf("len : %d\n", sub_command->clen); - //print_string_with_length_fix_CRLF(sub_command->cmd, sub_command->clen); - - sub_command->type = command->type; - - listAddNodeTail(commands, sub_command); - } - -done: - - if(sub_commands != NULL) - { - hi_free(sub_commands); - } - - if(slot_num >= 0 && commands != NULL - && listLength(commands) == 1) - { - listNode *list_node = listFirst(commands); - listDelNode(commands, list_node); - if(command->frag_seq) - { - hi_free(command->frag_seq); - command->frag_seq = NULL; - } - - command->slot_num = slot_num; - } - - return slot_num; -} - -static void *command_post_fragment(redisClusterContext *cc, - struct cmd *command, hilist *commands) -{ - struct cmd *sub_command; - listNode *list_node; - listIter *list_iter; - redisReply *reply, *sub_reply; - long long count = 0; - - list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_node = listNext(list_iter)) != NULL) - { - sub_command = list_node->value; - reply = sub_command->reply; - if(reply == NULL) - { - return NULL; - } - else if(reply->type == REDIS_REPLY_ERROR) - { - return reply; - } - - if (command->type == CMD_REQ_REDIS_MGET) { - if(reply->type != REDIS_REPLY_ARRAY) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be array)"); - return NULL; - } - }else if(command->type == CMD_REQ_REDIS_DEL){ - if(reply->type != REDIS_REPLY_INTEGER) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be integer)"); - return NULL; - } - - count += reply->integer; - }else if(command->type == CMD_REQ_REDIS_MSET){ - if(reply->type != REDIS_REPLY_STATUS || - reply->len != 2 || strcmp(reply->str, REDIS_STATUS_OK) != 0) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be status and ok)"); - return NULL; - } - }else { - NOT_REACHED(); - } - } - - reply = hi_calloc(1,sizeof(*reply)); - - if (reply == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - if (command->type == CMD_REQ_REDIS_MGET) { - int i; - uint32_t key_count; - - reply->type = REDIS_REPLY_ARRAY; - - key_count = hiarray_n(command->keys); - - reply->elements = key_count; - reply->element = hi_calloc(key_count, sizeof(*reply)); - if (reply->element == NULL) { - freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - for (i = key_count - 1; i >= 0; i--) { /* for each key */ - sub_reply = command->frag_seq[i]->reply; /* get it's reply */ - if (sub_reply == NULL) { - freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply is null"); - return NULL; - } - - if(sub_reply->type == REDIS_REPLY_STRING) - { - reply->element[i] = sub_reply; - } - else if(sub_reply->type == REDIS_REPLY_ARRAY) - { - if(sub_reply->elements == 0) - { - freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply elements error"); - return NULL; - } - - reply->element[i] = sub_reply->element[sub_reply->elements - 1]; - sub_reply->elements --; - } - } - }else if(command->type == CMD_REQ_REDIS_DEL){ - reply->type = REDIS_REPLY_INTEGER; - reply->integer = count; - }else if(command->type == CMD_REQ_REDIS_MSET){ - reply->type = REDIS_REPLY_STATUS; - uint32_t str_len = strlen(REDIS_STATUS_OK); - reply->str = hi_alloc((str_len + 1) * sizeof(char*)); - if(reply->str == NULL) - { - freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - reply->len = str_len; - memcpy(reply->str, REDIS_STATUS_OK, str_len); - reply->str[str_len] = '\0'; - }else { - NOT_REACHED(); - } - - return reply; -} - -/* - * Split the command into subcommands by slot - * - * Returns slot_num - * If slot_num < 0 or slot_num >= REDIS_CLUSTER_SLOTS means this function runs error; - * Otherwise if the commands > 1 , slot_num is the last subcommand slot number. - */ -static int command_format_by_slot(redisClusterContext *cc, - struct cmd *command, hilist *commands) -{ - struct keypos *kp; - int key_count; - int slot_num = -1; - - if(cc == NULL || commands == NULL || - command == NULL || - command->cmd == NULL || command->clen <= 0) - { - goto done; - } - - - redis_parse_cmd(command); - if(command->result == CMD_PARSE_ENOMEM) - { - __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "Parse command error: out of memory"); - goto done; - } - else if(command->result != CMD_PARSE_OK) - { - __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, command->errstr); - goto done; - } - - key_count = hiarray_n(command->keys); - - if(key_count <= 0) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "No keys in command(must have keys for redis cluster mode)"); - goto done; - } - else if(key_count == 1) - { - kp = hiarray_get(command->keys, 0); - slot_num = keyHashSlot(kp->start, kp->end - kp->start); - command->slot_num = slot_num; - - goto done; - } - - slot_num = command_pre_fragment(cc, command, commands); - -done: - - return slot_num; -} - - -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count) -{ - if(cc == NULL || max_redirect_count <= 0) - { - return; - } - - cc->max_redirect_count = max_redirect_count; -} - -void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) { - redisReply *reply = NULL; - int slot_num; - struct cmd *command = NULL, *sub_command; - hilist *commands = NULL; - listNode *list_node; - listIter *list_iter = NULL; - - if(cc == NULL) - { - return NULL; - } - - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - command = command_get(); - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - command->cmd = cmd; - command->clen = len; - - commands = listCreate(); - if(commands == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - commands->free = listCommandFree; - - slot_num = command_format_by_slot(cc, command, commands); - - if(slot_num < 0) - { - goto error; - } - else if(slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); - goto error; - } - - //all keys belong to one slot - if(listLength(commands) == 0) - { - reply = redis_cluster_command_execute(cc, command); - goto done; - } - - ASSERT(listLength(commands) != 1); - - list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_node = listNext(list_iter)) != NULL) - { - sub_command = list_node->value; - - reply = redis_cluster_command_execute(cc, sub_command); - if(reply == NULL) - { - goto error; - } - else if(reply->type == REDIS_REPLY_ERROR) - { - goto done; - } - - sub_command->reply = reply; - } - - reply = command_post_fragment(cc, command, commands); - -done: - - command->cmd = NULL; - command_destroy(command); - - if(commands != NULL) - { - listRelease(commands); - } - - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } - - cc->retry_count = 0; - - return reply; - -error: - - if(command != NULL) - { - command->cmd = NULL; - command_destroy(command); - } - - if(commands != NULL) - { - listRelease(commands); - } - - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } - - cc->retry_count = 0; - - return NULL; -} - -void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) { - redisReply *reply; - char *cmd; - int len; - - if(cc == NULL) - { - return NULL; - } - - len = redisvFormatCommand(&cmd,format,ap); - - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } else if (len == -2) { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); - return NULL; - } - - reply = redisClusterFormattedCommand(cc, cmd, len); - - free(cmd); - - return reply; -} - -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { - va_list ap; - redisReply *reply = NULL; - - va_start(ap,format); - reply = redisClustervCommand(cc, format, ap); - va_end(ap); - - return reply; -} - -void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen) { - redisReply *reply = NULL; - char *cmd; - int len; - - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return NULL; - } - - reply = redisClusterFormattedCommand(cc, cmd, len); - - free(cmd); - - return reply; -} - -int redisClusterAppendFormattedCommand(redisClusterContext *cc, - char *cmd, int len) { - int slot_num; - struct cmd *command = NULL, *sub_command; - hilist *commands = NULL; - listNode *list_node; - listIter *list_iter = NULL; - - if(cc->requests == NULL) - { - cc->requests = listCreate(); - if(cc->requests == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - cc->requests->free = listCommandFree; - } - - command = command_get(); - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - command->cmd = cmd; - command->clen = len; - - commands = listCreate(); - if(commands == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - commands->free = listCommandFree; - - slot_num = command_format_by_slot(cc, command, commands); - - if(slot_num < 0) - { - goto error; - } - else if(slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); - goto error; - } - - //all keys belong to one slot - if(listLength(commands) == 0) - { - if(__redisClusterAppendCommand(cc, command) == REDIS_OK) - { - goto done; - } - else - { - goto error; - } - } - - ASSERT(listLength(commands) != 1); - - list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_node = listNext(list_iter)) != NULL) - { - sub_command = list_node->value; - - if(__redisClusterAppendCommand(cc, sub_command) == REDIS_OK) - { - continue; - } - else - { - goto error; - } - } - -done: - - if(command->cmd != NULL) - { - command->cmd = NULL; - } - else - { - goto error; - } - - if(commands != NULL) - { - if(listLength(commands) > 0) - { - command->sub_commands = commands; - } - else - { - listRelease(commands); - } - } - - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } - - listAddNodeTail(cc->requests, command); - - return REDIS_OK; - -error: - - if(command != NULL) - { - command->cmd = NULL; - command_destroy(command); - } - - if(commands != NULL) - { - listRelease(commands); - } - - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } - - /* Attention: mybe here we must pop the - sub_commands that had append to the nodes. - But now we do not handle it. */ - - return REDIS_ERR; -} - - -int redisClustervAppendCommand(redisClusterContext *cc, - const char *format, va_list ap) { - int ret; - char *cmd; - int len; - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } else if (len == -2) { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); - return REDIS_ERR; - } - - ret = redisClusterAppendFormattedCommand(cc, cmd, len); - - free(cmd); - - return ret; -} - -int redisClusterAppendCommand(redisClusterContext *cc, - const char *format, ...) { - - int ret; - va_list ap; - - if(cc == NULL || format == NULL) - { - return REDIS_ERR; - } - - va_start(ap,format); - ret = redisClustervAppendCommand(cc, format, ap); - va_end(ap); - - return ret; -} - -int redisClusterAppendCommandArgv(redisClusterContext *cc, - int argc, const char **argv, const size_t *argvlen) { - int ret; - char *cmd; - int len; - - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - ret = redisClusterAppendFormattedCommand(cc, cmd, len); - - free(cmd); - - return ret; -} - -static int redisCLusterSendAll(redisClusterContext *cc) -{ - dictIterator *di; - dictEntry *de; - struct cluster_node *node; - redisContext *c = NULL; - int wdone = 0; - - if(cc == NULL || cc->nodes == NULL) - { - return REDIS_ERR; - } - - di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL) - { - continue; - } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) - { - continue; - } - - if (c->flags & REDIS_BLOCK) { - /* Write until done */ - do { - if (redisBufferWrite(c,&wdone) == REDIS_ERR) - { - dictReleaseIterator(di); - return REDIS_ERR; - } - } while (!wdone); - } - } - - dictReleaseIterator(di); - - return REDIS_OK; -} - -static int redisCLusterClearAll(redisClusterContext *cc) -{ - dictIterator *di; - dictEntry *de; - struct cluster_node *node; - redisContext *c = NULL; - - if (cc == NULL) { - return REDIS_ERR; - } - - if (cc->err) { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - if (cc->nodes == NULL) { - return REDIS_ERR; - } - di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL) - { - continue; - } - - c = node->con; - if(c == NULL) - { - continue; - } - - redisFree(c); - node->con = NULL; - } - - dictReleaseIterator(di); - - return REDIS_OK; -} - -int redisClusterGetReply(redisClusterContext *cc, void **reply) { - - struct cmd *command, *sub_command; - hilist *commands = NULL; - listNode *list_command, *list_sub_command; - listIter *list_iter; - int slot_num; - void *sub_reply; - - if(cc == NULL || reply == NULL) - return REDIS_ERR; - - cc->err = 0; - cc->errstr[0] = '\0'; - - *reply = NULL; - - if (cc->requests == NULL) - return REDIS_ERR; - - list_command = listFirst(cc->requests); - - //no more reply - if(list_command == NULL) - { - *reply = NULL; - return REDIS_OK; - } - - command = list_command->value; - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "command in the requests list is null"); - goto error; - } - - slot_num = command->slot_num; - if(slot_num >= 0) - { - listDelNode(cc->requests, list_command); - return __redisClusterGetReply(cc, slot_num, reply); - } - - commands = command->sub_commands; - if(commands == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "sub_commands in command is null"); - goto error; - } - - ASSERT(listLength(commands) != 1); - - list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_sub_command = listNext(list_iter)) != NULL) - { - sub_command = list_sub_command->value; - if(sub_command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "sub_command is null"); - goto error; - } - - slot_num = sub_command->slot_num; - if(slot_num < 0) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "sub_command slot_num is less then zero"); - goto error; - } - - if(__redisClusterGetReply(cc, slot_num, &sub_reply) != REDIS_OK) - { - goto error; - } - - sub_command->reply = sub_reply; - } - - *reply = command_post_fragment(cc, command, commands); - if(*reply == NULL) - { - goto error; - } - - listDelNode(cc->requests, list_command); - return REDIS_OK; - -error: - - listDelNode(cc->requests, list_command); - return REDIS_ERR; -} - -void redisClusterReset(redisClusterContext *cc) -{ - int status; - void *reply; - - if(cc == NULL || cc->nodes == NULL) - { - return; - } - - if (cc->err) { - redisCLusterClearAll(cc); - } else { - redisCLusterSendAll(cc); - - do { - status = redisClusterGetReply(cc, &reply); - if (status == REDIS_OK) { - freeReplyObject(reply); - } else { - redisCLusterClearAll(cc); - break; - } - } while(reply != NULL); - } - - if(cc->requests) - { - listRelease(cc->requests); - cc->requests = NULL; - } - - if(cc->need_update_route) - { - status = cluster_update_route(cc); - if(status != REDIS_OK) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - return; - } - cc->need_update_route = 0; - } -} - -/*############redis cluster async############*/ - -/* We want the error field to be accessible directly instead of requiring - * an indirection to the redisContext struct. */ -static void __redisClusterAsyncCopyError(redisClusterAsyncContext *acc) { - if (!acc) - return; - - redisClusterContext *cc = acc->cc; - acc->err = cc->err; - memcpy(acc->errstr, cc->errstr, 128); -} - -static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, - int type, const char *str) { - - size_t len; - - acc->err = type; - if (str != NULL) { - len = strlen(str); - len = len < (sizeof(acc->errstr)-1) ? len : (sizeof(acc->errstr)-1); - memcpy(acc->errstr,str,len); - acc->errstr[len] = '\0'; - } else { - /* Only REDIS_ERR_IO may lack a description! */ - assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, acc->errstr, sizeof(acc->errstr)); - } -} - -static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext *cc) { - redisClusterAsyncContext *acc; - - if(cc == NULL) - { - return NULL; - } - - acc = hi_alloc(sizeof(redisClusterAsyncContext)); - if (acc == NULL) - return NULL; - - acc->cc = cc; - - acc->err = 0; - acc->data = NULL; - acc->adapter = NULL; - acc->attach_fn = NULL; - - acc->onConnect = NULL; - acc->onDisconnect = NULL; - - return acc; -} - -static cluster_async_data *cluster_async_data_get(void) -{ - cluster_async_data *cad; - - cad = hi_alloc(sizeof(cluster_async_data)); - if(cad == NULL) - { - return NULL; - } - - cad->acc = NULL; - cad->command = NULL; - cad->callback = NULL; - cad->privdata = NULL; - cad->retry_count = 0; - - return cad; -} - -static void cluster_async_data_free(cluster_async_data *cad) -{ - if(cad == NULL) - { - return; - } - - if(cad->command != NULL) - { - command_destroy(cad->command); - } - - hi_free(cad); - cad = NULL; -} - -static void unlinkAsyncContextAndNode(redisAsyncContext* ac) -{ - cluster_node *node; - - if (ac->data) { - node = (cluster_node *)(ac->data); - node->acon = NULL; - } -} - -redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, - cluster_node *node) -{ - redisAsyncContext *ac; - - if(node == NULL) - { - return NULL; - } - - ac = node->acon; - if(ac != NULL) - { - if (ac->c.err == 0) { - return ac; - } else { - NOT_REACHED(); - } - } - - if(node->host == NULL || node->port <= 0) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); - return NULL; - } - - ac = redisAsyncConnect(node->host, node->port); - if(ac == NULL) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); - return NULL; - } - - if(acc->adapter) - { - acc->attach_fn(ac, acc->adapter); - } - - if(acc->onConnect) - { - redisAsyncSetConnectCallback(ac, acc->onConnect); - } - - if(acc->onDisconnect) - { - redisAsyncSetDisconnectCallback(ac, acc->onDisconnect); - } - - ac->data = node; - ac->dataHandler = unlinkAsyncContextAndNode; - node->acon = ac; - - return ac; -} - -static redisAsyncContext *actx_get_after_update_route_by_slot( - redisClusterAsyncContext *acc, int slot_num) -{ - int ret; - redisClusterContext *cc; - redisAsyncContext *ac; - cluster_node *node; - - if(acc == NULL || slot_num < 0) - { - return NULL; - } - - cc = acc->cc; - if(cc == NULL) - { - return NULL; - } - - ret = cluster_update_route(cc); - if(ret != REDIS_OK) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - return NULL; - } - - node = node_get_by_table(cc, (uint32_t)slot_num); - if(node == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "node get by table error"); - return NULL; - } - - ac = actx_get_by_node(acc, node); - if(ac == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "actx get by node error"); - return NULL; - } - else if(ac->err) - { - __redisClusterAsyncSetError(acc, ac->err, ac->errstr); - return NULL; - } - - return ac; -} - -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) { - - redisClusterContext *cc; - redisClusterAsyncContext *acc; - - cc = redisClusterConnectNonBlock(addrs, flags); - if(cc == NULL) - { - return NULL; - } - - acc = redisClusterAsyncInitialize(cc); - if (acc == NULL) { - redisClusterFree(cc); - return NULL; - } - - __redisClusterAsyncCopyError(acc); - - return acc; -} - - -int redisClusterAsyncSetConnectCallback( - redisClusterAsyncContext *acc, redisConnectCallback *fn) -{ - if (acc->onConnect == NULL) { - acc->onConnect = fn; - return REDIS_OK; - } - return REDIS_ERR; -} - -int redisClusterAsyncSetDisconnectCallback( - redisClusterAsyncContext *acc, redisDisconnectCallback *fn) -{ - if (acc->onDisconnect == NULL) { - acc->onDisconnect = fn; - return REDIS_OK; - } - return REDIS_ERR; -} - -static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *privdata) { - int ret; - redisReply *reply = r; - cluster_async_data *cad = privdata; - redisClusterAsyncContext *acc; - redisClusterContext *cc; - redisAsyncContext *ac_retry = NULL; - int error_type; - cluster_node *node; - struct cmd *command; - int64_t now, next; - - if(cad == NULL) - { - goto error; - } - - acc = cad->acc; - if(acc == NULL) - { - goto error; - } - - cc = acc->cc; - if(cc == NULL) - { - goto error; - } - - command = cad->command; - if(command == NULL) - { - goto error; - } - - if(reply == NULL) - { - //Note: - //I can't decide witch is the best way to deal with connect - //problem for hiredis cluster async api. - //But now the way is : when enough null reply for a node, - //we will update the route after the cluster node timeout. - //If you have a better idea, please contact with me. Thank you. - //My email: diguo58@gmail.com - - node = (cluster_node *)(ac->data); - ASSERT(node != NULL); - - __redisClusterAsyncSetError(acc, - ac->err, ac->errstr); - - if(cc->update_route_time != 0) - { - now = hi_usec_now(); - if(now >= cc->update_route_time) - { - ret = cluster_update_route(cc); - if(ret != REDIS_OK) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - } - - cc->update_route_time = 0LL; - } - - goto done; - } - - node->failure_count ++; - if(node->failure_count > cc->max_redirect_count) - { - char *cluster_timeout_str; - int cluster_timeout_str_len; - int cluster_timeout; - - node->failure_count = 0; - if(cc->update_route_time != 0) - { - goto done; - } - - cluster_timeout_str = cluster_config_get(cc, - "cluster-node-timeout", &cluster_timeout_str_len); - if(cluster_timeout_str == NULL) - { - __redisClusterAsyncSetError(acc, - cc->err, cc->errstr); - goto done; - } - - cluster_timeout = hi_atoi(cluster_timeout_str, - cluster_timeout_str_len); - free(cluster_timeout_str); - if(cluster_timeout <= 0) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, - "cluster_timeout_str convert to integer error"); - goto done; - } - - now = hi_usec_now(); - if (now < 0) { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, - "get now usec time error"); - goto done; - } - - next = now + (cluster_timeout * 1000LL); - - cc->update_route_time = next; - - } - - goto done; - } - - error_type = cluster_reply_error_type(reply); - - if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) - { - cad->retry_count ++; - if(cad->retry_count > cc->max_redirect_count) - { - cad->retry_count = 0; - __redisClusterAsyncSetError(acc, - REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, - "too many cluster redirect"); - goto done; - } - - switch(error_type) - { - case CLUSTER_ERR_MOVED: - ac_retry = actx_get_after_update_route_by_slot(acc, command->slot_num); - if(ac_retry == NULL) - { - goto done; - } - - break; - case CLUSTER_ERR_ASK: - node = node_get_by_ask_error_reply(cc, reply); - if(node == NULL) - { - __redisClusterAsyncSetError(acc, - cc->err, cc->errstr); - goto done; - } - - ac_retry = actx_get_by_node(acc, node); - if(ac_retry == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "actx get by node error"); - goto done; - } - else if(ac_retry->err) - { - __redisClusterAsyncSetError(acc, - ac_retry->err, ac_retry->errstr); - goto done; - } - - ret = redisAsyncCommand(ac_retry, - NULL,NULL,REDIS_COMMAND_ASKING); - if(ret != REDIS_OK) - { - goto error; - } - - break; - case CLUSTER_ERR_TRYAGAIN: - case CLUSTER_ERR_CROSSSLOT: - case CLUSTER_ERR_CLUSTERDOWN: - ac_retry = ac; - - break; - default: - - goto done; - break; - } - - goto retry; - } - -done: - - if(acc->err) - { - cad->callback(acc, NULL, cad->privdata); - } - else - { - cad->callback(acc, r, cad->privdata); - } - - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - if(acc->err) - { - acc->err = 0; - memset(acc->errstr, '\0', strlen(acc->errstr)); - } - - if(cad != NULL) - { - cluster_async_data_free(cad); - } - - return; - -retry: - - ret = redisAsyncFormattedCommand(ac_retry, - redisClusterAsyncCallback,cad,command->cmd,command->clen); - if(ret != REDIS_OK) - { - goto error; - } - - return; - -error: - - if(cad != NULL) - { - cluster_async_data_free(cad); - } -} - -int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, char *cmd, int len) { - - redisClusterContext *cc; - int status = REDIS_OK; - int slot_num; - cluster_node *node; - redisAsyncContext *ac; - struct cmd *command = NULL; - hilist *commands = NULL; - cluster_async_data *cad; - - if(acc == NULL) - { - return REDIS_ERR; - } - - cc = acc->cc; - - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - if(acc->err) - { - acc->err = 0; - memset(acc->errstr, '\0', strlen(acc->errstr)); - } - - command = command_get(); - if(command == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - command->cmd = malloc(len*sizeof(*command->cmd)); - if(command->cmd == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - memcpy(command->cmd, cmd, len); - command->clen = len; - - commands = listCreate(); - if(commands == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - commands->free = listCommandFree; - - slot_num = command_format_by_slot(cc, command, commands); - - if(slot_num < 0) - { - __redisClusterAsyncSetError(acc, - cc->err, cc->errstr); - goto error; - } - else if(slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER,"slot_num is out of range"); - goto error; - } - - //all keys not belong to one slot - if(listLength(commands) > 0) - { - ASSERT(listLength(commands) != 1); - - __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER, - "Asynchronous API now not support multi-key command"); - goto error; - } - - node = node_get_by_table(cc, (uint32_t) slot_num); - if(node == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "node get by table error"); - goto error; - } - - ac = actx_get_by_node(acc, node); - if(ac == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "actx get by node error"); - goto error; - } - else if(ac->err) - { - __redisClusterAsyncSetError(acc, ac->err, ac->errstr); - goto error; - } - - cad = cluster_async_data_get(); - if(cad == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } - - cad->acc = acc; - cad->command = command; - cad->callback = fn; - cad->privdata = privdata; - - status = redisAsyncFormattedCommand(ac, - redisClusterAsyncCallback,cad,cmd,len); - if(status != REDIS_OK) - { - goto error; - } - - if(commands != NULL) - { - listRelease(commands); - } - - return REDIS_OK; - -error: - - if(command != NULL) - { - command_destroy(command); - } - - if(commands != NULL) - { - listRelease(commands); - } - - return REDIS_ERR; -} - - -int redisClustervAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) { - int ret; - char *cmd; - int len; - - if(acc == NULL) - { - return REDIS_ERR; - } - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } else if (len == -2) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string"); - return REDIS_ERR; - } - - ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len); - - free(cmd); - - return ret; -} - -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { - int ret; - va_list ap; - - va_start(ap,format); - ret = redisClustervAsyncCommand(acc, fn, privdata, format, ap); - va_end(ap); - - return ret; -} - -int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - int ret; - char *cmd; - int len; - - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len); - - free(cmd); - - return ret; -} - -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { - - redisClusterContext *cc; - redisAsyncContext *ac; - dictIterator *di; - dictEntry *de; - dict *nodes; - struct cluster_node *node; - - if(acc == NULL) - { - return; - } - - cc = acc->cc; - - nodes = cc->nodes; - - if(nodes == NULL) - { - return; - } - - di = dictGetIterator(nodes); - - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - - ac = node->acon; - - if(ac == NULL || ac->err) - { - continue; - } - - redisAsyncDisconnect(ac); - - node->acon = NULL; - } -} - -void redisClusterAsyncFree(redisClusterAsyncContext *acc) -{ - redisClusterContext *cc; - - if(acc == NULL) - { - return; - } - - cc = acc->cc; - - redisClusterFree(cc); - - hi_free(acc); -} - diff --git a/ext/hiredis-vip-0.3.0/hircluster.h b/ext/hiredis-vip-0.3.0/hircluster.h deleted file mode 100644 index 5b9c5a358..000000000 --- a/ext/hiredis-vip-0.3.0/hircluster.h +++ /dev/null @@ -1,178 +0,0 @@ - -#ifndef __HIRCLUSTER_H -#define __HIRCLUSTER_H - -#include "hiredis.h" -#include "async.h" - -#define HIREDIS_VIP_MAJOR 0 -#define HIREDIS_VIP_MINOR 3 -#define HIREDIS_VIP_PATCH 0 - -#define REDIS_CLUSTER_SLOTS 16384 - -#define REDIS_ROLE_NULL 0 -#define REDIS_ROLE_MASTER 1 -#define REDIS_ROLE_SLAVE 2 - - -#define HIRCLUSTER_FLAG_NULL 0x0 -/* The flag to decide whether add slave node in - * redisClusterContext->nodes. This is set in the - * least significant bit of the flags field in - * redisClusterContext. (1000000000000) */ -#define HIRCLUSTER_FLAG_ADD_SLAVE 0x1000 -/* The flag to decide whether add open slot - * for master node. (10000000000000) */ -#define HIRCLUSTER_FLAG_ADD_OPENSLOT 0x2000 -/* The flag to decide whether get the route - * table by 'cluster slots' command. Default - * is 'cluster nodes' command.*/ -#define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS 0x4000 - -struct dict; -struct hilist; - -typedef struct cluster_node -{ - sds name; - sds addr; - sds host; - int port; - uint8_t role; - uint8_t myself; /* myself ? */ - redisContext *con; - redisAsyncContext *acon; - struct hilist *slots; - struct hilist *slaves; - int failure_count; - void *data; /* Not used by hiredis */ - struct hiarray *migrating; /* copen_slot[] */ - struct hiarray *importing; /* copen_slot[] */ -}cluster_node; - -typedef struct cluster_slot -{ - uint32_t start; - uint32_t end; - cluster_node *node; /* master that this slot region belong to */ -}cluster_slot; - -typedef struct copen_slot -{ - uint32_t slot_num; /* slot number */ - int migrate; /* migrating or importing? */ - sds remote_name; /* name for the node that this slot migrating to/importing from */ - cluster_node *node; /* master that this slot belong to */ -}copen_slot; - -#ifdef __cplusplus -extern "C" { -#endif - -/* Context for a connection to Redis cluster */ -typedef struct redisClusterContext { - int err; /* Error flags, 0 when there is no error */ - char errstr[128]; /* String representation of error when applicable */ - sds ip; - int port; - - int flags; - - enum redisConnectionType connection_type; - struct timeval *timeout; - - struct hiarray *slots; - - struct dict *nodes; - cluster_node *table[REDIS_CLUSTER_SLOTS]; - - uint64_t route_version; - - int max_redirect_count; - int retry_count; - - struct hilist *requests; - - int need_update_route; - int64_t update_route_time; -} redisClusterContext; - -redisClusterContext *redisClusterConnect(const char *addrs, int flags); -redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, - const struct timeval tv, int flags); -redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); - -void redisClusterFree(redisClusterContext *cc); - -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); - -void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); -void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); -void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); - -redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); - -int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); -int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); -int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); -int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); -int redisClusterGetReply(redisClusterContext *cc, void **reply); -void redisClusterReset(redisClusterContext *cc); - -int cluster_update_route(redisClusterContext *cc); -int test_cluster_update_route(redisClusterContext *cc); -struct dict *parse_cluster_nodes(redisClusterContext *cc, char *str, int str_len, int flags); -struct dict *parse_cluster_slots(redisClusterContext *cc, redisReply *reply, int flags); - - -/*############redis cluster async############*/ - -struct redisClusterAsyncContext; - -typedef int (adapterAttachFn)(redisAsyncContext*, void*); - -typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*); - -/* Context for an async connection to Redis */ -typedef struct redisClusterAsyncContext { - - redisClusterContext *cc; - - /* Setup error flags so they can be used directly. */ - int err; - char errstr[128]; /* String representation of error when applicable */ - - /* Not used by hiredis */ - void *data; - - void *adapter; - adapterAttachFn *attach_fn; - - /* Called when either the connection is terminated due to an error or per - * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ - redisDisconnectCallback *onDisconnect; - - /* Called when the first write event was received. */ - redisConnectCallback *onConnect; - -} redisClusterAsyncContext; - -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); -int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); -int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); -int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); -int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); -int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); -void redisClusterAsyncFree(redisClusterAsyncContext *acc); - -redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/ext/hiredis-vip-0.3.0/hiredis.c b/ext/hiredis-vip-0.3.0/hiredis.c deleted file mode 100644 index 73d0251bc..000000000 --- a/ext/hiredis-vip-0.3.0/hiredis.c +++ /dev/null @@ -1,1021 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2014, Pieter Noordhuis - * Copyright (c) 2015, Matt Stancliff , - * Jan-Erik Rediger - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include -#include - -#include "hiredis.h" -#include "net.h" -#include "sds.h" - -static redisReply *createReplyObject(int type); -static void *createStringObject(const redisReadTask *task, char *str, size_t len); -static void *createArrayObject(const redisReadTask *task, int elements); -static void *createIntegerObject(const redisReadTask *task, long long value); -static void *createNilObject(const redisReadTask *task); - -/* Default set of functions to build the reply. Keep in mind that such a - * function returning NULL is interpreted as OOM. */ -static redisReplyObjectFunctions defaultFunctions = { - createStringObject, - createArrayObject, - createIntegerObject, - createNilObject, - freeReplyObject -}; - -/* Create a reply object */ -static redisReply *createReplyObject(int type) { - redisReply *r = calloc(1,sizeof(*r)); - - if (r == NULL) - return NULL; - - r->type = type; - return r; -} - -/* Free a reply object */ -void freeReplyObject(void *reply) { - redisReply *r = reply; - size_t j; - - if (r == NULL) - return; - - switch(r->type) { - case REDIS_REPLY_INTEGER: - break; /* Nothing to free */ - case REDIS_REPLY_ARRAY: - if (r->element != NULL) { - for (j = 0; j < r->elements; j++) - if (r->element[j] != NULL) - freeReplyObject(r->element[j]); - free(r->element); - } - break; - case REDIS_REPLY_ERROR: - case REDIS_REPLY_STATUS: - case REDIS_REPLY_STRING: - if (r->str != NULL) - free(r->str); - break; - } - free(r); -} - -static void *createStringObject(const redisReadTask *task, char *str, size_t len) { - redisReply *r, *parent; - char *buf; - - r = createReplyObject(task->type); - if (r == NULL) - return NULL; - - buf = malloc(len+1); - if (buf == NULL) { - freeReplyObject(r); - return NULL; - } - - assert(task->type == REDIS_REPLY_ERROR || - task->type == REDIS_REPLY_STATUS || - task->type == REDIS_REPLY_STRING); - - /* Copy string value */ - memcpy(buf,str,len); - buf[len] = '\0'; - r->str = buf; - r->len = len; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createArrayObject(const redisReadTask *task, int elements) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_ARRAY); - if (r == NULL) - return NULL; - - if (elements > 0) { - r->element = calloc(elements,sizeof(redisReply*)); - if (r->element == NULL) { - freeReplyObject(r); - return NULL; - } - } - - r->elements = elements; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createIntegerObject(const redisReadTask *task, long long value) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_INTEGER); - if (r == NULL) - return NULL; - - r->integer = value; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createNilObject(const redisReadTask *task) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_NIL); - if (r == NULL) - return NULL; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -/* Return the number of digits of 'v' when converted to string in radix 10. - * Implementation borrowed from link in redis/src/util.c:string2ll(). */ -static uint32_t countDigits(uint64_t v) { - uint32_t result = 1; - for (;;) { - if (v < 10) return result; - if (v < 100) return result + 1; - if (v < 1000) return result + 2; - if (v < 10000) return result + 3; - v /= 10000U; - result += 4; - } -} - -/* Helper that calculates the bulk length given a certain string length. */ -static size_t bulklen(size_t len) { - return 1+countDigits(len)+2+len+2; -} - -int redisvFormatCommand(char **target, const char *format, va_list ap) { - const char *c = format; - char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - sds curarg, newarg; /* current argument */ - int touched = 0; /* was the current argument touched? */ - char **curargv = NULL, **newargv = NULL; - int argc = 0; - int totlen = 0; - int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ - int j; - - /* Abort if there is not target to set */ - if (target == NULL) - return -1; - - /* Build the command string accordingly to protocol */ - curarg = sdsempty(); - if (curarg == NULL) - return -1; - - while(*c != '\0') { - if (*c != '%' || c[1] == '\0') { - if (*c == ' ') { - if (touched) { - newargv = realloc(curargv,sizeof(char*)*(argc+1)); - if (newargv == NULL) goto memory_err; - curargv = newargv; - curargv[argc++] = curarg; - totlen += bulklen(sdslen(curarg)); - - /* curarg is put in argv so it can be overwritten. */ - curarg = sdsempty(); - if (curarg == NULL) goto memory_err; - touched = 0; - } - } else { - newarg = sdscatlen(curarg,c,1); - if (newarg == NULL) goto memory_err; - curarg = newarg; - touched = 1; - } - } else { - char *arg; - size_t size; - - /* Set newarg so it can be checked even if it is not touched. */ - newarg = curarg; - - switch(c[1]) { - case 's': - arg = va_arg(ap,char*); - size = strlen(arg); - if (size > 0) - newarg = sdscatlen(curarg,arg,size); - break; - case 'b': - arg = va_arg(ap,char*); - size = va_arg(ap,size_t); - if (size > 0) - newarg = sdscatlen(curarg,arg,size); - break; - case '%': - newarg = sdscat(curarg,"%"); - break; - default: - /* Try to detect printf format */ - { - static const char intfmts[] = "diouxX"; - static const char flags[] = "#0-+ "; - char _format[16]; - const char *_p = c+1; - size_t _l = 0; - va_list _cpy; - - /* Flags */ - while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; - - /* Field width */ - while (*_p != '\0' && isdigit(*_p)) _p++; - - /* Precision */ - if (*_p == '.') { - _p++; - while (*_p != '\0' && isdigit(*_p)) _p++; - } - - /* Copy va_list before consuming with va_arg */ - va_copy(_cpy,ap); - - /* Integer conversion (without modifiers) */ - if (strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); - goto fmt_valid; - } - - /* Double conversion (without modifiers) */ - if (strchr("eEfFgGaA",*_p) != NULL) { - va_arg(ap,double); - goto fmt_valid; - } - - /* Size: char */ - if (_p[0] == 'h' && _p[1] == 'h') { - _p += 2; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); /* char gets promoted to int */ - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: short */ - if (_p[0] == 'h') { - _p += 1; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); /* short gets promoted to int */ - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: long long */ - if (_p[0] == 'l' && _p[1] == 'l') { - _p += 2; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,long long); - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: long */ - if (_p[0] == 'l') { - _p += 1; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,long); - goto fmt_valid; - } - goto fmt_invalid; - } - - fmt_invalid: - va_end(_cpy); - goto format_err; - - fmt_valid: - _l = (_p+1)-c; - if (_l < sizeof(_format)-2) { - memcpy(_format,c,_l); - _format[_l] = '\0'; - newarg = sdscatvprintf(curarg,_format,_cpy); - - /* Update current position (note: outer blocks - * increment c twice so compensate here) */ - c = _p-1; - } - - va_end(_cpy); - break; - } - } - - if (newarg == NULL) goto memory_err; - curarg = newarg; - - touched = 1; - c++; - } - c++; - } - - /* Add the last argument if needed */ - if (touched) { - newargv = realloc(curargv,sizeof(char*)*(argc+1)); - if (newargv == NULL) goto memory_err; - curargv = newargv; - curargv[argc++] = curarg; - totlen += bulklen(sdslen(curarg)); - } else { - sdsfree(curarg); - } - - /* Clear curarg because it was put in curargv or was free'd. */ - curarg = NULL; - - /* Add bytes needed to hold multi bulk count */ - totlen += 1+countDigits(argc)+2; - - /* Build the command at protocol level */ - cmd = malloc(totlen+1); - if (cmd == NULL) goto memory_err; - - pos = sprintf(cmd,"*%d\r\n",argc); - for (j = 0; j < argc; j++) { - pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); - memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); - pos += sdslen(curargv[j]); - sdsfree(curargv[j]); - cmd[pos++] = '\r'; - cmd[pos++] = '\n'; - } - assert(pos == totlen); - cmd[pos] = '\0'; - - free(curargv); - *target = cmd; - return totlen; - -format_err: - error_type = -2; - goto cleanup; - -memory_err: - error_type = -1; - goto cleanup; - -cleanup: - if (curargv) { - while(argc--) - sdsfree(curargv[argc]); - free(curargv); - } - - sdsfree(curarg); - - /* No need to check cmd since it is the last statement that can fail, - * but do it anyway to be as defensive as possible. */ - if (cmd != NULL) - free(cmd); - - return error_type; -} - -/* Format a command according to the Redis protocol. This function - * takes a format similar to printf: - * - * %s represents a C null terminated string you want to interpolate - * %b represents a binary safe string - * - * When using %b you need to provide both the pointer to the string - * and the length in bytes as a size_t. Examples: - * - * len = redisFormatCommand(target, "GET %s", mykey); - * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); - */ -int redisFormatCommand(char **target, const char *format, ...) { - va_list ap; - int len; - va_start(ap,format); - len = redisvFormatCommand(target,format,ap); - va_end(ap); - - /* The API says "-1" means bad result, but we now also return "-2" in some - * cases. Force the return value to always be -1. */ - if (len < 0) - len = -1; - - return len; -} - -/* Format a command according to the Redis protocol using an sds string and - * sdscatfmt for the processing of arguments. This function takes the - * number of arguments, an array with arguments and an array with their - * lengths. If the latter is set to NULL, strlen will be used to compute the - * argument lengths. - */ -int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, - const size_t *argvlen) -{ - sds cmd; - unsigned long long totlen; - int j; - size_t len; - - /* Abort on a NULL target */ - if (target == NULL) - return -1; - - /* Calculate our total size */ - totlen = 1+countDigits(argc)+2; - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - totlen += bulklen(len); - } - - /* Use an SDS string for command construction */ - cmd = sdsempty(); - if (cmd == NULL) - return -1; - - /* We already know how much storage we need */ - cmd = sdsMakeRoomFor(cmd, totlen); - if (cmd == NULL) - return -1; - - /* Construct command */ - cmd = sdscatfmt(cmd, "*%i\r\n", argc); - for (j=0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - cmd = sdscatfmt(cmd, "$%T\r\n", len); - cmd = sdscatlen(cmd, argv[j], len); - cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); - } - - assert(sdslen(cmd)==totlen); - - *target = cmd; - return totlen; -} - -void redisFreeSdsCommand(sds cmd) { - sdsfree(cmd); -} - -/* Format a command according to the Redis protocol. This function takes the - * number of arguments, an array with arguments and an array with their - * lengths. If the latter is set to NULL, strlen will be used to compute the - * argument lengths. - */ -int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { - char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - size_t len; - int totlen, j; - - /* Abort on a NULL target */ - if (target == NULL) - return -1; - - /* Calculate number of bytes needed for the command */ - totlen = 1+countDigits(argc)+2; - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - totlen += bulklen(len); - } - - /* Build the command at protocol level */ - cmd = malloc(totlen+1); - if (cmd == NULL) - return -1; - - pos = sprintf(cmd,"*%d\r\n",argc); - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - pos += sprintf(cmd+pos,"$%zu\r\n",len); - memcpy(cmd+pos,argv[j],len); - pos += len; - cmd[pos++] = '\r'; - cmd[pos++] = '\n'; - } - assert(pos == totlen); - cmd[pos] = '\0'; - - *target = cmd; - return totlen; -} - -void redisFreeCommand(char *cmd) { - free(cmd); -} - -void __redisSetError(redisContext *c, int type, const char *str) { - size_t len; - - c->err = type; - if (str != NULL) { - len = strlen(str); - len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); - memcpy(c->errstr,str,len); - c->errstr[len] = '\0'; - } else { - /* Only REDIS_ERR_IO may lack a description! */ - assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, c->errstr, sizeof(c->errstr)); - } -} - -redisReader *redisReaderCreate(void) { - return redisReaderCreateWithFunctions(&defaultFunctions); -} - -static redisContext *redisContextInit(void) { - redisContext *c; - - c = calloc(1,sizeof(redisContext)); - if (c == NULL) - return NULL; - - c->err = 0; - c->errstr[0] = '\0'; - c->obuf = sdsempty(); - c->reader = redisReaderCreate(); - c->tcp.host = NULL; - c->tcp.source_addr = NULL; - c->unix_sock.path = NULL; - c->timeout = NULL; - - if (c->obuf == NULL || c->reader == NULL) { - redisFree(c); - return NULL; - } - - return c; -} - -void redisFree(redisContext *c) { - if (c == NULL) - return; - if (c->fd > 0) - close(c->fd); - if (c->obuf != NULL) - sdsfree(c->obuf); - if (c->reader != NULL) - redisReaderFree(c->reader); - if (c->tcp.host) - free(c->tcp.host); - if (c->tcp.source_addr) - free(c->tcp.source_addr); - if (c->unix_sock.path) - free(c->unix_sock.path); - if (c->timeout) - free(c->timeout); - free(c); -} - -int redisFreeKeepFd(redisContext *c) { - int fd = c->fd; - c->fd = -1; - redisFree(c); - return fd; -} - -int redisReconnect(redisContext *c) { - c->err = 0; - memset(c->errstr, '\0', strlen(c->errstr)); - - if (c->fd > 0) { - close(c->fd); - } - - sdsfree(c->obuf); - redisReaderFree(c->reader); - - c->obuf = sdsempty(); - c->reader = redisReaderCreate(); - - if (c->connection_type == REDIS_CONN_TCP) { - return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, - c->timeout, c->tcp.source_addr); - } else if (c->connection_type == REDIS_CONN_UNIX) { - return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); - } else { - /* Something bad happened here and shouldn't have. There isn't - enough information in the context to reconnect. */ - __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); - } - - return REDIS_ERR; -} - -/* Connect to a Redis instance. On error the field error in the returned - * context will be set to the return value of the error function. - * When no set of reply functions is given, the default set will be used. */ -redisContext *redisConnect(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; -} - -redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,&tv); - return c; -} - -redisContext *redisConnectNonBlock(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; -} - -redisContext *redisConnectBindNonBlock(const char *ip, int port, - const char *source_addr) { - redisContext *c = redisContextInit(); - c->flags &= ~REDIS_BLOCK; - redisContextConnectBindTcp(c,ip,port,NULL,source_addr); - return c; -} - -redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, - const char *source_addr) { - redisContext *c = redisContextInit(); - c->flags &= ~REDIS_BLOCK; - c->flags |= REDIS_REUSEADDR; - redisContextConnectBindTcp(c,ip,port,NULL,source_addr); - return c; -} - -redisContext *redisConnectUnix(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; -} - -redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,&tv); - return c; -} - -redisContext *redisConnectUnixNonBlock(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; -} - -redisContext *redisConnectFd(int fd) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->fd = fd; - c->flags |= REDIS_BLOCK | REDIS_CONNECTED; - return c; -} - -/* Set read/write timeout on a blocking socket. */ -int redisSetTimeout(redisContext *c, const struct timeval tv) { - if (c->flags & REDIS_BLOCK) - return redisContextSetTimeout(c,tv); - return REDIS_ERR; -} - -/* Enable connection KeepAlive. */ -int redisEnableKeepAlive(redisContext *c) { - if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) - return REDIS_ERR; - return REDIS_OK; -} - -/* Use this function to handle a read event on the descriptor. It will try - * and read some bytes from the socket and feed them to the reply parser. - * - * After this function is called, you may use redisContextReadReply to - * see if there is a reply available. */ -int redisBufferRead(redisContext *c) { - char buf[1024*16]; - int nread; - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - nread = read(c->fd,buf,sizeof(buf)); - if (nread == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - } else if (nread == 0) { - __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); - return REDIS_ERR; - } else { - if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - } - return REDIS_OK; -} - -/* Write the output buffer to the socket. - * - * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was - * succesfully written to the socket. When the buffer is empty after the - * write operation, "done" is set to 1 (if given). - * - * Returns REDIS_ERR if an error occured trying to write and sets - * c->errstr to hold the appropriate error string. - */ -int redisBufferWrite(redisContext *c, int *done) { - int nwritten; - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - if (sdslen(c->obuf) > 0) { - nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); - if (nwritten == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - } else if (nwritten > 0) { - if (nwritten == (signed)sdslen(c->obuf)) { - sdsfree(c->obuf); - c->obuf = sdsempty(); - } else { - sdsrange(c->obuf,nwritten,-1); - } - } - } - if (done != NULL) *done = (sdslen(c->obuf) == 0); - return REDIS_OK; -} - -/* Internal helper function to try and get a reply from the reader, - * or set an error in the context otherwise. */ -int redisGetReplyFromReader(redisContext *c, void **reply) { - if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - return REDIS_OK; -} - -int redisGetReply(redisContext *c, void **reply) { - int wdone = 0; - void *aux = NULL; - - /* Try to read pending replies */ - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - - /* For the blocking context, flush output buffer and read reply */ - if (aux == NULL && c->flags & REDIS_BLOCK) { - /* Write until done */ - do { - if (redisBufferWrite(c,&wdone) == REDIS_ERR) - return REDIS_ERR; - } while (!wdone); - - /* Read until there is a reply */ - do { - if (redisBufferRead(c) == REDIS_ERR) - return REDIS_ERR; - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - } while (aux == NULL); - } - - /* Set reply object */ - if (reply != NULL) *reply = aux; - return REDIS_OK; -} - - -/* Helper function for the redisAppendCommand* family of functions. - * - * Write a formatted command to the output buffer. When this family - * is used, you need to call redisGetReply yourself to retrieve - * the reply (or replies in pub/sub). - */ -int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { - sds newbuf; - - newbuf = sdscatlen(c->obuf,cmd,len); - if (newbuf == NULL) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - c->obuf = newbuf; - return REDIS_OK; -} - -int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { - - if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { - return REDIS_ERR; - } - - return REDIS_OK; -} - -int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { - char *cmd; - int len; - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } else if (len == -2) { - __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); - return REDIS_ERR; - } - - if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - free(cmd); - return REDIS_ERR; - } - - free(cmd); - return REDIS_OK; -} - -int redisAppendCommand(redisContext *c, const char *format, ...) { - va_list ap; - int ret; - - va_start(ap,format); - ret = redisvAppendCommand(c,format,ap); - va_end(ap); - return ret; -} - -int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - sds cmd; - int len; - - len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - sdsfree(cmd); - return REDIS_ERR; - } - - sdsfree(cmd); - return REDIS_OK; -} - -/* Helper function for the redisCommand* family of functions. - * - * Write a formatted command to the output buffer. If the given context is - * blocking, immediately read the reply into the "reply" pointer. When the - * context is non-blocking, the "reply" pointer will not be used and the - * command is simply appended to the write buffer. - * - * Returns the reply when a reply was succesfully retrieved. Returns NULL - * otherwise. When NULL is returned in a blocking context, the error field - * in the context will be set. - */ -static void *__redisBlockForReply(redisContext *c) { - void *reply; - - if (c->flags & REDIS_BLOCK) { - if (redisGetReply(c,&reply) != REDIS_OK) - return NULL; - return reply; - } - return NULL; -} - -void *redisvCommand(redisContext *c, const char *format, va_list ap) { - if (redisvAppendCommand(c,format,ap) != REDIS_OK) - return NULL; - return __redisBlockForReply(c); -} - -void *redisCommand(redisContext *c, const char *format, ...) { - va_list ap; - void *reply = NULL; - va_start(ap,format); - reply = redisvCommand(c,format,ap); - va_end(ap); - return reply; -} - -void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) - return NULL; - return __redisBlockForReply(c); -} diff --git a/ext/hiredis-vip-0.3.0/hiutil.c b/ext/hiredis-vip-0.3.0/hiutil.c deleted file mode 100644 index d10cdacea..000000000 --- a/ext/hiredis-vip-0.3.0/hiutil.c +++ /dev/null @@ -1,554 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - - -#include "hiutil.h" - -#ifdef HI_HAVE_BACKTRACE -# include -#endif - -int -hi_set_blocking(int sd) -{ - int flags; - - flags = fcntl(sd, F_GETFL, 0); - if (flags < 0) { - return flags; - } - - return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK); -} - -int -hi_set_nonblocking(int sd) -{ - int flags; - - flags = fcntl(sd, F_GETFL, 0); - if (flags < 0) { - return flags; - } - - return fcntl(sd, F_SETFL, flags | O_NONBLOCK); -} - -int -hi_set_reuseaddr(int sd) -{ - int reuse; - socklen_t len; - - reuse = 1; - len = sizeof(reuse); - - return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len); -} - -/* - * Disable Nagle algorithm on TCP socket. - * - * This option helps to minimize transmit latency by disabling coalescing - * of data to fill up a TCP segment inside the kernel. Sockets with this - * option must use readv() or writev() to do data transfer in bulk and - * hence avoid the overhead of small packets. - */ -int -hi_set_tcpnodelay(int sd) -{ - int nodelay; - socklen_t len; - - nodelay = 1; - len = sizeof(nodelay); - - return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); -} - -int -hi_set_linger(int sd, int timeout) -{ - struct linger linger; - socklen_t len; - - linger.l_onoff = 1; - linger.l_linger = timeout; - - len = sizeof(linger); - - return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len); -} - -int -hi_set_sndbuf(int sd, int size) -{ - socklen_t len; - - len = sizeof(size); - - return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len); -} - -int -hi_set_rcvbuf(int sd, int size) -{ - socklen_t len; - - len = sizeof(size); - - return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len); -} - -int -hi_get_soerror(int sd) -{ - int status, err; - socklen_t len; - - err = 0; - len = sizeof(err); - - status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len); - if (status == 0) { - errno = err; - } - - return status; -} - -int -hi_get_sndbuf(int sd) -{ - int status, size; - socklen_t len; - - size = 0; - len = sizeof(size); - - status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len); - if (status < 0) { - return status; - } - - return size; -} - -int -hi_get_rcvbuf(int sd) -{ - int status, size; - socklen_t len; - - size = 0; - len = sizeof(size); - - status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len); - if (status < 0) { - return status; - } - - return size; -} - -int -_hi_atoi(uint8_t *line, size_t n) -{ - int value; - - if (n == 0) { - return -1; - } - - for (value = 0; n--; line++) { - if (*line < '0' || *line > '9') { - return -1; - } - - value = value * 10 + (*line - '0'); - } - - if (value < 0) { - return -1; - } - - return value; -} - -void -_hi_itoa(uint8_t *s, int num) -{ - uint8_t c; - uint8_t sign = 0; - - if(s == NULL) - { - return; - } - - uint32_t len, i; - len = 0; - - if(num < 0) - { - sign = 1; - num = abs(num); - } - else if(num == 0) - { - s[len++] = '0'; - return; - } - - while(num % 10 || num /10) - { - c = num %10 + '0'; - num = num /10; - s[len+1] = s[len]; - s[len] = c; - len ++; - } - - if(sign == 1) - { - s[len++] = '-'; - } - - s[len] = '\0'; - - for(i = 0; i < len/2; i ++) - { - c = s[i]; - s[i] = s[len - i -1]; - s[len - i -1] = c; - } - -} - - -int -hi_valid_port(int n) -{ - if (n < 1 || n > UINT16_MAX) { - return 0; - } - - return 1; -} - -int _uint_len(uint32_t num) -{ - int n = 0; - - if(num == 0) - { - return 1; - } - - while(num != 0) - { - n ++; - num /= 10; - } - - return n; -} - -void * -_hi_alloc(size_t size, const char *name, int line) -{ - void *p; - - ASSERT(size != 0); - - p = malloc(size); - - if(name == NULL && line == 1) - { - - } - - return p; -} - -void * -_hi_zalloc(size_t size, const char *name, int line) -{ - void *p; - - p = _hi_alloc(size, name, line); - if (p != NULL) { - memset(p, 0, size); - } - - return p; -} - -void * -_hi_calloc(size_t nmemb, size_t size, const char *name, int line) -{ - return _hi_zalloc(nmemb * size, name, line); -} - -void * -_hi_realloc(void *ptr, size_t size, const char *name, int line) -{ - void *p; - - ASSERT(size != 0); - - p = realloc(ptr, size); - - if(name == NULL && line == 1) - { - - } - - return p; -} - -void -_hi_free(void *ptr, const char *name, int line) -{ - ASSERT(ptr != NULL); - - if(name == NULL && line == 1) - { - - } - - free(ptr); -} - -void -hi_stacktrace(int skip_count) -{ - if(skip_count > 0) - { - - } - -#ifdef HI_HAVE_BACKTRACE - void *stack[64]; - char **symbols; - int size, i, j; - - size = backtrace(stack, 64); - symbols = backtrace_symbols(stack, size); - if (symbols == NULL) { - return; - } - - skip_count++; /* skip the current frame also */ - - for (i = skip_count, j = 0; i < size; i++, j++) { - printf("[%d] %s\n", j, symbols[i]); - } - - free(symbols); -#endif -} - -void -hi_stacktrace_fd(int fd) -{ - if(fd > 0) - { - - } -#ifdef HI_HAVE_BACKTRACE - void *stack[64]; - int size; - - size = backtrace(stack, 64); - backtrace_symbols_fd(stack, size, fd); -#endif -} - -void -hi_assert(const char *cond, const char *file, int line, int panic) -{ - - printf("File: %s Line: %d: %s\n", file, line, cond); - - if (panic) { - hi_stacktrace(1); - abort(); - } - abort(); -} - -int -_vscnprintf(char *buf, size_t size, const char *fmt, va_list args) -{ - int n; - - n = vsnprintf(buf, size, fmt, args); - - /* - * The return value is the number of characters which would be written - * into buf not including the trailing '\0'. If size is == 0 the - * function returns 0. - * - * On error, the function also returns 0. This is to allow idiom such - * as len += _vscnprintf(...) - * - * See: http://lwn.net/Articles/69419/ - */ - if (n <= 0) { - return 0; - } - - if (n < (int) size) { - return n; - } - - return (int)(size - 1); -} - -int -_scnprintf(char *buf, size_t size, const char *fmt, ...) -{ - va_list args; - int n; - - va_start(args, fmt); - n = _vscnprintf(buf, size, fmt, args); - va_end(args); - - return n; -} - -/* - * Send n bytes on a blocking descriptor - */ -ssize_t -_hi_sendn(int sd, const void *vptr, size_t n) -{ - size_t nleft; - ssize_t nsend; - const char *ptr; - - ptr = vptr; - nleft = n; - while (nleft > 0) { - nsend = send(sd, ptr, nleft, 0); - if (nsend < 0) { - if (errno == EINTR) { - continue; - } - return nsend; - } - if (nsend == 0) { - return -1; - } - - nleft -= (size_t)nsend; - ptr += nsend; - } - - return (ssize_t)n; -} - -/* - * Recv n bytes from a blocking descriptor - */ -ssize_t -_hi_recvn(int sd, void *vptr, size_t n) -{ - size_t nleft; - ssize_t nrecv; - char *ptr; - - ptr = vptr; - nleft = n; - while (nleft > 0) { - nrecv = recv(sd, ptr, nleft, 0); - if (nrecv < 0) { - if (errno == EINTR) { - continue; - } - return nrecv; - } - if (nrecv == 0) { - break; - } - - nleft -= (size_t)nrecv; - ptr += nrecv; - } - - return (ssize_t)(n - nleft); -} - -/* - * Return the current time in microseconds since Epoch - */ -int64_t -hi_usec_now(void) -{ - struct timeval now; - int64_t usec; - int status; - - status = gettimeofday(&now, NULL); - if (status < 0) { - return -1; - } - - usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec; - - return usec; -} - -/* - * Return the current time in milliseconds since Epoch - */ -int64_t -hi_msec_now(void) -{ - return hi_usec_now() / 1000LL; -} - -void print_string_with_length(char *s, size_t len) -{ - char *token; - for(token = s; token <= s + len; token ++) - { - printf("%c", *token); - } - printf("\n"); -} - -void print_string_with_length_fix_CRLF(char *s, size_t len) -{ - char *token; - for(token = s; token < s + len; token ++) - { - if(*token == CR) - { - printf("\\r"); - } - else if(*token == LF) - { - printf("\\n"); - } - else - { - printf("%c", *token); - } - } - printf("\n"); -} - diff --git a/ext/hiredis-vip-0.3.0/hiutil.h b/ext/hiredis-vip-0.3.0/hiutil.h deleted file mode 100644 index d9e1ddb0b..000000000 --- a/ext/hiredis-vip-0.3.0/hiutil.h +++ /dev/null @@ -1,265 +0,0 @@ -#ifndef __HIUTIL_H_ -#define __HIUTIL_H_ - -#include -#include -#include - -#define HI_OK 0 -#define HI_ERROR -1 -#define HI_EAGAIN -2 -#define HI_ENOMEM -3 - -typedef int rstatus_t; /* return type */ - -#define LF (uint8_t) 10 -#define CR (uint8_t) 13 -#define CRLF "\x0d\x0a" -#define CRLF_LEN (sizeof("\x0d\x0a") - 1) - -#define NELEMS(a) ((sizeof(a)) / sizeof((a)[0])) - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - -#define SQUARE(d) ((d) * (d)) -#define VAR(s, s2, n) (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1)) -#define STDDEV(s, s2, n) (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n)))) - -#define HI_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1) -#define HI_INET6_ADDRSTRLEN \ - (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1) -#define HI_INET_ADDRSTRLEN MAX(HI_INET4_ADDRSTRLEN, HI_INET6_ADDRSTRLEN) -#define HI_UNIX_ADDRSTRLEN \ - (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) - -#define HI_MAXHOSTNAMELEN 256 - -/* - * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral - * type (uintmax_t) in ascii, including the null terminator '\0' - * - * From stdint.h, we have: - * # define UINT8_MAX (255) - * # define UINT16_MAX (65535) - * # define UINT32_MAX (4294967295U) - * # define UINT64_MAX (__UINT64_C(18446744073709551615)) - */ -#define HI_UINT8_MAXLEN (3 + 1) -#define HI_UINT16_MAXLEN (5 + 1) -#define HI_UINT32_MAXLEN (10 + 1) -#define HI_UINT64_MAXLEN (20 + 1) -#define HI_UINTMAX_MAXLEN HI_UINT64_MAXLEN - -/* - * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2 - * of 2. - */ -#define HI_ALIGNMENT sizeof(unsigned long) /* platform word */ -#define HI_ALIGN(d, n) (((d) + (n - 1)) & ~(n - 1)) -#define HI_ALIGN_PTR(p, n) \ - (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1)) - - - -#define str3icmp(m, c0, c1, c2) \ - ((m[0] == c0 || m[0] == (c0 ^ 0x20)) && \ - (m[1] == c1 || m[1] == (c1 ^ 0x20)) && \ - (m[2] == c2 || m[2] == (c2 ^ 0x20))) - -#define str4icmp(m, c0, c1, c2, c3) \ - (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20))) - -#define str5icmp(m, c0, c1, c2, c3, c4) \ - (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20))) - -#define str6icmp(m, c0, c1, c2, c3, c4, c5) \ - (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20))) - -#define str7icmp(m, c0, c1, c2, c3, c4, c5, c6) \ - (str6icmp(m, c0, c1, c2, c3, c4, c5) && \ - (m[6] == c6 || m[6] == (c6 ^ 0x20))) - -#define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ - (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) && \ - (m[7] == c7 || m[7] == (c7 ^ 0x20))) - -#define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ - (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ - (m[8] == c8 || m[8] == (c8 ^ 0x20))) - -#define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ - (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && \ - (m[9] == c9 || m[9] == (c9 ^ 0x20))) - -#define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ - (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && \ - (m[10] == c10 || m[10] == (c10 ^ 0x20))) - -#define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ - (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && \ - (m[11] == c11 || m[11] == (c11 ^ 0x20))) - -#define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) \ - (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) && \ - (m[12] == c12 || m[12] == (c12 ^ 0x20))) - -#define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) \ - (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) && \ - (m[13] == c13 || m[13] == (c13 ^ 0x20))) - -#define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) \ - (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) && \ - (m[14] == c14 || m[14] == (c14 ^ 0x20))) - -#define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15) \ - (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) && \ - (m[15] == c15 || m[15] == (c15 ^ 0x20))) - - - -/* - * Wrapper to workaround well known, safe, implicit type conversion when - * invoking system calls. - */ -#define hi_gethostname(_name, _len) \ - gethostname((char *)_name, (size_t)_len) - -#define hi_atoi(_line, _n) \ - _hi_atoi((uint8_t *)_line, (size_t)_n) -#define hi_itoa(_line, _n) \ - _hi_itoa((uint8_t *)_line, (int)_n) - -#define uint_len(_n) \ - _uint_len((uint32_t)_n) - - -int hi_set_blocking(int sd); -int hi_set_nonblocking(int sd); -int hi_set_reuseaddr(int sd); -int hi_set_tcpnodelay(int sd); -int hi_set_linger(int sd, int timeout); -int hi_set_sndbuf(int sd, int size); -int hi_set_rcvbuf(int sd, int size); -int hi_get_soerror(int sd); -int hi_get_sndbuf(int sd); -int hi_get_rcvbuf(int sd); - -int _hi_atoi(uint8_t *line, size_t n); -void _hi_itoa(uint8_t *s, int num); - -int hi_valid_port(int n); - -int _uint_len(uint32_t num); - - -/* - * Memory allocation and free wrappers. - * - * These wrappers enables us to loosely detect double free, dangling - * pointer access and zero-byte alloc. - */ -#define hi_alloc(_s) \ - _hi_alloc((size_t)(_s), __FILE__, __LINE__) - -#define hi_zalloc(_s) \ - _hi_zalloc((size_t)(_s), __FILE__, __LINE__) - -#define hi_calloc(_n, _s) \ - _hi_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__) - -#define hi_realloc(_p, _s) \ - _hi_realloc(_p, (size_t)(_s), __FILE__, __LINE__) - -#define hi_free(_p) do { \ - _hi_free(_p, __FILE__, __LINE__); \ - (_p) = NULL; \ -} while (0) - -void *_hi_alloc(size_t size, const char *name, int line); -void *_hi_zalloc(size_t size, const char *name, int line); -void *_hi_calloc(size_t nmemb, size_t size, const char *name, int line); -void *_hi_realloc(void *ptr, size_t size, const char *name, int line); -void _hi_free(void *ptr, const char *name, int line); - - -#define hi_strndup(_s, _n) \ - strndup((char *)(_s), (size_t)(_n)); - -/* - * Wrappers to send or receive n byte message on a blocking - * socket descriptor. - */ -#define hi_sendn(_s, _b, _n) \ - _hi_sendn(_s, _b, (size_t)(_n)) - -#define hi_recvn(_s, _b, _n) \ - _hi_recvn(_s, _b, (size_t)(_n)) - -/* - * Wrappers to read or write data to/from (multiple) buffers - * to a file or socket descriptor. - */ -#define hi_read(_d, _b, _n) \ - read(_d, _b, (size_t)(_n)) - -#define hi_readv(_d, _b, _n) \ - readv(_d, _b, (int)(_n)) - -#define hi_write(_d, _b, _n) \ - write(_d, _b, (size_t)(_n)) - -#define hi_writev(_d, _b, _n) \ - writev(_d, _b, (int)(_n)) - -ssize_t _hi_sendn(int sd, const void *vptr, size_t n); -ssize_t _hi_recvn(int sd, void *vptr, size_t n); - -/* - * Wrappers for defining custom assert based on whether macro - * HI_ASSERT_PANIC or HI_ASSERT_LOG was defined at the moment - * ASSERT was called. - */ -#ifdef HI_ASSERT_PANIC - -#define ASSERT(_x) do { \ - if (!(_x)) { \ - hi_assert(#_x, __FILE__, __LINE__, 1); \ - } \ -} while (0) - -#define NOT_REACHED() ASSERT(0) - -#elif HI_ASSERT_LOG - -#define ASSERT(_x) do { \ - if (!(_x)) { \ - hi_assert(#_x, __FILE__, __LINE__, 0); \ - } \ -} while (0) - -#define NOT_REACHED() ASSERT(0) - -#else - -#define ASSERT(_x) - -#define NOT_REACHED() - -#endif - -void hi_assert(const char *cond, const char *file, int line, int panic); -void hi_stacktrace(int skip_count); -void hi_stacktrace_fd(int fd); - -int _scnprintf(char *buf, size_t size, const char *fmt, ...); -int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args); -int64_t hi_usec_now(void); -int64_t hi_msec_now(void); - -void print_string_with_length(char *s, size_t len); -void print_string_with_length_fix_CRLF(char *s, size_t len); - -uint16_t crc16(const char *buf, int len); - -#endif diff --git a/ext/hiredis-vip-0.3.0/net.c b/ext/hiredis-vip-0.3.0/net.c deleted file mode 100644 index 60a2dc754..000000000 --- a/ext/hiredis-vip-0.3.0/net.c +++ /dev/null @@ -1,458 +0,0 @@ -/* Extracted from anet.c to work properly with Hiredis error reporting. - * - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2014, Pieter Noordhuis - * Copyright (c) 2015, Matt Stancliff , - * Jan-Erik Rediger - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "net.h" -#include "sds.h" - -/* Defined in hiredis.c */ -void __redisSetError(redisContext *c, int type, const char *str); - -static void redisContextCloseFd(redisContext *c) { - if (c && c->fd >= 0) { - close(c->fd); - c->fd = -1; - } -} - -static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { - char buf[128] = { 0 }; - size_t len = 0; - - if (prefix != NULL) - len = snprintf(buf,sizeof(buf),"%s: ",prefix); - __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len); - __redisSetError(c,type,buf); -} - -static int redisSetReuseAddr(redisContext *c) { - int on = 1; - if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -static int redisCreateSocket(redisContext *c, int type) { - int s; - if ((s = socket(type, SOCK_STREAM, 0)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - c->fd = s; - if (type == AF_INET) { - if (redisSetReuseAddr(c) == REDIS_ERR) { - return REDIS_ERR; - } - } - return REDIS_OK; -} - -static int redisSetBlocking(redisContext *c, int blocking) { - int flags; - - /* Set the socket nonblocking. - * Note that fcntl(2) for F_GETFL and F_SETFL can't be - * interrupted by a signal. */ - if ((flags = fcntl(c->fd, F_GETFL)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); - redisContextCloseFd(c); - return REDIS_ERR; - } - - if (blocking) - flags &= ~O_NONBLOCK; - else - flags |= O_NONBLOCK; - - if (fcntl(c->fd, F_SETFL, flags) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); - redisContextCloseFd(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -int redisKeepAlive(redisContext *c, int interval) { - int val = 1; - int fd = c->fd; - - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - - val = interval; - -#ifdef _OSX - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } -#else -#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) - val = interval; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - - val = interval/3; - if (val == 0) val = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - - val = 3; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } -#endif -#endif - - return REDIS_OK; -} - -static int redisSetTcpNoDelay(redisContext *c) { - int yes = 1; - if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); - redisContextCloseFd(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) - -static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) { - struct pollfd wfd[1]; - long msec; - - msec = -1; - wfd[0].fd = c->fd; - wfd[0].events = POLLOUT; - - /* Only use timeout when not NULL. */ - if (timeout != NULL) { - if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - redisContextCloseFd(c); - return REDIS_ERR; - } - - msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); - - if (msec < 0 || msec > INT_MAX) { - msec = INT_MAX; - } - } - - if (errno == EINPROGRESS) { - int res; - - if ((res = poll(wfd, 1, msec)) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); - redisContextCloseFd(c); - return REDIS_ERR; - } else if (res == 0) { - errno = ETIMEDOUT; - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); - return REDIS_ERR; - } - - if (redisCheckSocketError(c) != REDIS_OK) - return REDIS_ERR; - - return REDIS_OK; - } - - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); - return REDIS_ERR; -} - -int redisCheckSocketError(redisContext *c) { - int err = 0; - socklen_t errlen = sizeof(err); - - if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); - return REDIS_ERR; - } - - if (err) { - errno = err; - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - - return REDIS_OK; -} - -int redisContextSetTimeout(redisContext *c, const struct timeval tv) { - if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); - return REDIS_ERR; - } - if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); - return REDIS_ERR; - } - return REDIS_OK; -} - -static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout, - const char *source_addr) { - int s, rv, n; - char _port[6]; /* strlen("65535"); */ - struct addrinfo hints, *servinfo, *bservinfo, *p, *b; - int blocking = (c->flags & REDIS_BLOCK); - int reuseaddr = (c->flags & REDIS_REUSEADDR); - int reuses = 0; - - c->connection_type = REDIS_CONN_TCP; - c->tcp.port = port; - - /* We need to take possession of the passed parameters - * to make them reusable for a reconnect. - * We also carefully check we don't free data we already own, - * as in the case of the reconnect method. - * - * This is a bit ugly, but atleast it works and doesn't leak memory. - **/ - if (c->tcp.host != addr) { - if (c->tcp.host) - free(c->tcp.host); - - c->tcp.host = strdup(addr); - } - - if (timeout) { - if (c->timeout != timeout) { - if (c->timeout == NULL) - c->timeout = malloc(sizeof(struct timeval)); - - memcpy(c->timeout, timeout, sizeof(struct timeval)); - } - } else { - if (c->timeout) - free(c->timeout); - c->timeout = NULL; - } - - if (source_addr == NULL) { - free(c->tcp.source_addr); - c->tcp.source_addr = NULL; - } else if (c->tcp.source_addr != source_addr) { - free(c->tcp.source_addr); - c->tcp.source_addr = strdup(source_addr); - } - - snprintf(_port, 6, "%d", port); - memset(&hints,0,sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - /* Try with IPv6 if no IPv4 address was found. We do it in this order since - * in a Redis client you can't afford to test if you have IPv6 connectivity - * as this would add latency to every connect. Otherwise a more sensible - * route could be: Use IPv6 if both addresses are available and there is IPv6 - * connectivity. */ - if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { - hints.ai_family = AF_INET6; - if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { - __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); - return REDIS_ERR; - } - } - for (p = servinfo; p != NULL; p = p->ai_next) { -addrretry: - if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) - continue; - - c->fd = s; - if (redisSetBlocking(c,0) != REDIS_OK) - goto error; - if (c->tcp.source_addr) { - int bound = 0; - /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ - if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { - char buf[128]; - snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); - __redisSetError(c,REDIS_ERR_OTHER,buf); - goto error; - } - - if (reuseaddr) { - n = 1; - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, - sizeof(n)) < 0) { - goto error; - } - } - - for (b = bservinfo; b != NULL; b = b->ai_next) { - if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { - bound = 1; - break; - } - } - freeaddrinfo(bservinfo); - if (!bound) { - char buf[128]; - snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); - __redisSetError(c,REDIS_ERR_OTHER,buf); - goto error; - } - } - if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { - if (errno == EHOSTUNREACH) { - redisContextCloseFd(c); - continue; - } else if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ - } else if (errno == EADDRNOTAVAIL && reuseaddr) { - if (++reuses >= REDIS_CONNECT_RETRIES) { - goto error; - } else { - goto addrretry; - } - } else { - if (redisContextWaitReady(c,c->timeout) != REDIS_OK) - goto error; - } - } - if (blocking && redisSetBlocking(c,1) != REDIS_OK) - goto error; - if (redisSetTcpNoDelay(c) != REDIS_OK) - goto error; - - c->flags |= REDIS_CONNECTED; - rv = REDIS_OK; - goto end; - } - if (p == NULL) { - char buf[128]; - snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); - __redisSetError(c,REDIS_ERR_OTHER,buf); - goto error; - } - -error: - rv = REDIS_ERR; -end: - freeaddrinfo(servinfo); - return rv; // Need to return REDIS_OK if alright -} - -int redisContextConnectTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout) { - return _redisContextConnectTcp(c, addr, port, timeout, NULL); -} - -int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout, - const char *source_addr) { - return _redisContextConnectTcp(c, addr, port, timeout, source_addr); -} - -int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { - int blocking = (c->flags & REDIS_BLOCK); - struct sockaddr_un sa; - - if (redisCreateSocket(c,AF_LOCAL) < 0) - return REDIS_ERR; - if (redisSetBlocking(c,0) != REDIS_OK) - return REDIS_ERR; - - c->connection_type = REDIS_CONN_UNIX; - if (c->unix_sock.path != path) - c->unix_sock.path = strdup(path); - - if (timeout) { - if (c->timeout != timeout) { - if (c->timeout == NULL) - c->timeout = malloc(sizeof(struct timeval)); - - memcpy(c->timeout, timeout, sizeof(struct timeval)); - } - } else { - if (c->timeout) - free(c->timeout); - c->timeout = NULL; - } - - sa.sun_family = AF_LOCAL; - strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); - if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { - if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ - } else { - if (redisContextWaitReady(c,c->timeout) != REDIS_OK) - return REDIS_ERR; - } - } - - /* Reset socket to be blocking after connect(2). */ - if (blocking && redisSetBlocking(c,1) != REDIS_OK) - return REDIS_ERR; - - c->flags |= REDIS_CONNECTED; - return REDIS_OK; -} diff --git a/ext/hiredis-vip-0.3.0/read.c b/ext/hiredis-vip-0.3.0/read.c deleted file mode 100644 index df1a467a9..000000000 --- a/ext/hiredis-vip-0.3.0/read.c +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - - -#include "fmacros.h" -#include -#include -#ifndef _MSC_VER -#include -#endif -#include -#include -#include - -#include "read.h" -#include "sds.h" - -static void __redisReaderSetError(redisReader *r, int type, const char *str) { - size_t len; - - if (r->reply != NULL && r->fn && r->fn->freeObject) { - r->fn->freeObject(r->reply); - r->reply = NULL; - } - - /* Clear input buffer on errors. */ - if (r->buf != NULL) { - sdsfree(r->buf); - r->buf = NULL; - r->pos = r->len = 0; - } - - /* Reset task stack. */ - r->ridx = -1; - - /* Set error. */ - r->err = type; - len = strlen(str); - len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); - memcpy(r->errstr,str,len); - r->errstr[len] = '\0'; -} - -static size_t chrtos(char *buf, size_t size, char byte) { - size_t len = 0; - - switch(byte) { - case '\\': - case '"': - len = snprintf(buf,size,"\"\\%c\"",byte); - break; - case '\n': len = snprintf(buf,size,"\"\\n\""); break; - case '\r': len = snprintf(buf,size,"\"\\r\""); break; - case '\t': len = snprintf(buf,size,"\"\\t\""); break; - case '\a': len = snprintf(buf,size,"\"\\a\""); break; - case '\b': len = snprintf(buf,size,"\"\\b\""); break; - default: - if (isprint(byte)) - len = snprintf(buf,size,"\"%c\"",byte); - else - len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); - break; - } - - return len; -} - -static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { - char cbuf[8], sbuf[128]; - - chrtos(cbuf,sizeof(cbuf),byte); - snprintf(sbuf,sizeof(sbuf), - "Protocol error, got %s as reply type byte", cbuf); - __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); -} - -static void __redisReaderSetErrorOOM(redisReader *r) { - __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); -} - -static char *readBytes(redisReader *r, unsigned int bytes) { - char *p; - if (r->len-r->pos >= bytes) { - p = r->buf+r->pos; - r->pos += bytes; - return p; - } - return NULL; -} - -/* Find pointer to \r\n. */ -static char *seekNewline(char *s, size_t len) { - int pos = 0; - int _len = len-1; - - /* Position should be < len-1 because the character at "pos" should be - * followed by a \n. Note that strchr cannot be used because it doesn't - * allow to search a limited length and the buffer that is being searched - * might not have a trailing NULL character. */ - while (pos < _len) { - while(pos < _len && s[pos] != '\r') pos++; - if (s[pos] != '\r') { - /* Not found. */ - return NULL; - } else { - if (s[pos+1] == '\n') { - /* Found. */ - return s+pos; - } else { - /* Continue searching. */ - pos++; - } - } - } - return NULL; -} - -/* Read a long long value starting at *s, under the assumption that it will be - * terminated by \r\n. Ambiguously returns -1 for unexpected input. */ -static long long readLongLong(char *s) { - long long v = 0; - int dec, mult = 1; - char c; - - if (*s == '-') { - mult = -1; - s++; - } else if (*s == '+') { - mult = 1; - s++; - } - - while ((c = *(s++)) != '\r') { - dec = c - '0'; - if (dec >= 0 && dec < 10) { - v *= 10; - v += dec; - } else { - /* Should not happen... */ - return -1; - } - } - - return mult*v; -} - -static char *readLine(redisReader *r, int *_len) { - char *p, *s; - int len; - - p = r->buf+r->pos; - s = seekNewline(p,(r->len-r->pos)); - if (s != NULL) { - len = s-(r->buf+r->pos); - r->pos += len+2; /* skip \r\n */ - if (_len) *_len = len; - return p; - } - return NULL; -} - -static void moveToNextTask(redisReader *r) { - redisReadTask *cur, *prv; - while (r->ridx >= 0) { - /* Return a.s.a.p. when the stack is now empty. */ - if (r->ridx == 0) { - r->ridx--; - return; - } - - cur = &(r->rstack[r->ridx]); - prv = &(r->rstack[r->ridx-1]); - assert(prv->type == REDIS_REPLY_ARRAY); - if (cur->idx == prv->elements-1) { - r->ridx--; - } else { - /* Reset the type because the next item can be anything */ - assert(cur->idx < prv->elements); - cur->type = -1; - cur->elements = -1; - cur->idx++; - return; - } - } -} - -static int processLineItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj; - char *p; - int len; - - if ((p = readLine(r,&len)) != NULL) { - if (cur->type == REDIS_REPLY_INTEGER) { - if (r->fn && r->fn->createInteger) - obj = r->fn->createInteger(cur,readLongLong(p)); - else - obj = (void*)REDIS_REPLY_INTEGER; - } else { - /* Type will be error or status. */ - if (r->fn && r->fn->createString) - obj = r->fn->createString(cur,p,len); - else - obj = (void*)(size_t)(cur->type); - } - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - /* Set reply if this is the root object. */ - if (r->ridx == 0) r->reply = obj; - moveToNextTask(r); - return REDIS_OK; - } - - return REDIS_ERR; -} - -static int processBulkItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj = NULL; - char *p, *s; - long len; - unsigned long bytelen; - int success = 0; - - p = r->buf+r->pos; - s = seekNewline(p,r->len-r->pos); - if (s != NULL) { - p = r->buf+r->pos; - bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ - len = readLongLong(p); - - if (len < 0) { - /* The nil object can always be created. */ - if (r->fn && r->fn->createNil) - obj = r->fn->createNil(cur); - else - obj = (void*)REDIS_REPLY_NIL; - success = 1; - } else { - /* Only continue when the buffer contains the entire bulk item. */ - bytelen += len+2; /* include \r\n */ - if (r->pos+bytelen <= r->len) { - if (r->fn && r->fn->createString) - obj = r->fn->createString(cur,s+2,len); - else - obj = (void*)REDIS_REPLY_STRING; - success = 1; - } - } - - /* Proceed when obj was created. */ - if (success) { - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - r->pos += bytelen; - - /* Set reply if this is the root object. */ - if (r->ridx == 0) r->reply = obj; - moveToNextTask(r); - return REDIS_OK; - } - } - - return REDIS_ERR; -} - -static int processMultiBulkItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj; - char *p; - long elements; - int root = 0; - - /* Set error for nested multi bulks with depth > 7 */ - if (r->ridx == 8) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "No support for nested multi bulk replies with depth > 7"); - return REDIS_ERR; - } - - if ((p = readLine(r,NULL)) != NULL) { - elements = readLongLong(p); - root = (r->ridx == 0); - - if (elements == -1) { - if (r->fn && r->fn->createNil) - obj = r->fn->createNil(cur); - else - obj = (void*)REDIS_REPLY_NIL; - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - moveToNextTask(r); - } else { - if (r->fn && r->fn->createArray) - obj = r->fn->createArray(cur,elements); - else - obj = (void*)REDIS_REPLY_ARRAY; - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - /* Modify task stack when there are more than 0 elements. */ - if (elements > 0) { - cur->elements = elements; - cur->obj = obj; - r->ridx++; - r->rstack[r->ridx].type = -1; - r->rstack[r->ridx].elements = -1; - r->rstack[r->ridx].idx = 0; - r->rstack[r->ridx].obj = NULL; - r->rstack[r->ridx].parent = cur; - r->rstack[r->ridx].privdata = r->privdata; - } else { - moveToNextTask(r); - } - } - - /* Set reply if this is the root object. */ - if (root) r->reply = obj; - return REDIS_OK; - } - - return REDIS_ERR; -} - -static int processItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - char *p; - - /* check if we need to read type */ - if (cur->type < 0) { - if ((p = readBytes(r,1)) != NULL) { - switch (p[0]) { - case '-': - cur->type = REDIS_REPLY_ERROR; - break; - case '+': - cur->type = REDIS_REPLY_STATUS; - break; - case ':': - cur->type = REDIS_REPLY_INTEGER; - break; - case '$': - cur->type = REDIS_REPLY_STRING; - break; - case '*': - cur->type = REDIS_REPLY_ARRAY; - break; - default: - __redisReaderSetErrorProtocolByte(r,*p); - return REDIS_ERR; - } - } else { - /* could not consume 1 byte */ - return REDIS_ERR; - } - } - - /* process typed item */ - switch(cur->type) { - case REDIS_REPLY_ERROR: - case REDIS_REPLY_STATUS: - case REDIS_REPLY_INTEGER: - return processLineItem(r); - case REDIS_REPLY_STRING: - return processBulkItem(r); - case REDIS_REPLY_ARRAY: - return processMultiBulkItem(r); - default: - assert(NULL); - return REDIS_ERR; /* Avoid warning. */ - } -} - -redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { - redisReader *r; - - r = calloc(sizeof(redisReader),1); - if (r == NULL) - return NULL; - - r->err = 0; - r->errstr[0] = '\0'; - r->fn = fn; - r->buf = sdsempty(); - r->maxbuf = REDIS_READER_MAX_BUF; - if (r->buf == NULL) { - free(r); - return NULL; - } - - r->ridx = -1; - return r; -} - -void redisReaderFree(redisReader *r) { - if (r->reply != NULL && r->fn && r->fn->freeObject) - r->fn->freeObject(r->reply); - if (r->buf != NULL) - sdsfree(r->buf); - free(r); -} - -int redisReaderFeed(redisReader *r, const char *buf, size_t len) { - sds newbuf; - - /* Return early when this reader is in an erroneous state. */ - if (r->err) - return REDIS_ERR; - - /* Copy the provided buffer. */ - if (buf != NULL && len >= 1) { - /* Destroy internal buffer when it is empty and is quite large. */ - if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { - sdsfree(r->buf); - r->buf = sdsempty(); - r->pos = 0; - - /* r->buf should not be NULL since we just free'd a larger one. */ - assert(r->buf != NULL); - } - - newbuf = sdscatlen(r->buf,buf,len); - if (newbuf == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - r->buf = newbuf; - r->len = sdslen(r->buf); - } - - return REDIS_OK; -} - -int redisReaderGetReply(redisReader *r, void **reply) { - /* Default target pointer to NULL. */ - if (reply != NULL) - *reply = NULL; - - /* Return early when this reader is in an erroneous state. */ - if (r->err) - return REDIS_ERR; - - /* When the buffer is empty, there will never be a reply. */ - if (r->len == 0) - return REDIS_OK; - - /* Set first item to process when the stack is empty. */ - if (r->ridx == -1) { - r->rstack[0].type = -1; - r->rstack[0].elements = -1; - r->rstack[0].idx = -1; - r->rstack[0].obj = NULL; - r->rstack[0].parent = NULL; - r->rstack[0].privdata = r->privdata; - r->ridx = 0; - } - - /* Process items in reply. */ - while (r->ridx >= 0) - if (processItem(r) != REDIS_OK) - break; - - /* Return ASAP when an error occurred. */ - if (r->err) - return REDIS_ERR; - - /* Discard part of the buffer when we've consumed at least 1k, to avoid - * doing unnecessary calls to memmove() in sds.c. */ - if (r->pos >= 1024) { - sdsrange(r->buf,r->pos,-1); - r->pos = 0; - r->len = sdslen(r->buf); - } - - /* Emit a reply when there is one. */ - if (r->ridx == -1) { - if (reply != NULL) - *reply = r->reply; - r->reply = NULL; - } - return REDIS_OK; -} diff --git a/ext/hiredis-vip-0.3.0/sds.c b/ext/hiredis-vip-0.3.0/sds.c deleted file mode 100644 index 5d55b7792..000000000 --- a/ext/hiredis-vip-0.3.0/sds.c +++ /dev/null @@ -1,1095 +0,0 @@ -/* SDS (Simple Dynamic Strings), A C dynamic strings library. - * - * Copyright (c) 2006-2014, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -#include "sds.h" - -/* Create a new sds string with the content specified by the 'init' pointer - * and 'initlen'. - * If NULL is used for 'init' the string is initialized with zero bytes. - * - * The string is always null-termined (all the sds strings are, always) so - * even if you create an sds string with: - * - * mystring = sdsnewlen("abc",3"); - * - * You can print the string with printf() as there is an implicit \0 at the - * end of the string. However the string is binary safe and can contain - * \0 characters in the middle, as the length is stored in the sds header. */ -sds sdsnewlen(const void *init, size_t initlen) { - struct sdshdr *sh; - - if (init) { - sh = malloc(sizeof *sh+initlen+1); - } else { - sh = calloc(sizeof *sh+initlen+1,1); - } - if (sh == NULL) return NULL; - sh->len = initlen; - sh->free = 0; - if (initlen && init) - memcpy(sh->buf, init, initlen); - sh->buf[initlen] = '\0'; - return (char*)sh->buf; -} - -/* Create an empty (zero length) sds string. Even in this case the string - * always has an implicit null term. */ -sds sdsempty(void) { - return sdsnewlen("",0); -} - -/* Create a new sds string starting from a null termined C string. */ -sds sdsnew(const char *init) { - size_t initlen = (init == NULL) ? 0 : strlen(init); - return sdsnewlen(init, initlen); -} - -/* Duplicate an sds string. */ -sds sdsdup(const sds s) { - return sdsnewlen(s, sdslen(s)); -} - -/* Free an sds string. No operation is performed if 's' is NULL. */ -void sdsfree(sds s) { - if (s == NULL) return; - free(s-sizeof(struct sdshdr)); -} - -/* Set the sds string length to the length as obtained with strlen(), so - * considering as content only up to the first null term character. - * - * This function is useful when the sds string is hacked manually in some - * way, like in the following example: - * - * s = sdsnew("foobar"); - * s[2] = '\0'; - * sdsupdatelen(s); - * printf("%d\n", sdslen(s)); - * - * The output will be "2", but if we comment out the call to sdsupdatelen() - * the output will be "6" as the string was modified but the logical length - * remains 6 bytes. */ -void sdsupdatelen(sds s) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - int reallen = strlen(s); - sh->free += (sh->len-reallen); - sh->len = reallen; -} - -/* Modify an sds string on-place to make it empty (zero length). - * However all the existing buffer is not discarded but set as free space - * so that next append operations will not require allocations up to the - * number of bytes previously available. */ -void sdsclear(sds s) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - sh->free += sh->len; - sh->len = 0; - sh->buf[0] = '\0'; -} - -/* Enlarge the free space at the end of the sds string so that the caller - * is sure that after calling this function can overwrite up to addlen - * bytes after the end of the string, plus one more byte for nul term. - * - * Note: this does not change the *length* of the sds string as returned - * by sdslen(), but only the free buffer space we have. */ -sds sdsMakeRoomFor(sds s, size_t addlen) { - struct sdshdr *sh, *newsh; - size_t free = sdsavail(s); - size_t len, newlen; - - if (free >= addlen) return s; - len = sdslen(s); - sh = (void*) (s-sizeof *sh); - newlen = (len+addlen); - if (newlen < SDS_MAX_PREALLOC) - newlen *= 2; - else - newlen += SDS_MAX_PREALLOC; - newsh = realloc(sh, sizeof *newsh+newlen+1); - if (newsh == NULL) return NULL; - - newsh->free = newlen - len; - return newsh->buf; -} - -/* Reallocate the sds string so that it has no free space at the end. The - * contained string remains not altered, but next concatenation operations - * will require a reallocation. - * - * After the call, the passed sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdsRemoveFreeSpace(sds s) { - struct sdshdr *sh; - - sh = (void*) (s-sizeof *sh); - sh = realloc(sh, sizeof *sh+sh->len+1); - sh->free = 0; - return sh->buf; -} - -/* Return the total size of the allocation of the specifed sds string, - * including: - * 1) The sds header before the pointer. - * 2) The string. - * 3) The free buffer at the end if any. - * 4) The implicit null term. - */ -size_t sdsAllocSize(sds s) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - - return sizeof(*sh)+sh->len+sh->free+1; -} - -/* Increment the sds length and decrements the left free space at the - * end of the string according to 'incr'. Also set the null term - * in the new end of the string. - * - * This function is used in order to fix the string length after the - * user calls sdsMakeRoomFor(), writes something after the end of - * the current string, and finally needs to set the new length. - * - * Note: it is possible to use a negative increment in order to - * right-trim the string. - * - * Usage example: - * - * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the - * following schema, to cat bytes coming from the kernel to the end of an - * sds string without copying into an intermediate buffer: - * - * oldlen = sdslen(s); - * s = sdsMakeRoomFor(s, BUFFER_SIZE); - * nread = read(fd, s+oldlen, BUFFER_SIZE); - * ... check for nread <= 0 and handle it ... - * sdsIncrLen(s, nread); - */ -void sdsIncrLen(sds s, int incr) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - - assert(sh->free >= incr); - sh->len += incr; - sh->free -= incr; - assert(sh->free >= 0); - s[sh->len] = '\0'; -} - -/* Grow the sds to have the specified length. Bytes that were not part of - * the original length of the sds will be set to zero. - * - * if the specified length is smaller than the current length, no operation - * is performed. */ -sds sdsgrowzero(sds s, size_t len) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - size_t totlen, curlen = sh->len; - - if (len <= curlen) return s; - s = sdsMakeRoomFor(s,len-curlen); - if (s == NULL) return NULL; - - /* Make sure added region doesn't contain garbage */ - sh = (void*)(s-sizeof *sh); - memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ - totlen = sh->len+sh->free; - sh->len = len; - sh->free = totlen-sh->len; - return s; -} - -/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the - * end of the specified sds string 's'. - * - * After the call, the passed sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdscatlen(sds s, const void *t, size_t len) { - struct sdshdr *sh; - size_t curlen = sdslen(s); - - s = sdsMakeRoomFor(s,len); - if (s == NULL) return NULL; - sh = (void*) (s-sizeof *sh); - memcpy(s+curlen, t, len); - sh->len = curlen+len; - sh->free = sh->free-len; - s[curlen+len] = '\0'; - return s; -} - -/* Append the specified null termianted C string to the sds string 's'. - * - * After the call, the passed sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdscat(sds s, const char *t) { - return sdscatlen(s, t, strlen(t)); -} - -/* Append the specified sds 't' to the existing sds 's'. - * - * After the call, the modified sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdscatsds(sds s, const sds t) { - return sdscatlen(s, t, sdslen(t)); -} - -/* Destructively modify the sds string 's' to hold the specified binary - * safe string pointed by 't' of length 'len' bytes. */ -sds sdscpylen(sds s, const char *t, size_t len) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - size_t totlen = sh->free+sh->len; - - if (totlen < len) { - s = sdsMakeRoomFor(s,len-sh->len); - if (s == NULL) return NULL; - sh = (void*) (s-sizeof *sh); - totlen = sh->free+sh->len; - } - memcpy(s, t, len); - s[len] = '\0'; - sh->len = len; - sh->free = totlen-len; - return s; -} - -/* Like sdscpylen() but 't' must be a null-termined string so that the length - * of the string is obtained with strlen(). */ -sds sdscpy(sds s, const char *t) { - return sdscpylen(s, t, strlen(t)); -} - -/* Helper for sdscatlonglong() doing the actual number -> string - * conversion. 's' must point to a string with room for at least - * SDS_LLSTR_SIZE bytes. - * - * The function returns the lenght of the null-terminated string - * representation stored at 's'. */ -#define SDS_LLSTR_SIZE 21 -int sdsll2str(char *s, long long value) { - char *p, aux; - unsigned long long v; - size_t l; - - /* Generate the string representation, this method produces - * an reversed string. */ - v = (value < 0) ? -value : value; - p = s; - do { - *p++ = '0'+(v%10); - v /= 10; - } while(v); - if (value < 0) *p++ = '-'; - - /* Compute length and add null term. */ - l = p-s; - *p = '\0'; - - /* Reverse the string. */ - p--; - while(s < p) { - aux = *s; - *s = *p; - *p = aux; - s++; - p--; - } - return l; -} - -/* Identical sdsll2str(), but for unsigned long long type. */ -int sdsull2str(char *s, unsigned long long v) { - char *p, aux; - size_t l; - - /* Generate the string representation, this method produces - * an reversed string. */ - p = s; - do { - *p++ = '0'+(v%10); - v /= 10; - } while(v); - - /* Compute length and add null term. */ - l = p-s; - *p = '\0'; - - /* Reverse the string. */ - p--; - while(s < p) { - aux = *s; - *s = *p; - *p = aux; - s++; - p--; - } - return l; -} - -/* Like sdscatpritf() but gets va_list instead of being variadic. */ -sds sdscatvprintf(sds s, const char *fmt, va_list ap) { - va_list cpy; - char *buf, *t; - size_t buflen = 16; - - while(1) { - buf = malloc(buflen); - if (buf == NULL) return NULL; - buf[buflen-2] = '\0'; - va_copy(cpy,ap); - vsnprintf(buf, buflen, fmt, cpy); - if (buf[buflen-2] != '\0') { - free(buf); - buflen *= 2; - continue; - } - break; - } - t = sdscat(s, buf); - free(buf); - return t; -} - -/* Append to the sds string 's' a string obtained using printf-alike format - * specifier. - * - * After the call, the modified sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. - * - * Example: - * - * s = sdsnew("Sum is: "); - * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b); - * - * Often you need to create a string from scratch with the printf-alike - * format. When this is the need, just use sdsempty() as the target string: - * - * s = sdscatprintf(sdsempty(), "... your format ...", args); - */ -sds sdscatprintf(sds s, const char *fmt, ...) { - va_list ap; - char *t; - va_start(ap, fmt); - t = sdscatvprintf(s,fmt,ap); - va_end(ap); - return t; -} - -/* This function is similar to sdscatprintf, but much faster as it does - * not rely on sprintf() family functions implemented by the libc that - * are often very slow. Moreover directly handling the sds string as - * new data is concatenated provides a performance improvement. - * - * However this function only handles an incompatible subset of printf-alike - * format specifiers: - * - * %s - C String - * %S - SDS string - * %i - signed int - * %I - 64 bit signed integer (long long, int64_t) - * %u - unsigned int - * %U - 64 bit unsigned integer (unsigned long long, uint64_t) - * %T - A size_t variable. - * %% - Verbatim "%" character. - */ -sds sdscatfmt(sds s, char const *fmt, ...) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); - size_t initlen = sdslen(s); - const char *f = fmt; - int i; - va_list ap; - - va_start(ap,fmt); - f = fmt; /* Next format specifier byte to process. */ - i = initlen; /* Position of the next byte to write to dest str. */ - while(*f) { - char next, *str; - int l; - long long num; - unsigned long long unum; - - /* Make sure there is always space for at least 1 char. */ - if (sh->free == 0) { - s = sdsMakeRoomFor(s,1); - sh = (void*) (s-(sizeof(struct sdshdr))); - } - - switch(*f) { - case '%': - next = *(f+1); - f++; - switch(next) { - case 's': - case 'S': - str = va_arg(ap,char*); - l = (next == 's') ? strlen(str) : sdslen(str); - if (sh->free < l) { - s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); - } - memcpy(s+i,str,l); - sh->len += l; - sh->free -= l; - i += l; - break; - case 'i': - case 'I': - if (next == 'i') - num = va_arg(ap,int); - else - num = va_arg(ap,long long); - { - char buf[SDS_LLSTR_SIZE]; - l = sdsll2str(buf,num); - if (sh->free < l) { - s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); - } - memcpy(s+i,buf,l); - sh->len += l; - sh->free -= l; - i += l; - } - break; - case 'u': - case 'U': - case 'T': - if (next == 'u') - unum = va_arg(ap,unsigned int); - else if(next == 'U') - unum = va_arg(ap,unsigned long long); - else - unum = (unsigned long long)va_arg(ap,size_t); - { - char buf[SDS_LLSTR_SIZE]; - l = sdsull2str(buf,unum); - if (sh->free < l) { - s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); - } - memcpy(s+i,buf,l); - sh->len += l; - sh->free -= l; - i += l; - } - break; - default: /* Handle %% and generally %. */ - s[i++] = next; - sh->len += 1; - sh->free -= 1; - break; - } - break; - default: - s[i++] = *f; - sh->len += 1; - sh->free -= 1; - break; - } - f++; - } - va_end(ap); - - /* Add null-term */ - s[i] = '\0'; - return s; -} - - -/* Remove the part of the string from left and from right composed just of - * contiguous characters found in 'cset', that is a null terminted C string. - * - * After the call, the modified sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. - * - * Example: - * - * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); - * s = sdstrim(s,"A. :"); - * printf("%s\n", s); - * - * Output will be just "Hello World". - */ -void sdstrim(sds s, const char *cset) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - char *start, *end, *sp, *ep; - size_t len; - - sp = start = s; - ep = end = s+sdslen(s)-1; - while(sp <= end && strchr(cset, *sp)) sp++; - while(ep > start && strchr(cset, *ep)) ep--; - len = (sp > ep) ? 0 : ((ep-sp)+1); - if (sh->buf != sp) memmove(sh->buf, sp, len); - sh->buf[len] = '\0'; - sh->free = sh->free+(sh->len-len); - sh->len = len; -} - -/* Turn the string into a smaller (or equal) string containing only the - * substring specified by the 'start' and 'end' indexes. - * - * start and end can be negative, where -1 means the last character of the - * string, -2 the penultimate character, and so forth. - * - * The interval is inclusive, so the start and end characters will be part - * of the resulting string. - * - * The string is modified in-place. - * - * Example: - * - * s = sdsnew("Hello World"); - * sdsrange(s,1,-1); => "ello World" - */ -void sdsrange(sds s, int start, int end) { - struct sdshdr *sh = (void*) (s-sizeof *sh); - size_t newlen, len = sdslen(s); - - if (len == 0) return; - if (start < 0) { - start = len+start; - if (start < 0) start = 0; - } - if (end < 0) { - end = len+end; - if (end < 0) end = 0; - } - newlen = (start > end) ? 0 : (end-start)+1; - if (newlen != 0) { - if (start >= (signed)len) { - newlen = 0; - } else if (end >= (signed)len) { - end = len-1; - newlen = (start > end) ? 0 : (end-start)+1; - } - } else { - start = 0; - } - if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); - sh->buf[newlen] = 0; - sh->free = sh->free+(sh->len-newlen); - sh->len = newlen; -} - -/* Apply tolower() to every character of the sds string 's'. */ -void sdstolower(sds s) { - int len = sdslen(s), j; - - for (j = 0; j < len; j++) s[j] = tolower(s[j]); -} - -/* Apply toupper() to every character of the sds string 's'. */ -void sdstoupper(sds s) { - int len = sdslen(s), j; - - for (j = 0; j < len; j++) s[j] = toupper(s[j]); -} - -/* Compare two sds strings s1 and s2 with memcmp(). - * - * Return value: - * - * 1 if s1 > s2. - * -1 if s1 < s2. - * 0 if s1 and s2 are exactly the same binary string. - * - * If two strings share exactly the same prefix, but one of the two has - * additional characters, the longer string is considered to be greater than - * the smaller one. */ -int sdscmp(const sds s1, const sds s2) { - size_t l1, l2, minlen; - int cmp; - - l1 = sdslen(s1); - l2 = sdslen(s2); - minlen = (l1 < l2) ? l1 : l2; - cmp = memcmp(s1,s2,minlen); - if (cmp == 0) return l1-l2; - return cmp; -} - -/* Split 's' with separator in 'sep'. An array - * of sds strings is returned. *count will be set - * by reference to the number of tokens returned. - * - * On out of memory, zero length string, zero length - * separator, NULL is returned. - * - * Note that 'sep' is able to split a string using - * a multi-character separator. For example - * sdssplit("foo_-_bar","_-_"); will return two - * elements "foo" and "bar". - * - * This version of the function is binary-safe but - * requires length arguments. sdssplit() is just the - * same function but for zero-terminated strings. - */ -sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { - int elements = 0, slots = 5, start = 0, j; - sds *tokens; - - if (seplen < 1 || len < 0) return NULL; - - tokens = malloc(sizeof(sds)*slots); - if (tokens == NULL) return NULL; - - if (len == 0) { - *count = 0; - return tokens; - } - for (j = 0; j < (len-(seplen-1)); j++) { - /* make sure there is room for the next element and the final one */ - if (slots < elements+2) { - sds *newtokens; - - slots *= 2; - newtokens = realloc(tokens,sizeof(sds)*slots); - if (newtokens == NULL) goto cleanup; - tokens = newtokens; - } - /* search the separator */ - if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { - tokens[elements] = sdsnewlen(s+start,j-start); - if (tokens[elements] == NULL) goto cleanup; - elements++; - start = j+seplen; - j = j+seplen-1; /* skip the separator */ - } - } - /* Add the final element. We are sure there is room in the tokens array. */ - tokens[elements] = sdsnewlen(s+start,len-start); - if (tokens[elements] == NULL) goto cleanup; - elements++; - *count = elements; - return tokens; - -cleanup: - { - int i; - for (i = 0; i < elements; i++) sdsfree(tokens[i]); - free(tokens); - *count = 0; - return NULL; - } -} - -/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ -void sdsfreesplitres(sds *tokens, int count) { - if (!tokens) return; - while(count--) - sdsfree(tokens[count]); - free(tokens); -} - -/* Create an sds string from a long long value. It is much faster than: - * - * sdscatprintf(sdsempty(),"%lld\n", value); - */ -sds sdsfromlonglong(long long value) { - char buf[32], *p; - unsigned long long v; - - v = (value < 0) ? -value : value; - p = buf+31; /* point to the last character */ - do { - *p-- = '0'+(v%10); - v /= 10; - } while(v); - if (value < 0) *p-- = '-'; - p++; - return sdsnewlen(p,32-(p-buf)); -} - -/* Append to the sds string "s" an escaped string representation where - * all the non-printable characters (tested with isprint()) are turned into - * escapes in the form "\n\r\a...." or "\x". - * - * After the call, the modified sds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -sds sdscatrepr(sds s, const char *p, size_t len) { - s = sdscatlen(s,"\"",1); - while(len--) { - switch(*p) { - case '\\': - case '"': - s = sdscatprintf(s,"\\%c",*p); - break; - case '\n': s = sdscatlen(s,"\\n",2); break; - case '\r': s = sdscatlen(s,"\\r",2); break; - case '\t': s = sdscatlen(s,"\\t",2); break; - case '\a': s = sdscatlen(s,"\\a",2); break; - case '\b': s = sdscatlen(s,"\\b",2); break; - default: - if (isprint(*p)) - s = sdscatprintf(s,"%c",*p); - else - s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); - break; - } - p++; - } - return sdscatlen(s,"\"",1); -} - -/* Helper function for sdssplitargs() that returns non zero if 'c' - * is a valid hex digit. */ -int is_hex_digit(char c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'F'); -} - -/* Helper function for sdssplitargs() that converts a hex digit into an - * integer from 0 to 15 */ -int hex_digit_to_int(char c) { - switch(c) { - case '0': return 0; - case '1': return 1; - case '2': return 2; - case '3': return 3; - case '4': return 4; - case '5': return 5; - case '6': return 6; - case '7': return 7; - case '8': return 8; - case '9': return 9; - case 'a': case 'A': return 10; - case 'b': case 'B': return 11; - case 'c': case 'C': return 12; - case 'd': case 'D': return 13; - case 'e': case 'E': return 14; - case 'f': case 'F': return 15; - default: return 0; - } -} - -/* Split a line into arguments, where every argument can be in the - * following programming-language REPL-alike form: - * - * foo bar "newline are supported\n" and "\xff\x00otherstuff" - * - * The number of arguments is stored into *argc, and an array - * of sds is returned. - * - * The caller should free the resulting array of sds strings with - * sdsfreesplitres(). - * - * Note that sdscatrepr() is able to convert back a string into - * a quoted string in the same format sdssplitargs() is able to parse. - * - * The function returns the allocated tokens on success, even when the - * input string is empty, or NULL if the input contains unbalanced - * quotes or closed quotes followed by non space characters - * as in: "foo"bar or "foo' - */ -sds *sdssplitargs(const char *line, int *argc) { - const char *p = line; - char *current = NULL; - char **vector = NULL; - - *argc = 0; - while(1) { - /* skip blanks */ - while(*p && isspace(*p)) p++; - if (*p) { - /* get a token */ - int inq=0; /* set to 1 if we are in "quotes" */ - int insq=0; /* set to 1 if we are in 'single quotes' */ - int done=0; - - if (current == NULL) current = sdsempty(); - while(!done) { - if (inq) { - if (*p == '\\' && *(p+1) == 'x' && - is_hex_digit(*(p+2)) && - is_hex_digit(*(p+3))) - { - unsigned char byte; - - byte = (hex_digit_to_int(*(p+2))*16)+ - hex_digit_to_int(*(p+3)); - current = sdscatlen(current,(char*)&byte,1); - p += 3; - } else if (*p == '\\' && *(p+1)) { - char c; - - p++; - switch(*p) { - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - case 'b': c = '\b'; break; - case 'a': c = '\a'; break; - default: c = *p; break; - } - current = sdscatlen(current,&c,1); - } else if (*p == '"') { - /* closing quote must be followed by a space or - * nothing at all. */ - if (*(p+1) && !isspace(*(p+1))) goto err; - done=1; - } else if (!*p) { - /* unterminated quotes */ - goto err; - } else { - current = sdscatlen(current,p,1); - } - } else if (insq) { - if (*p == '\\' && *(p+1) == '\'') { - p++; - current = sdscatlen(current,"'",1); - } else if (*p == '\'') { - /* closing quote must be followed by a space or - * nothing at all. */ - if (*(p+1) && !isspace(*(p+1))) goto err; - done=1; - } else if (!*p) { - /* unterminated quotes */ - goto err; - } else { - current = sdscatlen(current,p,1); - } - } else { - switch(*p) { - case ' ': - case '\n': - case '\r': - case '\t': - case '\0': - done=1; - break; - case '"': - inq=1; - break; - case '\'': - insq=1; - break; - default: - current = sdscatlen(current,p,1); - break; - } - } - if (*p) p++; - } - /* add the token to the vector */ - vector = realloc(vector,((*argc)+1)*sizeof(char*)); - vector[*argc] = current; - (*argc)++; - current = NULL; - } else { - /* Even on empty input string return something not NULL. */ - if (vector == NULL) vector = malloc(sizeof(void*)); - return vector; - } - } - -err: - while((*argc)--) - sdsfree(vector[*argc]); - free(vector); - if (current) sdsfree(current); - *argc = 0; - return NULL; -} - -/* Modify the string substituting all the occurrences of the set of - * characters specified in the 'from' string to the corresponding character - * in the 'to' array. - * - * For instance: sdsmapchars(mystring, "ho", "01", 2) - * will have the effect of turning the string "hello" into "0ell1". - * - * The function returns the sds string pointer, that is always the same - * as the input pointer since no resize is needed. */ -sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { - size_t j, i, l = sdslen(s); - - for (j = 0; j < l; j++) { - for (i = 0; i < setlen; i++) { - if (s[j] == from[i]) { - s[j] = to[i]; - break; - } - } - } - return s; -} - -/* Join an array of C strings using the specified separator (also a C string). - * Returns the result as an sds string. */ -sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) { - sds join = sdsempty(); - int j; - - for (j = 0; j < argc; j++) { - join = sdscat(join, argv[j]); - if (j != argc-1) join = sdscatlen(join,sep,seplen); - } - return join; -} - -/* Like sdsjoin, but joins an array of SDS strings. */ -sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { - sds join = sdsempty(); - int j; - - for (j = 0; j < argc; j++) { - join = sdscatsds(join, argv[j]); - if (j != argc-1) join = sdscatlen(join,sep,seplen); - } - return join; -} - -#ifdef SDS_TEST_MAIN -#include -#include "testhelp.h" - -int main(void) { - { - struct sdshdr *sh; - sds x = sdsnew("foo"), y; - - test_cond("Create a string and obtain the length", - sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) - - sdsfree(x); - x = sdsnewlen("foo",2); - test_cond("Create a string with specified length", - sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) - - x = sdscat(x,"bar"); - test_cond("Strings concatenation", - sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); - - x = sdscpy(x,"a"); - test_cond("sdscpy() against an originally longer string", - sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) - - x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); - test_cond("sdscpy() against an originally shorter string", - sdslen(x) == 33 && - memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) - - sdsfree(x); - x = sdscatprintf(sdsempty(),"%d",123); - test_cond("sdscatprintf() seems working in the base case", - sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) - - sdsfree(x); - x = sdsnew("xxciaoyyy"); - sdstrim(x,"xy"); - test_cond("sdstrim() correctly trims characters", - sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) - - y = sdsdup(x); - sdsrange(y,1,1); - test_cond("sdsrange(...,1,1)", - sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,1,-1); - test_cond("sdsrange(...,1,-1)", - sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,-2,-1); - test_cond("sdsrange(...,-2,-1)", - sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,2,1); - test_cond("sdsrange(...,2,1)", - sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,1,100); - test_cond("sdsrange(...,1,100)", - sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - sdsfree(y); - y = sdsdup(x); - sdsrange(y,100,100); - test_cond("sdsrange(...,100,100)", - sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("foo"); - y = sdsnew("foa"); - test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("bar"); - y = sdsnew("bar"); - test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("aar"); - y = sdsnew("bar"); - test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) - - sdsfree(y); - sdsfree(x); - x = sdsnewlen("\a\n\0foo\r",7); - y = sdscatrepr(sdsempty(),x,sdslen(x)); - test_cond("sdscatrepr(...data...)", - memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) - - { - int oldfree; - - sdsfree(x); - x = sdsnew("0"); - sh = (void*) (x-(sizeof(struct sdshdr))); - test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0); - x = sdsMakeRoomFor(x,1); - sh = (void*) (x-(sizeof(struct sdshdr))); - test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0); - oldfree = sh->free; - x[1] = '1'; - sdsIncrLen(x,1); - test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1'); - test_cond("sdsIncrLen() -- len", sh->len == 2); - test_cond("sdsIncrLen() -- free", sh->free == oldfree-1); - } - } - test_report() - return 0; -} -#endif diff --git a/ext/hiredis-vip-0.3.0/sds.h b/ext/hiredis-vip-0.3.0/sds.h deleted file mode 100644 index 19a2abd31..000000000 --- a/ext/hiredis-vip-0.3.0/sds.h +++ /dev/null @@ -1,105 +0,0 @@ -/* SDS (Simple Dynamic Strings), A C dynamic strings library. - * - * Copyright (c) 2006-2014, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __SDS_H -#define __SDS_H - -#define SDS_MAX_PREALLOC (1024*1024) - -#include -#include -#ifdef _MSC_VER -#include "win32.h" -#endif - -typedef char *sds; - -struct sdshdr { - int len; - int free; - char buf[]; -}; - -static inline size_t sdslen(const sds s) { - struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); - return sh->len; -} - -static inline size_t sdsavail(const sds s) { - struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); - return sh->free; -} - -sds sdsnewlen(const void *init, size_t initlen); -sds sdsnew(const char *init); -sds sdsempty(void); -size_t sdslen(const sds s); -sds sdsdup(const sds s); -void sdsfree(sds s); -size_t sdsavail(const sds s); -sds sdsgrowzero(sds s, size_t len); -sds sdscatlen(sds s, const void *t, size_t len); -sds sdscat(sds s, const char *t); -sds sdscatsds(sds s, const sds t); -sds sdscpylen(sds s, const char *t, size_t len); -sds sdscpy(sds s, const char *t); - -sds sdscatvprintf(sds s, const char *fmt, va_list ap); -#ifdef __GNUC__ -sds sdscatprintf(sds s, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else -sds sdscatprintf(sds s, const char *fmt, ...); -#endif - -sds sdscatfmt(sds s, char const *fmt, ...); -void sdstrim(sds s, const char *cset); -void sdsrange(sds s, int start, int end); -void sdsupdatelen(sds s); -void sdsclear(sds s); -int sdscmp(const sds s1, const sds s2); -sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); -void sdsfreesplitres(sds *tokens, int count); -void sdstolower(sds s); -void sdstoupper(sds s); -sds sdsfromlonglong(long long value); -sds sdscatrepr(sds s, const char *p, size_t len); -sds *sdssplitargs(const char *line, int *argc); -sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); -sds sdsjoin(char **argv, int argc, char *sep, size_t seplen); -sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); - -/* Low level functions exposed to the user API */ -sds sdsMakeRoomFor(sds s, size_t addlen); -void sdsIncrLen(sds s, int incr); -sds sdsRemoveFreeSpace(sds s); -size_t sdsAllocSize(sds s); - -#endif diff --git a/ext/hiredis-vip-0.3.0/test.c b/ext/hiredis-vip-0.3.0/test.c deleted file mode 100644 index 8fde55446..000000000 --- a/ext/hiredis-vip-0.3.0/test.c +++ /dev/null @@ -1,806 +0,0 @@ -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "hiredis.h" -#include "net.h" - -enum connection_type { - CONN_TCP, - CONN_UNIX, - CONN_FD -}; - -struct config { - enum connection_type type; - - struct { - const char *host; - int port; - struct timeval timeout; - } tcp; - - struct { - const char *path; - } unix; -}; - -/* The following lines make up our testing "framework" :) */ -static int tests = 0, fails = 0; -#define test(_s) { printf("#%02d ", ++tests); printf(_s); } -#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} - -static long long usec(void) { - struct timeval tv; - gettimeofday(&tv,NULL); - return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; -} - -/* The assert() calls below have side effects, so we need assert() - * even if we are compiling without asserts (-DNDEBUG). */ -#ifdef NDEBUG -#undef assert -#define assert(e) (void)(e) -#endif - -static redisContext *select_database(redisContext *c) { - redisReply *reply; - - /* Switch to DB 9 for testing, now that we know we can chat. */ - reply = redisCommand(c,"SELECT 9"); - assert(reply != NULL); - freeReplyObject(reply); - - /* Make sure the DB is emtpy */ - reply = redisCommand(c,"DBSIZE"); - assert(reply != NULL); - if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { - /* Awesome, DB 9 is empty and we can continue. */ - freeReplyObject(reply); - } else { - printf("Database #9 is not empty, test can not continue\n"); - exit(1); - } - - return c; -} - -static int disconnect(redisContext *c, int keep_fd) { - redisReply *reply; - - /* Make sure we're on DB 9. */ - reply = redisCommand(c,"SELECT 9"); - assert(reply != NULL); - freeReplyObject(reply); - reply = redisCommand(c,"FLUSHDB"); - assert(reply != NULL); - freeReplyObject(reply); - - /* Free the context as well, but keep the fd if requested. */ - if (keep_fd) - return redisFreeKeepFd(c); - redisFree(c); - return -1; -} - -static redisContext *connect(struct config config) { - redisContext *c = NULL; - - if (config.type == CONN_TCP) { - c = redisConnect(config.tcp.host, config.tcp.port); - } else if (config.type == CONN_UNIX) { - c = redisConnectUnix(config.unix.path); - } else if (config.type == CONN_FD) { - /* Create a dummy connection just to get an fd to inherit */ - redisContext *dummy_ctx = redisConnectUnix(config.unix.path); - if (dummy_ctx) { - int fd = disconnect(dummy_ctx, 1); - printf("Connecting to inherited fd %d\n", fd); - c = redisConnectFd(fd); - } - } else { - assert(NULL); - } - - if (c == NULL) { - printf("Connection error: can't allocate redis context\n"); - exit(1); - } else if (c->err) { - printf("Connection error: %s\n", c->errstr); - redisFree(c); - exit(1); - } - - return select_database(c); -} - -static void test_format_commands(void) { - char *cmd; - int len; - - test("Format command without interpolation: "); - len = redisFormatCommand(&cmd,"SET foo bar"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%s string interpolation: "); - len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%s and an empty string: "); - len = redisFormatCommand(&cmd,"SET %s %s","foo",""); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(0+2)); - free(cmd); - - test("Format command with an empty string in between proper interpolations: "); - len = redisFormatCommand(&cmd,"SET %s %s","","foo"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && - len == 4+4+(3+2)+4+(0+2)+4+(3+2)); - free(cmd); - - test("Format command with %%b string interpolation: "); - len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%b and an empty string: "); - len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(0+2)); - free(cmd); - - test("Format command with literal %%: "); - len = redisFormatCommand(&cmd,"SET %% %%"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && - len == 4+4+(3+2)+4+(1+2)+4+(1+2)); - free(cmd); - - /* Vararg width depends on the type. These tests make sure that the - * width is correctly determined using the format and subsequent varargs - * can correctly be interpolated. */ -#define INTEGER_WIDTH_TEST(fmt, type) do { \ - type value = 123; \ - test("Format command with printf-delegation (" #type "): "); \ - len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ - test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ - len == 4+5+(12+2)+4+(9+2)); \ - free(cmd); \ -} while(0) - -#define FLOAT_WIDTH_TEST(type) do { \ - type value = 123.0; \ - test("Format command with printf-delegation (" #type "): "); \ - len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ - test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ - len == 4+5+(12+2)+4+(9+2)); \ - free(cmd); \ -} while(0) - - INTEGER_WIDTH_TEST("d", int); - INTEGER_WIDTH_TEST("hhd", char); - INTEGER_WIDTH_TEST("hd", short); - INTEGER_WIDTH_TEST("ld", long); - INTEGER_WIDTH_TEST("lld", long long); - INTEGER_WIDTH_TEST("u", unsigned int); - INTEGER_WIDTH_TEST("hhu", unsigned char); - INTEGER_WIDTH_TEST("hu", unsigned short); - INTEGER_WIDTH_TEST("lu", unsigned long); - INTEGER_WIDTH_TEST("llu", unsigned long long); - FLOAT_WIDTH_TEST(float); - FLOAT_WIDTH_TEST(double); - - test("Format command with invalid printf format: "); - len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); - test_cond(len == -1); - - const char *argv[3]; - argv[0] = "SET"; - argv[1] = "foo\0xxx"; - argv[2] = "bar"; - size_t lens[3] = { 3, 7, 3 }; - int argc = 3; - - test("Format command by passing argc/argv without lengths: "); - len = redisFormatCommandArgv(&cmd,argc,argv,NULL); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command by passing argc/argv with lengths: "); - len = redisFormatCommandArgv(&cmd,argc,argv,lens); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(7+2)+4+(3+2)); - free(cmd); -} - -static void test_append_formatted_commands(struct config config) { - redisContext *c; - redisReply *reply; - char *cmd; - int len; - - c = connect(config); - - test("Append format command: "); - - len = redisFormatCommand(&cmd, "SET foo bar"); - - test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); - - assert(redisGetReply(c, (void*)&reply) == REDIS_OK); - - free(cmd); - freeReplyObject(reply); - - disconnect(c, 0); -} - -static void test_reply_reader(void) { - redisReader *reader; - void *reply; - int ret; - int i; - - test("Error handling in reply parser: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"@foo\r\n",6); - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); - redisReaderFree(reader); - - /* when the reply already contains multiple items, they must be free'd - * on an error. valgrind will bark when this doesn't happen. */ - test("Memory cleanup in reply parser: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"*2\r\n",4); - redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); - redisReaderFeed(reader,(char*)"@foo\r\n",6); - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); - redisReaderFree(reader); - - test("Set error on nested multi bulks with depth > 7: "); - reader = redisReaderCreate(); - - for (i = 0; i < 9; i++) { - redisReaderFeed(reader,(char*)"*1\r\n",4); - } - - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strncasecmp(reader->errstr,"No support for",14) == 0); - redisReaderFree(reader); - - test("Works with NULL functions for reply: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"+OK\r\n",5); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReaderFree(reader); - - test("Works when a single newline (\\r\\n) covers two calls to feed: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"+OK\r",4); - ret = redisReaderGetReply(reader,&reply); - assert(ret == REDIS_OK && reply == NULL); - redisReaderFeed(reader,(char*)"\n",1); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReaderFree(reader); - - test("Don't reset state after protocol error: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"x",1); - ret = redisReaderGetReply(reader,&reply); - assert(ret == REDIS_ERR); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && reply == NULL); - redisReaderFree(reader); - - /* Regression test for issue #45 on GitHub. */ - test("Don't do empty allocation for empty multi bulk: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"*0\r\n",4); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && - ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && - ((redisReply*)reply)->elements == 0); - freeReplyObject(reply); - redisReaderFree(reader); -} - -static void test_free_null(void) { - void *redisContext = NULL; - void *reply = NULL; - - test("Don't fail when redisFree is passed a NULL value: "); - redisFree(redisContext); - test_cond(redisContext == NULL); - - test("Don't fail when freeReplyObject is passed a NULL value: "); - freeReplyObject(reply); - test_cond(reply == NULL); -} - -static void test_blocking_connection_errors(void) { - redisContext *c; - - test("Returns error when host cannot be resolved: "); - c = redisConnect((char*)"idontexist.test", 6379); - test_cond(c->err == REDIS_ERR_OTHER && - (strcmp(c->errstr,"Name or service not known") == 0 || - strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || - strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || - strcmp(c->errstr,"No address associated with hostname") == 0 || - strcmp(c->errstr,"Temporary failure in name resolution") == 0 || - strcmp(c->errstr,"no address associated with name") == 0)); - redisFree(c); - - test("Returns error when the port is not open: "); - c = redisConnect((char*)"localhost", 1); - test_cond(c->err == REDIS_ERR_IO && - strcmp(c->errstr,"Connection refused") == 0); - redisFree(c); - - test("Returns error when the unix socket path doesn't accept connections: "); - c = redisConnectUnix((char*)"/tmp/idontexist.sock"); - test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ - redisFree(c); -} - -static void test_blocking_connection(struct config config) { - redisContext *c; - redisReply *reply; - - c = connect(config); - - test("Is able to deliver commands: "); - reply = redisCommand(c,"PING"); - test_cond(reply->type == REDIS_REPLY_STATUS && - strcasecmp(reply->str,"pong") == 0) - freeReplyObject(reply); - - test("Is a able to send commands verbatim: "); - reply = redisCommand(c,"SET foo bar"); - test_cond (reply->type == REDIS_REPLY_STATUS && - strcasecmp(reply->str,"ok") == 0) - freeReplyObject(reply); - - test("%%s String interpolation works: "); - reply = redisCommand(c,"SET %s %s","foo","hello world"); - freeReplyObject(reply); - reply = redisCommand(c,"GET foo"); - test_cond(reply->type == REDIS_REPLY_STRING && - strcmp(reply->str,"hello world") == 0); - freeReplyObject(reply); - - test("%%b String interpolation works: "); - reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); - freeReplyObject(reply); - reply = redisCommand(c,"GET foo"); - test_cond(reply->type == REDIS_REPLY_STRING && - memcmp(reply->str,"hello\x00world",11) == 0) - - test("Binary reply length is correct: "); - test_cond(reply->len == 11) - freeReplyObject(reply); - - test("Can parse nil replies: "); - reply = redisCommand(c,"GET nokey"); - test_cond(reply->type == REDIS_REPLY_NIL) - freeReplyObject(reply); - - /* test 7 */ - test("Can parse integer replies: "); - reply = redisCommand(c,"INCR mycounter"); - test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) - freeReplyObject(reply); - - test("Can parse multi bulk replies: "); - freeReplyObject(redisCommand(c,"LPUSH mylist foo")); - freeReplyObject(redisCommand(c,"LPUSH mylist bar")); - reply = redisCommand(c,"LRANGE mylist 0 -1"); - test_cond(reply->type == REDIS_REPLY_ARRAY && - reply->elements == 2 && - !memcmp(reply->element[0]->str,"bar",3) && - !memcmp(reply->element[1]->str,"foo",3)) - freeReplyObject(reply); - - /* m/e with multi bulk reply *before* other reply. - * specifically test ordering of reply items to parse. */ - test("Can handle nested multi bulk replies: "); - freeReplyObject(redisCommand(c,"MULTI")); - freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); - freeReplyObject(redisCommand(c,"PING")); - reply = (redisCommand(c,"EXEC")); - test_cond(reply->type == REDIS_REPLY_ARRAY && - reply->elements == 2 && - reply->element[0]->type == REDIS_REPLY_ARRAY && - reply->element[0]->elements == 2 && - !memcmp(reply->element[0]->element[0]->str,"bar",3) && - !memcmp(reply->element[0]->element[1]->str,"foo",3) && - reply->element[1]->type == REDIS_REPLY_STATUS && - strcasecmp(reply->element[1]->str,"pong") == 0); - freeReplyObject(reply); - - disconnect(c, 0); -} - -static void test_blocking_connection_timeouts(struct config config) { - redisContext *c; - redisReply *reply; - ssize_t s; - const char *cmd = "DEBUG SLEEP 3\r\n"; - struct timeval tv; - - c = connect(config); - test("Successfully completes a command when the timeout is not exceeded: "); - reply = redisCommand(c,"SET foo fast"); - freeReplyObject(reply); - tv.tv_sec = 0; - tv.tv_usec = 10000; - redisSetTimeout(c, tv); - reply = redisCommand(c, "GET foo"); - test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); - freeReplyObject(reply); - disconnect(c, 0); - - c = connect(config); - test("Does not return a reply when the command times out: "); - s = write(c->fd, cmd, strlen(cmd)); - tv.tv_sec = 0; - tv.tv_usec = 10000; - redisSetTimeout(c, tv); - reply = redisCommand(c, "GET foo"); - test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); - freeReplyObject(reply); - - test("Reconnect properly reconnects after a timeout: "); - redisReconnect(c); - reply = redisCommand(c, "PING"); - test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); - freeReplyObject(reply); - - test("Reconnect properly uses owned parameters: "); - config.tcp.host = "foo"; - config.unix.path = "foo"; - redisReconnect(c); - reply = redisCommand(c, "PING"); - test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); - freeReplyObject(reply); - - disconnect(c, 0); -} - -static void test_blocking_io_errors(struct config config) { - redisContext *c; - redisReply *reply; - void *_reply; - int major, minor; - - /* Connect to target given by config. */ - c = connect(config); - { - /* Find out Redis version to determine the path for the next test */ - const char *field = "redis_version:"; - char *p, *eptr; - - reply = redisCommand(c,"INFO"); - p = strstr(reply->str,field); - major = strtol(p+strlen(field),&eptr,10); - p = eptr+1; /* char next to the first "." */ - minor = strtol(p,&eptr,10); - freeReplyObject(reply); - } - - test("Returns I/O error when the connection is lost: "); - reply = redisCommand(c,"QUIT"); - if (major > 2 || (major == 2 && minor > 0)) { - /* > 2.0 returns OK on QUIT and read() should be issued once more - * to know the descriptor is at EOF. */ - test_cond(strcasecmp(reply->str,"OK") == 0 && - redisGetReply(c,&_reply) == REDIS_ERR); - freeReplyObject(reply); - } else { - test_cond(reply == NULL); - } - - /* On 2.0, QUIT will cause the connection to be closed immediately and - * the read(2) for the reply on QUIT will set the error to EOF. - * On >2.0, QUIT will return with OK and another read(2) needed to be - * issued to find out the socket was closed by the server. In both - * conditions, the error will be set to EOF. */ - assert(c->err == REDIS_ERR_EOF && - strcmp(c->errstr,"Server closed the connection") == 0); - redisFree(c); - - c = connect(config); - test("Returns I/O error on socket timeout: "); - struct timeval tv = { 0, 1000 }; - assert(redisSetTimeout(c,tv) == REDIS_OK); - test_cond(redisGetReply(c,&_reply) == REDIS_ERR && - c->err == REDIS_ERR_IO && errno == EAGAIN); - redisFree(c); -} - -static void test_invalid_timeout_errors(struct config config) { - redisContext *c; - - test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); - - config.tcp.timeout.tv_sec = 0; - config.tcp.timeout.tv_usec = 10000001; - - c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); - - test_cond(c->err == REDIS_ERR_IO); - redisFree(c); - - test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); - - config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; - config.tcp.timeout.tv_usec = 0; - - c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); - - test_cond(c->err == REDIS_ERR_IO); - redisFree(c); -} - -static void test_throughput(struct config config) { - redisContext *c = connect(config); - redisReply **replies; - int i, num; - long long t1, t2; - - test("Throughput:\n"); - for (i = 0; i < 500; i++) - freeReplyObject(redisCommand(c,"LPUSH mylist foo")); - - num = 1000; - replies = malloc(sizeof(redisReply*)*num); - t1 = usec(); - for (i = 0; i < num; i++) { - replies[i] = redisCommand(c,"PING"); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); - - replies = malloc(sizeof(redisReply*)*num); - t1 = usec(); - for (i = 0; i < num; i++) { - replies[i] = redisCommand(c,"LRANGE mylist 0 499"); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); - assert(replies[i] != NULL && replies[i]->elements == 500); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); - - num = 10000; - replies = malloc(sizeof(redisReply*)*num); - for (i = 0; i < num; i++) - redisAppendCommand(c,"PING"); - t1 = usec(); - for (i = 0; i < num; i++) { - assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); - - replies = malloc(sizeof(redisReply*)*num); - for (i = 0; i < num; i++) - redisAppendCommand(c,"LRANGE mylist 0 499"); - t1 = usec(); - for (i = 0; i < num; i++) { - assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); - assert(replies[i] != NULL && replies[i]->elements == 500); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); - - disconnect(c, 0); -} - -// static long __test_callback_flags = 0; -// static void __test_callback(redisContext *c, void *privdata) { -// ((void)c); -// /* Shift to detect execution order */ -// __test_callback_flags <<= 8; -// __test_callback_flags |= (long)privdata; -// } -// -// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { -// ((void)c); -// /* Shift to detect execution order */ -// __test_callback_flags <<= 8; -// __test_callback_flags |= (long)privdata; -// if (reply) freeReplyObject(reply); -// } -// -// static redisContext *__connect_nonblock() { -// /* Reset callback flags */ -// __test_callback_flags = 0; -// return redisConnectNonBlock("127.0.0.1", port, NULL); -// } -// -// static void test_nonblocking_connection() { -// redisContext *c; -// int wdone = 0; -// -// test("Calls command callback when command is issued: "); -// c = __connect_nonblock(); -// redisSetCommandCallback(c,__test_callback,(void*)1); -// redisCommand(c,"PING"); -// test_cond(__test_callback_flags == 1); -// redisFree(c); -// -// test("Calls disconnect callback on redisDisconnect: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)2); -// redisDisconnect(c); -// test_cond(__test_callback_flags == 2); -// redisFree(c); -// -// test("Calls disconnect callback and free callback on redisFree: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)2); -// redisSetFreeCallback(c,__test_callback,(void*)4); -// redisFree(c); -// test_cond(__test_callback_flags == ((2 << 8) | 4)); -// -// test("redisBufferWrite against empty write buffer: "); -// c = __connect_nonblock(); -// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); -// redisFree(c); -// -// test("redisBufferWrite against not yet connected fd: "); -// c = __connect_nonblock(); -// redisCommand(c,"PING"); -// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && -// strncmp(c->error,"write:",6) == 0); -// redisFree(c); -// -// test("redisBufferWrite against closed fd: "); -// c = __connect_nonblock(); -// redisCommand(c,"PING"); -// redisDisconnect(c); -// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && -// strncmp(c->error,"write:",6) == 0); -// redisFree(c); -// -// test("Process callbacks in the right sequence: "); -// c = __connect_nonblock(); -// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); -// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); -// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); -// -// /* Write output buffer */ -// wdone = 0; -// while(!wdone) { -// usleep(500); -// redisBufferWrite(c,&wdone); -// } -// -// /* Read until at least one callback is executed (the 3 replies will -// * arrive in a single packet, causing all callbacks to be executed in -// * a single pass). */ -// while(__test_callback_flags == 0) { -// assert(redisBufferRead(c) == REDIS_OK); -// redisProcessCallbacks(c); -// } -// test_cond(__test_callback_flags == 0x010203); -// redisFree(c); -// -// test("redisDisconnect executes pending callbacks with NULL reply: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)1); -// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); -// redisDisconnect(c); -// test_cond(__test_callback_flags == 0x0201); -// redisFree(c); -// } - -int main(int argc, char **argv) { - struct config cfg = { - .tcp = { - .host = "127.0.0.1", - .port = 6379 - }, - .unix = { - .path = "/tmp/redis.sock" - } - }; - int throughput = 1; - int test_inherit_fd = 1; - - /* Ignore broken pipe signal (for I/O error tests). */ - signal(SIGPIPE, SIG_IGN); - - /* Parse command line options. */ - argv++; argc--; - while (argc) { - if (argc >= 2 && !strcmp(argv[0],"-h")) { - argv++; argc--; - cfg.tcp.host = argv[0]; - } else if (argc >= 2 && !strcmp(argv[0],"-p")) { - argv++; argc--; - cfg.tcp.port = atoi(argv[0]); - } else if (argc >= 2 && !strcmp(argv[0],"-s")) { - argv++; argc--; - cfg.unix.path = argv[0]; - } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { - throughput = 0; - } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { - test_inherit_fd = 0; - } else { - fprintf(stderr, "Invalid argument: %s\n", argv[0]); - exit(1); - } - argv++; argc--; - } - - test_format_commands(); - test_reply_reader(); - test_blocking_connection_errors(); - test_free_null(); - - printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); - cfg.type = CONN_TCP; - test_blocking_connection(cfg); - test_blocking_connection_timeouts(cfg); - test_blocking_io_errors(cfg); - test_invalid_timeout_errors(cfg); - test_append_formatted_commands(cfg); - if (throughput) test_throughput(cfg); - - printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); - cfg.type = CONN_UNIX; - test_blocking_connection(cfg); - test_blocking_connection_timeouts(cfg); - test_blocking_io_errors(cfg); - if (throughput) test_throughput(cfg); - - if (test_inherit_fd) { - printf("\nTesting against inherited fd (%s):\n", cfg.unix.path); - cfg.type = CONN_FD; - test_blocking_connection(cfg); - } - - - if (fails) { - printf("*** %d TESTS FAILED ***\n", fails); - return 1; - } - - printf("ALL TESTS PASSED\n"); - return 0; -} diff --git a/make-mac.mk b/make-mac.mk index fccfea7bf..6625dc85e 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -28,10 +28,9 @@ include objects.mk ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http_parser.o ifeq ($(ZT_CONTROLLER),1) - LIBS+=-L/usr/local/opt/libpq/lib -lpq + LIBS+=-L/usr/local/opt/libpq/lib -lpq -Lext/redis-plus-plus-1.1.1/install/macos/lib -lredis++ -Lext/hiredis-0.14.1/lib/macos -lhiredis DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER - INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-vip-0.3.0 - ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o + INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ endif # Official releases are signed with our Apple cert and apply software updates by default diff --git a/objects.mk b/objects.mk index ed415a508..efa2f3c0f 100644 --- a/objects.mk +++ b/objects.mk @@ -33,7 +33,6 @@ ONE_OBJS=\ controller/FileDB.o \ controller/LFDB.o \ controller/PostgreSQL.o \ - controller/Redis.o \ osdep/EthernetTap.o \ osdep/ManagedRoute.o \ osdep/Http.o \ From 563655a1a410f6b86d86b3f3747bc1a8d2e88d76 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 11:56:19 -0700 Subject: [PATCH 05/13] Redis now usable as a message queue --- controller/PostgreSQL.cpp | 107 +++++++++++++++++++++++++++++++++----- controller/PostgreSQL.hpp | 7 +-- make-mac.mk | 3 +- 3 files changed, 101 insertions(+), 16 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 0640cb8ee..31d5b9118 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -68,6 +68,10 @@ std::string join(const std::vector &elements, const char * const se using namespace ZeroTier; +using Attrs = std::vector>; +using Item = std::pair; +using ItemStream = std::vector; + PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc) : DB() , _myId(myId) @@ -124,9 +128,9 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R opts.db = 0; poolOpts.size = 10; if (_rc->clusterMode) { - _cluster = new sw::redis::RedisCluster(opts, poolOpts); + _cluster = std::make_shared(opts, poolOpts); } else { - _redis = new sw::redis::Redis(opts, poolOpts); + _redis = std::make_shared(opts, poolOpts); } } @@ -145,17 +149,15 @@ PostgreSQL::~PostgreSQL() _run = 0; std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - _heartbeatThread.join(); _membersDbWatcher.join(); _networksDbWatcher.join(); + _commitQueue.stop(); for (int i = 0; i < ZT_CENTRAL_CONTROLLER_COMMIT_THREADS; ++i) { _commitThread[i].join(); } _onlineNotificationThread.join(); - delete _redis; - delete _cluster; + fprintf(stderr, "~PostgreSQL() done\n"); } @@ -651,6 +653,7 @@ void PostgreSQL::heartbeat() PQfinish(conn); conn = NULL; + fprintf(stderr, "Exited heartbeat thread\n"); } void PostgreSQL::membersDbWatcher() @@ -664,10 +667,10 @@ void PostgreSQL::membersDbWatcher() initializeMembers(conn); - if (false) { - // PQfinish(conn); - // conn = NULL; - // _membersWatcher_RabbitMQ(); + if (_rc) { + PQfinish(conn); + conn = NULL; + _membersWatcher_Redis(); } else { _membersWatcher_Postgres(conn); PQfinish(conn); @@ -722,9 +725,47 @@ void PostgreSQL::_membersWatcher_Postgres(PGconn *conn) { } } -void PostgreSQL::_membersWatcher_Reids() { - char buff[11] = {0}; +void PostgreSQL::_membersWatcher_Redis() { + char buf[11] = {0}; + std::string key = "member-stream:{" + std::string(_myAddress.toString(buf)) + "}"; + while (_run == 1) { + json tmp; + std::unordered_map result; + if (_rc->clusterMode) { + _cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } else { + _redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } + if (!result.empty()) { + for (auto element : result) { + fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); + for (auto rec : element.second) { + std::string id = rec.first; + auto attrs = rec.second; + fprintf(stdout, "Record ID: %s\n", id.c_str()); + fprintf(stdout, "attrs len: %lu\n", attrs.size()); + for (auto a : attrs) { + fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); + try { + tmp = json::parse(a.second); + json &ov = tmp["old_val"]; + json &nv = tmp["new_val"]; + json oldConfig, newConfig; + if (ov.is_object()) oldConfig = ov; + if (nv.is_object()) newConfig = nv; + if (oldConfig.is_object()||newConfig.is_object()) { + _memberChanged(oldConfig,newConfig,(this->_ready >= 2)); + } + } catch (...) { + fprintf(stderr, "json parse error in networkWatcher_Redis\n"); + } + } + } + } + } + } + fprintf(stderr, "membersWatcher ended\n"); } void PostgreSQL::networksDbWatcher() @@ -795,7 +836,48 @@ void PostgreSQL::_networksWatcher_Postgres(PGconn *conn) { } void PostgreSQL::_networksWatcher_Redis() { + char buf[11] = {0}; + std::string key = "network-stream:{" + std::string(_myAddress.toString(buf)) + "}"; + + while (_run == 1) { + json tmp; + std::unordered_map result; + if (_rc->clusterMode) { + _cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } else { + _redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end())); + } + + if (!result.empty()) { + for (auto element : result) { + fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); + for (auto rec : element.second) { + std::string id = rec.first; + auto attrs = rec.second; + fprintf(stdout, "Record ID: %s\n", id.c_str()); + fprintf(stdout, "attrs len: %lu\n", attrs.size()); + for (auto a : attrs) { + fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); + try { + tmp = json::parse(a.second); + json &ov = tmp["old_val"]; + json &nv = tmp["new_val"]; + json oldConfig, newConfig; + if (ov.is_object()) oldConfig = ov; + if (nv.is_object()) newConfig = nv; + if (oldConfig.is_object()||newConfig.is_object()) { + _networkChanged(oldConfig,newConfig,(this->_ready >= 2)); + } + } catch (...) { + fprintf(stderr, "json parse error in networkWatcher_Redis\n"); + } + } + } + } + } + } + fprintf(stderr, "networksWatcher ended\n"); } void PostgreSQL::commitThread() @@ -1293,6 +1375,7 @@ void PostgreSQL::commitThread() fprintf(stderr, "ERROR: %s commitThread should still be running! Exiting Controller.\n", _myAddressStr.c_str()); exit(7); } + fprintf(stderr, "commitThread finished\n"); } void PostgreSQL::onlineNotificationThread() diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 986559acf..44347cd81 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -20,6 +20,7 @@ #define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4 +#include #include extern "C" { @@ -64,7 +65,7 @@ private: void networksDbWatcher(); void _networksWatcher_Postgres(PGconn *conn); - void _membersWatcher_Reids(); + void _membersWatcher_Redis(); void _networksWatcher_Redis(); void commitThread(); @@ -100,8 +101,8 @@ private: int _listenPort; RedisConfig *_rc; - sw::redis::Redis *_redis; - sw::redis::RedisCluster *_cluster; + std::shared_ptr _redis; + std::shared_ptr _cluster; }; } // namespace ZeroTier diff --git a/make-mac.mk b/make-mac.mk index 6625dc85e..5e7a67c20 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -28,9 +28,10 @@ include objects.mk ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http_parser.o ifeq ($(ZT_CONTROLLER),1) - LIBS+=-L/usr/local/opt/libpq/lib -lpq -Lext/redis-plus-plus-1.1.1/install/macos/lib -lredis++ -Lext/hiredis-0.14.1/lib/macos -lhiredis + LIBS+=-L/usr/local/opt/libpq/lib -lpq ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ + endif # Official releases are signed with our Apple cert and apply software updates by default From c6518afa7a611a59b00fe941cd53055066a02e41 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 12:37:05 -0700 Subject: [PATCH 06/13] Make sure the streams clean up after themselves --- controller/PostgreSQL.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 31d5b9118..e3d513a06 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -761,6 +761,11 @@ void PostgreSQL::_membersWatcher_Redis() { fprintf(stderr, "json parse error in networkWatcher_Redis\n"); } } + if (_rc->clusterMode) { + _cluster->xdel(key, id); + } else { + _redis->xdel(key, id); + } } } } @@ -873,6 +878,11 @@ void PostgreSQL::_networksWatcher_Redis() { fprintf(stderr, "json parse error in networkWatcher_Redis\n"); } } + if (_rc->clusterMode) { + _cluster->xdel(key, id); + } else { + _redis->xdel(key, id); + } } } } From aab96964b6ce93f452d0e170e969dd50a2ea2540 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 12:48:58 -0700 Subject: [PATCH 07/13] Put debug output behind ZT_TRACE --- controller/PostgreSQL.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index e3d513a06..505d76527 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -157,7 +157,6 @@ PostgreSQL::~PostgreSQL() _commitThread[i].join(); } _onlineNotificationThread.join(); - fprintf(stderr, "~PostgreSQL() done\n"); } @@ -739,14 +738,20 @@ void PostgreSQL::_membersWatcher_Redis() { } if (!result.empty()) { for (auto element : result) { +#ifdef ZT_TRACE fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); +#endif for (auto rec : element.second) { std::string id = rec.first; auto attrs = rec.second; +#ifdef ZT_TRACE fprintf(stdout, "Record ID: %s\n", id.c_str()); fprintf(stdout, "attrs len: %lu\n", attrs.size()); +#endif for (auto a : attrs) { +#ifdef ZT_TRACE fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); +#endif try { tmp = json::parse(a.second); json &ov = tmp["old_val"]; @@ -855,15 +860,20 @@ void PostgreSQL::_networksWatcher_Redis() { if (!result.empty()) { for (auto element : result) { - +#ifdef ZT_TRACE fprintf(stdout, "Received notification from: %s\n", element.first.c_str()); +#endif for (auto rec : element.second) { std::string id = rec.first; auto attrs = rec.second; +#ifdef ZT_TRACE fprintf(stdout, "Record ID: %s\n", id.c_str()); fprintf(stdout, "attrs len: %lu\n", attrs.size()); +#endif for (auto a : attrs) { +#ifdef ZT_TRACE fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str()); +#endif try { tmp = json::parse(a.second); json &ov = tmp["old_val"]; From 5babd01d407b45d70d31291fec18442d78fe7717 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 12:58:09 -0700 Subject: [PATCH 08/13] centos8 binaries for libhiredis and libredis++ --- ext/hiredis-0.14.1/lib/centos8/libhiredis.a | Bin 0 -> 485314 bytes .../centos8/include/sw/redis++/command.h | 2233 +++++++++++++++++ .../centos8/include/sw/redis++/command_args.h | 180 ++ .../include/sw/redis++/command_options.h | 211 ++ .../centos8/include/sw/redis++/connection.h | 194 ++ .../include/sw/redis++/connection_pool.h | 115 + .../centos8/include/sw/redis++/errors.h | 159 ++ .../centos8/include/sw/redis++/pipeline.h | 49 + .../centos8/include/sw/redis++/queued_redis.h | 1844 ++++++++++++++ .../include/sw/redis++/queued_redis.hpp | 208 ++ .../centos8/include/sw/redis++/redis++.h | 25 + .../centos8/include/sw/redis++/redis.h | 1523 +++++++++++ .../centos8/include/sw/redis++/redis.hpp | 1365 ++++++++++ .../include/sw/redis++/redis_cluster.h | 1395 ++++++++++ .../include/sw/redis++/redis_cluster.hpp | 1415 +++++++++++ .../centos8/include/sw/redis++/reply.h | 363 +++ .../centos8/include/sw/redis++/sentinel.h | 138 + .../centos8/include/sw/redis++/shards.h | 115 + .../centos8/include/sw/redis++/shards_pool.h | 137 + .../centos8/include/sw/redis++/subscriber.h | 231 ++ .../centos8/include/sw/redis++/transaction.h | 77 + .../centos8/include/sw/redis++/utils.h | 269 ++ 22 files changed, 12246 insertions(+) create mode 100644 ext/hiredis-0.14.1/lib/centos8/libhiredis.a create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h diff --git a/ext/hiredis-0.14.1/lib/centos8/libhiredis.a b/ext/hiredis-0.14.1/lib/centos8/libhiredis.a new file mode 100644 index 0000000000000000000000000000000000000000..0b9638798529747795a78056068d5a6a980a4f02 GIT binary patch literal 485314 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 z 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

FvN`Rfv+f3HZdHlRkiTKo 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 Cl30!a literal 0 HcmV?d00001 diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h new file mode 100644 index 000000000..3a4b24c5e --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h @@ -0,0 +1,2233 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_H + +#include +#include +#include +#include +#include "connection.h" +#include "command_options.h" +#include "command_args.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +namespace cmd { + +// CONNECTION command. +inline void auth(Connection &connection, const StringView &password) { + connection.send("AUTH %b", password.data(), password.size()); +} + +inline void echo(Connection &connection, const StringView &msg) { + connection.send("ECHO %b", msg.data(), msg.size()); +} + +inline void ping(Connection &connection) { + connection.send("PING"); +} + +inline void quit(Connection &connection) { + connection.send("QUIT"); +} + +inline void ping(Connection &connection, const StringView &msg) { + // If *msg* is empty, Redis returns am empty reply of REDIS_REPLY_STRING type. + connection.send("PING %b", msg.data(), msg.size()); +} + +inline void select(Connection &connection, long long idx) { + connection.send("SELECT %lld", idx); +} + +inline void swapdb(Connection &connection, long long idx1, long long idx2) { + connection.send("SWAPDB %lld %lld", idx1, idx2); +} + +// SERVER commands. + +inline void bgrewriteaof(Connection &connection) { + connection.send("BGREWRITEAOF"); +} + +inline void bgsave(Connection &connection) { + connection.send("BGSAVE"); +} + +inline void dbsize(Connection &connection) { + connection.send("DBSIZE"); +} + +inline void flushall(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHALL ASYNC"); + } else { + connection.send("FLUSHALL"); + } +} + +inline void flushdb(Connection &connection, bool async) { + if (async) { + connection.send("FLUSHDB ASYNC"); + } else { + connection.send("FLUSHDB"); + } +} + +inline void info(Connection &connection) { + connection.send("INFO"); +} + +inline void info(Connection &connection, const StringView §ion) { + connection.send("INFO %b", section.data(), section.size()); +} + +inline void lastsave(Connection &connection) { + connection.send("LASTSAVE"); +} + +inline void save(Connection &connection) { + connection.send("SAVE"); +} + +// KEY commands. + +inline void del(Connection &connection, const StringView &key) { + connection.send("DEL %b", key.data(), key.size()); +} + +template +inline void del_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "DEL" << std::make_pair(first, last); + + connection.send(args); +} + +inline void dump(Connection &connection, const StringView &key) { + connection.send("DUMP %b", key.data(), key.size()); +} + +inline void exists(Connection &connection, const StringView &key) { + connection.send("EXISTS %b", key.data(), key.size()); +} + +template +inline void exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void expire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("EXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void expireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("EXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void keys(Connection &connection, const StringView &pattern) { + connection.send("KEYS %b", pattern.data(), pattern.size()); +} + +inline void move(Connection &connection, const StringView &key, long long db) { + connection.send("MOVE %b %lld", + key.data(), key.size(), + db); +} + +inline void persist(Connection &connection, const StringView &key) { + connection.send("PERSIST %b", key.data(), key.size()); +} + +inline void pexpire(Connection &connection, + const StringView &key, + long long timeout) { + connection.send("PEXPIRE %b %lld", + key.data(), key.size(), + timeout); +} + +inline void pexpireat(Connection &connection, + const StringView &key, + long long timestamp) { + connection.send("PEXPIREAT %b %lld", + key.data(), key.size(), + timestamp); +} + +inline void pttl(Connection &connection, const StringView &key) { + connection.send("PTTL %b", key.data(), key.size()); +} + +inline void randomkey(Connection &connection) { + connection.send("RANDOMKEY"); +} + +inline void rename(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAME %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +inline void renamenx(Connection &connection, + const StringView &key, + const StringView &newkey) { + connection.send("RENAMENX %b %b", + key.data(), key.size(), + newkey.data(), newkey.size()); +} + +void restore(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + bool replace); + +inline void scan(Connection &connection, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SCAN %lld MATCH %b COUNT %lld", + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void touch(Connection &connection, const StringView &key) { + connection.send("TOUCH %b", key.data(), key.size()); +} + +template +inline void touch_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "TOUCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void ttl(Connection &connection, const StringView &key) { + connection.send("TTL %b", key.data(), key.size()); +} + +inline void type(Connection &connection, const StringView &key) { + connection.send("TYPE %b", key.data(), key.size()); +} + +inline void unlink(Connection &connection, const StringView &key) { + connection.send("UNLINK %b", key.data(), key.size()); +} + +template +inline void unlink_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "UNLINK" << std::make_pair(first, last); + + connection.send(args); +} + +inline void wait(Connection &connection, long long numslave, long long timeout) { + connection.send("WAIT %lld %lld", numslave, timeout); +} + +// STRING commands. + +inline void append(Connection &connection, const StringView &key, const StringView &str) { + connection.send("APPEND %b %b", + key.data(), key.size(), + str.data(), str.size()); +} + +inline void bitcount(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("BITCOUNT %b %lld %lld", + key.data(), key.size(), + start, end); +} + +void bitop(Connection &connection, + BitOp op, + const StringView &destination, + const StringView &key); + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last); + +inline void bitpos(Connection &connection, + const StringView &key, + long long bit, + long long start, + long long end) { + connection.send("BITPOS %b %lld %lld %lld", + key.data(), key.size(), + bit, + start, + end); +} + +inline void decr(Connection &connection, const StringView &key) { + connection.send("DECR %b", key.data(), key.size()); +} + +inline void decrby(Connection &connection, const StringView &key, long long decrement) { + connection.send("DECRBY %b %lld", + key.data(), key.size(), + decrement); +} + +inline void get(Connection &connection, const StringView &key) { + connection.send("GET %b", + key.data(), key.size()); +} + +inline void getbit(Connection &connection, const StringView &key, long long offset) { + connection.send("GETBIT %b %lld", + key.data(), key.size(), + offset); +} + +inline void getrange(Connection &connection, + const StringView &key, + long long start, + long long end) { + connection.send("GETRANGE %b %lld %lld", + key.data(), key.size(), + start, + end); +} + +inline void getset(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("GETSET %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void incr(Connection &connection, const StringView &key) { + connection.send("INCR %b", key.data(), key.size()); +} + +inline void incrby(Connection &connection, const StringView &key, long long increment) { + connection.send("INCRBY %b %lld", + key.data(), key.size(), + increment); +} + +inline void incrbyfloat(Connection &connection, const StringView &key, double increment) { + connection.send("INCRBYFLOAT %b %f", + key.data(), key.size(), + increment); +} + +template +inline void mget(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MGET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void mset(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSET" << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void msetnx(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "MSETNX" << std::make_pair(first, last); + + connection.send(args); +} + +inline void psetex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("PSETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +void set(Connection &connection, + const StringView &key, + const StringView &val, + long long ttl, + UpdateType type); + +inline void setex(Connection &connection, + const StringView &key, + long long ttl, + const StringView &val) { + connection.send("SETEX %b %lld %b", + key.data(), key.size(), + ttl, + val.data(), val.size()); +} + +inline void setnx(Connection &connection, + const StringView &key, + const StringView &val) { + connection.send("SETNX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void setrange(Connection &connection, + const StringView &key, + long long offset, + const StringView &val) { + connection.send("SETRANGE %b %lld %b", + key.data(), key.size(), + offset, + val.data(), val.size()); +} + +inline void strlen(Connection &connection, const StringView &key) { + connection.send("STRLEN %b", key.data(), key.size()); +} + +// LIST commands. + +inline void blpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BLPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void blpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BLPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpop(Connection &connection, const StringView &key, long long timeout) { + connection.send("BRPOP %b %lld", + key.data(), key.size(), + timeout); +} + +template +inline void brpop_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BRPOP" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void brpoplpush(Connection &connection, + const StringView &source, + const StringView &destination, + long long timeout) { + connection.send("BRPOPLPUSH %b %b %lld", + source.data(), source.size(), + destination.data(), destination.size(), + timeout); +} + +inline void lindex(Connection &connection, const StringView &key, long long index) { + connection.send("LINDEX %b %lld", + key.data(), key.size(), + index); +} + +void linsert(Connection &connection, + const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + +inline void llen(Connection &connection, + const StringView &key) { + connection.send("LLEN %b", key.data(), key.size()); +} + +inline void lpop(Connection &connection, const StringView &key) { + connection.send("LPOP %b", + key.data(), key.size()); +} + +inline void lpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void lpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "LPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void lpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("LPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +inline void lrange(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void lrem(Connection &connection, + const StringView &key, + long long count, + const StringView &val) { + connection.send("LREM %b %lld %b", + key.data(), key.size(), + count, + val.data(), val.size()); +} + +inline void lset(Connection &connection, + const StringView &key, + long long index, + const StringView &val) { + connection.send("LSET %b %lld %b", + key.data(), key.size(), + index, + val.data(), val.size()); +} + +inline void ltrim(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("LTRIM %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +inline void rpop(Connection &connection, const StringView &key) { + connection.send("RPOP %b", key.data(), key.size()); +} + +inline void rpoplpush(Connection &connection, + const StringView &source, + const StringView &destination) { + connection.send("RPOPLPUSH %b %b", + source.data(), source.size(), + destination.data(), destination.size()); +} + +inline void rpush(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSH %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +template +inline void rpush_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "RPUSH" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void rpushx(Connection &connection, const StringView &key, const StringView &val) { + connection.send("RPUSHX %b %b", + key.data(), key.size(), + val.data(), val.size()); +} + +// HASH commands. + +inline void hdel(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HDEL %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +template +inline void hdel_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hexists(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HEXISTS %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hget(Connection &connection, const StringView &key, const StringView &field) { + connection.send("HGET %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hgetall(Connection &connection, const StringView &key) { + connection.send("HGETALL %b", key.data(), key.size()); +} + +inline void hincrby(Connection &connection, + const StringView &key, + const StringView &field, + long long increment) { + connection.send("HINCRBY %b %b %lld", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hincrbyfloat(Connection &connection, + const StringView &key, + const StringView &field, + double increment) { + connection.send("HINCRBYFLOAT %b %b %f", + key.data(), key.size(), + field.data(), field.size(), + increment); +} + +inline void hkeys(Connection &connection, const StringView &key) { + connection.send("HKEYS %b", key.data(), key.size()); +} + +inline void hlen(Connection &connection, const StringView &key) { + connection.send("HLEN %b", key.data(), key.size()); +} + +template +inline void hmget(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMGET" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void hmset(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "HMSET" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void hscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("HSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void hset(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSET %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hsetnx(Connection &connection, + const StringView &key, + const StringView &field, + const StringView &val) { + connection.send("HSETNX %b %b %b", + key.data(), key.size(), + field.data(), field.size(), + val.data(), val.size()); +} + +inline void hstrlen(Connection &connection, + const StringView &key, + const StringView &field) { + connection.send("HSTRLEN %b %b", + key.data(), key.size(), + field.data(), field.size()); +} + +inline void hvals(Connection &connection, const StringView &key) { + connection.send("HVALS %b", key.data(), key.size()); +} + +// SET commands + +inline void sadd(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SADD %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void sadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void scard(Connection &connection, const StringView &key) { + connection.send("SCARD %b", key.data(), key.size()); +} + +template +inline void sdiff(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFF" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sdiffstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SDIFFSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sdiffstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SDIFFSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void sinter(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTER" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sinterstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SINTERSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SINTERSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +inline void sismember(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SISMEMBER %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void smembers(Connection &connection, const StringView &key) { + connection.send("SMEMBERS %b", key.data(), key.size()); +} + +inline void smove(Connection &connection, + const StringView &source, + const StringView &destination, + const StringView &member) { + connection.send("SMOVE %b %b %b", + source.data(), source.size(), + destination.data(), destination.size(), + member.data(), member.size()); +} + +inline void spop(Connection &connection, const StringView &key) { + connection.send("SPOP %b", key.data(), key.size()); +} + +inline void spop_range(Connection &connection, const StringView &key, long long count) { + connection.send("SPOP %b %lld", + key.data(), key.size(), + count); +} + +inline void srandmember(Connection &connection, const StringView &key) { + connection.send("SRANDMEMBER %b", key.data(), key.size()); +} + +inline void srandmember_range(Connection &connection, + const StringView &key, + long long count) { + connection.send("SRANDMEMBER %b %lld", + key.data(), key.size(), + count); +} + +inline void srem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("SREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void srem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void sscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("SSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +template +inline void sunion(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNION" << std::make_pair(first, last); + + connection.send(args); +} + +inline void sunionstore(Connection &connection, + const StringView &destination, + const StringView &key) { + connection.send("SUNIONSTORE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void sunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "SUNIONSTORE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// Sorted Set commands. + +inline void bzpopmax(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMAX %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmax_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMAX" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +inline void bzpopmin(Connection &connection, const StringView &key, long long timeout) { + connection.send("BZPOPMIN %b %lld", key.data(), key.size(), timeout); +} + +template +void bzpopmin_range(Connection &connection, + Input first, + Input last, + long long timeout) { + assert(first != last); + + CmdArgs args; + args << "BZPOPMIN" << std::make_pair(first, last) << timeout; + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed); + +inline void zadd(Connection &connection, + const StringView &key, + const StringView &member, + double score, + UpdateType type, + bool changed) { + auto tmp = {std::make_pair(member, score)}; + + zadd_range(connection, key, tmp.begin(), tmp.end(), type, changed); +} + +inline void zcard(Connection &connection, const StringView &key) { + connection.send("ZCARD %b", key.data(), key.size()); +} + +template +inline void zcount(Connection &connection, + const StringView &key, + const Interval &interval) { + connection.send("ZCOUNT %b %s %s", + key.data(), key.size(), + interval.min().c_str(), + interval.max().c_str()); +} + +inline void zincrby(Connection &connection, + const StringView &key, + double increment, + const StringView &member) { + connection.send("ZINCRBY %b %f %b", + key.data(), key.size(), + increment, + member.data(), member.size()); +} + +inline void zinterstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZINTERSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +template +inline void zlexcount(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZLEXCOUNT %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zpopmax(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMAX %b %lld", + key.data(), key.size(), + count); +} + +inline void zpopmin(Connection &connection, const StringView &key, long long count) { + connection.send("ZPOPMIN %b %lld", + key.data(), key.size(), + count); +} + +inline void zrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); +} + +template +void zrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } else { + connection.send("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size(), + opts.offset, + opts.count); + } +} + +inline void zrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zrem(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREM %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +template +inline void zrem_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "ZREM" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void zremrangebylex(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYLEX %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zremrangebyrank(Connection &connection, + const StringView &key, + long long start, + long long stop) { + connection.send("zremrangebyrank %b %lld %lld", + key.data(), key.size(), + start, + stop); +} + +template +inline void zremrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREMRANGEBYSCORE %b %b %b", + key.data(), key.size(), + min.data(), min.size(), + max.data(), max.size()); +} + +inline void zrevrange(Connection &connection, + const StringView &key, + long long start, + long long stop, + bool with_scores) { + if (with_scores) { + connection.send("ZREVRANGE %b %lld %lld WITHSCORES", + key.data(), key.size(), + start, + stop); + } else { + connection.send("ZREVRANGE %b %lld %lld", + key.data(), key.size(), + start, + stop); + } +} + +template +inline void zrevrangebylex(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + connection.send("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); +} + +template +void zrevrangebyscore(Connection &connection, + const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores) { + const auto &min = interval.min(); + const auto &max = interval.max(); + + if (with_scores) { + connection.send("ZREVRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } else { + connection.send("ZREVRANGEBYSCORE %b %b %b LIMIT %lld %lld", + key.data(), key.size(), + max.data(), max.size(), + min.data(), min.size(), + opts.offset, + opts.count); + } +} + +inline void zrevrank(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZREVRANK %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zscan(Connection &connection, + const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + connection.send("ZSCAN %b %lld MATCH %b COUNT %lld", + key.data(), key.size(), + cursor, + pattern.data(), pattern.size(), + count); +} + +inline void zscore(Connection &connection, + const StringView &key, + const StringView &member) { + connection.send("ZSCORE %b %b", + key.data(), key.size(), + member.data(), member.size()); +} + +inline void zunionstore(Connection &connection, + const StringView &destination, + const StringView &key, + double weight) { + connection.send("ZUNIONSTORE %b 1 %b WEIGHTS %f", + destination.data(), destination.size(), + key.data(), key.size(), + weight); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr); + +// HYPERLOGLOG commands. + +inline void pfadd(Connection &connection, + const StringView &key, + const StringView &element) { + connection.send("PFADD %b %b", + key.data(), key.size(), + element.data(), element.size()); +} + +template +inline void pfadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFADD" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfcount(Connection &connection, const StringView &key) { + connection.send("PFCOUNT %b", key.data(), key.size()); +} + +template +inline void pfcount_range(Connection &connection, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFCOUNT" << std::make_pair(first, last); + + connection.send(args); +} + +inline void pfmerge(Connection &connection, const StringView &destination, const StringView &key) { + connection.send("PFMERGE %b %b", + destination.data(), destination.size(), + key.data(), key.size()); +} + +template +inline void pfmerge_range(Connection &connection, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "PFMERGE" << destination << std::make_pair(first, last); + + connection.send(args); +} + +// GEO commands. + +inline void geoadd(Connection &connection, + const StringView &key, + const std::tuple &member) { + const auto &mem = std::get<0>(member); + + connection.send("GEOADD %b %f %f %b", + key.data(), key.size(), + std::get<1>(member), + std::get<2>(member), + mem.data(), mem.size()); +} + +template +inline void geoadd_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOADD" << key; + + while (first != last) { + const auto &member = *first; + args << std::get<1>(member) << std::get<2>(member) << std::get<0>(member); + ++first; + } + + connection.send(args); +} + +void geodist(Connection &connection, + const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit); + +template +inline void geohash_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOHASH" << key << std::make_pair(first, last); + + connection.send(args); +} + +template +inline void geopos_range(Connection &connection, + const StringView &key, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + args << "GEOPOS" << key << std::make_pair(first, last); + + connection.send(args); +} + +void georadius(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadius_store(Connection &connection, + const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void georadiusbymember(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +void georadiusbymember_store(Connection &connection, + const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +// SCRIPTING commands. + +inline void eval(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVAL" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void evalsha(Connection &connection, + const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + CmdArgs cmd_args; + + cmd_args << "EVALSHA" << script << keys.size() + << std::make_pair(keys.begin(), keys.end()) + << std::make_pair(args.begin(), args.end()); + + connection.send(cmd_args); +} + +inline void script_exists(Connection &connection, const StringView &sha) { + connection.send("SCRIPT EXISTS %b", sha.data(), sha.size()); +} + +template +inline void script_exists_range(Connection &connection, Input first, Input last) { + assert(first != last); + + CmdArgs args; + args << "SCRIPT" << "EXISTS" << std::make_pair(first, last); + + connection.send(args); +} + +inline void script_flush(Connection &connection) { + connection.send("SCRIPT FLUSH"); +} + +inline void script_kill(Connection &connection) { + connection.send("SCRIPT KILL"); +} + +inline void script_load(Connection &connection, const StringView &script) { + connection.send("SCRIPT LOAD %b", script.data(), script.size()); +} + +// PUBSUB commands. + +inline void psubscribe(Connection &connection, const StringView &pattern) { + connection.send("PSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void psubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void publish(Connection &connection, + const StringView &channel, + const StringView &message) { + connection.send("PUBLISH %b %b", + channel.data(), channel.size(), + message.data(), message.size()); +} + +inline void punsubscribe(Connection &connection) { + connection.send("PUNSUBSCRIBE"); +} + +inline void punsubscribe(Connection &connection, const StringView &pattern) { + connection.send("PUNSUBSCRIBE %b", pattern.data(), pattern.size()); +} + +template +inline void punsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("PUNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "PUNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void subscribe(Connection &connection, const StringView &channel) { + connection.send("SUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void subscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("SUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "SUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +inline void unsubscribe(Connection &connection) { + connection.send("UNSUBSCRIBE"); +} + +inline void unsubscribe(Connection &connection, const StringView &channel) { + connection.send("UNSUBSCRIBE %b", channel.data(), channel.size()); +} + +template +inline void unsubscribe_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNSUBSCRIBE: no key specified"); + } + + CmdArgs args; + args << "UNSUBSCRIBE" << std::make_pair(first, last); + + connection.send(args); +} + +// Transaction commands. + +inline void discard(Connection &connection) { + connection.send("DISCARD"); +} + +inline void exec(Connection &connection) { + connection.send("EXEC"); +} + +inline void multi(Connection &connection) { + connection.send("MULTI"); +} + +inline void unwatch(Connection &connection, const StringView &key) { + connection.send("UNWATCH %b", key.data(), key.size()); +} + +template +inline void unwatch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("UNWATCH: no key specified"); + } + + CmdArgs args; + args << "UNWATCH" << std::make_pair(first, last); + + connection.send(args); +} + +inline void watch(Connection &connection, const StringView &key) { + connection.send("WATCH %b", key.data(), key.size()); +} + +template +inline void watch_range(Connection &connection, Input first, Input last) { + if (first == last) { + throw Error("WATCH: no key specified"); + } + + CmdArgs args; + args << "WATCH" << std::make_pair(first, last); + + connection.send(args); +} + +// Stream commands. + +inline void xack(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XACK %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +template +void xack_range(Connection &connection, + const StringView &key, + const StringView &group, + Input first, + Input last) { + CmdArgs args; + args << "XACK" << key << group << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last) { + CmdArgs args; + args << "XADD" << key << id << std::make_pair(first, last); + + connection.send(args); +} + +template +void xadd_maxlen_range(Connection &connection, + const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + CmdArgs args; + args << "XADD" << key << "MAXLEN"; + + if (approx) { + args << "~"; + } + + args << count << id << std::make_pair(first, last); + + connection.send(args); +} + +inline void xclaim(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + const StringView &id) { + connection.send("XCLAIM %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size(), + min_idle_time, + id.data(), id.size()); +} + +template +void xclaim_range(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer, + long long min_idle_time, + Input first, + Input last) { + CmdArgs args; + args << "XCLAIM" << key << group << consumer << min_idle_time << std::make_pair(first, last); + + connection.send(args); +} + +inline void xdel(Connection &connection, const StringView &key, const StringView &id) { + connection.send("XDEL %b %b", key.data(), key.size(), id.data(), id.size()); +} + +template +void xdel_range(Connection &connection, const StringView &key, Input first, Input last) { + CmdArgs args; + args << "XDEL" << key << std::make_pair(first, last); + + connection.send(args); +} + +inline void xgroup_create(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream) { + CmdArgs args; + args << "XGROUP" << "CREATE" << key << group << id; + + if (mkstream) { + args << "MKSTREAM"; + } + + connection.send(args); +} + +inline void xgroup_setid(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &id) { + connection.send("XGROUP SETID %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + id.data(), id.size()); +} + +inline void xgroup_destroy(Connection &connection, + const StringView &key, + const StringView &group) { + connection.send("XGROUP DESTROY %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xgroup_delconsumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &consumer) { + connection.send("XGROUP DELCONSUMER %b %b %b", + key.data(), key.size(), + group.data(), group.size(), + consumer.data(), consumer.size()); +} + +inline void xlen(Connection &connection, const StringView &key) { + connection.send("XLEN %b", key.data(), key.size()); +} + +inline void xpending(Connection &connection, const StringView &key, const StringView &group) { + connection.send("XPENDING %b %b", + key.data(), key.size(), + group.data(), group.size()); +} + +inline void xpending_detail(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XPENDING %b %b %b %b %lld", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xpending_per_consumer(Connection &connection, + const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + connection.send("XPENDING %b %b %b %b %lld %b", + key.data(), key.size(), + group.data(), group.size(), + start.data(), start.size(), + end.data(), end.size(), + count, + consumer.data(), consumer.size()); +} + +inline void xrange(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end) { + connection.send("XRANGE %b %b %b", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size()); +} + +inline void xrange_count(Connection &connection, + const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + connection.send("XRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + start.data(), start.size(), + end.data(), end.size(), + count); +} + +inline void xread(Connection &connection, + const StringView &key, + const StringView &id, + long long count) { + connection.send("XREAD COUNT %lld STREAMS %b %b", + count, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_range(Connection &connection, Input first, Input last, long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xread_block(Connection &connection, + const StringView &key, + const StringView &id, + long long timeout, + long long count) { + connection.send("XREAD COUNT %lld BLOCK %lld STREAMS %b %b", + count, + timeout, + key.data(), key.size(), + id.data(), id.size()); +} + +template +void xread_block_range(Connection &connection, + Input first, + Input last, + long long timeout, + long long count) { + CmdArgs args; + args << "XREAD" << "COUNT" << count << "BLOCK" << timeout << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xreadgroup_block(Connection &connection, + const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS" << key << id; + + connection.send(args); +} + +template +void xreadgroup_block_range(Connection &connection, + const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long timeout, + long long count, + bool noack) { + CmdArgs args; + args << "XREADGROUP" << "GROUP" << group << consumer + << "COUNT" << count << "BLOCK" << timeout; + + if (noack) { + args << "NOACK"; + } + + args << "STREAMS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + connection.send(args); +} + +inline void xrevrange(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start) { + connection.send("XREVRANGE %b %b %b", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size()); +} + +inline void xrevrange_count(Connection &connection, + const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + connection.send("XREVRANGE %b %b %b COUNT %lld", + key.data(), key.size(), + end.data(), end.size(), + start.data(), start.size(), + count); +} + +void xtrim(Connection &connection, const StringView &key, long long count, bool approx); + +namespace detail { + +void set_bitop(CmdArgs &args, BitOp op); + +void set_update_type(CmdArgs &args, UpdateType type); + +void set_aggregation_type(CmdArgs &args, Aggregation type); + +template +void zinterstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zinterstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZINTERSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::false_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last) + << std::make_pair(first, last); + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +template +void zunionstore(std::true_type, + Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + CmdArgs args; + args << "ZUNIONSTORE" << destination << std::distance(first, last); + + for (auto iter = first; iter != last; ++iter) { + args << iter->first; + } + + args << "WEIGHTS"; + + for (auto iter = first; iter != last; ++iter) { + args << iter->second; + } + + set_aggregation_type(args, aggr); + + connection.send(args); +} + +void set_geo_unit(CmdArgs &args, GeoUnit unit); + +void set_georadius_store_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + +void set_georadius_parameters(CmdArgs &args, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash); + +} + +} + +} + +} + +namespace sw { + +namespace redis { + +namespace cmd { + +template +void bitop_range(Connection &connection, + BitOp op, + const StringView &destination, + Input first, + Input last) { + assert(first != last); + + CmdArgs args; + + detail::set_bitop(args, op); + + args << destination << std::make_pair(first, last); + + connection.send(args); +} + +template +void zadd_range(Connection &connection, + const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + assert(first != last); + + CmdArgs args; + + args << "ZADD" << key; + + detail::set_update_type(args, type); + + if (changed) { + args << "CH"; + } + + while (first != last) { + // Swap the pair to pair. + args << first->second << first->first; + ++first; + } + + connection.send(args); +} + +template +void zinterstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zinterstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +template +void zunionstore_range(Connection &connection, + const StringView &destination, + Input first, + Input last, + Aggregation aggr) { + assert(first != last); + + detail::zunionstore(typename IsKvPairIter::type(), + connection, + destination, + first, + last, + aggr); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h new file mode 100644 index 000000000..0beb71e5c --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h @@ -0,0 +1,180 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H + +#include +#include +#include +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +class CmdArgs { +public: + template + CmdArgs& append(Arg &&arg); + + template + CmdArgs& append(Arg &&arg, Args &&...args); + + // All overloads of operator<< are for internal use only. + CmdArgs& operator<<(const StringView &arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& operator<<(T &&arg); + + template + CmdArgs& operator<<(const std::pair &range); + + template + auto operator<<(const std::tuple &) -> + typename std::enable_if::type { + return *this; + } + + template + auto operator<<(const std::tuple &arg) -> + typename std::enable_if::type; + + const char** argv() { + return _argv.data(); + } + + const std::size_t* argv_len() { + return _argv_len.data(); + } + + std::size_t size() const { + return _argv.size(); + } + +private: + // Deep copy. + CmdArgs& _append(std::string arg); + + // Shallow copy. + CmdArgs& _append(const StringView &arg); + + // Shallow copy. + CmdArgs& _append(const char *arg); + + template ::type>::value, + int>::type = 0> + CmdArgs& _append(T &&arg) { + return operator<<(std::forward(arg)); + } + + template + CmdArgs& _append(std::true_type, const std::pair &range); + + template + CmdArgs& _append(std::false_type, const std::pair &range); + + std::vector _argv; + std::vector _argv_len; + + std::list _args; +}; + +template +inline CmdArgs& CmdArgs::append(Arg &&arg) { + return _append(std::forward(arg)); +} + +template +inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) { + _append(std::forward(arg)); + + return append(std::forward(args)...); +} + +inline CmdArgs& CmdArgs::operator<<(const StringView &arg) { + _argv.push_back(arg.data()); + _argv_len.push_back(arg.size()); + + return *this; +} + +template +inline CmdArgs& CmdArgs::operator<<(const std::pair &range) { + return _append(IsKvPair())>::type>(), range); +} + +template ::type>::value, + int>::type> +inline CmdArgs& CmdArgs::operator<<(T &&arg) { + return _append(std::to_string(std::forward(arg))); +} + +template +auto CmdArgs::operator<<(const std::tuple &arg) -> + typename std::enable_if::type { + operator<<(std::get(arg)); + + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(std::string arg) { + _args.push_back(std::move(arg)); + return operator<<(_args.back()); +} + +inline CmdArgs& CmdArgs::_append(const StringView &arg) { + return operator<<(arg); +} + +inline CmdArgs& CmdArgs::_append(const char *arg) { + return operator<<(arg); +} + +template +CmdArgs& CmdArgs::_append(std::false_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << *first; + ++first; + } + + return *this; +} + +template +CmdArgs& CmdArgs::_append(std::true_type, const std::pair &range) { + auto first = range.first; + auto last = range.second; + while (first != last) { + *this << first->first << first->second; + ++first; + } + + return *this; +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h new file mode 100644 index 000000000..ca766c086 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h @@ -0,0 +1,211 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H +#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H + +#include +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class UpdateType { + EXIST, + NOT_EXIST, + ALWAYS +}; + +enum class InsertPosition { + BEFORE, + AFTER +}; + +enum class BoundType { + CLOSED, + OPEN, + LEFT_OPEN, + RIGHT_OPEN +}; + +// (-inf, +inf) +template +class UnboundedInterval; + +// [min, max], (min, max), (min, max], [min, max) +template +class BoundedInterval; + +// [min, +inf), (min, +inf) +template +class LeftBoundedInterval; + +// (-inf, max], (-inf, max) +template +class RightBoundedInterval; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(double min, double max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(double min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(double max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +template <> +class UnboundedInterval { +public: + const std::string& min() const; + + const std::string& max() const; +}; + +template <> +class BoundedInterval { +public: + BoundedInterval(const std::string &min, const std::string &max, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const { + return _max; + } + +private: + std::string _min; + std::string _max; +}; + +template <> +class LeftBoundedInterval { +public: + LeftBoundedInterval(const std::string &min, BoundType type); + + const std::string& min() const { + return _min; + } + + const std::string& max() const; + +private: + std::string _min; +}; + +template <> +class RightBoundedInterval { +public: + RightBoundedInterval(const std::string &max, BoundType type); + + const std::string& min() const; + + const std::string& max() const { + return _max; + } + +private: + std::string _max; +}; + +struct LimitOptions { + long long offset = 0; + long long count = -1; +}; + +enum class Aggregation { + SUM, + MIN, + MAX +}; + +enum class BitOp { + AND, + OR, + XOR, + NOT +}; + +enum class GeoUnit { + M, + KM, + MI, + FT +}; + +template +struct WithCoord : TupleWithType, T> {}; + +template +struct WithDist : TupleWithType {}; + +template +struct WithHash : TupleWithType {}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h new file mode 100644 index 000000000..5ad419225 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h @@ -0,0 +1,194 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "reply.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +enum class ConnectionType { + TCP = 0, + UNIX +}; + +struct ConnectionOptions { +public: + ConnectionOptions() = default; + + explicit ConnectionOptions(const std::string &uri); + + ConnectionOptions(const ConnectionOptions &) = default; + ConnectionOptions& operator=(const ConnectionOptions &) = default; + + ConnectionOptions(ConnectionOptions &&) = default; + ConnectionOptions& operator=(ConnectionOptions &&) = default; + + ~ConnectionOptions() = default; + + ConnectionType type = ConnectionType::TCP; + + std::string host; + + int port = 6379; + + std::string path; + + std::string password; + + int db = 0; + + bool keep_alive = false; + + std::chrono::milliseconds connect_timeout{0}; + + std::chrono::milliseconds socket_timeout{0}; + +private: + ConnectionOptions _parse_options(const std::string &uri) const; + + ConnectionOptions _parse_tcp_options(const std::string &path) const; + + ConnectionOptions _parse_unix_options(const std::string &path) const; + + auto _split_string(const std::string &str, const std::string &delimiter) const -> + std::pair; +}; + +class CmdArgs; + +class Connection { +public: + explicit Connection(const ConnectionOptions &opts); + + Connection(const Connection &) = delete; + Connection& operator=(const Connection &) = delete; + + Connection(Connection &&) = default; + Connection& operator=(Connection &&) = default; + + ~Connection() = default; + + // Check if the connection is broken. Client needs to do this check + // before sending some command to the connection. If it's broken, + // client needs to reconnect it. + bool broken() const noexcept { + return _ctx->err != REDIS_OK; + } + + void reset() noexcept { + _ctx->err = 0; + } + + void reconnect(); + + auto last_active() const + -> std::chrono::time_point { + return _last_active; + } + + template + void send(const char *format, Args &&...args); + + void send(int argc, const char **argv, const std::size_t *argv_len); + + void send(CmdArgs &args); + + ReplyUPtr recv(); + + const ConnectionOptions& options() const { + return _opts; + } + + friend void swap(Connection &lhs, Connection &rhs) noexcept; + +private: + class Connector; + + struct ContextDeleter { + void operator()(redisContext *context) const { + if (context != nullptr) { + redisFree(context); + } + }; + }; + + using ContextUPtr = std::unique_ptr; + + void _set_options(); + + void _auth(); + + void _select_db(); + + redisContext* _context(); + + ContextUPtr _ctx; + + // The time that the connection is created or the time that + // the connection is used, i.e. *context()* is called. + std::chrono::time_point _last_active{}; + + ConnectionOptions _opts; +}; + +using ConnectionSPtr = std::shared_ptr; + +enum class Role { + MASTER, + SLAVE +}; + +// Inline implementaions. + +template +inline void Connection::send(const char *format, Args &&...args) { + auto ctx = _context(); + + assert(ctx != nullptr); + + if (redisAppendCommand(ctx, + format, + std::forward(args)...) != REDIS_OK) { + throw_error(*ctx, "Failed to send command"); + } + + assert(!broken()); +} + +inline redisContext* Connection::_context() { + _last_active = std::chrono::steady_clock::now(); + + return _ctx.get(); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h new file mode 100644 index 000000000..6f2663ad7 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H +#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +struct ConnectionPoolOptions { + // Max number of connections, including both in-use and idle ones. + std::size_t size = 1; + + // Max time to wait for a connection. 0ms means client waits forever. + std::chrono::milliseconds wait_timeout{0}; + + // Max lifetime of a connection. 0ms means we never expire the connection. + std::chrono::milliseconds connection_lifetime{0}; +}; + +class ConnectionPool { +public: + ConnectionPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool(SimpleSentinel sentinel, + const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + ConnectionPool() = default; + + ConnectionPool(ConnectionPool &&that); + ConnectionPool& operator=(ConnectionPool &&that); + + ConnectionPool(const ConnectionPool &) = delete; + ConnectionPool& operator=(const ConnectionPool &) = delete; + + ~ConnectionPool() = default; + + // Fetch a connection from pool. + Connection fetch(); + + ConnectionOptions connection_options(); + + void release(Connection connection); + + // Create a new connection. + Connection create(); + +private: + void _move(ConnectionPool &&that); + + // NOT thread-safe + Connection _create(); + + Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked); + + Connection _fetch(); + + void _wait_for_connection(std::unique_lock &lock); + + bool _need_reconnect(const Connection &connection, + const std::chrono::milliseconds &connection_lifetime) const; + + void _update_connection_opts(const std::string &host, int port) { + _opts.host = host; + _opts.port = port; + } + + bool _role_changed(const ConnectionOptions &opts) const { + return opts.port != _opts.port || opts.host != _opts.host; + } + + ConnectionOptions _opts; + + ConnectionPoolOptions _pool_opts; + + std::deque _pool; + + std::size_t _used_connections = 0; + + std::mutex _mutex; + + std::condition_variable _cv; + + SimpleSentinel _sentinel; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h new file mode 100644 index 000000000..44d629e50 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h @@ -0,0 +1,159 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H +#define SEWENEW_REDISPLUSPLUS_ERRORS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +enum ReplyErrorType { + ERR, + MOVED, + ASK +}; + +class Error : public std::exception { +public: + explicit Error(const std::string &msg) : _msg(msg) {} + + Error(const Error &) = default; + Error& operator=(const Error &) = default; + + Error(Error &&) = default; + Error& operator=(Error &&) = default; + + virtual ~Error() = default; + + virtual const char* what() const noexcept { + return _msg.data(); + } + +private: + std::string _msg; +}; + +class IoError : public Error { +public: + explicit IoError(const std::string &msg) : Error(msg) {} + + IoError(const IoError &) = default; + IoError& operator=(const IoError &) = default; + + IoError(IoError &&) = default; + IoError& operator=(IoError &&) = default; + + virtual ~IoError() = default; +}; + +class TimeoutError : public IoError { +public: + explicit TimeoutError(const std::string &msg) : IoError(msg) {} + + TimeoutError(const TimeoutError &) = default; + TimeoutError& operator=(const TimeoutError &) = default; + + TimeoutError(TimeoutError &&) = default; + TimeoutError& operator=(TimeoutError &&) = default; + + virtual ~TimeoutError() = default; +}; + +class ClosedError : public Error { +public: + explicit ClosedError(const std::string &msg) : Error(msg) {} + + ClosedError(const ClosedError &) = default; + ClosedError& operator=(const ClosedError &) = default; + + ClosedError(ClosedError &&) = default; + ClosedError& operator=(ClosedError &&) = default; + + virtual ~ClosedError() = default; +}; + +class ProtoError : public Error { +public: + explicit ProtoError(const std::string &msg) : Error(msg) {} + + ProtoError(const ProtoError &) = default; + ProtoError& operator=(const ProtoError &) = default; + + ProtoError(ProtoError &&) = default; + ProtoError& operator=(ProtoError &&) = default; + + virtual ~ProtoError() = default; +}; + +class OomError : public Error { +public: + explicit OomError(const std::string &msg) : Error(msg) {} + + OomError(const OomError &) = default; + OomError& operator=(const OomError &) = default; + + OomError(OomError &&) = default; + OomError& operator=(OomError &&) = default; + + virtual ~OomError() = default; +}; + +class ReplyError : public Error { +public: + explicit ReplyError(const std::string &msg) : Error(msg) {} + + ReplyError(const ReplyError &) = default; + ReplyError& operator=(const ReplyError &) = default; + + ReplyError(ReplyError &&) = default; + ReplyError& operator=(ReplyError &&) = default; + + virtual ~ReplyError() = default; +}; + +class WatchError : public Error { +public: + explicit WatchError() : Error("Watched key has been modified") {} + + WatchError(const WatchError &) = default; + WatchError& operator=(const WatchError &) = default; + + WatchError(WatchError &&) = default; + WatchError& operator=(WatchError &&) = default; + + virtual ~WatchError() = default; +}; + + +// MovedError and AskError are defined in shards.h +class MovedError; + +class AskError; + +void throw_error(redisContext &context, const std::string &err_info); + +void throw_error(const redisReply &reply); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h new file mode 100644 index 000000000..52b01253f --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H +#define SEWENEW_REDISPLUSPLUS_PIPELINE_H + +#include +#include +#include "connection.h" + +namespace sw { + +namespace redis { + +class PipelineImpl { +public: + template + void command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + } + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t /*cmd_num*/) { + // Reconnect to Redis to discard all commands. + connection.reconnect(); + } +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h new file mode 100644 index 000000000..71d975ee3 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h @@ -0,0 +1,1844 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H + +#include +#include +#include +#include +#include "connection.h" +#include "utils.h" +#include "reply.h" +#include "command.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +class QueuedReplies; + +// If any command throws, QueuedRedis resets the connection, and becomes invalid. +// In this case, the only thing we can do is to destory the QueuedRedis object. +template +class QueuedRedis { +public: + QueuedRedis(QueuedRedis &&) = default; + QueuedRedis& operator=(QueuedRedis &&) = default; + + // When it destructs, the underlying *Connection* will be closed, + // and any command that has NOT been executed will be ignored. + ~QueuedRedis() = default; + + Redis redis(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type; + + template + QueuedRedis& command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type; + + QueuedReplies exec(); + + void discard(); + + // CONNECTION commands. + + QueuedRedis& auth(const StringView &password) { + return command(cmd::auth, password); + } + + QueuedRedis& echo(const StringView &msg) { + return command(cmd::echo, msg); + } + + QueuedRedis& ping() { + return command(cmd::ping); + } + + QueuedRedis& ping(const StringView &msg) { + return command(cmd::ping, msg); + } + + // We DO NOT support the QUIT command. See *Redis::quit* doc for details. + // + // QueuedRedis& quit(); + + QueuedRedis& select(long long idx) { + return command(cmd::select, idx); + } + + QueuedRedis& swapdb(long long idx1, long long idx2) { + return command(cmd::swapdb, idx1, idx2); + } + + // SERVER commands. + + QueuedRedis& bgrewriteaof() { + return command(cmd::bgrewriteaof); + } + + QueuedRedis& bgsave() { + return command(cmd::bgsave); + } + + QueuedRedis& dbsize() { + return command(cmd::dbsize); + } + + QueuedRedis& flushall(bool async = false) { + return command(cmd::flushall, async); + } + + QueuedRedis& flushdb(bool async = false) { + return command(cmd::flushdb, async); + } + + QueuedRedis& info() { + return command(cmd::info); + } + + QueuedRedis& info(const StringView §ion) { + return command(cmd::info, section); + } + + QueuedRedis& lastsave() { + return command(cmd::lastsave); + } + + QueuedRedis& save() { + return command(cmd::save); + } + + // KEY commands. + + QueuedRedis& del(const StringView &key) { + return command(cmd::del, key); + } + + template + QueuedRedis& del(Input first, Input last) { + return command(cmd::del_range, first, last); + } + + template + QueuedRedis& del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + QueuedRedis& dump(const StringView &key) { + return command(cmd::dump, key); + } + + QueuedRedis& exists(const StringView &key) { + return command(cmd::exists, key); + } + + template + QueuedRedis& exists(Input first, Input last) { + return command(cmd::exists_range, first, last); + } + + template + QueuedRedis& exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + QueuedRedis& expire(const StringView &key, long long timeout) { + return command(cmd::expire, key, timeout); + } + + QueuedRedis& expire(const StringView &key, + const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); + } + + QueuedRedis& expireat(const StringView &key, long long timestamp) { + return command(cmd::expireat, key, timestamp); + } + + QueuedRedis& expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& keys(const StringView &pattern) { + return command(cmd::keys, pattern); + } + + QueuedRedis& move(const StringView &key, long long db) { + return command(cmd::move, key, db); + } + + QueuedRedis& persist(const StringView &key) { + return command(cmd::persist, key); + } + + QueuedRedis& pexpire(const StringView &key, long long timeout) { + return command(cmd::pexpire, key, timeout); + } + + QueuedRedis& pexpire(const StringView &key, + const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); + } + + QueuedRedis& pexpireat(const StringView &key, long long timestamp) { + return command(cmd::pexpireat, key, timestamp); + } + + QueuedRedis& pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); + } + + QueuedRedis& pttl(const StringView &key) { + return command(cmd::pttl, key); + } + + QueuedRedis& randomkey() { + return command(cmd::randomkey); + } + + QueuedRedis& rename(const StringView &key, const StringView &newkey) { + return command(cmd::rename, key, newkey); + } + + QueuedRedis& renamenx(const StringView &key, const StringView &newkey) { + return command(cmd::renamenx, key, newkey); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false) { + return command(cmd::restore, key, val, ttl, replace); + } + + QueuedRedis& restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false) { + return restore(key, val, ttl.count(), replace); + } + + // TODO: sort + + QueuedRedis& scan(long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::scan, cursor, pattern, count); + } + + QueuedRedis& scan(long long cursor) { + return scan(cursor, "*", 10); + } + + QueuedRedis& scan(long long cursor, + const StringView &pattern) { + return scan(cursor, pattern, 10); + } + + QueuedRedis& scan(long long cursor, + long long count) { + return scan(cursor, "*", count); + } + + QueuedRedis& touch(const StringView &key) { + return command(cmd::touch, key); + } + + template + QueuedRedis& touch(Input first, Input last) { + return command(cmd::touch_range, first, last); + } + + template + QueuedRedis& touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + QueuedRedis& ttl(const StringView &key) { + return command(cmd::ttl, key); + } + + QueuedRedis& type(const StringView &key) { + return command(cmd::type, key); + } + + QueuedRedis& unlink(const StringView &key) { + return command(cmd::unlink, key); + } + + template + QueuedRedis& unlink(Input first, Input last) { + return command(cmd::unlink_range, first, last); + } + + template + QueuedRedis& unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + QueuedRedis& wait(long long numslaves, long long timeout) { + return command(cmd::wait, numslaves, timeout); + } + + QueuedRedis& wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); + } + + // STRING commands. + + QueuedRedis& append(const StringView &key, const StringView &str) { + return command(cmd::append, key, str); + } + + QueuedRedis& bitcount(const StringView &key, + long long start = 0, + long long end = -1) { + return command(cmd::bitcount, key, start, end); + } + + QueuedRedis& bitop(BitOp op, + const StringView &destination, + const StringView &key) { + return command(cmd::bitop, op, destination, key); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + Input first, + Input last) { + return command(cmd::bitop_range, op, destination, first, last); + } + + template + QueuedRedis& bitop(BitOp op, + const StringView &destination, + std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + QueuedRedis& bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1) { + return command(cmd::bitpos, key, bit, start, end); + } + + QueuedRedis& decr(const StringView &key) { + return command(cmd::decr, key); + } + + QueuedRedis& decrby(const StringView &key, long long decrement) { + return command(cmd::decrby, key, decrement); + } + + QueuedRedis& get(const StringView &key) { + return command(cmd::get, key); + } + + QueuedRedis& getbit(const StringView &key, long long offset) { + return command(cmd::getbit, key, offset); + } + + QueuedRedis& getrange(const StringView &key, long long start, long long end) { + return command(cmd::getrange, key, start, end); + } + + QueuedRedis& getset(const StringView &key, const StringView &val) { + return command(cmd::getset, key, val); + } + + QueuedRedis& incr(const StringView &key) { + return command(cmd::incr, key); + } + + QueuedRedis& incrby(const StringView &key, long long increment) { + return command(cmd::incrby, key, increment); + } + + QueuedRedis& incrbyfloat(const StringView &key, double increment) { + return command(cmd::incrbyfloat, key, increment); + } + + template + QueuedRedis& mget(Input first, Input last) { + return command(cmd::mget, first, last); + } + + template + QueuedRedis& mget(std::initializer_list il) { + return mget(il.begin(), il.end()); + } + + template + QueuedRedis& mset(Input first, Input last) { + return command(cmd::mset, first, last); + } + + template + QueuedRedis& mset(std::initializer_list il) { + return mset(il.begin(), il.end()); + } + + template + QueuedRedis& msetnx(Input first, Input last) { + return command(cmd::msetnx, first, last); + } + + template + QueuedRedis& msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + QueuedRedis& psetex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::psetex, key, ttl, val); + } + + QueuedRedis& psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); + } + + QueuedRedis& set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS) { + _set_cmd_indexes.push_back(_cmd_num); + + return command(cmd::set, key, val, ttl.count(), type); + } + + QueuedRedis& setex(const StringView &key, + long long ttl, + const StringView &val) { + return command(cmd::setex, key, ttl, val); + } + + QueuedRedis& setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + return setex(key, ttl.count(), val); + } + + QueuedRedis& setnx(const StringView &key, const StringView &val) { + return command(cmd::setnx, key, val); + } + + QueuedRedis& setrange(const StringView &key, + long long offset, + const StringView &val) { + return command(cmd::setrange, key, offset, val); + } + + QueuedRedis& strlen(const StringView &key) { + return command(cmd::strlen, key); + } + + // LIST commands. + + QueuedRedis& blpop(const StringView &key, long long timeout) { + return command(cmd::blpop, key, timeout); + } + + QueuedRedis& blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(key, timeout.count()); + } + + template + QueuedRedis& blpop(Input first, Input last, long long timeout) { + return command(cmd::blpop_range, first, last, timeout); + } + + template + QueuedRedis& blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(first, last, timeout.count()); + } + + template + QueuedRedis& blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpop(const StringView &key, long long timeout) { + return command(cmd::brpop, key, timeout); + } + + QueuedRedis& brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(key, timeout.count()); + } + + template + QueuedRedis& brpop(Input first, Input last, long long timeout) { + return command(cmd::brpop_range, first, last, timeout); + } + + template + QueuedRedis& brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(first, last, timeout.count()); + } + + template + QueuedRedis& brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + long long timeout) { + return command(cmd::brpoplpush, source, destination, timeout); + } + + QueuedRedis& brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpoplpush(source, destination, timeout.count()); + } + + QueuedRedis& lindex(const StringView &key, long long index) { + return command(cmd::lindex, key, index); + } + + QueuedRedis& linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val) { + return command(cmd::linsert, key, position, pivot, val); + } + + QueuedRedis& llen(const StringView &key) { + return command(cmd::llen, key); + } + + QueuedRedis& lpop(const StringView &key) { + return command(cmd::lpop, key); + } + + QueuedRedis& lpush(const StringView &key, const StringView &val) { + return command(cmd::lpush, key, val); + } + + template + QueuedRedis& lpush(const StringView &key, Input first, Input last) { + return command(cmd::lpush_range, key, first, last); + } + + template + QueuedRedis& lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + QueuedRedis& lpushx(const StringView &key, const StringView &val) { + return command(cmd::lpushx, key, val); + } + + QueuedRedis& lrange(const StringView &key, + long long start, + long long stop) { + return command(cmd::lrange, key, start, stop); + } + + QueuedRedis& lrem(const StringView &key, long long count, const StringView &val) { + return command(cmd::lrem, key, count, val); + } + + QueuedRedis& lset(const StringView &key, long long index, const StringView &val) { + return command(cmd::lset, key, index, val); + } + + QueuedRedis& ltrim(const StringView &key, long long start, long long stop) { + return command(cmd::ltrim, key, start, stop); + } + + QueuedRedis& rpop(const StringView &key) { + return command(cmd::rpop, key); + } + + QueuedRedis& rpoplpush(const StringView &source, const StringView &destination) { + return command(cmd::rpoplpush, source, destination); + } + + QueuedRedis& rpush(const StringView &key, const StringView &val) { + return command(cmd::rpush, key, val); + } + + template + QueuedRedis& rpush(const StringView &key, Input first, Input last) { + return command(cmd::rpush_range, key, first, last); + } + + template + QueuedRedis& rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + QueuedRedis& rpushx(const StringView &key, const StringView &val) { + return command(cmd::rpushx, key, val); + } + + // HASH commands. + + QueuedRedis& hdel(const StringView &key, const StringView &field) { + return command(cmd::hdel, key, field); + } + + template + QueuedRedis& hdel(const StringView &key, Input first, Input last) { + return command(cmd::hdel_range, key, first, last); + } + + template + QueuedRedis& hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + QueuedRedis& hexists(const StringView &key, const StringView &field) { + return command(cmd::hexists, key, field); + } + + QueuedRedis& hget(const StringView &key, const StringView &field) { + return command(cmd::hget, key, field); + } + + QueuedRedis& hgetall(const StringView &key) { + return command(cmd::hgetall, key); + } + + QueuedRedis& hincrby(const StringView &key, + const StringView &field, + long long increment) { + return command(cmd::hincrby, key, field, increment); + } + + QueuedRedis& hincrbyfloat(const StringView &key, + const StringView &field, + double increment) { + return command(cmd::hincrbyfloat, key, field, increment); + } + + QueuedRedis& hkeys(const StringView &key) { + return command(cmd::hkeys, key); + } + + QueuedRedis& hlen(const StringView &key) { + return command(cmd::hlen, key); + } + + template + QueuedRedis& hmget(const StringView &key, Input first, Input last) { + return command(cmd::hmget, key, first, last); + } + + template + QueuedRedis& hmget(const StringView &key, std::initializer_list il) { + return hmget(key, il.begin(), il.end()); + } + + template + QueuedRedis& hmset(const StringView &key, Input first, Input last) { + return command(cmd::hmset, key, first, last); + } + + template + QueuedRedis& hmset(const StringView &key, std::initializer_list il) { + return hmset(key, il.begin(), il.end()); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::hscan, key, cursor, pattern, count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return hscan(key, cursor, pattern, 10); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor, + long long count) { + return hscan(key, cursor, "*", count); + } + + QueuedRedis& hscan(const StringView &key, + long long cursor) { + return hscan(key, cursor, "*", 10); + } + + QueuedRedis& hset(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hset, key, field, val); + } + + QueuedRedis& hset(const StringView &key, const std::pair &item) { + return hset(key, item.first, item.second); + } + + QueuedRedis& hsetnx(const StringView &key, const StringView &field, const StringView &val) { + return command(cmd::hsetnx, key, field, val); + } + + QueuedRedis& hsetnx(const StringView &key, const std::pair &item) { + return hsetnx(key, item.first, item.second); + } + + QueuedRedis& hstrlen(const StringView &key, const StringView &field) { + return command(cmd::hstrlen, key, field); + } + + QueuedRedis& hvals(const StringView &key) { + return command(cmd::hvals, key); + } + + // SET commands. + + QueuedRedis& sadd(const StringView &key, const StringView &member) { + return command(cmd::sadd, key, member); + } + + template + QueuedRedis& sadd(const StringView &key, Input first, Input last) { + return command(cmd::sadd_range, key, first, last); + } + + template + QueuedRedis& sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + QueuedRedis& scard(const StringView &key) { + return command(cmd::scard, key); + } + + template + QueuedRedis& sdiff(Input first, Input last) { + return command(cmd::sdiff, first, last); + } + + template + QueuedRedis& sdiff(std::initializer_list il) { + return sdiff(il.begin(), il.end()); + } + + QueuedRedis& sdiffstore(const StringView &destination, const StringView &key) { + return command(cmd::sdiffstore, destination, key); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sdiffstore_range, destination, first, last); + } + + template + QueuedRedis& sdiffstore(const StringView &destination, std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + QueuedRedis& sinter(Input first, Input last) { + return command(cmd::sinter, first, last); + } + + template + QueuedRedis& sinter(std::initializer_list il) { + return sinter(il.begin(), il.end()); + } + + QueuedRedis& sinterstore(const StringView &destination, const StringView &key) { + return command(cmd::sinterstore, destination, key); + } + + template + QueuedRedis& sinterstore(const StringView &destination, + Input first, + Input last) { + return command(cmd::sinterstore_range, destination, first, last); + } + + template + QueuedRedis& sinterstore(const StringView &destination, std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + QueuedRedis& sismember(const StringView &key, const StringView &member) { + return command(cmd::sismember, key, member); + } + + QueuedRedis& smembers(const StringView &key) { + return command(cmd::smembers, key); + } + + QueuedRedis& smove(const StringView &source, + const StringView &destination, + const StringView &member) { + return command(cmd::smove, source, destination, member); + } + + QueuedRedis& spop(const StringView &key) { + return command(cmd::spop, key); + } + + QueuedRedis& spop(const StringView &key, long long count) { + return command(cmd::spop_range, key, count); + } + + QueuedRedis& srandmember(const StringView &key) { + return command(cmd::srandmember, key); + } + + QueuedRedis& srandmember(const StringView &key, long long count) { + return command(cmd::srandmember_range, key, count); + } + + QueuedRedis& srem(const StringView &key, const StringView &member) { + return command(cmd::srem, key, member); + } + + template + QueuedRedis& srem(const StringView &key, Input first, Input last) { + return command(cmd::srem_range, key, first, last); + } + + template + QueuedRedis& srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::sscan, key, cursor, pattern, count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return sscan(key, cursor, pattern, 10); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor, + long long count) { + return sscan(key, cursor, "*", count); + } + + QueuedRedis& sscan(const StringView &key, + long long cursor) { + return sscan(key, cursor, "*", 10); + } + + template + QueuedRedis& sunion(Input first, Input last) { + return command(cmd::sunion, first, last); + } + + template + QueuedRedis& sunion(std::initializer_list il) { + return sunion(il.begin(), il.end()); + } + + QueuedRedis& sunionstore(const StringView &destination, const StringView &key) { + return command(cmd::sunionstore, destination, key); + } + + template + QueuedRedis& sunionstore(const StringView &destination, Input first, Input last) { + return command(cmd::sunionstore_range, destination, first, last); + } + + template + QueuedRedis& sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + QueuedRedis& bzpopmax(const StringView &key, long long timeout) { + return command(cmd::bzpopmax, key, timeout); + } + + QueuedRedis& bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(key, timeout.count()); + } + + template + QueuedRedis& bzpopmax(Input first, Input last, long long timeout) { + return command(cmd::bzpopmax_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, long long timeout) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmax(il.begin(), il.end(), timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, long long timeout) { + return command(cmd::bzpopmin, key, timeout); + } + + QueuedRedis& bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(key, timeout.count()); + } + + template + QueuedRedis& bzpopmin(Input first, Input last, long long timeout) { + return command(cmd::bzpopmin_range, first, last, timeout); + } + + template + QueuedRedis& bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(first, last, timeout.count()); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, long long timeout) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + QueuedRedis& bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + QueuedRedis& zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd, key, member, score, type, changed); + } + + template + QueuedRedis& zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return command(cmd::zadd_range, key, first, last, type, changed); + } + + QueuedRedis& zcard(const StringView &key) { + return command(cmd::zcard, key); + } + + template + QueuedRedis& zcount(const StringView &key, const Interval &interval) { + return command(cmd::zcount, key, interval); + } + + QueuedRedis& zincrby(const StringView &key, double increment, const StringView &member) { + return command(cmd::zincrby, key, increment, member); + } + + QueuedRedis& zinterstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zinterstore, destination, key, weight); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zinterstore_range, destination, first, last, type); + } + + template + QueuedRedis& zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + QueuedRedis& zlexcount(const StringView &key, const Interval &interval) { + return command(cmd::zlexcount, key, interval); + } + + QueuedRedis& zpopmax(const StringView &key) { + return command(cmd::zpopmax, key, 1); + } + + QueuedRedis& zpopmax(const StringView &key, long long count) { + return command(cmd::zpopmax, key, count); + } + + QueuedRedis& zpopmin(const StringView &key) { + return command(cmd::zpopmin, key, 1); + } + + QueuedRedis& zpopmin(const StringView &key, long long count) { + return command(cmd::zpopmin, key, count); + } + + // NOTE: *QueuedRedis::zrange*'s parameters are different from *Redis::zrange*. + // *Redis::zrange* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::zrange*. So we have to use an extra parameter: *with_scores*, + // to decide whether we should send *WITHSCORES* option to Redis. This also applies to + // other commands with the *WITHSCORES* option, e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, + // *ZREVRANGEBYSCORE*. + QueuedRedis& zrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrangebylex(const StringView &key, const Interval &interval) { + return zrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrank(const StringView &key, const StringView &member) { + return command(cmd::zrank, key, member); + } + + QueuedRedis& zrem(const StringView &key, const StringView &member) { + return command(cmd::zrem, key, member); + } + + template + QueuedRedis& zrem(const StringView &key, Input first, Input last) { + return command(cmd::zrem_range, key, first, last); + } + + template + QueuedRedis& zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + QueuedRedis& zremrangebylex(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebylex, key, interval); + } + + QueuedRedis& zremrangebyrank(const StringView &key, long long start, long long stop) { + return command(cmd::zremrangebyrank, key, start, stop); + } + + template + QueuedRedis& zremrangebyscore(const StringView &key, const Interval &interval) { + return command(cmd::zremrangebyscore, key, interval); + } + + // See comments on *ZRANGE*. + QueuedRedis& zrevrange(const StringView &key, + long long start, + long long stop, + bool with_scores = false) { + return command(cmd::zrevrange, key, start, stop, with_scores); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts) { + return command(cmd::zrevrangebylex, key, interval, opts); + } + + template + QueuedRedis& zrevrangebylex(const StringView &key, const Interval &interval) { + return zrevrangebylex(key, interval, {}); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + bool with_scores = false) { + return command(cmd::zrevrangebyscore, key, interval, opts, with_scores); + } + + // See comments on *ZRANGE*. + template + QueuedRedis& zrevrangebyscore(const StringView &key, + const Interval &interval, + bool with_scores = false) { + return zrevrangebyscore(key, interval, {}, with_scores); + } + + QueuedRedis& zrevrank(const StringView &key, const StringView &member) { + return command(cmd::zrevrank, key, member); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count) { + return command(cmd::zscan, key, cursor, pattern, count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + const StringView &pattern) { + return zscan(key, cursor, pattern, 10); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor, + long long count) { + return zscan(key, cursor, "*", count); + } + + QueuedRedis& zscan(const StringView &key, + long long cursor) { + return zscan(key, cursor, "*", 10); + } + + QueuedRedis& zscore(const StringView &key, const StringView &member) { + return command(cmd::zscore, key, member); + } + + QueuedRedis& zunionstore(const StringView &destination, + const StringView &key, + double weight) { + return command(cmd::zunionstore, destination, key, weight); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM) { + return command(cmd::zunionstore_range, destination, first, last, type); + } + + template + QueuedRedis& zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + QueuedRedis& pfadd(const StringView &key, const StringView &element) { + return command(cmd::pfadd, key, element); + } + + template + QueuedRedis& pfadd(const StringView &key, Input first, Input last) { + return command(cmd::pfadd_range, key, first, last); + } + + template + QueuedRedis& pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + QueuedRedis& pfcount(const StringView &key) { + return command(cmd::pfcount, key); + } + + template + QueuedRedis& pfcount(Input first, Input last) { + return command(cmd::pfcount_range, first, last); + } + + template + QueuedRedis& pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + QueuedRedis& pfmerge(const StringView &destination, const StringView &key) { + return command(cmd::pfmerge, destination, key); + } + + template + QueuedRedis& pfmerge(const StringView &destination, Input first, Input last) { + return command(cmd::pfmerge_range, destination, first, last); + } + + template + QueuedRedis& pfmerge(const StringView &destination, std::initializer_list il) { + return pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + QueuedRedis& geoadd(const StringView &key, + const std::tuple &member) { + return command(cmd::geoadd, key, member); + } + + template + QueuedRedis& geoadd(const StringView &key, + Input first, + Input last) { + return command(cmd::geoadd_range, key, first, last); + } + + template + QueuedRedis& geoadd(const StringView &key, std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + QueuedRedis& geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M) { + return command(cmd::geodist, key, member1, member2, unit); + } + + template + QueuedRedis& geohash(const StringView &key, Input first, Input last) { + return command(cmd::geohash_range, key, first, last); + } + + template + QueuedRedis& geohash(const StringView &key, std::initializer_list il) { + return geohash(key, il.begin(), il.end()); + } + + template + QueuedRedis& geopos(const StringView &key, Input first, Input last) { + return command(cmd::geopos_range, key, first, last); + } + + template + QueuedRedis& geopos(const StringView &key, std::initializer_list il) { + return geopos(key, il.begin(), il.end()); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadius_store, + key, + loc, + radius, + unit, + destination, + store_dist, + count); + } + + // NOTE: *QueuedRedis::georadius*'s parameters are different from *Redis::georadius*. + // *Redis::georadius* is overloaded by the output iterator, however, there's no such + // iterator in *QueuedRedis::georadius*. So we have to use extra parameters to decide + // whether we should send options to Redis. This also applies to *GEORADIUSBYMEMBER*. + QueuedRedis& georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count) { + _georadius_cmd_indexes.push_back(_cmd_num); + + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + destination, + store_dist, + count); + } + + // See the comments on *GEORADIUS*. + QueuedRedis& georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + bool with_coord, + bool with_dist, + bool with_hash) { + return command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + with_coord, + with_dist, + with_hash); + } + + // SCRIPTING commands. + + QueuedRedis& eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::eval, script, keys, args); + } + + QueuedRedis& evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + return command(cmd::evalsha, script, keys, args); + } + + template + QueuedRedis& script_exists(Input first, Input last) { + return command(cmd::script_exists_range, first, last); + } + + template + QueuedRedis& script_exists(std::initializer_list il) { + return script_exists(il.begin(), il.end()); + } + + QueuedRedis& script_flush() { + return command(cmd::script_flush); + } + + QueuedRedis& script_kill() { + return command(cmd::script_kill); + } + + QueuedRedis& script_load(const StringView &script) { + return command(cmd::script_load, script); + } + + // PUBSUB commands. + + QueuedRedis& publish(const StringView &channel, const StringView &message) { + return command(cmd::publish, channel, message); + } + + // Stream commands. + + QueuedRedis& xack(const StringView &key, const StringView &group, const StringView &id) { + return command(cmd::xack, key, group, id); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, Input first, Input last) { + return command(cmd::xack_range, key, group, first, last); + } + + template + QueuedRedis& xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, Input first, Input last) { + return command(cmd::xadd_range, key, id, first, last); + } + + template + QueuedRedis& xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true) { + return command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + } + + template + QueuedRedis& xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id) { + return command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last) { + return command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + } + + template + QueuedRedis& xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il) { + return xclaim(key, group, consumer, min_idle_time, il.begin(), il.end()); + } + + QueuedRedis& xdel(const StringView &key, const StringView &id) { + return command(cmd::xdel, key, id); + } + + template + QueuedRedis& xdel(const StringView &key, Input first, Input last) { + return command(cmd::xdel_range, key, first, last); + } + + template + QueuedRedis& xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + QueuedRedis& xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false) { + return command(cmd::xgroup_create, key, group, id, mkstream); + } + + QueuedRedis& xgroup_setid(const StringView &key, + const StringView &group, + const StringView &id) { + return command(cmd::xgroup_setid, key, group, id); + } + + QueuedRedis& xgroup_destroy(const StringView &key, const StringView &group) { + return command(cmd::xgroup_destroy, key, group); + } + + QueuedRedis& xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer) { + return command(cmd::xgroup_delconsumer, key, group, consumer); + } + + QueuedRedis& xlen(const StringView &key) { + return command(cmd::xlen, key); + } + + QueuedRedis& xpending(const StringView &key, const StringView &group) { + return command(cmd::xpending, key, group); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xpending_detail, key, group, start, end, count); + } + + QueuedRedis& xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer) { + return command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end) { + return command(cmd::xrange, key, start, end); + } + + QueuedRedis& xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count) { + return command(cmd::xrange, key, start, end, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id, long long count) { + return command(cmd::xread, key, id, count); + } + + QueuedRedis& xread(const StringView &key, const StringView &id) { + return xread(key, id, 0); + } + + template + auto xread(Input first, Input last, long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_range, first, last, count); + } + + template + auto xread(Input first, Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, 0); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return command(cmd::xread_block, key, id, timeout.count(), count); + } + + QueuedRedis& xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xread(key, id, timeout, 0); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xread_block_range, first, last, timeout.count(), count); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xread(first, last, timeout, 0); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack) { + return command(cmd::xreadgroup, group, consumer, key, id, count, noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count) { + return xreadgroup(group, consumer, key, id, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first ,last, 0, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) { + return command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count) { + return xreadgroup(group, consumer, key, id, timeout, count, false); + } + + QueuedRedis& xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout) { + return xreadgroup(group, consumer, key, id, timeout, 0, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, count, false); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout) + -> typename std::enable_if::value, + QueuedRedis&>::type { + return xreadgroup(group, consumer, first, last, timeout, 0, false); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start) { + return command(cmd::xrevrange, key, end, start); + } + + QueuedRedis& xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count) { + return command(cmd::xrevrange, key, end, start, count); + } + + QueuedRedis& xtrim(const StringView &key, long long count, bool approx = true) { + return command(cmd::xtrim, key, count, approx); + } + +private: + friend class Redis; + + friend class RedisCluster; + + template + QueuedRedis(const ConnectionSPtr &connection, Args &&...args); + + void _sanity_check() const; + + void _reset(); + + void _invalidate(); + + void _rewrite_replies(std::vector &replies) const; + + template + void _rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const; + + ConnectionSPtr _connection; + + Impl _impl; + + std::size_t _cmd_num = 0; + + std::vector _set_cmd_indexes; + + std::vector _georadius_cmd_indexes; + + bool _valid = true; +}; + +class QueuedReplies { +public: + std::size_t size() const; + + redisReply& get(std::size_t idx); + + template + Result get(std::size_t idx); + + template + void get(std::size_t idx, Output output); + +private: + template + friend class QueuedRedis; + + explicit QueuedReplies(std::vector replies) : _replies(std::move(replies)) {} + + void _index_check(std::size_t idx) const; + + std::vector _replies; +}; + +} + +} + +#include "queued_redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp new file mode 100644 index 000000000..409f48aca --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp @@ -0,0 +1,208 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP + +namespace sw { + +namespace redis { + +template +template +QueuedRedis::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) : + _connection(connection), + _impl(std::forward(args)...) { + assert(_connection); +} + +template +Redis QueuedRedis::redis() { + return Redis(_connection); +} + +template +template +auto QueuedRedis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, + QueuedRedis&>::type { + try { + _sanity_check(); + + _impl.command(*_connection, cmd, std::forward(args)...); + + ++_cmd_num; + } catch (const Error &e) { + _invalidate(); + throw; + } + + return *this; +} + +template +template +QueuedRedis& QueuedRedis::command(const StringView &cmd_name, Args &&...args) { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +template +auto QueuedRedis::command(Input first, Input last) + -> typename std::enable_if::value, QueuedRedis&>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +QueuedReplies QueuedRedis::exec() { + try { + _sanity_check(); + + auto replies = _impl.exec(*_connection, _cmd_num); + + _rewrite_replies(replies); + + _reset(); + + return QueuedReplies(std::move(replies)); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::discard() { + try { + _sanity_check(); + + _impl.discard(*_connection, _cmd_num); + + _reset(); + } catch (const Error &e) { + _invalidate(); + throw; + } +} + +template +void QueuedRedis::_sanity_check() const { + if (!_valid) { + throw Error("Not in valid state"); + } + + if (_connection->broken()) { + throw Error("Connection is broken"); + } +} + +template +inline void QueuedRedis::_reset() { + _cmd_num = 0; + + _set_cmd_indexes.clear(); + + _georadius_cmd_indexes.clear(); +} + +template +void QueuedRedis::_invalidate() { + _valid = false; + + _reset(); +} + +template +void QueuedRedis::_rewrite_replies(std::vector &replies) const { + _rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies); + + _rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies); +} + +template +template +void QueuedRedis::_rewrite_replies(const std::vector &indexes, + Func rewriter, + std::vector &replies) const { + for (auto idx : indexes) { + assert(idx < replies.size()); + + auto &reply = replies[idx]; + + assert(reply); + + rewriter(*reply); + } +} + +inline std::size_t QueuedReplies::size() const { + return _replies.size(); +} + +inline redisReply& QueuedReplies::get(std::size_t idx) { + _index_check(idx); + + auto &reply = _replies[idx]; + + assert(reply); + + return *reply; +} + +template +inline Result QueuedReplies::get(std::size_t idx) { + auto &reply = get(idx); + + return reply::parse(reply); +} + +template +inline void QueuedReplies::get(std::size_t idx, Output output) { + auto &reply = get(idx); + + reply::to_array(reply, output); +} + +inline void QueuedReplies::_index_check(std::size_t idx) const { + if (idx >= size()) { + throw Error("Out of range"); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h new file mode 100644 index 000000000..0da0ebb16 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h @@ -0,0 +1,25 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H +#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H + +#include "redis.h" +#include "redis_cluster.h" +#include "queued_redis.h" +#include "sentinel.h" + +#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h new file mode 100644 index 000000000..b54afb96b --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h @@ -0,0 +1,1523 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_H +#define SEWENEW_REDISPLUSPLUS_REDIS_H + +#include +#include +#include +#include +#include +#include "connection_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "sentinel.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class Redis { +public: + Redis(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : _pool(pool_opts, connection_opts) {} + + // Construct Redis instance with URI: + // "tcp://127.0.0.1", "tcp://127.0.0.1:6379", or "unix://path/to/socket" + explicit Redis(const std::string &uri); + + Redis(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role, + const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(SimpleSentinel(sentinel, master_name, role), pool_opts, connection_opts) {} + + Redis(const Redis &) = delete; + Redis& operator=(const Redis &) = delete; + + Redis(Redis &&) = default; + Redis& operator=(Redis &&) = default; + + Pipeline pipeline(); + + Transaction transaction(bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type; + + template + Result command(const StringView &cmd_name, Args &&...args); + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // CONNECTION commands. + + void auth(const StringView &password); + + std::string echo(const StringView &msg); + + std::string ping(); + + std::string ping(const StringView &msg); + + // After sending QUIT, only the current connection will be close, while + // other connections in the pool is still open. This is a strange behavior. + // So we DO NOT support the QUIT command. If you want to quit connection to + // server, just destroy the Redis object. + // + // void quit(); + + // We get a connection from the pool, and send the SELECT command to switch + // to a specified DB. However, when we try to send other commands to the + // given DB, we might get a different connection from the pool, and these + // commands, in fact, work on other DB. e.g. + // + // redis.select(1); // get a connection from the pool and switch to the 1th DB + // redis.get("key"); // might get another connection from the pool, + // // and try to get 'key' on the default DB + // + // Obviously, this is NOT what we expect. So we DO NOT support SELECT command. + // In order to select a DB, we can specify the DB index with the ConnectionOptions. + // + // However, since Pipeline and Transaction always send multiple commands on a + // single connection, these two classes have a *select* method. + // + // void select(long long idx); + + void swapdb(long long idx1, long long idx2); + + // SERVER commands. + + void bgrewriteaof(); + + void bgsave(); + + long long dbsize(); + + void flushall(bool async = false); + + void flushdb(bool async = false); + + std::string info(); + + std::string info(const StringView §ion); + + long long lastsave(); + + void save(); + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + template + void keys(const StringView &pattern, Output output); + + bool move(const StringView &key, long long db); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + OptionalString randomkey(); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + template + long long scan(long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long scan(long long cursor, + Output output); + + template + long long scan(long long cursor, + const StringView &pattern, + Output output); + + template + long long scan(long long cursor, + long long count, + Output output); + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + long long wait(long long numslaves, long long timeout); + + long long wait(long long numslaves, const std::chrono::milliseconds &timeout); + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + // If *Input* is an iterator of a container of string, + // we use the default weight, i.e. 1, and send + // *ZINTERSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command. + // If *Input* is an iterator of a container of pair, i.e. key-weight pair, + // we send the command with the given weights: + // *ZINTERSTORE destination numkeys key [key ...] + // [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]* + // + // The following code use the default weight: + // + // vector keys = {"k1", "k2", "k3"}; + // redis.zinterstore(destination, keys.begin(), keys.end()); + // + // On the other hand, the following code use the given weights: + // + // vector> keys_with_weights = {{"k1", 1}, {"k2", 2}, {"k3", 3}}; + // redis.zinterstore(destination, keys_with_weights.begin(), keys_with_weights.end()); + // + // NOTE: `keys_with_weights` can also be of type `unordered_map`. + // However, it will be slower than vector>, since we use + // `distance(first, last)` to calculate the *numkeys* parameter. + // + // This also applies to *ZUNIONSTORE* command. + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + // There's no aggregation type parameter for single key overload, since these 3 types + // have the same effect. + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + // See *zinterstore* comment for how to use this method. + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + void script_exists(Input first, Input last, Output output); + + template + void script_exists(std::initializer_list il, Output output) { + script_exists(il.begin(), il.end(), output); + } + + void script_flush(); + + void script_kill(); + + std::string script_load(const StringView &script); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Transaction commands. + void watch(const StringView &key); + + template + void watch(Input first, Input last); + + template + void watch(std::initializer_list il) { + watch(il.begin(), il.end()); + } + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first ,last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class ConnectionPoolGuard { + public: + ConnectionPoolGuard(ConnectionPool &pool, + Connection &connection) : _pool(pool), _connection(connection) {} + + ~ConnectionPoolGuard() { + _pool.release(std::move(_connection)); + } + + private: + ConnectionPool &_pool; + Connection &_connection; + }; + + template + friend class QueuedRedis; + + friend class RedisCluster; + + // For internal use. + explicit Redis(const ConnectionSPtr &connection); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + // Pool Mode. + // Public constructors create a *Redis* instance with a pool. + // In this case, *_connection* is a null pointer, and is never used. + ConnectionPool _pool; + + // Single Connection Mode. + // Private constructor creats a *Redis* instance with a single connection. + // This is used when we create Transaction, Pipeline and Subscriber. + // In this case, *_pool* is empty, and is never used. + ConnectionSPtr _connection; +}; + +} + +} + +#include "redis.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp new file mode 100644 index 000000000..3a227a6f1 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp @@ -0,0 +1,1365 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_HPP + +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +template +auto Redis::command(Cmd cmd, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (_connection) { + // Single Connection Mode. + // TODO: In this case, should we reconnect? + if (_connection->broken()) { + throw Error("Connection is broken"); + } + + return _command(*_connection, cmd, std::forward(args)...); + } else { + // Pool Mode, i.e. get connection from pool. + auto connection = _pool.fetch(); + + assert(!connection.broken()); + + ConnectionPoolGuard guard(_pool, connection); + + return _command(connection, cmd, std::forward(args)...); + } +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, ReplyUPtr>::type { + auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) { + CmdArgs cmd_args; + cmd_args.append(cmd_name, std::forward(args)...); + connection.send(cmd_args); + }; + + return command(cmd, cmd_name, std::forward(args)...); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last) { + throw Error("command: empty range"); + } + + auto cmd = [](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +Result Redis::command(const StringView &cmd_name, Args &&...args) { + auto r = command(cmd_name, std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(const StringView &cmd_name, Args &&...args) + -> typename std::enable_if::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto Redis::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto Redis::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long Redis::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool Redis::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool Redis::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +template +void Redis::keys(const StringView &pattern, Output output) { + auto reply = command(cmd::keys, pattern); + + reply::to_array(*reply, output); +} + +inline bool Redis::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool Redis::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void Redis::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long Redis::scan(long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::scan, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::scan(long long cursor, + const StringView &pattern, + Output output) { + return scan(cursor, pattern, 10, output); +} + +template +inline long long Redis::scan(long long cursor, + long long count, + Output output) { + return scan(cursor, "*", count, output); +} + +template +inline long long Redis::scan(long long cursor, + Output output) { + return scan(cursor, "*", 10, output); +} + +template +long long Redis::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) { + return wait(numslaves, timeout.count()); +} + +// STRING commands. + +template +long long Redis::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = command(cmd::bitop_range, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool Redis::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void Redis::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void Redis::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair Redis::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair Redis::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair Redis::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString Redis::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long Redis::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long Redis::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long Redis::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void Redis::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void Redis::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void Redis::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long Redis::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void Redis::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void Redis::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long Redis::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void Redis::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long Redis::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto Redis::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto Redis::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto Redis::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long Redis::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long Redis::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long Redis::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void Redis::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void Redis::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void Redis::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void Redis::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long Redis::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long Redis::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool Redis::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long Redis::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void Redis::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long Redis::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void Redis::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void Redis::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void Redis::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::eval, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::eval, script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + auto reply = command(cmd::evalsha, script, keys, args); + + return reply::parse(*reply); +} + +template +void Redis::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + auto reply = command(cmd::evalsha, script, keys, args); + + reply::to_array(*reply, output); +} + +template +void Redis::script_exists(Input first, Input last, Output output) { + if (first == last) { + throw Error("SCRIPT EXISTS: no key specified"); + } + + auto reply = command(cmd::script_exists_range, first, last); + + reply::to_array(*reply, output); +} + +// Transaction commands. + +template +void Redis::watch(Input first, Input last) { + auto reply = command(cmd::watch_range, first, last); + + reply::parse(*reply); +} + +// Stream commands. + +template +long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string Redis::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void Redis::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long Redis::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto Redis::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void Redis::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = command(cmd::xreadgroup_block, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto Redis::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = command(cmd::xreadgroup_block_range, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void Redis::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + auto reply = connection.recv(); + + return reply; +} + +template +inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h new file mode 100644 index 000000000..50a221367 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h @@ -0,0 +1,1395 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H + +#include +#include +#include +#include +#include "shards_pool.h" +#include "reply.h" +#include "command_options.h" +#include "utils.h" +#include "subscriber.h" +#include "pipeline.h" +#include "transaction.h" +#include "redis.h" + +namespace sw { + +namespace redis { + +template +class QueuedRedis; + +using Transaction = QueuedRedis; + +using Pipeline = QueuedRedis; + +class RedisCluster { +public: + RedisCluster(const ConnectionOptions &connection_opts, + const ConnectionPoolOptions &pool_opts = {}) : + _pool(pool_opts, connection_opts) {} + + // Construct RedisCluster with URI: + // "tcp://127.0.0.1" or "tcp://127.0.0.1:6379" + // Only need to specify one URI. + explicit RedisCluster(const std::string &uri); + + RedisCluster(const RedisCluster &) = delete; + RedisCluster& operator=(const RedisCluster &) = delete; + + RedisCluster(RedisCluster &&) = default; + RedisCluster& operator=(RedisCluster &&) = default; + + Redis redis(const StringView &hash_tag); + + Pipeline pipeline(const StringView &hash_tag); + + Transaction transaction(const StringView &hash_tag, bool piped = false); + + Subscriber subscriber(); + + template + auto command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type; + + template + auto command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type; + + template + auto command(Input first, Input last) + -> typename std::enable_if::value, Result>::type; + + template + auto command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type; + + // KEY commands. + + long long del(const StringView &key); + + template + long long del(Input first, Input last); + + template + long long del(std::initializer_list il) { + return del(il.begin(), il.end()); + } + + OptionalString dump(const StringView &key); + + long long exists(const StringView &key); + + template + long long exists(Input first, Input last); + + template + long long exists(std::initializer_list il) { + return exists(il.begin(), il.end()); + } + + bool expire(const StringView &key, long long timeout); + + bool expire(const StringView &key, const std::chrono::seconds &timeout); + + bool expireat(const StringView &key, long long timestamp); + + bool expireat(const StringView &key, + const std::chrono::time_point &tp); + + bool persist(const StringView &key); + + bool pexpire(const StringView &key, long long timeout); + + bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout); + + bool pexpireat(const StringView &key, long long timestamp); + + bool pexpireat(const StringView &key, + const std::chrono::time_point &tp); + + long long pttl(const StringView &key); + + void rename(const StringView &key, const StringView &newkey); + + bool renamenx(const StringView &key, const StringView &newkey); + + void restore(const StringView &key, + const StringView &val, + long long ttl, + bool replace = false); + + void restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0}, + bool replace = false); + + // TODO: sort + + long long touch(const StringView &key); + + template + long long touch(Input first, Input last); + + template + long long touch(std::initializer_list il) { + return touch(il.begin(), il.end()); + } + + long long ttl(const StringView &key); + + std::string type(const StringView &key); + + long long unlink(const StringView &key); + + template + long long unlink(Input first, Input last); + + template + long long unlink(std::initializer_list il) { + return unlink(il.begin(), il.end()); + } + + // STRING commands. + + long long append(const StringView &key, const StringView &str); + + long long bitcount(const StringView &key, long long start = 0, long long end = -1); + + long long bitop(BitOp op, const StringView &destination, const StringView &key); + + template + long long bitop(BitOp op, const StringView &destination, Input first, Input last); + + template + long long bitop(BitOp op, const StringView &destination, std::initializer_list il) { + return bitop(op, destination, il.begin(), il.end()); + } + + long long bitpos(const StringView &key, + long long bit, + long long start = 0, + long long end = -1); + + long long decr(const StringView &key); + + long long decrby(const StringView &key, long long decrement); + + OptionalString get(const StringView &key); + + long long getbit(const StringView &key, long long offset); + + std::string getrange(const StringView &key, long long start, long long end); + + OptionalString getset(const StringView &key, const StringView &val); + + long long incr(const StringView &key); + + long long incrby(const StringView &key, long long increment); + + double incrbyfloat(const StringView &key, double increment); + + template + void mget(Input first, Input last, Output output); + + template + void mget(std::initializer_list il, Output output) { + mget(il.begin(), il.end(), output); + } + + template + void mset(Input first, Input last); + + template + void mset(std::initializer_list il) { + mset(il.begin(), il.end()); + } + + template + bool msetnx(Input first, Input last); + + template + bool msetnx(std::initializer_list il) { + return msetnx(il.begin(), il.end()); + } + + void psetex(const StringView &key, + long long ttl, + const StringView &val); + + void psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val); + + bool set(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0), + UpdateType type = UpdateType::ALWAYS); + + void setex(const StringView &key, + long long ttl, + const StringView &val); + + void setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val); + + bool setnx(const StringView &key, const StringView &val); + + long long setrange(const StringView &key, long long offset, const StringView &val); + + long long strlen(const StringView &key); + + // LIST commands. + + OptionalStringPair blpop(const StringView &key, long long timeout); + + OptionalStringPair blpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(Input first, Input last, long long timeout); + + template + OptionalStringPair blpop(std::initializer_list il, long long timeout) { + return blpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair blpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair blpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return blpop(il.begin(), il.end(), timeout); + } + + OptionalStringPair brpop(const StringView &key, long long timeout); + + OptionalStringPair brpop(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(Input first, Input last, long long timeout); + + template + OptionalStringPair brpop(std::initializer_list il, long long timeout) { + return brpop(il.begin(), il.end(), timeout); + } + + template + OptionalStringPair brpop(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + template + OptionalStringPair brpop(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return brpop(il.begin(), il.end(), timeout); + } + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + long long timeout); + + OptionalString brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + + OptionalString lindex(const StringView &key, long long index); + + long long linsert(const StringView &key, + InsertPosition position, + const StringView &pivot, + const StringView &val); + + long long llen(const StringView &key); + + OptionalString lpop(const StringView &key); + + long long lpush(const StringView &key, const StringView &val); + + template + long long lpush(const StringView &key, Input first, Input last); + + template + long long lpush(const StringView &key, std::initializer_list il) { + return lpush(key, il.begin(), il.end()); + } + + long long lpushx(const StringView &key, const StringView &val); + + template + void lrange(const StringView &key, long long start, long long stop, Output output); + + long long lrem(const StringView &key, long long count, const StringView &val); + + void lset(const StringView &key, long long index, const StringView &val); + + void ltrim(const StringView &key, long long start, long long stop); + + OptionalString rpop(const StringView &key); + + OptionalString rpoplpush(const StringView &source, const StringView &destination); + + long long rpush(const StringView &key, const StringView &val); + + template + long long rpush(const StringView &key, Input first, Input last); + + template + long long rpush(const StringView &key, std::initializer_list il) { + return rpush(key, il.begin(), il.end()); + } + + long long rpushx(const StringView &key, const StringView &val); + + // HASH commands. + + long long hdel(const StringView &key, const StringView &field); + + template + long long hdel(const StringView &key, Input first, Input last); + + template + long long hdel(const StringView &key, std::initializer_list il) { + return hdel(key, il.begin(), il.end()); + } + + bool hexists(const StringView &key, const StringView &field); + + OptionalString hget(const StringView &key, const StringView &field); + + template + void hgetall(const StringView &key, Output output); + + long long hincrby(const StringView &key, const StringView &field, long long increment); + + double hincrbyfloat(const StringView &key, const StringView &field, double increment); + + template + void hkeys(const StringView &key, Output output); + + long long hlen(const StringView &key); + + template + void hmget(const StringView &key, Input first, Input last, Output output); + + template + void hmget(const StringView &key, std::initializer_list il, Output output) { + hmget(key, il.begin(), il.end(), output); + } + + template + void hmset(const StringView &key, Input first, Input last); + + template + void hmset(const StringView &key, std::initializer_list il) { + hmset(key, il.begin(), il.end()); + } + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long hscan(const StringView &key, + long long cursor, + Output output); + + bool hset(const StringView &key, const StringView &field, const StringView &val); + + bool hset(const StringView &key, const std::pair &item); + + bool hsetnx(const StringView &key, const StringView &field, const StringView &val); + + bool hsetnx(const StringView &key, const std::pair &item); + + long long hstrlen(const StringView &key, const StringView &field); + + template + void hvals(const StringView &key, Output output); + + // SET commands. + + long long sadd(const StringView &key, const StringView &member); + + template + long long sadd(const StringView &key, Input first, Input last); + + template + long long sadd(const StringView &key, std::initializer_list il) { + return sadd(key, il.begin(), il.end()); + } + + long long scard(const StringView &key); + + template + void sdiff(Input first, Input last, Output output); + + template + void sdiff(std::initializer_list il, Output output) { + sdiff(il.begin(), il.end(), output); + } + + long long sdiffstore(const StringView &destination, const StringView &key); + + template + long long sdiffstore(const StringView &destination, + Input first, + Input last); + + template + long long sdiffstore(const StringView &destination, + std::initializer_list il) { + return sdiffstore(destination, il.begin(), il.end()); + } + + template + void sinter(Input first, Input last, Output output); + + template + void sinter(std::initializer_list il, Output output) { + sinter(il.begin(), il.end(), output); + } + + long long sinterstore(const StringView &destination, const StringView &key); + + template + long long sinterstore(const StringView &destination, + Input first, + Input last); + + template + long long sinterstore(const StringView &destination, + std::initializer_list il) { + return sinterstore(destination, il.begin(), il.end()); + } + + bool sismember(const StringView &key, const StringView &member); + + template + void smembers(const StringView &key, Output output); + + bool smove(const StringView &source, + const StringView &destination, + const StringView &member); + + OptionalString spop(const StringView &key); + + template + void spop(const StringView &key, long long count, Output output); + + OptionalString srandmember(const StringView &key); + + template + void srandmember(const StringView &key, long long count, Output output); + + long long srem(const StringView &key, const StringView &member); + + template + long long srem(const StringView &key, Input first, Input last); + + template + long long srem(const StringView &key, std::initializer_list il) { + return srem(key, il.begin(), il.end()); + } + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long sscan(const StringView &key, + long long cursor, + Output output); + + template + void sunion(Input first, Input last, Output output); + + template + void sunion(std::initializer_list il, Output output) { + sunion(il.begin(), il.end(), output); + } + + long long sunionstore(const StringView &destination, const StringView &key); + + template + long long sunionstore(const StringView &destination, Input first, Input last); + + template + long long sunionstore(const StringView &destination, std::initializer_list il) { + return sunionstore(destination, il.begin(), il.end()); + } + + // SORTED SET commands. + + auto bzpopmax(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmax(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmax(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + template + auto bzpopmax(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmax(il.begin(), il.end(), timeout); + } + + auto bzpopmin(const StringView &key, long long timeout) + -> Optional>; + + auto bzpopmin(const StringView &key, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(Input first, Input last, long long timeout) + -> Optional>; + + template + auto bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional>; + + template + auto bzpopmin(std::initializer_list il, long long timeout) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + template + auto bzpopmin(std::initializer_list il, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) + -> Optional> { + return bzpopmin(il.begin(), il.end(), timeout); + } + + // We don't support the INCR option, since you can always use ZINCRBY instead. + long long zadd(const StringView &key, + const StringView &member, + double score, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + Input first, + Input last, + UpdateType type = UpdateType::ALWAYS, + bool changed = false); + + template + long long zadd(const StringView &key, + std::initializer_list il, + UpdateType type = UpdateType::ALWAYS, + bool changed = false) { + return zadd(key, il.begin(), il.end(), type, changed); + } + + long long zcard(const StringView &key); + + template + long long zcount(const StringView &key, const Interval &interval); + + double zincrby(const StringView &key, double increment, const StringView &member); + + long long zinterstore(const StringView &destination, const StringView &key, double weight); + + template + long long zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zinterstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zinterstore(destination, il.begin(), il.end(), type); + } + + template + long long zlexcount(const StringView &key, const Interval &interval); + + Optional> zpopmax(const StringView &key); + + template + void zpopmax(const StringView &key, long long count, Output output); + + Optional> zpopmin(const StringView &key); + + template + void zpopmin(const StringView &key, long long count, Output output); + + // If *output* is an iterator of a container of string, + // we send *ZRANGE key start stop* command. + // If it's an iterator of a container of pair, + // we send *ZRANGE key start stop WITHSCORES* command. + // + // The following code sends *ZRANGE* without the *WITHSCORES* option: + // + // vector result; + // redis.zrange("key", 0, -1, back_inserter(result)); + // + // On the other hand, the following code sends command with *WITHSCORES* option: + // + // unordered_map with_score; + // redis.zrange("key", 0, -1, inserter(with_score, with_score.end())); + // + // This also applies to other commands with the *WITHSCORES* option, + // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*. + template + void zrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrank(const StringView &key, const StringView &member); + + long long zrem(const StringView &key, const StringView &member); + + template + long long zrem(const StringView &key, Input first, Input last); + + template + long long zrem(const StringView &key, std::initializer_list il) { + return zrem(key, il.begin(), il.end()); + } + + template + long long zremrangebylex(const StringView &key, const Interval &interval); + + long long zremrangebyrank(const StringView &key, long long start, long long stop); + + template + long long zremrangebyscore(const StringView &key, const Interval &interval); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrange(const StringView &key, long long start, long long stop, Output output); + + template + void zrevrangebylex(const StringView &key, const Interval &interval, Output output); + + template + void zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, const Interval &interval, Output output); + + // See *zrange* comment on how to send command with *WITHSCORES* option. + template + void zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output); + + OptionalLongLong zrevrank(const StringView &key, const StringView &member); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + long long count, + Output output); + + template + long long zscan(const StringView &key, + long long cursor, + Output output); + + OptionalDouble zscore(const StringView &key, const StringView &member); + + long long zunionstore(const StringView &destination, const StringView &key, double weight); + + template + long long zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type = Aggregation::SUM); + + template + long long zunionstore(const StringView &destination, + std::initializer_list il, + Aggregation type = Aggregation::SUM) { + return zunionstore(destination, il.begin(), il.end(), type); + } + + // HYPERLOGLOG commands. + + bool pfadd(const StringView &key, const StringView &element); + + template + bool pfadd(const StringView &key, Input first, Input last); + + template + bool pfadd(const StringView &key, std::initializer_list il) { + return pfadd(key, il.begin(), il.end()); + } + + long long pfcount(const StringView &key); + + template + long long pfcount(Input first, Input last); + + template + long long pfcount(std::initializer_list il) { + return pfcount(il.begin(), il.end()); + } + + void pfmerge(const StringView &destination, const StringView &key); + + template + void pfmerge(const StringView &destination, Input first, Input last); + + template + void pfmerge(const StringView &destination, std::initializer_list il) { + pfmerge(destination, il.begin(), il.end()); + } + + // GEO commands. + + long long geoadd(const StringView &key, + const std::tuple &member); + + template + long long geoadd(const StringView &key, + Input first, + Input last); + + template + long long geoadd(const StringView &key, + std::initializer_list il) { + return geoadd(key, il.begin(), il.end()); + } + + OptionalDouble geodist(const StringView &key, + const StringView &member1, + const StringView &member2, + GeoUnit unit = GeoUnit::M); + + template + void geohash(const StringView &key, Input first, Input last, Output output); + + template + void geohash(const StringView &key, std::initializer_list il, Output output) { + geohash(key, il.begin(), il.end(), output); + } + + template + void geopos(const StringView &key, Input first, Input last, Output output); + + template + void geopos(const StringView &key, std::initializer_list il, Output output) { + geopos(key, il.begin(), il.end(), output); + } + + // TODO: + // 1. since we have different overloads for georadius and georadius-store, + // we might use the GEORADIUS_RO command in the future. + // 2. there're too many parameters for this method, we might refactor it. + OptionalLongLong georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // If *output* is an iterator of a container of string, we send *GEORADIUS* command + // without any options and only get the members in the specified geo range. + // If *output* is an iterator of a container of a tuple, the type of the tuple decides + // options we send with the *GEORADIUS* command. If the tuple has an element of type + // double, we send the *WITHDIST* option. If it has an element of type string, we send + // the *WITHHASH* option. If it has an element of type pair, we send + // the *WITHCOORD* option. For example: + // + // The following code only gets the members in range, i.e. without any option. + // + // vector members; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(members)) + // + // The following code sends the command with *WITHDIST* option. + // + // vector> with_dist; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist)) + // + // The following code sends the command with *WITHDIST* and *WITHHASH* options. + // + // vector> with_dist_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_hash)) + // + // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options. + // + // vector, string>> with_dist_coord_hash; + // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true, + // back_inserter(with_dist_coord_hash)) + // + // This also applies to *GEORADIUSBYMEMBER*. + template + void georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + OptionalLongLong georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + const StringView &destination, + bool store_dist, + long long count); + + // See comments on *GEORADIUS*. + template + void georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output); + + // SCRIPTING commands. + + template + Result eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + template + Result evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args); + + template + void evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output); + + // PUBSUB commands. + + long long publish(const StringView &channel, const StringView &message); + + // Stream commands. + + long long xack(const StringView &key, const StringView &group, const StringView &id); + + template + long long xack(const StringView &key, const StringView &group, Input first, Input last); + + template + long long xack(const StringView &key, const StringView &group, std::initializer_list il) { + return xack(key, group, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, const StringView &id, Input first, Input last); + + template + std::string xadd(const StringView &key, const StringView &id, std::initializer_list il) { + return xadd(key, id, il.begin(), il.end()); + } + + template + std::string xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx = true); + + template + std::string xadd(const StringView &key, + const StringView &id, + std::initializer_list il, + long long count, + bool approx = true) { + return xadd(key, id, il.begin(), il.end(), count, approx); + } + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output); + + template + void xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + std::initializer_list il, + Output output) { + xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output); + } + + long long xdel(const StringView &key, const StringView &id); + + template + long long xdel(const StringView &key, Input first, Input last); + + template + long long xdel(const StringView &key, std::initializer_list il) { + return xdel(key, il.begin(), il.end()); + } + + void xgroup_create(const StringView &key, + const StringView &group, + const StringView &id, + bool mkstream = false); + + void xgroup_setid(const StringView &key, const StringView &group, const StringView &id); + + long long xgroup_destroy(const StringView &key, const StringView &group); + + long long xgroup_delconsumer(const StringView &key, + const StringView &group, + const StringView &consumer); + + long long xlen(const StringView &key); + + template + auto xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple; + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output); + + template + void xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + Output output) { + xread(key, id, 0, output); + } + + template + auto xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, Input last, Output output) + -> typename std::enable_if::value>::type { + xread(first, last, 0, output); + } + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output); + + template + void xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + xread(key, id, timeout, 0, output); + } + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xread(first, last, timeout, 0, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + Output output) { + xreadgroup(group, consumer, key, id, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + Output output) { + xreadgroup(group, consumer, key, id, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first ,last, 0, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output); + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, count, false, output); + } + + template + void xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + Output output) { + return xreadgroup(group, consumer, key, id, timeout, 0, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type; + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, count, false, output); + } + + template + auto xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + Output output) + -> typename std::enable_if::value>::type { + xreadgroup(group, consumer, first, last, timeout, 0, false, output); + } + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output); + + template + void xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output); + + long long xtrim(const StringView &key, long long count, bool approx = true); + +private: + class Command { + public: + explicit Command(const StringView &cmd_name) : _cmd_name(cmd_name) {} + + template + void operator()(Connection &connection, Args &&...args) const { + CmdArgs cmd_args; + cmd_args.append(_cmd_name, std::forward(args)...); + connection.send(cmd_args); + } + + private: + StringView _cmd_name; + }; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type; + + template + auto _generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type; + + template + ReplyUPtr _command(Cmd cmd, Connection &connection, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::true_type, const StringView &key, Args &&...args); + + template + ReplyUPtr _command(Cmd cmd, std::false_type, Input &&first, Args &&...args); + + template + ReplyUPtr _command(const StringView &cmd_name, const IndexSequence &, Args &&...args) { + return command(cmd_name, NthValue(std::forward(args)...)...); + } + + template + ReplyUPtr _range_command(Cmd cmd, std::true_type, Input input, Args &&...args); + + template + ReplyUPtr _range_command(Cmd cmd, std::false_type, Input input, Args &&...args); + + void _asking(Connection &connection); + + template + ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args); + + template + ReplyUPtr _score_command(Cmd cmd, Args &&... args); + + ShardsPool _pool; +}; + +} + +} + +#include "redis_cluster.hpp" + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp new file mode 100644 index 000000000..61da3f062 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp @@ -0,0 +1,1415 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP +#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP + +#include +#include "command.h" +#include "reply.h" +#include "utils.h" +#include "errors.h" +#include "shards_pool.h" + +namespace sw { + +namespace redis { + +template +auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, ReplyUPtr>::type { + return _command(cmd, + std::is_convertible::type, StringView>(), + std::forward(key), + std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && !IsIter::type>::value, ReplyUPtr>::type { + auto cmd = Command(cmd_name); + + return _generic_command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if::value + || std::is_arithmetic::type>::value, Result>::type { + auto r = command(cmd_name, std::forward(key), std::forward(args)...); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args) + -> typename std::enable_if<(std::is_convertible::value + || std::is_arithmetic::type>::value) + && IsIter::type>::value, void>::type { + auto r = _command(cmd_name, + MakeIndexSequence(), + std::forward(key), + std::forward(args)...); + + assert(r); + + reply::to_array(*r, LastValue(std::forward(args)...)); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, ReplyUPtr>::type { + if (first == last || std::next(first) == last) { + throw Error("command: invalid range"); + } + + const auto &key = *first; + ++first; + + auto cmd = [&key](Connection &connection, Input first, Input last) { + CmdArgs cmd_args; + cmd_args.append(key); + while (first != last) { + cmd_args.append(*first); + ++first; + } + connection.send(cmd_args); + }; + + return command(cmd, first, last); +} + +template +auto RedisCluster::command(Input first, Input last) + -> typename std::enable_if::value, Result>::type { + auto r = command(first, last); + + assert(r); + + return reply::parse(*r); +} + +template +auto RedisCluster::command(Input first, Input last, Output output) + -> typename std::enable_if::value, void>::type { + auto r = command(first, last); + + assert(r); + + reply::to_array(*r, output); +} + +// KEY commands. + +template +long long RedisCluster::del(Input first, Input last) { + if (first == last) { + throw Error("DEL: no key specified"); + } + + auto reply = command(cmd::del_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::exists(Input first, Input last) { + if (first == last) { + throw Error("EXISTS: no key specified"); + } + + auto reply = command(cmd::exists_range, first, last); + + return reply::parse(*reply); +} + +inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) { + return expire(key, timeout.count()); +} + +inline bool RedisCluster::expireat(const StringView &key, + const std::chrono::time_point &tp) { + return expireat(key, tp.time_since_epoch().count()); +} + +inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) { + return pexpire(key, timeout.count()); +} + +inline bool RedisCluster::pexpireat(const StringView &key, + const std::chrono::time_point &tp) { + return pexpireat(key, tp.time_since_epoch().count()); +} + +inline void RedisCluster::restore(const StringView &key, + const StringView &val, + const std::chrono::milliseconds &ttl, + bool replace) { + return restore(key, val, ttl.count(), replace); +} + +template +long long RedisCluster::touch(Input first, Input last) { + if (first == last) { + throw Error("TOUCH: no key specified"); + } + + auto reply = command(cmd::touch_range, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::unlink(Input first, Input last) { + if (first == last) { + throw Error("UNLINK: no key specified"); + } + + auto reply = command(cmd::unlink_range, first, last); + + return reply::parse(*reply); +} + +// STRING commands. + +template +long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("BITOP: no key specified"); + } + + auto reply = _command(cmd::bitop_range, destination, op, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::mget(Input first, Input last, Output output) { + if (first == last) { + throw Error("MGET: no key specified"); + } + + auto reply = command(cmd::mget, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::mset(Input first, Input last) { + if (first == last) { + throw Error("MSET: no key specified"); + } + + auto reply = command(cmd::mset, first, last); + + reply::parse(*reply); +} + +template +bool RedisCluster::msetnx(Input first, Input last) { + if (first == last) { + throw Error("MSETNX: no key specified"); + } + + auto reply = command(cmd::msetnx, first, last); + + return reply::parse(*reply); +} + +inline void RedisCluster::psetex(const StringView &key, + const std::chrono::milliseconds &ttl, + const StringView &val) { + return psetex(key, ttl.count(), val); +} + +inline void RedisCluster::setex(const StringView &key, + const std::chrono::seconds &ttl, + const StringView &val) { + setex(key, ttl.count(), val); +} + +// LIST commands. + +template +OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BLPOP: no key specified"); + } + + auto reply = command(cmd::blpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::blpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return blpop(first, last, timeout.count()); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) { + if (first == last) { + throw Error("BRPOP: no key specified"); + } + + auto reply = command(cmd::brpop_range, first, last, timeout); + + return reply::parse(*reply); +} + +template +OptionalStringPair RedisCluster::brpop(Input first, + Input last, + const std::chrono::seconds &timeout) { + return brpop(first, last, timeout.count()); +} + +inline OptionalString RedisCluster::brpoplpush(const StringView &source, + const StringView &destination, + const std::chrono::seconds &timeout) { + return brpoplpush(source, destination, timeout.count()); +} + +template +inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("LPUSH: no key specified"); + } + + auto reply = command(cmd::lpush_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = command(cmd::lrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("RPUSH: no key specified"); + } + + auto reply = command(cmd::rpush_range, key, first, last); + + return reply::parse(*reply); +} + +// HASH commands. + +template +inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HDEL: no key specified"); + } + + auto reply = command(cmd::hdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +inline void RedisCluster::hgetall(const StringView &key, Output output) { + auto reply = command(cmd::hgetall, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hkeys(const StringView &key, Output output) { + auto reply = command(cmd::hkeys, key); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("HMGET: no key specified"); + } + + auto reply = command(cmd::hmget, key, first, last); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::hmset(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("HMSET: no key specified"); + } + + auto reply = command(cmd::hmset, key, first, last); + + reply::parse(*reply); +} + +template +long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::hscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return hscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return hscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::hscan(const StringView &key, + long long cursor, + Output output) { + return hscan(key, cursor, "*", 10, output); +} + +template +inline void RedisCluster::hvals(const StringView &key, Output output) { + auto reply = command(cmd::hvals, key); + + reply::to_array(*reply, output); +} + +// SET commands. + +template +long long RedisCluster::sadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SADD: no key specified"); + } + + auto reply = command(cmd::sadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sdiff(Input first, Input last, Output output) { + if (first == last) { + throw Error("SDIFF: no key specified"); + } + + auto reply = command(cmd::sdiff, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sdiffstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SDIFFSTORE: no key specified"); + } + + auto reply = command(cmd::sdiffstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::sinter(Input first, Input last, Output output) { + if (first == last) { + throw Error("SINTER: no key specified"); + } + + auto reply = command(cmd::sinter, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sinterstore(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("SINTERSTORE: no key specified"); + } + + auto reply = command(cmd::sinterstore_range, destination, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::smembers(const StringView &key, Output output) { + auto reply = command(cmd::smembers, key); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::spop(const StringView &key, long long count, Output output) { + auto reply = command(cmd::spop_range, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::srandmember(const StringView &key, long long count, Output output) { + auto reply = command(cmd::srandmember_range, key, count); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::srem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("SREM: no key specified"); + } + + auto reply = command(cmd::srem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::sscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return sscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return sscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::sscan(const StringView &key, + long long cursor, + Output output) { + return sscan(key, cursor, "*", 10, output); +} + +template +void RedisCluster::sunion(Input first, Input last, Output output) { + if (first == last) { + throw Error("SUNION: no key specified"); + } + + auto reply = command(cmd::sunion, first, last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) { + if (first == last) { + throw Error("SUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::sunionstore_range, destination, first, last); + + return reply::parse(*reply); +} + +// SORTED SET commands. + +inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmax(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmax_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmax(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmax(first, last, timeout.count()); +} + +inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(key, timeout.count()); +} + +template +auto RedisCluster::bzpopmin(Input first, Input last, long long timeout) + -> Optional> { + auto reply = command(cmd::bzpopmin_range, first, last, timeout); + + return reply::parse>>(*reply); +} + +template +inline auto RedisCluster::bzpopmin(Input first, + Input last, + const std::chrono::seconds &timeout) + -> Optional> { + return bzpopmin(first, last, timeout.count()); +} + +template +long long RedisCluster::zadd(const StringView &key, + Input first, + Input last, + UpdateType type, + bool changed) { + if (first == last) { + throw Error("ZADD: no key specified"); + } + + auto reply = command(cmd::zadd_range, key, first, last, type, changed); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zcount, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zinterstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZINTERSTORE: no key specified"); + } + + auto reply = command(cmd::zinterstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zlexcount, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zpopmax(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmax, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zpopmin(const StringView &key, long long count, Output output) { + auto reply = command(cmd::zpopmin, key, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) { + zrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + Output output) { + zrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrangebyscore, + key, + interval, + opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zrem(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("ZREM: no key specified"); + } + + auto reply = command(cmd::zrem_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebylex, key, interval); + + return reply::parse(*reply); +} + +template +long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) { + auto reply = command(cmd::zremrangebyscore, key, interval); + + return reply::parse(*reply); +} + +template +void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) { + auto reply = _score_command(cmd::zrevrange, key, start, stop); + + reply::to_array(*reply, output); +} + +template +inline void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + Output output) { + zrevrangebylex(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebylex(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = command(cmd::zrevrangebylex, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { + zrevrangebyscore(key, interval, {}, output); +} + +template +void RedisCluster::zrevrangebyscore(const StringView &key, + const Interval &interval, + const LimitOptions &opts, + Output output) { + auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + long long count, + Output output) { + auto reply = command(cmd::zscan, key, cursor, pattern, count); + + return reply::parse_scan_reply(*reply, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + const StringView &pattern, + Output output) { + return zscan(key, cursor, pattern, 10, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + long long count, + Output output) { + return zscan(key, cursor, "*", count, output); +} + +template +inline long long RedisCluster::zscan(const StringView &key, + long long cursor, + Output output) { + return zscan(key, cursor, "*", 10, output); +} + +template +long long RedisCluster::zunionstore(const StringView &destination, + Input first, + Input last, + Aggregation type) { + if (first == last) { + throw Error("ZUNIONSTORE: no key specified"); + } + + auto reply = command(cmd::zunionstore_range, + destination, + first, + last, + type); + + return reply::parse(*reply); +} + +// HYPERLOGLOG commands. + +template +bool RedisCluster::pfadd(const StringView &key, Input first, Input last) { + if (first == last) { + throw Error("PFADD: no key specified"); + } + + auto reply = command(cmd::pfadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +long long RedisCluster::pfcount(Input first, Input last) { + if (first == last) { + throw Error("PFCOUNT: no key specified"); + } + + auto reply = command(cmd::pfcount_range, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::pfmerge(const StringView &destination, + Input first, + Input last) { + if (first == last) { + throw Error("PFMERGE: no key specified"); + } + + auto reply = command(cmd::pfmerge_range, destination, first, last); + + reply::parse(*reply); +} + +// GEO commands. + +template +inline long long RedisCluster::geoadd(const StringView &key, + Input first, + Input last) { + if (first == last) { + throw Error("GEOADD: no key specified"); + } + + auto reply = command(cmd::geoadd_range, key, first, last); + + return reply::parse(*reply); +} + +template +void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOHASH: no key specified"); + } + + auto reply = command(cmd::geohash_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) { + if (first == last) { + throw Error("GEOPOS: no key specified"); + } + + auto reply = command(cmd::geopos_range, key, first, last); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadius(const StringView &key, + const std::pair &loc, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadius, + key, + loc, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::georadiusbymember(const StringView &key, + const StringView &member, + double radius, + GeoUnit unit, + long long count, + bool asc, + Output output) { + auto reply = command(cmd::georadiusbymember, + key, + member, + radius, + unit, + count, + asc, + WithCoord::type>::value, + WithDist::type>::value, + WithHash::type>::value); + + reply::to_array(*reply, output); +} + +// SCRIPTING commands. + +template +Result RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::eval(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::eval, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +template +Result RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = _command(cmd::evalsha, *keys.begin(), script, keys, args); + + return reply::parse(*reply); +} + +template +void RedisCluster::evalsha(const StringView &script, + std::initializer_list keys, + std::initializer_list args, + Output output) { + if (keys.size() == 0) { + throw Error("DO NOT support Lua script without key"); + } + + auto reply = command(cmd::evalsha, *keys.begin(), script, keys, args); + + reply::to_array(*reply, output); +} + +// Stream commands. + +template +long long RedisCluster::xack(const StringView &key, + const StringView &group, + Input first, + Input last) { + auto reply = command(cmd::xack_range, key, group, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last) { + auto reply = command(cmd::xadd_range, key, id, first, last); + + return reply::parse(*reply); +} + +template +std::string RedisCluster::xadd(const StringView &key, + const StringView &id, + Input first, + Input last, + long long count, + bool approx) { + auto reply = command(cmd::xadd_maxlen_range, key, id, first, last, count, approx); + + return reply::parse(*reply); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + const StringView &id, + Output output) { + auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xclaim(const StringView &key, + const StringView &group, + const StringView &consumer, + const std::chrono::milliseconds &min_idle_time, + Input first, + Input last, + Output output) { + auto reply = command(cmd::xclaim_range, + key, + group, + consumer, + min_idle_time.count(), + first, + last); + + reply::to_array(*reply, output); +} + +template +long long RedisCluster::xdel(const StringView &key, Input first, Input last) { + auto reply = command(cmd::xdel_range, key, first, last); + + return reply::parse(*reply); +} + +template +auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output) + -> std::tuple { + auto reply = command(cmd::xpending, key, group); + + return reply::parse_xpending_reply(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xpending_detail, key, group, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xpending(const StringView &key, + const StringView &group, + const StringView &start, + const StringView &end, + long long count, + const StringView &consumer, + Output output) { + auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + Output output) { + auto reply = command(cmd::xrange, key, start, end); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrange(const StringView &key, + const StringView &start, + const StringView &end, + long long count, + Output output) { + auto reply = command(cmd::xrange_count, key, start, end, count); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + long long count, + Output output) { + auto reply = command(cmd::xread, key, id, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, Input last, long long count, Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_range, first, last, count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xread(const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + Output output) { + auto reply = command(cmd::xread_block, key, id, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xread(Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREAD: no key specified"); + } + + auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_range, + first->first, + group, + consumer, + first, + last, + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + const StringView &key, + const StringView &id, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) { + auto reply = _command(cmd::xreadgroup_block, + key, + group, + consumer, + key, + id, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +auto RedisCluster::xreadgroup(const StringView &group, + const StringView &consumer, + Input first, + Input last, + const std::chrono::milliseconds &timeout, + long long count, + bool noack, + Output output) + -> typename std::enable_if::value>::type { + if (first == last) { + throw Error("XREADGROUP: no key specified"); + } + + auto reply = _command(cmd::xreadgroup_block_range, + first->first, + group, + consumer, + first, + last, + timeout.count(), + count, + noack); + + if (!reply::is_nil(*reply)) { + reply::to_array(*reply, output); + } +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + Output output) { + auto reply = command(cmd::xrevrange, key, end, start); + + reply::to_array(*reply, output); +} + +template +void RedisCluster::xrevrange(const StringView &key, + const StringView &end, + const StringView &start, + long long count, + Output output) { + auto reply = command(cmd::xrevrange_count, key, end, start, count); + + reply::to_array(*reply, output); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::value, + ReplyUPtr>::type { + return command(cmd, std::forward(key), std::forward(args)...); +} + +template +auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args) + -> typename std::enable_if::type>::value, + ReplyUPtr>::type { + auto k = std::to_string(std::forward(key)); + return command(cmd, k, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) { + return _command(cmd, key, key, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) { + return _range_command(cmd, + std::is_convertible< + typename std::decay< + decltype(*std::declval())>::type, StringView>(), + std::forward(first), + std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) { + return _command(cmd, *input, input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) { + return _command(cmd, std::get<0>(*input), input, std::forward(args)...); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) { + assert(!connection.broken()); + + cmd(connection, std::forward(args)...); + + return connection.recv(); +} + +template +ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) { + for (auto idx = 0; idx < 2; ++idx) { + try { + auto guarded_connection = _pool.fetch(key); + + return _command(cmd, guarded_connection.connection(), std::forward(args)...); + } catch (const IoError &err) { + // When master is down, one of its replicas will be promoted to be the new master. + // If we try to send command to the old master, we'll get an *IoError*. + // In this case, we need to update the slots mapping. + _pool.update(); + } catch (const ClosedError &err) { + // Node might be removed. + // 1. Get up-to-date slot mapping to check if the node still exists. + _pool.update(); + + // TODO: + // 2. If it's NOT exist, update slot mapping, and retry. + // 3. If it's still exist, that means the node is down, NOT removed, throw exception. + } catch (const MovedError &err) { + // Slot mapping has been changed, update it and try again. + _pool.update(); + } catch (const AskError &err) { + auto guarded_connection = _pool.fetch(err.node()); + auto &connection = guarded_connection.connection(); + + // 1. send ASKING command. + _asking(connection); + + // 2. resend last command. + try { + return _command(cmd, connection, std::forward(args)...); + } catch (const MovedError &err) { + throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state"); + } + } // For other exceptions, just throw it. + } + + // Possible failures: + // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx', + // while the destination node has NOT run it. + // In this case, client will be redirected by both nodes with MovedError. + // 2. Other failures... + throw Error("Failed to send command with key: " + std::string(key.data(), key.size())); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., true); +} + +template +inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) { + return command(cmd, std::forward(args)..., false); +} + +template +inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) { + return _score_command(typename IsKvPairIter::type(), + cmd, + std::forward(args)...); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h new file mode 100644 index 000000000..b309de5bb --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h @@ -0,0 +1,363 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H +#define SEWENEW_REDISPLUSPLUS_REPLY_H + +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +struct ReplyDeleter { + void operator()(redisReply *reply) const { + if (reply != nullptr) { + freeReplyObject(reply); + } + } +}; + +using ReplyUPtr = std::unique_ptr; + +namespace reply { + +template +struct ParseTag {}; + +template +inline T parse(redisReply &reply) { + return parse(ParseTag(), reply); +} + +void parse(ParseTag, redisReply &reply); + +std::string parse(ParseTag, redisReply &reply); + +long long parse(ParseTag, redisReply &reply); + +double parse(ParseTag, redisReply &reply); + +bool parse(ParseTag, redisReply &reply); + +template +Optional parse(ParseTag>, redisReply &reply); + +template +std::pair parse(ParseTag>, redisReply &reply); + +template +std::tuple parse(ParseTag>, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template ::value, int>::type = 0> +T parse(ParseTag, redisReply &reply); + +template +long long parse_scan_reply(redisReply &reply, Output output); + +inline bool is_error(redisReply &reply) { + return reply.type == REDIS_REPLY_ERROR; +} + +inline bool is_nil(redisReply &reply) { + return reply.type == REDIS_REPLY_NIL; +} + +inline bool is_string(redisReply &reply) { + return reply.type == REDIS_REPLY_STRING; +} + +inline bool is_status(redisReply &reply) { + return reply.type == REDIS_REPLY_STATUS; +} + +inline bool is_integer(redisReply &reply) { + return reply.type == REDIS_REPLY_INTEGER; +} + +inline bool is_array(redisReply &reply) { + return reply.type == REDIS_REPLY_ARRAY; +} + +std::string to_status(redisReply &reply); + +template +void to_array(redisReply &reply, Output output); + +// Rewrite set reply to bool type +void rewrite_set_reply(redisReply &reply); + +// Rewrite georadius reply to OptionalLongLong type +void rewrite_georadius_reply(redisReply &reply); + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple; + +} + +// Inline implementations. + +namespace reply { + +namespace detail { + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.element == nullptr) { + // Empty array. + return; + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + auto *sub_reply = reply.element[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null array element reply"); + } + + *output = parse::type>(*sub_reply); + + ++output; + } +} + +bool is_flat_array(redisReply &reply); + +template +void to_flat_array(redisReply &reply, Output output) { + if (reply.element == nullptr) { + // Empty array. + return; + } + + if (reply.elements % 2 != 0) { + throw ProtoError("Not string pair array reply"); + } + + for (std::size_t idx = 0; idx != reply.elements; idx += 2) { + auto *key_reply = reply.element[idx]; + auto *val_reply = reply.element[idx + 1]; + if (key_reply == nullptr || val_reply == nullptr) { + throw ProtoError("Null string array reply"); + } + + using Pair = typename IterType::type; + using FirstType = typename std::decay::type; + using SecondType = typename std::decay::type; + *output = std::make_pair(parse(*key_reply), + parse(*val_reply)); + + ++output; + } +} + +template +void to_array(std::true_type, redisReply &reply, Output output) { + if (is_flat_array(reply)) { + to_flat_array(reply, output); + } else { + to_array(reply, output); + } +} + +template +void to_array(std::false_type, redisReply &reply, Output output) { + to_array(reply, output); +} + +template +std::tuple parse_tuple(redisReply **reply, std::size_t idx) { + assert(reply != nullptr); + + auto *sub_reply = reply[idx]; + if (sub_reply == nullptr) { + throw ProtoError("Null reply"); + } + + return std::make_tuple(parse(*sub_reply)); +} + +template +auto parse_tuple(redisReply **reply, std::size_t idx) -> + typename std::enable_if>::type { + assert(reply != nullptr); + + return std::tuple_cat(parse_tuple(reply, idx), + parse_tuple(reply, idx + 1)); +} + +} + +template +Optional parse(ParseTag>, redisReply &reply) { + if (reply::is_nil(reply)) { + return {}; + } + + return Optional(parse(reply)); +} + +template +std::pair parse(ParseTag>, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != 2) { + throw ProtoError("NOT key-value PAIR reply"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null PAIR reply"); + } + + auto *first = reply.element[0]; + auto *second = reply.element[1]; + if (first == nullptr || second == nullptr) { + throw ProtoError("Null pair reply"); + } + + return std::make_pair(parse::type>(*first), + parse::type>(*second)); +} + +template +std::tuple parse(ParseTag>, redisReply &reply) { + constexpr auto size = sizeof...(Args); + + static_assert(size > 0, "DO NOT support parsing tuple with 0 element"); + + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + if (reply.elements != size) { + throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements"); + } + + if (reply.element == nullptr) { + throw ProtoError("Null TUPLE reply"); + } + + return detail::parse_tuple(reply.element, 0); +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::back_inserter(container)); + + return container; +} + +template ::value, int>::type> +T parse(ParseTag, redisReply &reply) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + T container; + + to_array(reply, std::inserter(container, container.end())); + + return container; +} + +template +long long parse_scan_reply(redisReply &reply, Output output) { + if (reply.elements != 2 || reply.element == nullptr) { + throw ProtoError("Invalid scan reply"); + } + + auto *cursor_reply = reply.element[0]; + auto *data_reply = reply.element[1]; + if (cursor_reply == nullptr || data_reply == nullptr) { + throw ProtoError("Invalid cursor reply or data reply"); + } + + auto cursor_str = reply::parse(*cursor_reply); + auto new_cursor = 0; + try { + new_cursor = std::stoll(cursor_str); + } catch (const std::exception &e) { + throw ProtoError("Invalid cursor reply: " + cursor_str); + } + + reply::to_array(*data_reply, output); + + return new_cursor; +} + +template +void to_array(redisReply &reply, Output output) { + if (!is_array(reply)) { + throw ProtoError("Expect ARRAY reply"); + } + + detail::to_array(typename IsKvPairIter::type(), reply, output); +} + +template +auto parse_xpending_reply(redisReply &reply, Output output) + -> std::tuple { + if (!is_array(reply) || reply.elements != 4) { + throw ProtoError("expect array reply with 4 elements"); + } + + for (std::size_t idx = 0; idx != reply.elements; ++idx) { + if (reply.element[idx] == nullptr) { + throw ProtoError("null array reply"); + } + } + + auto num = parse(*(reply.element[0])); + auto start = parse(*(reply.element[1])); + auto end = parse(*(reply.element[2])); + + auto &entry_reply = *(reply.element[3]); + if (!is_nil(entry_reply)) { + to_array(entry_reply, output); + } + + return std::make_tuple(num, std::move(start), std::move(end)); +} + +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h new file mode 100644 index 000000000..e80d1e56a --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h @@ -0,0 +1,138 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H +#define SEWENEW_REDISPLUSPLUS_SENTINEL_H + +#include +#include +#include +#include +#include +#include "connection.h" +#include "shards.h" +#include "reply.h" + +namespace sw { + +namespace redis { + +struct SentinelOptions { + std::vector> nodes; + + std::string password; + + bool keep_alive = true; + + std::chrono::milliseconds connect_timeout{100}; + + std::chrono::milliseconds socket_timeout{100}; + + std::chrono::milliseconds retry_interval{100}; + + std::size_t max_retry = 2; +}; + +class Sentinel { +public: + explicit Sentinel(const SentinelOptions &sentinel_opts); + + Sentinel(const Sentinel &) = delete; + Sentinel& operator=(const Sentinel &) = delete; + + Sentinel(Sentinel &&) = delete; + Sentinel& operator=(Sentinel &&) = delete; + + ~Sentinel() = default; + +private: + Connection master(const std::string &master_name, const ConnectionOptions &opts); + + Connection slave(const std::string &master_name, const ConnectionOptions &opts); + + class Iterator; + + friend class SimpleSentinel; + + std::list _parse_options(const SentinelOptions &opts) const; + + Optional _get_master_addr_by_name(Connection &connection, const StringView &name); + + std::vector _get_slave_addr_by_name(Connection &connection, const StringView &name); + + Connection _connect_redis(const Node &node, ConnectionOptions opts); + + Role _get_role(Connection &connection); + + std::vector _parse_slave_info(redisReply &reply) const; + + std::list _healthy_sentinels; + + std::list _broken_sentinels; + + SentinelOptions _sentinel_opts; + + std::mutex _mutex; +}; + +class SimpleSentinel { +public: + SimpleSentinel(const std::shared_ptr &sentinel, + const std::string &master_name, + Role role); + + SimpleSentinel() = default; + + SimpleSentinel(const SimpleSentinel &) = default; + SimpleSentinel& operator=(const SimpleSentinel &) = default; + + SimpleSentinel(SimpleSentinel &&) = default; + SimpleSentinel& operator=(SimpleSentinel &&) = default; + + ~SimpleSentinel() = default; + + explicit operator bool() const { + return bool(_sentinel); + } + + Connection create(const ConnectionOptions &opts); + +private: + std::shared_ptr _sentinel; + + std::string _master_name; + + Role _role = Role::MASTER; +}; + +class StopIterError : public Error { +public: + StopIterError() : Error("StopIterError") {} + + StopIterError(const StopIterError &) = default; + StopIterError& operator=(const StopIterError &) = default; + + StopIterError(StopIterError &&) = default; + StopIterError& operator=(StopIterError &&) = default; + + virtual ~StopIterError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h new file mode 100644 index 000000000..a0593acbc --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h @@ -0,0 +1,115 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_H + +#include +#include +#include "errors.h" + +namespace sw { + +namespace redis { + +using Slot = std::size_t; + +struct SlotRange { + Slot min; + Slot max; +}; + +inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) { + return lhs.max < rhs.max; +} + +struct Node { + std::string host; + int port; +}; + +inline bool operator==(const Node &lhs, const Node &rhs) { + return lhs.host == rhs.host && lhs.port == rhs.port; +} + +struct NodeHash { + std::size_t operator()(const Node &node) const noexcept { + auto host_hash = std::hash{}(node.host); + auto port_hash = std::hash{}(node.port); + return host_hash ^ (port_hash << 1); + } +}; + +using Shards = std::map; + +class RedirectionError : public ReplyError { +public: + RedirectionError(const std::string &msg); + + RedirectionError(const RedirectionError &) = default; + RedirectionError& operator=(const RedirectionError &) = default; + + RedirectionError(RedirectionError &&) = default; + RedirectionError& operator=(RedirectionError &&) = default; + + virtual ~RedirectionError() = default; + + Slot slot() const { + return _slot; + } + + const Node& node() const { + return _node; + } + +private: + std::pair _parse_error(const std::string &msg) const; + + Slot _slot = 0; + Node _node; +}; + +class MovedError : public RedirectionError { +public: + explicit MovedError(const std::string &msg) : RedirectionError(msg) {} + + MovedError(const MovedError &) = default; + MovedError& operator=(const MovedError &) = default; + + MovedError(MovedError &&) = default; + MovedError& operator=(MovedError &&) = default; + + virtual ~MovedError() = default; +}; + +class AskError : public RedirectionError { +public: + explicit AskError(const std::string &msg) : RedirectionError(msg) {} + + AskError(const AskError &) = default; + AskError& operator=(const AskError &) = default; + + AskError(AskError &&) = default; + AskError& operator=(AskError &&) = default; + + virtual ~AskError() = default; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h new file mode 100644 index 000000000..1184806e9 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h @@ -0,0 +1,137 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H +#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H + +#include +#include +#include +#include +#include +#include "reply.h" +#include "connection_pool.h" +#include "shards.h" + +namespace sw { + +namespace redis { + +using ConnectionPoolSPtr = std::shared_ptr; + +class GuardedConnection { +public: + GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool), + _connection(_pool->fetch()) { + assert(!_connection.broken()); + } + + GuardedConnection(const GuardedConnection &) = delete; + GuardedConnection& operator=(const GuardedConnection &) = delete; + + GuardedConnection(GuardedConnection &&) = default; + GuardedConnection& operator=(GuardedConnection &&) = default; + + ~GuardedConnection() { + _pool->release(std::move(_connection)); + } + + Connection& connection() { + return _connection; + } + +private: + ConnectionPoolSPtr _pool; + Connection _connection; +}; + +class ShardsPool { +public: + ShardsPool() = default; + + ShardsPool(const ShardsPool &that) = delete; + ShardsPool& operator=(const ShardsPool &that) = delete; + + ShardsPool(ShardsPool &&that); + ShardsPool& operator=(ShardsPool &&that); + + ~ShardsPool() = default; + + ShardsPool(const ConnectionPoolOptions &pool_opts, + const ConnectionOptions &connection_opts); + + // Fetch a connection by key. + GuardedConnection fetch(const StringView &key); + + // Randomly pick a connection. + GuardedConnection fetch(); + + // Fetch a connection by node. + GuardedConnection fetch(const Node &node); + + void update(); + + ConnectionOptions connection_options(const StringView &key); + + ConnectionOptions connection_options(); + +private: + void _move(ShardsPool &&that); + + void _init_pool(const Shards &shards); + + Shards _cluster_slots(Connection &connection) const; + + ReplyUPtr _cluster_slots_command(Connection &connection) const; + + Shards _parse_reply(redisReply &reply) const; + + std::pair _parse_slot_info(redisReply &reply) const; + + // Get slot by key. + std::size_t _slot(const StringView &key) const; + + // Randomly pick a slot. + std::size_t _slot() const; + + ConnectionPoolSPtr& _get_pool(Slot slot); + + GuardedConnection _fetch(Slot slot); + + ConnectionOptions _connection_options(Slot slot); + + using NodeMap = std::unordered_map; + + NodeMap::iterator _add_node(const Node &node); + + ConnectionPoolOptions _pool_opts; + + ConnectionOptions _connection_opts; + + Shards _shards; + + NodeMap _pools; + + std::mutex _mutex; + + static const std::size_t SHARDS = 16383; +}; + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h new file mode 100644 index 000000000..8b7c5cfb4 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h @@ -0,0 +1,231 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H +#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H + +#include +#include +#include +#include "connection.h" +#include "reply.h" +#include "command.h" +#include "utils.h" + +namespace sw { + +namespace redis { + +// @NOTE: Subscriber is NOT thread-safe. +// Subscriber uses callbacks to handle messages. There are 6 kinds of messages: +// 1) MESSAGE: message sent to a channel. +// 2) PMESSAGE: message sent to channels of a given pattern. +// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel. +// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel. +// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern. +// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern. +// +// Use Subscriber::on_message(MsgCallback) to set the callback function for message of +// *MESSAGE* type, and the callback interface is: +// void (std::string channel, std::string msg) +// +// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of +// *PMESSAGE* type, and the callback interface is: +// void (std::string pattern, std::string channel, std::string msg) +// +// Messages of other types are called *META MESSAGE*, they have the same callback interface. +// Use Subscriber::on_meta(MetaCallback) to set the callback function: +// void (Subscriber::MsgType type, OptionalString channel, long long num) +// +// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to +// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all +// channels/patterns, *channel* will be null. So the second parameter of meta callback +// is of type *OptionalString*. +// +// All these callback interfaces pass std::string by value, and you can take their ownership +// (i.e. std::move) safely. +// +// If you don't set callback for a specific kind of message, Subscriber::consume() will +// receive the message, and ignore it, i.e. no callback will be called. +class Subscriber { +public: + Subscriber(const Subscriber &) = delete; + Subscriber& operator=(const Subscriber &) = delete; + + Subscriber(Subscriber &&) = default; + Subscriber& operator=(Subscriber &&) = default; + + ~Subscriber() = default; + + enum class MsgType { + SUBSCRIBE, + UNSUBSCRIBE, + PSUBSCRIBE, + PUNSUBSCRIBE, + MESSAGE, + PMESSAGE + }; + + template + void on_message(MsgCb msg_callback); + + template + void on_pmessage(PMsgCb pmsg_callback); + + template + void on_meta(MetaCb meta_callback); + + void subscribe(const StringView &channel); + + template + void subscribe(Input first, Input last); + + template + void subscribe(std::initializer_list channels) { + subscribe(channels.begin(), channels.end()); + } + + void unsubscribe(); + + void unsubscribe(const StringView &channel); + + template + void unsubscribe(Input first, Input last); + + template + void unsubscribe(std::initializer_list channels) { + unsubscribe(channels.begin(), channels.end()); + } + + void psubscribe(const StringView &pattern); + + template + void psubscribe(Input first, Input last); + + template + void psubscribe(std::initializer_list channels) { + psubscribe(channels.begin(), channels.end()); + } + + void punsubscribe(); + + void punsubscribe(const StringView &channel); + + template + void punsubscribe(Input first, Input last); + + template + void punsubscribe(std::initializer_list channels) { + punsubscribe(channels.begin(), channels.end()); + } + + void consume(); + +private: + friend class Redis; + + friend class RedisCluster; + + explicit Subscriber(Connection connection); + + MsgType _msg_type(redisReply *reply) const; + + void _check_connection(); + + void _handle_message(redisReply &reply); + + void _handle_pmessage(redisReply &reply); + + void _handle_meta(MsgType type, redisReply &reply); + + using MsgCallback = std::function; + + using PatternMsgCallback = std::function; + + using MetaCallback = std::function; + + using TypeIndex = std::unordered_map; + static const TypeIndex _msg_type_index; + + Connection _connection; + + MsgCallback _msg_callback = nullptr; + + PatternMsgCallback _pmsg_callback = nullptr; + + MetaCallback _meta_callback = nullptr; +}; + +template +void Subscriber::on_message(MsgCb msg_callback) { + _msg_callback = msg_callback; +} + +template +void Subscriber::on_pmessage(PMsgCb pmsg_callback) { + _pmsg_callback = pmsg_callback; +} + +template +void Subscriber::on_meta(MetaCb meta_callback) { + _meta_callback = meta_callback; +} + +template +void Subscriber::subscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::subscribe_range(_connection, first, last); +} + +template +void Subscriber::unsubscribe(Input first, Input last) { + _check_connection(); + + cmd::unsubscribe_range(_connection, first, last); +} + +template +void Subscriber::psubscribe(Input first, Input last) { + if (first == last) { + return; + } + + _check_connection(); + + cmd::psubscribe_range(_connection, first, last); +} + +template +void Subscriber::punsubscribe(Input first, Input last) { + _check_connection(); + + cmd::punsubscribe_range(_connection, first, last); +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h new file mode 100644 index 000000000..f19f24889 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h @@ -0,0 +1,77 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H +#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H + +#include +#include +#include "connection.h" +#include "errors.h" + +namespace sw { + +namespace redis { + +class TransactionImpl { +public: + explicit TransactionImpl(bool piped) : _piped(piped) {} + + template + void command(Connection &connection, Cmd cmd, Args &&...args); + + std::vector exec(Connection &connection, std::size_t cmd_num); + + void discard(Connection &connection, std::size_t cmd_num); + +private: + void _open_transaction(Connection &connection); + + void _close_transaction(); + + void _get_queued_reply(Connection &connection); + + void _get_queued_replies(Connection &connection, std::size_t cmd_num); + + std::vector _exec(Connection &connection); + + void _discard(Connection &connection); + + bool _in_transaction = false; + + bool _piped; +}; + +template +void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) { + assert(!connection.broken()); + + if (!_in_transaction) { + _open_transaction(connection); + } + + cmd(connection, std::forward(args)...); + + if (!_piped) { + _get_queued_reply(connection); + } +} + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h new file mode 100644 index 000000000..e29e64e14 --- /dev/null +++ b/ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h @@ -0,0 +1,269 @@ +/************************************************************************** + Copyright (c) 2017 sewenew + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + *************************************************************************/ + +#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H +#define SEWENEW_REDISPLUSPLUS_UTILS_H + +#include +#include +#include + +namespace sw { + +namespace redis { + +// By now, not all compilers support std::string_view, +// so we make our own implementation. +class StringView { +public: + constexpr StringView() noexcept = default; + + constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {} + + StringView(const char *data) : _data(data), _size(std::strlen(data)) {} + + StringView(const std::string &str) : _data(str.data()), _size(str.size()) {} + + constexpr StringView(const StringView &) noexcept = default; + + StringView& operator=(const StringView &) noexcept = default; + + constexpr const char* data() const noexcept { + return _data; + } + + constexpr std::size_t size() const noexcept { + return _size; + } + +private: + const char *_data = nullptr; + std::size_t _size = 0; +}; + +template +class Optional { +public: + Optional() = default; + + Optional(const Optional &) = default; + Optional& operator=(const Optional &) = default; + + Optional(Optional &&) = default; + Optional& operator=(Optional &&) = default; + + ~Optional() = default; + + template + explicit Optional(Args &&...args) : _value(true, T(std::forward(args)...)) {} + + explicit operator bool() const { + return _value.first; + } + + T& value() { + return _value.second; + } + + const T& value() const { + return _value.second; + } + + T* operator->() { + return &(_value.second); + } + + const T* operator->() const { + return &(_value.second); + } + + T& operator*() { + return _value.second; + } + + const T& operator*() const { + return _value.second; + } + +private: + std::pair _value; +}; + +using OptionalString = Optional; + +using OptionalLongLong = Optional; + +using OptionalDouble = Optional; + +using OptionalStringPair = Optional>; + +template +struct IsKvPair : std::false_type {}; + +template +struct IsKvPair> : std::true_type {}; + +template +using Void = void; + +template > +struct IsInserter : std::false_type {}; + +template +//struct IsInserter> : std::true_type {}; +struct IsInserter::value>::type> + : std::true_type {}; + +template > +struct IterType { + using type = typename std::iterator_traits::value_type; +}; + +template +//struct IterType> { +struct IterType::value>::type> { + typename std::enable_if::value>::type> { + using type = typename std::decay::type; +}; + +template > +struct IsIter : std::false_type {}; + +template +struct IsIter::value>::type> : std::true_type {}; + +template +//struct IsIter::iterator_category>> +struct IsIter::value_type>::value>::type> + : std::integral_constant::value> {}; + +template +struct IsKvPairIter : IsKvPair::type> {}; + +template +struct TupleWithType : std::false_type {}; + +template +struct TupleWithType> : std::false_type {}; + +template +struct TupleWithType> : TupleWithType> {}; + +template +struct TupleWithType> : std::true_type {}; + +template +struct IndexSequence {}; + +template +struct MakeIndexSequence : MakeIndexSequence {}; + +template +struct MakeIndexSequence<0, Is...> : IndexSequence {}; + +// NthType and NthValue are taken from +// https://stackoverflow.com/questions/14261183 +template +struct NthType {}; + +template +struct NthType<0, Arg, Args...> { + using type = Arg; +}; + +template +struct NthType { + using type = typename NthType::type; +}; + +template +struct LastType { + using type = typename NthType::type; +}; + +struct InvalidLastType {}; + +template <> +struct LastType<> { + using type = InvalidLastType; +}; + +template +auto NthValue(Arg &&arg, Args &&...) + -> typename std::enable_if<(I == 0), decltype(std::forward(arg))>::type { + return std::forward(arg); +} + +template +auto NthValue(Arg &&, Args &&...args) + -> typename std::enable_if<(I > 0), + decltype(std::forward::type>( + std::declval::type>()))>::type { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template +auto LastValue(Args &&...args) + -> decltype(std::forward::type>( + std::declval::type>())) { + return std::forward::type>( + NthValue(std::forward(args)...)); +} + +template > +struct HasPushBack : std::false_type {}; + +template +struct HasPushBack().push_back(std::declval()) + )>::value>::type> : std::true_type {}; + +template > +struct HasInsert : std::false_type {}; + +template +struct HasInsert().insert(std::declval(), + std::declval())), + typename T::iterator>::value>::type> : std::true_type {}; + +template +struct IsSequenceContainer + : std::integral_constant::value + && !std::is_same::type, std::string>::value> {}; + +template +struct IsAssociativeContainer + : std::integral_constant::value && !HasPushBack::value> {}; + +uint16_t crc16(const char *buf, int len); + +} + +} + +#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H From 5d47697adedfaa3380b50c18d80d660dfcd5ecfe Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Tue, 12 May 2020 13:04:16 -0700 Subject: [PATCH 09/13] helps to add the actual library --- .../install/centos8/lib/libredis++.a | Bin 0 -> 1168436 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a b/ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a new file mode 100644 index 0000000000000000000000000000000000000000..c605b0ef0c1b0b43ccedb4b6969d3adb53897fe5 GIT binary patch literal 1168436 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

u({r6lS_Qx9HkM&_6YluJ2 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ð5-ga1*iXg@IM1Si=n6gU%=`A6gd5N$=Wne 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

Tzxf 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

RR^N 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 Date: Tue, 12 May 2020 14:04:03 -0700 Subject: [PATCH 10/13] fix centos8 build --- .../install/centos8/lib/libredis++.a | Bin 1168436 -> 1168436 bytes make-linux.mk | 9 ++++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a b/ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a index c605b0ef0c1b0b43ccedb4b6969d3adb53897fe5..fbf88b667147cb995cc74a4cdf1c2766ff2ccf73 100644 GIT binary patch delta 275 zcmdn8!hOpM_X%<=rWQs9jY?aU7`M0p*#_p*<#q^&Z~y4RXt@}~x7@Dxf>9z5$TT#V z4rGdLPY7d5YKHK^O7Gre%G?d&8-nD;wB;q zc2)b2)4bb%oaTE}2okf{e$STw1z6e=BK=?{|Mmwn1*|~+HMHCgbl8mbK&BDcPvYAz JrVA$L007$-W6%Hq delta 270 zcmdn8!hOpM_X%<=#wNyQjY?aU7`M1E8=DwSpZI`7eESC%Mytg@DI>G(dM_9y^1#gL zK&IICgfOP0X0XbKr+LM<-?_<@wHv`rn9A(Lf#B|$%gl8Mq}gJ++ztWp?GNKwUJ8P& zG-y9p$O6QyK+Fcj?Ay;3avT8JV{9}XC?(Op>H!B3a{@6J5OZ%|^?)a#A1uF}uYmVL zG>B;ec2fI~)4bb%oaTE}2o~FZ&zAp1G|&ykV866Kn90BW!At=wh*!4*-7#Z5NTC@> Mv-tLl>4M2Q0H}^-!T Date: Tue, 12 May 2020 15:17:57 -0700 Subject: [PATCH 11/13] can now build centos8 docker container with Redis support --- .dockerignore | 2 ++ ext/central-controller-docker/Dockerfile | 10 +++--- ext/central-controller-docker/main.sh | 40 ++++++++++++------------ make-mac.mk | 4 +-- 4 files changed, 29 insertions(+), 27 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..4acef401c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git/ +workspace/ diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index 04d4826c3..2866e6995 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -1,22 +1,22 @@ # Dockerfile for ZeroTier Central Controllers -FROM centos:7 as builder +FROM centos:8 as builder MAINTAINER Adam Ierymekno , Grant Limberg ARG git_branch=master RUN yum update -y -RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm +RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm && dnf -qy module disable postgresql RUN yum -y install epel-release && yum -y update && yum clean all RUN yum groupinstall -y "Development Tools" -RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel glibc-static libstdc++-static clang jemalloc jemalloc-devel +RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel clang jemalloc jemalloc-devel # RUN git clone http://git.int.zerotier.com/zerotier/ZeroTierOne.git # RUN if [ "$git_branch" != "master" ]; then cd ZeroTierOne && git checkout -b $git_branch origin/$git_branch; fi ADD . /ZeroTierOne RUN cd ZeroTierOne && make clean && make central-controller -FROM centos:7 -RUN yum install -y yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm && yum -y install epel-release && yum -y update && yum clean all +FROM centos:8 +RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm && dnf -qy module disable postgresql && yum -y install epel-release && yum -y update && yum clean all RUN yum install -y jemalloc jemalloc-devel postgresql10 COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one diff --git a/ext/central-controller-docker/main.sh b/ext/central-controller-docker/main.sh index b8d3b142c..6f6a01f69 100755 --- a/ext/central-controller-docker/main.sh +++ b/ext/central-controller-docker/main.sh @@ -25,30 +25,30 @@ if [ -z "$ZT_DB_PASSWORD" ]; then exit 1 fi -RMQ="" -if [ "$ZT_USE_RABBITMQ" == "true" ]; then - if [ -z "$RABBITMQ_HOST" ]; then - echo '*** FAILED: RABBITMQ_HOST environment variable not defined' +REDIS="" +if [ "$ZT_USE_REDIS" == "true" ]; then + if [ -z "$ZT_REDIS_HOST" ]; then + echo '*** FAILED: ZT_REDIS_HOST environment variable not defined' exit 1 fi - if [ -z "$RABBITMQ_PORT" ]; then - echo '*** FAILED: RABBITMQ_PORT environment variable not defined' + + if [ -z "$ZT_REDIS_PORT" ]; then + echo '*** FAILED: ZT_REDIS_PORT enivronment variable not defined' exit 1 fi - if [ -z "$RABBITMQ_USERNAME" ]; then - echo '*** FAILED: RABBITMQ_USERNAME environment variable not defined' + + if [ -z "$ZT_REDIS_CLUSTER_MODE" ]; + echo '*** FAILED: ZT_REDIS_CLUSTER_MODE environment variable not defined' exit 1 fi - if [ -z "$RABBITMQ_PASSWORD" ]; then - echo '*** FAILED: RABBITMQ_PASSWORD environment variable not defined' - exit 1 - fi - RMQ=", \"rabbitmq\": { - \"host\": \"${RABBITMQ_HOST}\", - \"port\": ${RABBITMQ_PORT}, - \"username\": \"${RABBITMQ_USERNAME}\", - \"password\": \"${RABBITMQ_PASSWORD}\" - }" + + REDIS="\"redis\": { + \"hostname\": \"${ZT_REDIS_HOST}\", + \"port\": ${ZT_REDIS_PORT}, + \"clusterMode\": ${ZT_REDIS_CLUSTER_MODE}, + \"password\": \"${ZT_REDIS_PASSWORD}\" + } + " fi mkdir -p /var/lib/zerotier-one @@ -62,14 +62,14 @@ DEFAULT_PORT=9993 echo "{ \"settings\": { + \"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\", \"portMappingEnabled\": true, \"softwareUpdate\": \"disable\", \"interfacePrefixBlacklist\": [ \"inot\", \"nat64\" ], - \"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\" - ${RMQ} + ${REDIS} } } " > /var/lib/zerotier-one/local.conf diff --git a/make-mac.mk b/make-mac.mk index 5e7a67c20..f7ff8759b 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -152,8 +152,8 @@ official: FORCE make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg central-controller-docker: FORCE - #docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . - docker build -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + clean: rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules macui/build zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* From 15c0c1db39f51591a27f599835ee2f9b606237f0 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 13 May 2020 09:46:41 -0700 Subject: [PATCH 12/13] finish the RabbitMQ-ectomy --- controller/PostgreSQL.cpp | 12 +++++++----- ext/central-controller-docker/main.sh | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 505d76527..286a734e0 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -128,8 +128,10 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, R opts.db = 0; poolOpts.size = 10; if (_rc->clusterMode) { + fprintf(stderr, "Using Redis in Cluster Mode\n"); _cluster = std::make_shared(opts, poolOpts); } else { + fprintf(stderr, "Using Redis in Standalone Mode\n"); _redis = std::make_shared(opts, poolOpts); } } @@ -613,7 +615,7 @@ void PostgreSQL::heartbeat() std::string build = std::to_string(ZEROTIER_ONE_VERSION_BUILD); std::string now = std::to_string(OSUtils::now()); std::string host_port = std::to_string(_listenPort); - std::string use_rabbitmq = (false) ? "true" : "false"; + std::string use_redis = (_rc != NULL) ? "true" : "false"; const char *values[10] = { controllerId, hostname, @@ -624,16 +626,16 @@ void PostgreSQL::heartbeat() rev.c_str(), build.c_str(), host_port.c_str(), - use_rabbitmq.c_str() + use_redis.c_str() }; PGresult *res = PQexecParams(conn, - "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq) " - "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) " + "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq, use_redis) " + "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10, $11) " "ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, " "public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, " "v_rev = EXCLUDED.v_rev, v_build = EXCLUDED.v_rev, host_port = EXCLUDED.host_port, " - "use_rabbitmq = EXCLUDED.use_rabbitmq", + "use_redis = EXCLUDED.use_redis", 10, // number of parameters NULL, // oid field. ignore values, // values for substitution diff --git a/ext/central-controller-docker/main.sh b/ext/central-controller-docker/main.sh index 6f6a01f69..e0ebabae0 100755 --- a/ext/central-controller-docker/main.sh +++ b/ext/central-controller-docker/main.sh @@ -37,7 +37,7 @@ if [ "$ZT_USE_REDIS" == "true" ]; then exit 1 fi - if [ -z "$ZT_REDIS_CLUSTER_MODE" ]; + if [ -z "$ZT_REDIS_CLUSTER_MODE" ]; then echo '*** FAILED: ZT_REDIS_CLUSTER_MODE environment variable not defined' exit 1 fi @@ -49,6 +49,8 @@ if [ "$ZT_USE_REDIS" == "true" ]; then \"password\": \"${ZT_REDIS_PASSWORD}\" } " +else + REDIS="\"redis\": {}" fi mkdir -p /var/lib/zerotier-one From 701960def5e6066ba11097e1288fbeed02ec91cd Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 13 May 2020 17:23:27 -0700 Subject: [PATCH 13/13] Track member status in Redis --- controller/PostgreSQL.cpp | 138 +++++++++++++++++++++++++++++++++----- controller/PostgreSQL.hpp | 4 ++ 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 286a734e0..05d2de7b1 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -229,12 +229,14 @@ void PostgreSQL::eraseNetwork(const uint64_t networkId) tmp.first["objtype"] = "_delete_network"; tmp.second = true; _commitQueue.post(tmp); + nlohmann::json nullJson; + _networkChanged(tmp.first, nullJson, true); } void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) { char tmp2[24]; - std::pair tmp; + std::pair tmp, nw; Utils::hex(networkId, tmp2); tmp.first["nwid"] = tmp2; Utils::hex(memberId, tmp2); @@ -242,6 +244,8 @@ void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId) tmp.first["objtype"] = "_delete_member"; tmp.second = true; _commitQueue.post(tmp); + nlohmann::json nullJson; + _memberChanged(tmp.first, nullJson, true); } void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress) @@ -630,8 +634,8 @@ void PostgreSQL::heartbeat() }; PGresult *res = PQexecParams(conn, - "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq, use_redis) " - "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10, $11) " + "INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_redis) " + "VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) " "ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, " "public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, " "v_rev = EXCLUDED.v_rev, v_build = EXCLUDED.v_rev, host_port = EXCLUDED.host_port, " @@ -1401,6 +1405,15 @@ void PostgreSQL::commitThread() } void PostgreSQL::onlineNotificationThread() +{ + if (_rc != NULL) { + onlineNotification_Redis(); + } else { + onlineNotification_Postgres(); + } +} + +void PostgreSQL::onlineNotification_Postgres() { PGconn *conn = getPgConn(); if (PQstatus(conn) == CONNECTION_BAD) { @@ -1410,9 +1423,7 @@ void PostgreSQL::onlineNotificationThread() } _connected = 1; - //int64_t lastUpdatedNetworkStatus = 0; - std::unordered_map< std::pair,int64_t,_PairHasher > lastOnlineCumulative; - + nlohmann::json jtmp1, jtmp2; while (_run == 1) { if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "ERROR: Online Notification thread lost connection to Postgres."); @@ -1420,9 +1431,6 @@ void PostgreSQL::onlineNotificationThread() exit(5); } - // map used to send notifications to front end - std::unordered_map> updateMap; - std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; { std::lock_guard l(_lastOnline_l); @@ -1443,20 +1451,13 @@ void PostgreSQL::onlineNotificationThread() OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", i->first.second); - auto found = _networks.find(nwid_i); - if (found == _networks.end()) { - continue; // skip members trying to join non-existant networks + if(!get(nwid_i, jtmp1, i->first.second, jtmp2)) { + continue; // skip non existent networks/members } std::string networkId(nwidTmp); std::string memberId(memTmp); - std::vector &members = updateMap[networkId]; - members.push_back(memberId); - - lastOnlineCumulative[i->first] = i->second.first; - - const char *qvals[2] = { networkId.c_str(), memberId.c_str() @@ -1526,6 +1527,107 @@ void PostgreSQL::onlineNotificationThread() } } +void PostgreSQL::onlineNotification_Redis() +{ + _connected = 1; + + char buf[11] = {0}; + std::string controllerId = std::string(_myAddress.toString(buf)); + + while (_run == 1) { + std::unordered_map< std::pair,std::pair,_PairHasher > lastOnline; + { + std::lock_guard l(_lastOnline_l); + lastOnline.swap(_lastOnline); + } + + if (_rc->clusterMode) { + auto tx = _cluster->redis(controllerId).transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } else { + auto tx = _redis->transaction(true); + _doRedisUpdate(tx, controllerId, lastOnline); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +} + +void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, + std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline) + +{ + nlohmann::json jtmp1, jtmp2; + for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) { + uint64_t nwid_i = i->first.first; + uint64_t memberid_i = i->first.second; + char nwidTmp[64]; + char memTmp[64]; + char ipTmp[64]; + OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i); + OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", memberid_i); + + if (!get(nwid_i, jtmp1, memberid_i, jtmp2)){ + continue; // skip non existent members/networks + } + auto found = _networks.find(nwid_i); + if (found == _networks.end()) { + continue; // skip members trying to join non-existant networks + } + + std::string networkId(nwidTmp); + std::string memberId(memTmp); + + int64_t ts = i->second.first; + std::string ipAddr = i->second.second.toIpString(ipTmp); + std::string timestamp = std::to_string(ts); + + std::unordered_map record = { + {"id", memberId}, + {"address", ipAddr}, + {"last_updated", std::to_string(ts)} + }; + tx.zadd("nodes-online:{"+controllerId+"}", memberId, ts) + .zadd("network-nodes-online:{"+controllerId+"}:"+networkId, memberId, ts) + .sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId) + .hmset("network:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); + } + + tx.exec(); + + // expire records from all-nodes and network-nodes member list + uint64_t expireOld = OSUtils::now() - 300000; + + auto cursor = 0LL; + std::unordered_set keys; + // can't scan for keys in a transaction, so we need to fall back to _cluster or _redis + // to get all network-members keys + if(_rc->clusterMode) { + auto r = _cluster->redis(controllerId); + while(true) { + cursor = r.scan(cursor, "network-nodes-online:{"+controllerId+"}:*", INT_MAX, std::inserter(keys, keys.begin())); + if (cursor == 0) { + break; + } + } + } else { + while(true) { + cursor = _redis->scan(cursor, "network-nodes-online:"+controllerId+":*", INT_MAX, std::inserter(keys, keys.begin())); + if (cursor == 0) { + break; + } + } + } + + tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + + for(const auto &k : keys) { + tx.zremrangebyscore(k, sw::redis::RightBoundedInterval(expireOld, sw::redis::BoundType::LEFT_OPEN)); + } + + tx.exec(); +} + PGconn *PostgreSQL::getPgConn(OverrideMode m) { if (m == ALLOW_PGBOUNCER_OVERRIDE) { diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 44347cd81..f61670132 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -70,6 +70,10 @@ private: void commitThread(); void onlineNotificationThread(); + void onlineNotification_Postgres(); + void onlineNotification_Redis(); + void _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, + std::unordered_map< std::pair,std::pair,_PairHasher > &lastOnline); enum OverrideMode { ALLOW_PGBOUNCER_OVERRIDE = 0,