├── .clang-format ├── .gitignore ├── .travis.yml ├── Jamroot ├── LICENSE.md ├── Makefile ├── README.md ├── redispp.sln ├── redispp.vcxproj ├── redispp.vcxproj.filters ├── src ├── Jamfile ├── redispp.cpp └── redispp.h └── test ├── Jamfile ├── multi.cpp ├── perf.cpp ├── test.cpp └── trans.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | BreakAfterJavaFieldAnnotations: false 40 | BreakStringLiterals: true 41 | ColumnLimit: 80 42 | CommentPragmas: '^ IWYU pragma:' 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 44 | ConstructorInitializerIndentWidth: 4 45 | ContinuationIndentWidth: 4 46 | Cpp11BracedListStyle: true 47 | DerivePointerAlignment: false 48 | DisableFormat: false 49 | ExperimentalAutoDetectBinPacking: false 50 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 51 | IncludeCategories: 52 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 53 | Priority: 2 54 | - Regex: '^(<|"(gtest|isl|json)/)' 55 | Priority: 3 56 | - Regex: '.*' 57 | Priority: 1 58 | IncludeIsMainRegex: '$' 59 | IndentCaseLabels: false 60 | IndentWidth: 2 61 | IndentWrappedFunctionNames: false 62 | JavaScriptQuotes: Leave 63 | JavaScriptWrapImports: true 64 | KeepEmptyLinesAtTheStartOfBlocks: true 65 | MacroBlockBegin: '' 66 | MacroBlockEnd: '' 67 | MaxEmptyLinesToKeep: 1 68 | NamespaceIndentation: None 69 | ObjCBlockIndentWidth: 2 70 | ObjCSpaceAfterProperty: false 71 | ObjCSpaceBeforeProtocolList: true 72 | PenaltyBreakBeforeFirstCallParameter: 19 73 | PenaltyBreakComment: 300 74 | PenaltyBreakFirstLessLess: 120 75 | PenaltyBreakString: 1000 76 | PenaltyExcessCharacter: 1000000 77 | PenaltyReturnTypeOnItsOwnLine: 60 78 | PointerAlignment: Left 79 | ReflowComments: true 80 | SortIncludes: true 81 | SpaceAfterCStyleCast: false 82 | SpaceAfterTemplateKeyword: true 83 | SpaceBeforeAssignmentOperators: true 84 | SpaceBeforeParens: ControlStatements 85 | SpaceInEmptyParentheses: false 86 | SpacesBeforeTrailingComments: 1 87 | SpacesInAngles: false 88 | SpacesInContainerLiterals: true 89 | SpacesInCStyleCastParentheses: false 90 | SpacesInParentheses: false 91 | SpacesInSquareBrackets: false 92 | Standard: Cpp11 93 | TabWidth: 8 94 | UseTab: Never 95 | ... 96 | 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | unittests 16 | transtest 17 | perftest 18 | multitest 19 | .project 20 | .cproject 21 | 22 | *.swp 23 | *~ 24 | 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | os: linux 3 | dist: trusty 4 | script: make unittests && ./unittests 5 | sudo: required 6 | services: 7 | - docker 8 | before_install: 9 | - docker pull redis 10 | - docker run -p 127.0.0.1:6379:6379 -d redis 11 | compiler: 12 | - clang 13 | - gcc 14 | install: 15 | - if [ "$CXX" = "clang++" ]; then export CC="clang-4.0" CXX="clang++-4.0"; fi 16 | - if [ "$CXX" = "g++" ]; then export CC="gcc-6" CXX="g++-6"; fi 17 | addons: 18 | apt: 19 | sources: 20 | - ubuntu-toolchain-r-test 21 | - llvm-toolchain-trusty-4.0 22 | packages: 23 | - g++-6 24 | - libboost-test1.55-dev 25 | - clang-4.0 26 | - libboost-test1.55-dev 27 | -------------------------------------------------------------------------------- /Jamroot: -------------------------------------------------------------------------------- 1 | project Jamroot 2 | : usage-requirements ./src 3 | ; 4 | 5 | use-project /redispp : src ; 6 | use-project /test-redis : test ; 7 | 8 | build-project /redispp ; 9 | build-project /test-redis ; 10 | 11 | alias install : install-lib install-headers ; 12 | install install-lib : /redispp : /usr/local/lib ; 13 | install install-headers : [ glob src/*.h ] : /usr/local/include ; 14 | 15 | explicit install install-lib install-headers ; 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: libredispp.a libredispp.so unittests perftest multitest transtest 2 | 3 | CXX ?= g++ 4 | CXXFLAGS ?= -std=c++11 -g -O0 -Isrc $(EXTRA_CXXFLAGS) -Werror 5 | 6 | VPATH += src test 7 | 8 | %.o: %.cpp 9 | $(CXX) $(CXXFLAGS) -c $^ -o $@ 10 | 11 | libredispp.a: redispp.o 12 | ar cr libredispp.a redispp.o 13 | 14 | %.pic.o: %.cpp 15 | $(CXX) -fPIC $(CXXFLAGS) -c $^ -o $@ 16 | 17 | libredispp.so: redispp.pic.o 18 | $(CXX) -shared $^ -o $@ 19 | 20 | unittests: test.o libredispp.a 21 | $(CXX) $^ libredispp.a -o $@ 22 | 23 | perftest: perf.o libredispp.a 24 | $(CXX) $^ libredispp.a -o $@ 25 | 26 | multitest: multi.o libredispp.a 27 | $(CXX) $^ libredispp.a -o $@ 28 | 29 | transtest: trans.o libredispp.a 30 | $(CXX) $^ libredispp.a -o $@ 31 | 32 | clang-format: 33 | for f in src/*.cpp src/*.h test/*.cpp; do clang-format $$f | sponge $$f; done 34 | 35 | clean: 36 | rm -f *.o libredispp.a libredispp.so perftest unittests multitest transtest 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Another C++ client for Redis [![Build Status](https://img.shields.io/travis/brianwatling/redispp/master.svg?style=flat-square&label=Travis)](https://travis-ci.org/brianwatling/redispp) 2 | 3 | - Supports pipelining, using the same functions as synchronous requests 4 | - The included performance test runs about 5 times faster with pipelining than with synchronous requests (single client/thread, on my laptop, to localhost) 5 | - Depends on boost library 6 | - Tested on Linux (clang-4.0 and g++-6), Windows (VC++ 2010), Mac (g++, OS X 10.6.5) 7 | - Includes makefile, bjam jamfiles, and VC++ project 8 | - Written against Redis 2.0.4 (and currently tested against docker redis 3.2.9) 9 | 10 | ## Performance 11 | 12 | This client's pipelining allows it to really push the redis server, even with one client. I ran the included performance benchmark on my Ubuntu 9.10 64-bit virtual machine. The physical machine is quad-core @ 3Ghz with 8GB RAM @ 800Mhz. At 256 requests 'on-the-wire', I reached approximately 230k writes per second with one connection (a write is: conn.set("somemediumkey2", "somemediumvalue")). I was able to reach nearly 260k writes per second with 4096 requests 'on-the-wire'. Below is test/perf.cpp's output for various amounts of outstanding requests. 13 | 14 | 256 requests on the wire: 15 | 16 | bwatling@ubuntu:~/Desktop/redispp$ for cur in `seq 1 10`; do ./test/bin/perf.test/gcc-4.4.1/release/perf 6379 1000000; done 17 | 1000000 writes in 4451367 usecs ~= 224650 requests per second 18 | 1000000 writes in 4301082 usecs ~= 232500 requests per second 19 | 1000000 writes in 4294144 usecs ~= 232875 requests per second 20 | 1000000 writes in 4255403 usecs ~= 234995 requests per second 21 | 1000000 writes in 4272437 usecs ~= 234058 requests per second 22 | 1000000 writes in 4273374 usecs ~= 234007 requests per second 23 | 1000000 writes in 4251377 usecs ~= 235218 requests per second 24 | 1000000 writes in 4288723 usecs ~= 233170 requests per second 25 | 1000000 writes in 4247717 usecs ~= 235421 requests per second 26 | 1000000 writes in 4257261 usecs ~= 234893 requests per second 27 | 28 | 4096 requests on the wire: 29 | 30 | bwatling@ubuntu:~/Desktop/redispp$ for cur in `seq 1 10`; do ./test/bin/perf.test/gcc-4.4.1/release/perf 6379 1000000; done 31 | 1000000 writes in 4035970 usecs ~= 247772 requests per second 32 | 1000000 writes in 3855737 usecs ~= 259354 requests per second 33 | 1000000 writes in 3876598 usecs ~= 257958 requests per second 34 | 1000000 writes in 3867489 usecs ~= 258566 requests per second 35 | 1000000 writes in 3887749 usecs ~= 257218 requests per second 36 | 1000000 writes in 3826811 usecs ~= 261314 requests per second 37 | 1000000 writes in 3864827 usecs ~= 258744 requests per second 38 | 1000000 writes in 3893552 usecs ~= 256835 requests per second 39 | 1000000 writes in 3881562 usecs ~= 257628 requests per second 40 | 1000000 writes in 3869083 usecs ~= 258459 requests per second 41 | 42 | In comparison, with a single client and no pipelining this machine could handle approximately 50k writes per second (using redispp and the same for credis). 43 | 44 | ## Simple example 45 | 46 | ```cpp 47 | redispp::Connection conn("127.0.0.1", "6379", "password", false); 48 | conn.set("hello", "world"); 49 | ``` 50 | 51 | ## Pipelining Example 52 | 53 | - Reply objects take care of reading the response lazily, on demand 54 | - The response is read in either the destructor or when the return value is used 55 | - The objects can be nested/scoped in any order. All outstanding replies are read and cached for later when a newer request's response is used. 56 | - See test/perf.cpp or test/test.cpp for more examples 57 | 58 | Up to 64 requests 'on the wire': 59 | 60 | ```cpp 61 | VoidReply replies[64]; 62 | 63 | for(size_t i = 0; i < count; ++i) 64 | { 65 | replies[i & 63] = conn.set(keys[i], values[i]); 66 | } 67 | ``` 68 | 69 | Save an object using pipelining. ~BoolReply takes care of reading the responses in order. 70 | 71 | ```cpp 72 | { 73 | BoolReply a = conn.hset("computer", "os", "linux"); 74 | BoolReply b = conn.hset("computer", "speed", "3Ghz"); 75 | BoolReply c = conn.hset("computer", "RAM", "8GB"); 76 | BoolReply d = conn.hset("computer", "cores", "4"); 77 | } 78 | //here all the replies have been cleared off conn's socket 79 | ``` 80 | 81 | Start loading a value, then use it later: 82 | 83 | ```cpp 84 | StringReply value = conn.get("world"); 85 | //do stuff 86 | std::string theValue = value; 87 | ``` 88 | 89 | These are resolved immediately: 90 | 91 | ```cpp 92 | int hlen = conn.hlen("computer"); 93 | std::string value = conn.get("world"); 94 | ``` 95 | 96 | This demonstrates arbitrary nesting/scoping and works as expected (see test/test.cpp). There's no problems caused by a and readA outliving b and c: 97 | 98 | ```cpp 99 | { 100 | VoidReply a = conn.set("one", "a"); 101 | StringReply readA = conn.get("one"); 102 | { 103 | BoolReply b = conn.hset("two", "two", "b"); 104 | VoidReply c = conn.set("three", "c"); 105 | } 106 | BOOST_CHECK(readA.result() == "a"); 107 | } 108 | ``` 109 | 110 | ## Multi Bulk Replies 111 | 112 | Request that have multi-bulk replies supply a MultiBulkEnumerator as the return type. The MultiBulkEnumerator will read the data lazily as requested. 113 | 114 | Read out a list: 115 | 116 | ```cpp 117 | conn.lpush("hello", "a") 118 | conn.lpush("hello", "b") 119 | conn.lpush("hello", "c") 120 | MultiBulkEnumerator result = conn.lrange("hello", 1, 3); 121 | std::string result; 122 | while(result.next(&result)) 123 | std::cout << result << std::endl; 124 | ``` 125 | 126 | ## Transactions 127 | 128 | The client has basic support for transactions. It currently can open a MULTI and close it with an EXEC. Closing with a DISCARD is not supported yet. WATCH and UNWATCH may also come soon. Here's an example of how to use transactions. Note: it's very important to use the defered reply objects with transactions, or else the connection will be corrupted. (see trans.cpp for more detail). 129 | 130 | ```cpp 131 | Transaction trans(&conn); 132 | VoidReply one = conn.set("x", "1"); 133 | VoidReply two = conn.set("y", "21"); 134 | StringReply three = conn.get("x"); 135 | trans.commit(); 136 | //access one, two, and three here 137 | ``` 138 | 139 | ## Building 140 | 141 | - You should be able to build libredispp.a and libredispp.so by typing 'make' 142 | - Bjam users can type 'bjam' 143 | - Windows can use the included VC++ 2010 project file. Be warned I've set it up to simply call bjam. It should be fairly simple to create a regular project or include the source in your own. 144 | - **WARNING** The unit tests will not pass unless you change TEST_PORT in test/test.cpp. The *entire* redis database will be cleared 145 | - You should run the unit and performance tests with a temporary database, with no production data 146 | - The performance test will not run unless you start it with a port (ie ./perf 6379) 147 | 148 | ## TODO 149 | 150 | - add a way to listen for messages after subscribing to a channel 151 | - fill in the missing requests 152 | - cleanup code, move stuff out of the header to the .cpp file 153 | - implement a clean method for watch and related functions (using transaction objects) 154 | - write a consistent hashing wrapper? 155 | 156 | ## License 157 | 158 | See [LICENSE.md](LICENSE.md). Credit is appreciated (but not required), and I would like to hear about how you use it (but again, not required). 159 | 160 | ## Contributors 161 | 162 | - Brian Watling 163 | - Alex Ianus 164 | 165 | -------------------------------------------------------------------------------- /redispp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual C++ Express 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "redispp", "redispp.vcxproj", "{DFEA0E78-B9F0-4413-9578-FE9FCBCABC26}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {DFEA0E78-B9F0-4413-9578-FE9FCBCABC26}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {DFEA0E78-B9F0-4413-9578-FE9FCBCABC26}.Debug|Win32.Build.0 = Debug|Win32 14 | {DFEA0E78-B9F0-4413-9578-FE9FCBCABC26}.Release|Win32.ActiveCfg = Release|Win32 15 | {DFEA0E78-B9F0-4413-9578-FE9FCBCABC26}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /redispp.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {DFEA0E78-B9F0-4413-9578-FE9FCBCABC26} 15 | redispp 16 | MakeFileProj 17 | 18 | 19 | 20 | Makefile 21 | 22 | 23 | Makefile 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | <_ProjectFileVersion>10.0.30319.1 37 | $(Configuration)\ 38 | $(Configuration)\ 39 | bjam -j 4 40 | bjam -j 4 -a 41 | bjam --clean 42 | 43 | WIN32;_DEBUG;$(NMakePreprocessorDefinitions) 44 | $(NMakeIncludeSearchPath) 45 | $(NMakeForcedIncludes) 46 | $(NMakeAssemblySearchPath) 47 | $(NMakeForcedUsingAssemblies) 48 | $(Configuration)\ 49 | $(Configuration)\ 50 | bjam -j 4 release 51 | bjam -j 4 -a release 52 | bjam --clean release 53 | 54 | WIN32;NDEBUG;$(NMakePreprocessorDefinitions) 55 | $(NMakeIncludeSearchPath) 56 | $(NMakeForcedIncludes) 57 | $(NMakeAssemblySearchPath) 58 | $(NMakeForcedUsingAssemblies) 59 | C:\boost_1_45_0\boost_1_45_0;$(IncludePath) 60 | C:\boost_1_45_0\boost_1_45_0\stage\lib;$(LibraryPath) 61 | C:\boost_1_45_0\boost_1_45_0;$(IncludePath) 62 | C:\boost_1_45_0\boost_1_45_0\stage\lib;$(LibraryPath) 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /redispp.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | {075b0762-4afb-4df6-b569-c03beb2881f9} 18 | 19 | 20 | 21 | 22 | Source Files 23 | 24 | 25 | Tests 26 | 27 | 28 | Tests 29 | 30 | 31 | 32 | 33 | Header Files 34 | 35 | 36 | 37 | 38 | Tests 39 | 40 | 41 | Source Files 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/Jamfile: -------------------------------------------------------------------------------- 1 | project redispp 2 | : usage-requirements . 3 | ; 4 | 5 | lib redispp : redispp.cpp /site-config//socket : static ; 6 | -------------------------------------------------------------------------------- /src/redispp.cpp: -------------------------------------------------------------------------------- 1 | #include "redispp.h" 2 | #include 3 | #ifdef _WIN32 4 | #include 5 | #include 6 | 7 | typedef int ssize_t; 8 | typedef char* RecvBufferType; 9 | 10 | int close(SOCKET sock) { return closesocket(sock); } 11 | 12 | static bool setSocketFlag(SOCKET sock, int level, int optname, bool value) { 13 | BOOL val = value ? TRUE : FALSE; 14 | return 0 == setsockopt(sock, level, optname, (char*)&val, sizeof(val)); 15 | } 16 | 17 | static const char* getLastErrorMessage() { 18 | return gai_strerror(WSAGetLastError()); 19 | } 20 | 21 | #else 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | typedef int SOCKET; 29 | typedef void* RecvBufferType; 30 | 31 | static bool setSocketFlag(SOCKET sock, int level, int optname, bool value) { 32 | int val = value ? 1 : 0; 33 | return 0 == setsockopt(sock, level, optname, &val, sizeof(val)); 34 | } 35 | 36 | static const char* getLastErrorMessage() { return strerror(errno); } 37 | 38 | #endif 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | namespace redispp { 46 | 47 | class ClientSocket : boost::noncopyable { 48 | public: 49 | ClientSocket(const char* host, const char* port) 50 | : sockFd(-1), streamBuf(this) { 51 | struct addrinfo hints; 52 | struct addrinfo* res = NULL; 53 | 54 | memset(&hints, 0, sizeof(hints)); 55 | hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever 56 | hints.ai_socktype = SOCK_STREAM; 57 | hints.ai_flags = AI_PASSIVE; // fill in my IP for me 58 | if (getaddrinfo(host, port, &hints, &res)) { 59 | throw std::runtime_error(std::string("error getting address info for ") + 60 | host + ":" + port + " (" + 61 | getLastErrorMessage() + ")"); 62 | } 63 | 64 | sockFd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 65 | if (sockFd < 0) { 66 | freeaddrinfo(res); 67 | throw std::runtime_error(std::string("error connecting to ") + host + 68 | ":" + port + " (" + getLastErrorMessage() + ")"); 69 | } 70 | 71 | setSocketFlag(sockFd, SOL_SOCKET, SO_REUSEADDR, true); 72 | setSocketFlag(sockFd, SOL_SOCKET, SO_KEEPALIVE, true); 73 | 74 | if (connect(sockFd, res->ai_addr, res->ai_addrlen)) { 75 | freeaddrinfo(res); 76 | close(sockFd); 77 | throw std::runtime_error(std::string("error connecting to ") + host + 78 | ":" + port + "(" + getLastErrorMessage() + ")"); 79 | } 80 | 81 | freeaddrinfo(res); 82 | } 83 | 84 | #ifndef _WIN32 85 | ClientSocket(const char* unixDomainSocket) : sockFd(-1), streamBuf(this) { 86 | struct sockaddr_un sockaddr; 87 | sockaddr.sun_family = AF_UNIX; 88 | strncpy(sockaddr.sun_path, unixDomainSocket, sizeof(sockaddr.sun_path)); 89 | 90 | sockFd = socket(AF_UNIX, SOCK_STREAM, 0); 91 | if (sockFd < 0) { 92 | throw std::runtime_error(std::string("error connecting to ") + 93 | unixDomainSocket + " (" + getLastErrorMessage() + 94 | ")"); 95 | } 96 | 97 | setSocketFlag(sockFd, SOL_SOCKET, SO_REUSEADDR, true); 98 | setSocketFlag(sockFd, SOL_SOCKET, SO_KEEPALIVE, true); 99 | 100 | if (connect(sockFd, (const struct sockaddr*)&sockaddr, sizeof(sockaddr))) { 101 | close(sockFd); 102 | throw std::runtime_error(std::string("error connecting to ") + 103 | unixDomainSocket + "(" + getLastErrorMessage() + 104 | ")"); 105 | } 106 | } 107 | #endif 108 | 109 | void tcpNoDelay(bool enable) { 110 | const bool ret = setSocketFlag(sockFd, IPPROTO_TCP, TCP_NODELAY, enable); 111 | if (!ret) { 112 | throw std::runtime_error(std::string("error setting TCP_NODELAY: ") + 113 | getLastErrorMessage()); 114 | } 115 | } 116 | 117 | void write(const void* data, size_t len) { 118 | size_t sent = 0; 119 | while (sent < len) { 120 | const ssize_t ret = 121 | ::send(sockFd, (const char*)data + sent, len - sent, 0); 122 | if (ret <= 0) { 123 | throw std::runtime_error(std::string("error writing to socket: ") + 124 | getLastErrorMessage()); 125 | } 126 | sent += ret; 127 | } 128 | } 129 | 130 | size_t read(void* data, size_t len) { 131 | const ssize_t ret = ::recv(sockFd, (RecvBufferType)data, len, 0); 132 | if (ret <= 0) { 133 | throw std::runtime_error(std::string("error reading from socket: ") + 134 | getLastErrorMessage()); 135 | } 136 | return ret; 137 | } 138 | 139 | ~ClientSocket() { 140 | if (sockFd >= 0) { 141 | close(sockFd); 142 | } 143 | } 144 | 145 | std::streambuf* getStreamBuf() { return &streamBuf; } 146 | 147 | class StreamBuf : public std::streambuf { 148 | public: 149 | StreamBuf(ClientSocket* conn) : conn(conn) { 150 | setp(outBuffer, outBuffer + sizeof(outBuffer) - 1); 151 | } 152 | 153 | int_type overflow(int_type c) { 154 | if (!traits_type::eq_int_type(traits_type::eof(), c)) { 155 | traits_type::assign(*pptr(), traits_type::to_char_type(c)); 156 | pbump(1); 157 | } 158 | return sync() == 0 ? traits_type::not_eof(c) : traits_type::eof(); 159 | } 160 | 161 | std::streamsize xsputn(const char* buf, std::streamsize size) { 162 | sync(); 163 | conn->write(buf, size); 164 | return size; 165 | } 166 | 167 | int sync() { 168 | if (pbase() != pptr()) { 169 | conn->write(pbase(), pptr() - pbase()); 170 | setp(outBuffer, outBuffer + sizeof(outBuffer) - 1); 171 | } 172 | return 1; 173 | } 174 | 175 | int_type underflow() { 176 | const size_t got = conn->read(inBuffer, sizeof(inBuffer)); 177 | setg(inBuffer, inBuffer, inBuffer + got); 178 | return traits_type::to_int_type(*gptr()); 179 | } 180 | 181 | std::streamsize xsgetn(char* dest, std::streamsize size) { 182 | std::streamsize numRead = 0; 183 | while (numRead < size) { 184 | char* const beg = gptr(); 185 | char* const end = egptr(); 186 | if (beg < end) { 187 | const std::streamsize avail = end - beg; 188 | const std::streamsize toRead = 189 | std::min(size - numRead, avail); 190 | char* const newBeg = beg + toRead; 191 | std::copy(beg, newBeg, dest + numRead); 192 | numRead += toRead; 193 | if (newBeg != end) { 194 | setg(newBeg, newBeg, end); 195 | return numRead; 196 | } 197 | } 198 | setg(inBuffer, inBuffer, inBuffer); 199 | const std::streamsize remaining = size - numRead; 200 | if (remaining > 0) { 201 | if (remaining > std::streamsize(sizeof(inBuffer))) { 202 | while (numRead < size) { 203 | numRead += conn->read(dest + numRead, size - numRead); 204 | } 205 | } else { 206 | underflow(); 207 | } 208 | } 209 | } 210 | return numRead; 211 | } 212 | 213 | private: 214 | char outBuffer[1400]; 215 | char inBuffer[1400]; 216 | ClientSocket* conn; 217 | }; 218 | 219 | private: 220 | SOCKET sockFd; 221 | StreamBuf streamBuf; 222 | }; 223 | 224 | class Buffer { 225 | public: 226 | Buffer(size_t bufferSize) 227 | : buffer(new char[bufferSize]), spot(buffer), end(buffer + bufferSize), 228 | marked(buffer) {} 229 | 230 | ~Buffer() { delete[] buffer; } 231 | 232 | void write(char c) { 233 | checkSpace(sizeof(char)); 234 | *spot = c; 235 | ++spot; 236 | } 237 | 238 | void write(const char* str) { 239 | const size_t len = strlen(str); 240 | checkSpace(len); 241 | memcpy(spot, str, len); 242 | spot += len; 243 | } 244 | 245 | void write(const char* str, size_t len) { 246 | checkSpace(len); 247 | memcpy(spot, str, len); 248 | spot += len; 249 | } 250 | 251 | void write(const std::string& str) { write(str.c_str(), str.size()); } 252 | 253 | void write(size_t i) { 254 | namespace qi = boost::spirit::qi; 255 | namespace karma = boost::spirit::karma; 256 | namespace ascii = boost::spirit::ascii; 257 | using karma::uint_; 258 | using karma::generate; 259 | checkSpace(11); 260 | generate(spot, uint_, i); 261 | } 262 | 263 | void writeArg(const char* const& arg) { 264 | const size_t len = strlen(arg); 265 | checkSpace(len + 1 + 11 + 4); // 1 $, 11 for length, 4 for \r\n's 266 | writeArgLen(len); 267 | memcpy(spot, arg, len); 268 | spot += len; 269 | *spot++ = '\r'; 270 | *spot++ = '\n'; 271 | } 272 | 273 | void writeArg(std::string const& arg) { 274 | const size_t len = arg.length(); 275 | checkSpace(len + 1 + 11 + 4); // 1 $, 11 for length, 4 for \r\n's 276 | writeArgLen(len); 277 | memcpy(spot, &(arg[0]), len); 278 | spot += len; 279 | *spot++ = '\r'; 280 | *spot++ = '\n'; 281 | } 282 | 283 | void writeArg(int64_t const& arg) { 284 | namespace qi = boost::spirit::qi; 285 | namespace karma = boost::spirit::karma; 286 | namespace ascii = boost::spirit::ascii; 287 | using karma::int_; 288 | using karma::generate; 289 | 290 | char numberBuf[22]; 291 | char* spot = numberBuf; 292 | generate(spot, int_, arg); 293 | *spot = 0; 294 | writeArg((const char*)numberBuf); 295 | } 296 | 297 | void mark() { marked = spot; } 298 | 299 | void reset() { 300 | spot = buffer; 301 | marked = buffer; 302 | } 303 | 304 | void resetToMark() { spot = marked; } 305 | 306 | void checkSpace(size_t needed) { 307 | if (spot + needed >= end) { 308 | throw std::runtime_error("buffer is full: spot + needed >= end"); 309 | } 310 | } 311 | 312 | size_t length() const { return spot - buffer; } 313 | 314 | char* data() { return buffer; } 315 | 316 | const char* data() const { return buffer; } 317 | 318 | private: 319 | void writeArgLen(unsigned int len) { 320 | namespace qi = boost::spirit::qi; 321 | namespace karma = boost::spirit::karma; 322 | namespace ascii = boost::spirit::ascii; 323 | using karma::uint_; 324 | using karma::generate; 325 | 326 | *spot++ = '$'; 327 | generate(spot, uint_, len); 328 | *spot++ = '\r'; 329 | *spot++ = '\n'; 330 | } 331 | 332 | char* buffer; 333 | char* spot; 334 | char* end; 335 | char* marked; 336 | }; 337 | 338 | #define EXECUTE_COMMAND_SYNC(cmd) \ 339 | do { \ 340 | buffer->resetToMark(); \ 341 | _##cmd##Command.execute(buffer); \ 342 | ioStream->write(buffer->data(), buffer->length()); \ 343 | } while (0) 344 | 345 | #define EXECUTE_COMMAND_SYNC1(cmd, arg1) \ 346 | do { \ 347 | buffer->resetToMark(); \ 348 | _##cmd##Command.execute(arg1, buffer); \ 349 | ioStream->write(buffer->data(), buffer->length()); \ 350 | } while (0) 351 | 352 | #define EXECUTE_COMMAND_SYNC2(cmd, arg1, arg2) \ 353 | do { \ 354 | buffer->resetToMark(); \ 355 | _##cmd##Command.execute(arg1, arg2, buffer); \ 356 | ioStream->write(buffer->data(), buffer->length()); \ 357 | } while (0) 358 | 359 | #define EXECUTE_COMMAND_SYNC3(cmd, arg1, arg2, arg3) \ 360 | do { \ 361 | buffer->resetToMark(); \ 362 | _##cmd##Command.execute(arg1, arg2, arg3, buffer); \ 363 | ioStream->write(buffer->data(), buffer->length()); \ 364 | } while (0) 365 | 366 | NullReplyException::NullReplyException() 367 | : std::out_of_range("Casting null bulk reply to string") {} 368 | 369 | BaseReply::BaseReply(Connection* conn) : conn(conn) { 370 | conn->outstandingReplies.push_back(*this); 371 | if (conn->transaction) { 372 | conn->transaction->replies.count += 1; 373 | } 374 | } 375 | 376 | BaseReply::BaseReply(const BaseReply& other) : conn(other.conn) { 377 | other.conn = NULL; 378 | if (conn) 379 | conn->outstandingReplies.insert(conn->outstandingReplies.iterator_to(other), 380 | *this); 381 | const_cast(other).unlink(); 382 | } 383 | 384 | BaseReply& BaseReply::operator=(const BaseReply& other) { 385 | unlink(); 386 | conn = other.conn; 387 | if (conn) 388 | conn->outstandingReplies.insert(conn->outstandingReplies.iterator_to(other), 389 | *this); 390 | other.conn = NULL; 391 | const_cast(other).unlink(); 392 | return *this; 393 | } 394 | 395 | void BaseReply::clearPendingResults() { 396 | ReplyList::iterator cur = conn->outstandingReplies.begin(); 397 | ReplyList::iterator const end = conn->outstandingReplies.iterator_to(*this); 398 | while (cur != end) { 399 | BaseReply& reply = *cur; 400 | ++cur; 401 | reply.readResult(); 402 | } 403 | } 404 | 405 | VoidReply::VoidReply(Connection* conn) : BaseReply(conn), storedResult(false) {} 406 | 407 | VoidReply::~VoidReply() { 408 | try { 409 | result(); 410 | } catch (...) { 411 | } 412 | } 413 | 414 | bool VoidReply::result() { 415 | if (conn) { 416 | clearPendingResults(); 417 | Connection* const tmp = conn; 418 | conn = NULL; 419 | tmp->readStatusCodeReply(); 420 | storedResult = true; 421 | unlink(); 422 | } 423 | return storedResult; 424 | } 425 | 426 | BoolReply::BoolReply(Connection* conn) : BaseReply(conn), storedResult(false) {} 427 | 428 | BoolReply::~BoolReply() { 429 | try { 430 | result(); 431 | } catch (...) { 432 | } 433 | } 434 | 435 | bool BoolReply::result() { 436 | if (conn) { 437 | clearPendingResults(); 438 | Connection* const tmp = conn; 439 | conn = NULL; 440 | storedResult = tmp->readIntegerReply() > 0; 441 | unlink(); 442 | } 443 | return storedResult; 444 | } 445 | 446 | IntReply::IntReply(Connection* conn) : BaseReply(conn), storedResult(0) {} 447 | 448 | IntReply::~IntReply() { 449 | try { 450 | result(); 451 | } catch (...) { 452 | } 453 | } 454 | 455 | int64_t IntReply::result() { 456 | if (conn) { 457 | clearPendingResults(); 458 | Connection* const tmp = conn; 459 | conn = NULL; 460 | storedResult = tmp->readIntegerReply(); 461 | unlink(); 462 | } 463 | return storedResult; 464 | } 465 | 466 | StringReply::StringReply(Connection* conn) : BaseReply(conn) {} 467 | 468 | StringReply::~StringReply() { 469 | try { 470 | result(); 471 | } catch (...) { 472 | } 473 | } 474 | 475 | const boost::optional& StringReply::result() { 476 | if (conn) { 477 | clearPendingResults(); 478 | Connection* const tmp = conn; 479 | conn = NULL; 480 | tmp->readBulkReply(storedResult); 481 | unlink(); 482 | } 483 | return storedResult; 484 | } 485 | 486 | MultiBulkEnumerator::MultiBulkEnumerator(Connection* conn) 487 | : BaseReply(conn), headerDone(false), count(0) {} 488 | 489 | MultiBulkEnumerator::~MultiBulkEnumerator() { 490 | try { 491 | if (conn && count > 0) { 492 | std::string tmp; 493 | while (next(&tmp)) 494 | ; 495 | } 496 | } catch (...) { 497 | } 498 | } 499 | 500 | bool MultiBulkEnumerator::nextOptional(boost::optional& out) { 501 | if (!pending.empty()) { 502 | out = pending.front(); 503 | pending.pop_front(); 504 | return true; 505 | } 506 | if (!conn) { 507 | return false; 508 | } 509 | 510 | char code = 0; 511 | if (!headerDone) { 512 | clearPendingResults(); 513 | conn->readErrorReply(); 514 | headerDone = true; 515 | if (!(*conn->ioStream >> code >> count)) { 516 | conn = NULL; 517 | unlink(); 518 | throw std::runtime_error("error reading bulk response header"); 519 | } 520 | if (code != '*') { 521 | conn = NULL; 522 | unlink(); 523 | throw std::runtime_error(std::string("bad multi-bulk header code: ") + 524 | code); 525 | } 526 | if (count < 0) { 527 | conn = NULL; 528 | unlink(); 529 | return false; 530 | } 531 | } 532 | if (count <= 0) { 533 | conn = NULL; 534 | unlink(); 535 | return false; 536 | } 537 | --count; 538 | code = conn->statusCode(); 539 | if (code == '$') { 540 | out = conn->readBulkReply(); 541 | } else if (code == ':') { 542 | out = boost::lexical_cast(conn->readIntegerReply()); 543 | } else { 544 | conn = NULL; 545 | unlink(); 546 | throw std::runtime_error( 547 | std::string("Unsupported multi-bulk element header code: ") + code); 548 | } 549 | 550 | return true; 551 | } 552 | 553 | bool MultiBulkEnumerator::next(std::string* out) { 554 | bool result; 555 | boost::optional optionalOut; 556 | result = nextOptional(optionalOut); 557 | if (!result) { 558 | return result; 559 | } 560 | if (!optionalOut) { 561 | throw NullReplyException(); 562 | } 563 | out->swap(*optionalOut); 564 | return result; 565 | } 566 | 567 | Connection::Connection(const std::string& host, const std::string& port, 568 | const std::string& password, bool noDelay, 569 | size_t bufferSize) 570 | : connection(new ClientSocket(host.c_str(), port.c_str())), 571 | ioStream(new std::iostream(connection->getStreamBuf())), 572 | buffer(new Buffer(bufferSize)), transaction(NULL) { 573 | if (noDelay) { 574 | connection->tcpNoDelay(true); 575 | } 576 | 577 | if (!password.empty()) { 578 | authenticate(password.c_str()); 579 | } 580 | } 581 | 582 | #ifndef _WIN32 583 | Connection::Connection(const std::string& unixDomainSocket, 584 | const std::string& password, size_t bufferSize) 585 | : connection(new ClientSocket(unixDomainSocket.c_str())), 586 | ioStream(new std::iostream(connection->getStreamBuf())), 587 | buffer(new Buffer(bufferSize)), transaction(NULL) { 588 | if (!password.empty()) { 589 | authenticate(password.c_str()); 590 | } 591 | } 592 | #endif 593 | 594 | Connection::~Connection() { 595 | ioStream.reset(); // make sure this is cleared first, since it references the 596 | // connection 597 | } 598 | 599 | static inline std::istream& getlineRN(std::istream& is, std::string& str) { 600 | return std::getline(is, str, '\r'); 601 | } 602 | 603 | char Connection::statusCode() { 604 | char code = 0; 605 | 606 | *ioStream >> std::ws; 607 | if ((code = ioStream->peek()) == EOF) { 608 | throw std::runtime_error("No data available on stream"); 609 | } 610 | 611 | return code; 612 | } 613 | 614 | void Connection::readErrorReply() { 615 | char code = statusCode(); 616 | 617 | if (code == '-') { 618 | std::string error; 619 | *ioStream >> code; 620 | std::getline(*ioStream, error); 621 | throw std::runtime_error(std::string("Received Error: ") + error); 622 | } 623 | } 624 | 625 | std::string Connection::readStatusCodeReply() { 626 | std::string ret; 627 | readStatusCodeReply(&ret); 628 | return ret; 629 | } 630 | 631 | void Connection::readStatusCodeReply(std::string* out) { 632 | readErrorReply(); 633 | 634 | char code = 0; 635 | if (!(*ioStream >> code) || !getlineRN(*ioStream, *out)) { 636 | throw std::runtime_error("error reading status response"); 637 | } 638 | if (code != '+') { 639 | throw std::runtime_error(std::string("read error response: ") + *out); 640 | } 641 | } 642 | 643 | int64_t Connection::readIntegerReply() { 644 | readErrorReply(); 645 | 646 | char code = 0; 647 | int64_t ret = 0; 648 | if (!(*ioStream >> code >> ret)) { 649 | throw std::runtime_error("error reading integer response"); 650 | } 651 | return ret; 652 | } 653 | 654 | boost::optional Connection::readBulkReply() { 655 | boost::optional ret; 656 | readBulkReply(ret); 657 | return ret; 658 | } 659 | 660 | void Connection::readBulkReply(boost::optional& out) { 661 | readErrorReply(); 662 | 663 | char code = 0; 664 | int count = 0; 665 | if (!(*ioStream >> code >> count)) { 666 | throw std::runtime_error("error reading bulk response header"); 667 | } 668 | if (code != '$') { 669 | throw std::runtime_error(std::string("bad bulk header code: ") + code); 670 | } 671 | if (count < 0) { 672 | out = boost::optional(); 673 | } else { 674 | ioStream->get(); //'\r' 675 | ioStream->get(); //'\n' 676 | out = std::string(); 677 | out->resize(count, '\0'); 678 | ioStream->read((char*)out->c_str(), out->size()); 679 | } 680 | } 681 | 682 | void Connection::quit() { EXECUTE_COMMAND_SYNC(Quit); } 683 | 684 | VoidReply Connection::authenticate(const char* password) { 685 | EXECUTE_COMMAND_SYNC1(Auth, password); 686 | return VoidReply(this); 687 | } 688 | 689 | BoolReply Connection::exists(const std::string& name) { 690 | EXECUTE_COMMAND_SYNC1(Exists, name); 691 | return BoolReply(this); 692 | } 693 | 694 | BoolReply Connection::del(const std::string& name) { 695 | EXECUTE_COMMAND_SYNC1(Del, name); 696 | return BoolReply(this); 697 | } 698 | 699 | static std::string s_none = "none"; 700 | static std::string s_string = "string"; 701 | static std::string s_list = "list"; 702 | static std::string s_set = "set"; 703 | static std::string s_zset = "zset"; 704 | static std::string s_hash = "hash"; 705 | 706 | Type Connection::type(const std::string& name) { 707 | EXECUTE_COMMAND_SYNC1(Type, name); 708 | std::string t = readStatusCodeReply(); 709 | if (t == s_none) 710 | return None; 711 | if (t == s_string) 712 | return String; 713 | if (t == s_list) 714 | return List; 715 | if (t == s_set) 716 | return Set; 717 | if (t == s_zset) 718 | return ZSet; 719 | if (t == s_hash) 720 | return Hash; 721 | 722 | return None; 723 | } 724 | 725 | MultiBulkEnumerator Connection::keys(const std::string& pattern) { 726 | EXECUTE_COMMAND_SYNC1(Keys, pattern); 727 | return MultiBulkEnumerator(this); 728 | } 729 | 730 | StringReply Connection::randomKey() { 731 | EXECUTE_COMMAND_SYNC(RandomKey); 732 | return StringReply(this); 733 | } 734 | 735 | VoidReply Connection::rename(const std::string& oldName, 736 | const std::string& newName) { 737 | EXECUTE_COMMAND_SYNC2(Rename, oldName, newName); 738 | return VoidReply(this); 739 | } 740 | 741 | BoolReply Connection::renameNX(const std::string& oldName, 742 | const std::string& newName) { 743 | EXECUTE_COMMAND_SYNC2(RenameNX, oldName, newName); 744 | return BoolReply(this); 745 | } 746 | 747 | IntReply Connection::dbSize() { 748 | EXECUTE_COMMAND_SYNC(DbSize); 749 | return IntReply(this); 750 | } 751 | 752 | BoolReply Connection::expire(const std::string& name, int seconds) { 753 | EXECUTE_COMMAND_SYNC2(Expire, name, seconds); 754 | return BoolReply(this); 755 | } 756 | 757 | BoolReply Connection::expireAt(const std::string& name, int timestamp) { 758 | EXECUTE_COMMAND_SYNC2(ExpireAt, name, timestamp); 759 | return BoolReply(this); 760 | } 761 | 762 | // TODO: persist 763 | 764 | IntReply Connection::ttl(const std::string& name) { 765 | EXECUTE_COMMAND_SYNC1(Ttl, name); 766 | return IntReply(this); 767 | } 768 | 769 | VoidReply Connection::select(int db) { 770 | EXECUTE_COMMAND_SYNC1(Select, db); 771 | return VoidReply(this); 772 | } 773 | 774 | BoolReply Connection::move(const std::string& name, int db) { 775 | EXECUTE_COMMAND_SYNC2(Move, name, db); 776 | return BoolReply(this); 777 | } 778 | 779 | VoidReply Connection::flushDb() { 780 | EXECUTE_COMMAND_SYNC(FlushDb); 781 | return VoidReply(this); 782 | } 783 | 784 | VoidReply Connection::flushAll() { 785 | EXECUTE_COMMAND_SYNC(FlushAll); 786 | return VoidReply(this); 787 | } 788 | 789 | VoidReply Connection::set(const std::string& name, const std::string& value) { 790 | EXECUTE_COMMAND_SYNC2(Set, name, value); 791 | return VoidReply(this); 792 | } 793 | 794 | StringReply Connection::get(const std::string& name) { 795 | EXECUTE_COMMAND_SYNC1(Get, name); 796 | return StringReply(this); 797 | } 798 | 799 | // TODO: mget 800 | 801 | StringReply Connection::getSet(const std::string& name, 802 | const std::string& value) { 803 | EXECUTE_COMMAND_SYNC2(GetSet, name, value); 804 | return StringReply(this); 805 | } 806 | 807 | BoolReply Connection::setNX(const std::string& name, const std::string& value) { 808 | EXECUTE_COMMAND_SYNC2(SetNX, name, value); 809 | return BoolReply(this); 810 | } 811 | 812 | VoidReply Connection::setEx(const std::string& name, int time, 813 | const std::string& value) { 814 | EXECUTE_COMMAND_SYNC3(SetEx, name, time, value); 815 | return VoidReply(this); 816 | } 817 | 818 | IntReply Connection::incr(const std::string& name) { 819 | EXECUTE_COMMAND_SYNC1(Incr, name); 820 | return IntReply(this); 821 | } 822 | 823 | IntReply Connection::incrBy(const std::string& name, int value) { 824 | EXECUTE_COMMAND_SYNC2(IncrBy, name, value); 825 | return IntReply(this); 826 | } 827 | 828 | IntReply Connection::decr(const std::string& name) { 829 | EXECUTE_COMMAND_SYNC1(Decr, name); 830 | return IntReply(this); 831 | } 832 | 833 | IntReply Connection::decrBy(const std::string& name, int value) { 834 | EXECUTE_COMMAND_SYNC2(DecrBy, name, value); 835 | return IntReply(this); 836 | } 837 | 838 | IntReply Connection::append(const std::string& name, const std::string& value) { 839 | EXECUTE_COMMAND_SYNC2(Append, name, value); 840 | return IntReply(this); 841 | } 842 | 843 | StringReply Connection::subStr(const std::string& name, int start, int end) { 844 | EXECUTE_COMMAND_SYNC3(SubStr, name, start, end); 845 | return StringReply(this); 846 | } 847 | 848 | IntReply Connection::rpush(const std::string& key, const std::string& value) { 849 | EXECUTE_COMMAND_SYNC2(RPush, key, value); 850 | return IntReply(this); 851 | } 852 | 853 | IntReply Connection::lpush(const std::string& key, const std::string& value) { 854 | EXECUTE_COMMAND_SYNC2(LPush, key, value); 855 | return IntReply(this); 856 | } 857 | 858 | IntReply Connection::llen(const std::string& key) { 859 | EXECUTE_COMMAND_SYNC1(LLen, key); 860 | return IntReply(this); 861 | } 862 | 863 | MultiBulkEnumerator Connection::lrange(const std::string& key, int start, 864 | int end) { 865 | EXECUTE_COMMAND_SYNC3(LRange, key, start, end); 866 | return MultiBulkEnumerator(this); 867 | } 868 | 869 | VoidReply Connection::ltrim(const std::string& key, int start, int end) { 870 | EXECUTE_COMMAND_SYNC3(LTrim, key, start, end); 871 | return VoidReply(this); 872 | } 873 | 874 | StringReply Connection::lindex(const std::string& key, int index) { 875 | EXECUTE_COMMAND_SYNC2(LIndex, key, index); 876 | return StringReply(this); 877 | } 878 | 879 | VoidReply Connection::lset(const std::string& key, int index, 880 | const std::string& value) { 881 | EXECUTE_COMMAND_SYNC3(LSet, key, index, value); 882 | return VoidReply(this); 883 | } 884 | 885 | IntReply Connection::lrem(const std::string& key, int count, 886 | const std::string& value) { 887 | EXECUTE_COMMAND_SYNC3(LRem, key, count, value); 888 | return IntReply(this); 889 | } 890 | 891 | StringReply Connection::lpop(const std::string& key) { 892 | EXECUTE_COMMAND_SYNC1(LPop, key); 893 | return StringReply(this); 894 | } 895 | 896 | StringReply Connection::rpop(const std::string& key) { 897 | EXECUTE_COMMAND_SYNC1(RPop, key); 898 | return StringReply(this); 899 | } 900 | 901 | MultiBulkEnumerator Connection::blpop(ArgList keys, int timeout) { 902 | EXECUTE_COMMAND_SYNC2(BLPop, keys, timeout); 903 | return MultiBulkEnumerator(this); 904 | } 905 | 906 | MultiBulkEnumerator Connection::brpop(ArgList keys, int timeout) { 907 | EXECUTE_COMMAND_SYNC2(BRPop, keys, timeout); 908 | return MultiBulkEnumerator(this); 909 | } 910 | 911 | StringReply Connection::rpopLpush(const std::string& src, 912 | const std::string& dest) { 913 | EXECUTE_COMMAND_SYNC2(RPopLPush, src, dest); 914 | return StringReply(this); 915 | } 916 | 917 | BoolReply Connection::sadd(const std::string& key, const std::string& member) { 918 | EXECUTE_COMMAND_SYNC2(SAdd, key, member); 919 | return BoolReply(this); 920 | } 921 | 922 | BoolReply Connection::srem(const std::string& key, const std::string& member) { 923 | EXECUTE_COMMAND_SYNC2(SRem, key, member); 924 | return BoolReply(this); 925 | } 926 | 927 | StringReply Connection::spop(const std::string& key) { 928 | EXECUTE_COMMAND_SYNC1(SPop, key); 929 | return StringReply(this); 930 | } 931 | 932 | BoolReply Connection::smove(const std::string& src, const std::string& dest, 933 | const std::string& member) { 934 | EXECUTE_COMMAND_SYNC3(SMove, src, dest, member); 935 | return BoolReply(this); 936 | } 937 | 938 | IntReply Connection::scard(const std::string& key) { 939 | EXECUTE_COMMAND_SYNC1(SCard, key); 940 | return IntReply(this); 941 | } 942 | 943 | BoolReply Connection::sisMember(const std::string& key, 944 | const std::string& member) { 945 | EXECUTE_COMMAND_SYNC2(SIsMember, key, member); 946 | return BoolReply(this); 947 | } 948 | 949 | MultiBulkEnumerator Connection::sinter(const ArgList& keys) { 950 | EXECUTE_COMMAND_SYNC1(SInter, keys); 951 | return MultiBulkEnumerator(this); 952 | } 953 | 954 | IntReply Connection::sinterStore(const std::string& key, const ArgList& keys) { 955 | EXECUTE_COMMAND_SYNC2(SInterStore, key, keys); 956 | return IntReply(this); 957 | } 958 | 959 | MultiBulkEnumerator Connection::sunion(const ArgList& keys) { 960 | EXECUTE_COMMAND_SYNC1(SUnion, keys); 961 | return MultiBulkEnumerator(this); 962 | } 963 | 964 | IntReply Connection::sunionStore(const std::string& key, const ArgList& keys) { 965 | EXECUTE_COMMAND_SYNC2(SUnionStore, key, keys); 966 | return IntReply(this); 967 | } 968 | 969 | MultiBulkEnumerator Connection::sdiff(const ArgList& keys) { 970 | EXECUTE_COMMAND_SYNC1(SDiff, keys); 971 | return MultiBulkEnumerator(this); 972 | } 973 | 974 | IntReply Connection::sdiffStore(const std::string& key, const ArgList& keys) { 975 | EXECUTE_COMMAND_SYNC2(SDiffStore, key, keys); 976 | return IntReply(this); 977 | } 978 | 979 | MultiBulkEnumerator Connection::smembers(const std::string& key) { 980 | EXECUTE_COMMAND_SYNC1(SMembers, key); 981 | return MultiBulkEnumerator(this); 982 | } 983 | 984 | StringReply Connection::srandMember(const std::string& key) { 985 | EXECUTE_COMMAND_SYNC1(SRandMember, key); 986 | return StringReply(this); 987 | } 988 | 989 | // TODO: all Z* functions 990 | 991 | BoolReply Connection::hset(const std::string& key, const std::string& field, 992 | const std::string& value) { 993 | EXECUTE_COMMAND_SYNC3(HSet, key, field, value); 994 | return BoolReply(this); 995 | } 996 | 997 | StringReply Connection::hget(const std::string& key, const std::string& field) { 998 | EXECUTE_COMMAND_SYNC2(HGet, key, field); 999 | return StringReply(this); 1000 | } 1001 | 1002 | BoolReply Connection::hsetNX(const std::string& key, const std::string& field, 1003 | const std::string& value) { 1004 | EXECUTE_COMMAND_SYNC3(HSetNX, key, field, value); 1005 | return BoolReply(this); 1006 | } 1007 | 1008 | MultiBulkEnumerator Connection::hmget(const std::string& key, 1009 | const ArgList& fields) { 1010 | EXECUTE_COMMAND_SYNC2(HMGet, key, fields); 1011 | return MultiBulkEnumerator(this); 1012 | } 1013 | 1014 | VoidReply Connection::hmset(const std::string& key, 1015 | const KeyValueList& fields) { 1016 | EXECUTE_COMMAND_SYNC2(HMSet, key, fields); 1017 | return VoidReply(this); 1018 | } 1019 | 1020 | IntReply Connection::hincrBy(const std::string& key, const std::string& field, 1021 | int value) { 1022 | EXECUTE_COMMAND_SYNC3(HIncrBy, key, field, value); 1023 | return IntReply(this); 1024 | } 1025 | 1026 | BoolReply Connection::hexists(const std::string& key, 1027 | const std::string& field) { 1028 | EXECUTE_COMMAND_SYNC2(HExists, key, field); 1029 | return BoolReply(this); 1030 | } 1031 | 1032 | BoolReply Connection::hdel(const std::string& key, const std::string& field) { 1033 | EXECUTE_COMMAND_SYNC2(HDel, key, field); 1034 | return BoolReply(this); 1035 | } 1036 | 1037 | IntReply Connection::hlen(const std::string& key) { 1038 | EXECUTE_COMMAND_SYNC1(HLen, key); 1039 | return IntReply(this); 1040 | } 1041 | 1042 | MultiBulkEnumerator Connection::hkeys(const std::string& key) { 1043 | EXECUTE_COMMAND_SYNC1(HKeys, key); 1044 | return MultiBulkEnumerator(this); 1045 | } 1046 | 1047 | MultiBulkEnumerator Connection::hvals(const std::string& key) { 1048 | EXECUTE_COMMAND_SYNC1(HVals, key); 1049 | return MultiBulkEnumerator(this); 1050 | } 1051 | 1052 | MultiBulkEnumerator Connection::hgetAll(const std::string& key) { 1053 | EXECUTE_COMMAND_SYNC1(HGetAll, key); 1054 | return MultiBulkEnumerator(this); 1055 | } 1056 | 1057 | MultiBulkEnumerator Connection::scriptExists(const ArgList& scripts) { 1058 | EXECUTE_COMMAND_SYNC2(Script, std::string("exists"), scripts); 1059 | return MultiBulkEnumerator(this); 1060 | } 1061 | 1062 | VoidReply Connection::scriptFlush() { 1063 | EXECUTE_COMMAND_SYNC1(Script, std::string("flush")); 1064 | return VoidReply(this); 1065 | } 1066 | 1067 | VoidReply Connection::scriptKill() { 1068 | EXECUTE_COMMAND_SYNC1(Script, std::string("kill")); 1069 | return VoidReply(this); 1070 | } 1071 | 1072 | StringReply Connection::scriptLoad(const std::string& script) { 1073 | EXECUTE_COMMAND_SYNC2(Script, std::string("load"), script); 1074 | return StringReply(this); 1075 | } 1076 | 1077 | MultiBulkEnumerator Connection::eval(const std::string& script, 1078 | const ArgList& keys, const ArgList& args) { 1079 | EXECUTE_COMMAND_SYNC3(Eval, script, keys, args); 1080 | return MultiBulkEnumerator(this); 1081 | } 1082 | 1083 | MultiBulkEnumerator Connection::evalSha(const std::string& sha, 1084 | const ArgList& keys, 1085 | const ArgList& args) { 1086 | EXECUTE_COMMAND_SYNC3(EvalSha, sha, keys, args); 1087 | return MultiBulkEnumerator(this); 1088 | } 1089 | 1090 | VoidReply Connection::save() { 1091 | EXECUTE_COMMAND_SYNC(Save); 1092 | return VoidReply(this); 1093 | } 1094 | 1095 | VoidReply Connection::bgSave() { 1096 | EXECUTE_COMMAND_SYNC(BgSave); 1097 | return VoidReply(this); 1098 | } 1099 | 1100 | VoidReply Connection::bgReWriteAOF() { 1101 | EXECUTE_COMMAND_SYNC(BgReWriteAOF); 1102 | return VoidReply(this); 1103 | } 1104 | 1105 | IntReply Connection::lastSave() { 1106 | EXECUTE_COMMAND_SYNC(LastSave); 1107 | return IntReply(this); 1108 | } 1109 | 1110 | void Connection::shutdown() { EXECUTE_COMMAND_SYNC(Shutdown); } 1111 | 1112 | StringReply Connection::info() { 1113 | EXECUTE_COMMAND_SYNC(Info); 1114 | return StringReply(this); 1115 | } 1116 | 1117 | void Connection::subscribe(const std::string& channel) { 1118 | EXECUTE_COMMAND_SYNC1(Subscribe, channel); 1119 | } 1120 | 1121 | void Connection::unsubscribe(const std::string& channel) { 1122 | EXECUTE_COMMAND_SYNC1(Unsubscribe, channel); 1123 | } 1124 | 1125 | void Connection::psubscribe(const std::string& channel) { 1126 | EXECUTE_COMMAND_SYNC1(PSubscribe, channel); 1127 | } 1128 | 1129 | void Connection::punsubscribe(const std::string& channel) { 1130 | EXECUTE_COMMAND_SYNC1(PUnsubscribe, channel); 1131 | } 1132 | 1133 | IntReply Connection::publish(const std::string& channel, 1134 | const std::string& message) { 1135 | EXECUTE_COMMAND_SYNC2(Publish, channel, message); 1136 | return IntReply(this); 1137 | } 1138 | 1139 | void Connection::multi() { EXECUTE_COMMAND_SYNC(Multi); } 1140 | 1141 | void Connection::exec() { EXECUTE_COMMAND_SYNC(Exec); } 1142 | 1143 | void Connection::discard() { EXECUTE_COMMAND_SYNC(Discard); } 1144 | 1145 | Transaction::Transaction(Connection* conn) : conn(conn), replies(conn) { 1146 | if (conn->transaction) 1147 | throw std::runtime_error( 1148 | "cannot start a transaction while the connection is already in one"); 1149 | 1150 | conn->multi(); 1151 | 1152 | conn->transaction = this; 1153 | replies.state = Dirty; 1154 | } 1155 | 1156 | Transaction::~Transaction() { 1157 | try { 1158 | abort(); 1159 | } catch (...) { 1160 | } 1161 | conn->transaction = NULL; 1162 | } 1163 | 1164 | void Transaction::commit() { 1165 | if (replies.state == Dirty) { 1166 | replies.state = Committed; 1167 | conn->exec(); 1168 | replies.readResult(); 1169 | } 1170 | } 1171 | 1172 | void Transaction::abort() { 1173 | if (replies.state == Dirty) { 1174 | replies.state = Aborted; 1175 | conn->discard(); 1176 | replies.readResult(); 1177 | } 1178 | } 1179 | 1180 | void QueuedReply::readResult() { 1181 | if (!conn) 1182 | return; 1183 | 1184 | Connection* const tmp = conn; 1185 | conn = NULL; 1186 | tmp->readStatusCodeReply(); // one +OK for the MULTI 1187 | // one +QUEUED per queued request (including this QueuedReply) 1188 | for (size_t i = 0; i < count; ++i) { 1189 | tmp->readStatusCodeReply(); 1190 | } 1191 | if (state == Committed) { 1192 | const int64_t expectedCount = tmp->readIntegerReply(); 1193 | if (count != expectedCount) 1194 | throw std::runtime_error("transaction item count did not match"); 1195 | } else if (state == Aborted) { 1196 | tmp->readStatusCodeReply(); 1197 | // TODO: discard all remaining items to be parsed 1198 | } 1199 | } 1200 | 1201 | }; // namespace redispp 1202 | -------------------------------------------------------------------------------- /src/redispp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace redispp { 16 | 17 | class NullReplyException : std::out_of_range { 18 | public: 19 | NullReplyException(); 20 | }; 21 | 22 | typedef std::pair KeyValuePair; 23 | typedef std::list ArgList; 24 | typedef std::list KeyValueList; 25 | 26 | template struct Command { 27 | Command(const char* name) : cmdName(std::string(name)), numArgs(0) {} 28 | 29 | virtual ~Command() {} 30 | 31 | std::string header() { 32 | std::string header; 33 | 34 | header = "*"; 35 | header += boost::lexical_cast(numArgs + 1); 36 | header += "\r\n"; 37 | header += "$"; 38 | header += boost::lexical_cast(cmdName.length()); 39 | header += "\r\n"; 40 | header += cmdName; 41 | header += "\r\n"; 42 | 43 | return header; 44 | } 45 | 46 | void execute(BufferType const& dest) { dest->write(header()); } 47 | 48 | template void execute(T const& arg1, BufferType const& dest) { 49 | numArgs = 1; 50 | dest->write(header()); 51 | dest->writeArg(arg1); 52 | } 53 | 54 | template 55 | void execute(T1 const& arg1, T2 const& arg2, BufferType const& dest) { 56 | numArgs = 2; 57 | dest->write(header()); 58 | dest->writeArg(arg1); 59 | dest->writeArg(arg2); 60 | } 61 | 62 | template 63 | void execute(T1 const& arg1, T2 const& arg2, T3 const& arg3, 64 | BufferType const& dest) { 65 | numArgs = 3; 66 | dest->write(header()); 67 | dest->writeArg(arg1); 68 | dest->writeArg(arg2); 69 | dest->writeArg(arg3); 70 | } 71 | 72 | void execute(const ArgList& args, const BufferType& dest) { 73 | numArgs = args.size(); 74 | dest->write(header()); 75 | 76 | BOOST_FOREACH (std::string arg, args) { dest->writeArg(arg); } 77 | } 78 | 79 | void execute(const std::string& arg1, const ArgList& args, 80 | const BufferType& dest) { 81 | numArgs = args.size() + 1; 82 | dest->write(header()); 83 | dest->writeArg(arg1); 84 | 85 | BOOST_FOREACH (std::string arg, args) { dest->writeArg(arg); } 86 | } 87 | 88 | void execute(const std::string& arg1, const KeyValueList& args, 89 | const BufferType& dest) { 90 | numArgs = args.size() * 2 + 1; 91 | dest->write(header()); 92 | dest->writeArg(arg1); 93 | 94 | BOOST_FOREACH (KeyValuePair arg, args) { 95 | dest->writeArg(arg.first); 96 | dest->writeArg(arg.second); 97 | } 98 | } 99 | 100 | void execute(const ArgList& args, const int argn, const BufferType& dest) { 101 | numArgs = args.size() + 1; 102 | dest->write(header()); 103 | 104 | BOOST_FOREACH (std::string arg, args) { dest->writeArg(arg); } 105 | dest->writeArg(argn); 106 | } 107 | 108 | void execute(const std::string& arg1, const ArgList& argList1, 109 | const ArgList& argList2, const BufferType& dest) { 110 | numArgs = argList1.size() + argList2.size() + 2; 111 | dest->write(header()); 112 | dest->writeArg(arg1); 113 | dest->writeArg(argList1.size()); 114 | 115 | BOOST_FOREACH (std::string arg, argList1) { dest->writeArg(arg); } 116 | 117 | BOOST_FOREACH (std::string arg, argList2) { dest->writeArg(arg); } 118 | } 119 | 120 | std::string cmdName; 121 | int numArgs; 122 | }; 123 | 124 | #define DEFINE_COMMAND(name, args) \ 125 | struct name##Command : public Command> { \ 126 | name##Command() : Command>(#name) {} \ 127 | }; \ 128 | name##Command _##name##Command; 129 | 130 | enum Type { 131 | None, 132 | String, 133 | List, 134 | Set, 135 | ZSet, 136 | Hash, 137 | }; 138 | 139 | class Connection; 140 | class ClientSocket; 141 | class Buffer; 142 | 143 | typedef boost::intrusive::list_base_hook< 144 | boost::intrusive::link_mode> 145 | auto_unlink_hook; 146 | 147 | class BaseReply : public auto_unlink_hook { 148 | friend class Connection; 149 | 150 | public: 151 | BaseReply() : conn(NULL) {} 152 | 153 | BaseReply(const BaseReply& other); 154 | 155 | BaseReply& operator=(const BaseReply& other); 156 | 157 | virtual ~BaseReply() {} 158 | 159 | protected: 160 | virtual void readResult() = 0; 161 | 162 | void clearPendingResults(); 163 | 164 | BaseReply(Connection* conn); 165 | 166 | mutable Connection* conn; 167 | }; 168 | 169 | typedef boost::intrusive::list> 171 | ReplyList; 172 | 173 | class Transaction; 174 | 175 | enum TransactionState { 176 | Blank, 177 | Dirty, 178 | Aborted, 179 | Committed, 180 | }; 181 | 182 | class QueuedReply : public BaseReply { 183 | friend class BaseReply; 184 | friend class Connection; 185 | friend class Transaction; 186 | 187 | public: 188 | QueuedReply() : count(0), state(Blank) {} 189 | 190 | ~QueuedReply() {} 191 | 192 | protected: 193 | virtual void readResult(); 194 | 195 | private: 196 | QueuedReply(Connection* conn) : BaseReply(conn), count(0), state(Blank) {} 197 | 198 | size_t count; 199 | TransactionState state; 200 | }; 201 | 202 | class VoidReply : public BaseReply { 203 | friend class Connection; 204 | 205 | public: 206 | VoidReply() : storedResult(false) {} 207 | 208 | ~VoidReply(); 209 | 210 | VoidReply(const VoidReply& other) 211 | : BaseReply(other), storedResult(other.storedResult) {} 212 | 213 | VoidReply& operator=(const VoidReply& other) { 214 | result(); 215 | BaseReply::operator=(other); 216 | storedResult = other.storedResult; 217 | return *this; 218 | } 219 | 220 | bool result(); 221 | 222 | operator bool() { return result(); } 223 | 224 | protected: 225 | virtual void readResult() { result(); } 226 | 227 | private: 228 | VoidReply(Connection* conn); 229 | 230 | bool storedResult; 231 | }; 232 | 233 | class BoolReply : public BaseReply { 234 | friend class Connection; 235 | 236 | public: 237 | BoolReply() : storedResult(false) {} 238 | 239 | ~BoolReply(); 240 | 241 | BoolReply(const BoolReply& other) 242 | : BaseReply(other), storedResult(other.storedResult) {} 243 | 244 | BoolReply& operator=(const BoolReply& other) { 245 | result(); 246 | BaseReply::operator=(other); 247 | storedResult = other.storedResult; 248 | return *this; 249 | } 250 | 251 | bool result(); 252 | 253 | operator bool() { return result(); } 254 | 255 | protected: 256 | virtual void readResult() { result(); } 257 | 258 | private: 259 | BoolReply(Connection* conn); 260 | 261 | bool storedResult; 262 | }; 263 | 264 | class IntReply : public BaseReply { 265 | friend class Connection; 266 | 267 | public: 268 | IntReply() : storedResult(0) {} 269 | 270 | ~IntReply(); 271 | 272 | IntReply(const IntReply& other) 273 | : BaseReply(other), storedResult(other.storedResult) {} 274 | 275 | IntReply& operator=(const IntReply& other) { 276 | result(); 277 | BaseReply::operator=(other); 278 | storedResult = other.storedResult; 279 | return *this; 280 | } 281 | 282 | int64_t result(); 283 | 284 | operator int() { return result(); } 285 | 286 | protected: 287 | virtual void readResult() { result(); } 288 | 289 | private: 290 | IntReply(Connection* conn); 291 | 292 | int64_t storedResult; 293 | }; 294 | 295 | class StringReply : public BaseReply { 296 | friend class Connection; 297 | 298 | public: 299 | StringReply() {} 300 | 301 | ~StringReply(); 302 | 303 | StringReply(const StringReply& other) 304 | : BaseReply(other), storedResult(other.storedResult) {} 305 | 306 | StringReply& operator=(const StringReply& other) { 307 | result(); 308 | BaseReply::operator=(other); 309 | storedResult = other.storedResult; 310 | return *this; 311 | } 312 | 313 | const boost::optional& result(); 314 | 315 | operator std::string() { 316 | result(); 317 | if (!storedResult) { 318 | throw NullReplyException(); 319 | } 320 | return *storedResult; 321 | } 322 | 323 | protected: 324 | virtual void readResult() { result(); } 325 | 326 | private: 327 | StringReply(Connection* conn); 328 | 329 | boost::optional storedResult; 330 | }; 331 | 332 | class MultiBulkEnumerator : public BaseReply { 333 | friend class Connection; 334 | 335 | public: 336 | MultiBulkEnumerator() : headerDone(false), count(0) {} 337 | 338 | ~MultiBulkEnumerator(); 339 | 340 | MultiBulkEnumerator(const MultiBulkEnumerator& other) 341 | : BaseReply(other), headerDone(other.headerDone), count(other.count) { 342 | pending.splice(pending.begin(), other.pending); 343 | } 344 | 345 | MultiBulkEnumerator& operator=(const MultiBulkEnumerator& other) { 346 | if (conn && count > 0) { 347 | // assume unread data can be discarded, this is the only object that 348 | // could/would have read it 349 | std::string tmp; 350 | while (next(&tmp)) 351 | ; 352 | } 353 | pending.clear(); 354 | BaseReply::operator=(other); 355 | headerDone = other.headerDone; 356 | count = other.count; 357 | return *this; 358 | } 359 | 360 | bool nextOptional(boost::optional& out); 361 | bool next(std::string* out); 362 | 363 | protected: 364 | virtual void readResult() { 365 | if (conn && (!headerDone || count > 0)) { 366 | std::list> readPending; 367 | boost::optional tmp; 368 | while (nextOptional(tmp)) { 369 | readPending.push_back(tmp); 370 | } 371 | pending.splice(pending.end(), readPending); 372 | } 373 | } 374 | 375 | MultiBulkEnumerator(Connection* conn); 376 | 377 | bool headerDone; 378 | int count; 379 | mutable std::list> pending; 380 | }; 381 | 382 | class Connection; 383 | 384 | class Transaction : boost::noncopyable { 385 | friend class BaseReply; 386 | 387 | public: 388 | Transaction(Connection* conn); 389 | 390 | ~Transaction(); 391 | 392 | void commit(); 393 | 394 | void abort(); 395 | 396 | private: 397 | Connection* conn; 398 | QueuedReply replies; 399 | }; 400 | 401 | class Connection { 402 | friend class BaseReply; 403 | friend class QueuedReply; 404 | friend class VoidReply; 405 | friend class BoolReply; 406 | friend class IntReply; 407 | friend class StringReply; 408 | friend class MultiBulkEnumerator; 409 | friend class Transaction; 410 | 411 | public: 412 | static const size_t kDefaultBufferSize = 4 * 1024; 413 | 414 | Connection(const std::string& host, const std::string& port, 415 | const std::string& password, bool noDelay = false, 416 | size_t bufferSize = kDefaultBufferSize); 417 | #ifndef _WIN32 418 | Connection(const std::string& unixDomainSocket, const std::string& password, 419 | size_t bufferSize = kDefaultBufferSize); 420 | #endif 421 | 422 | ~Connection(); 423 | 424 | void quit(); 425 | 426 | VoidReply authenticate(const char* password); 427 | 428 | BoolReply exists(const std::string& name); 429 | BoolReply del(const std::string& name); 430 | 431 | Type type(const std::string& name); 432 | 433 | MultiBulkEnumerator keys(const std::string& pattern); 434 | StringReply randomKey(); 435 | 436 | VoidReply rename(const std::string& oldName, const std::string& newName); 437 | BoolReply renameNX(const std::string& oldName, const std::string& newName); 438 | 439 | IntReply dbSize(); 440 | 441 | BoolReply expire(const std::string& name, int seconds); 442 | BoolReply expireAt(const std::string& name, int timestamp); 443 | // TODO: persist 444 | IntReply ttl(const std::string& name); 445 | 446 | VoidReply select(int db); 447 | BoolReply move(const std::string& name, int db); 448 | 449 | VoidReply flushDb(); 450 | VoidReply flushAll(); 451 | 452 | VoidReply set(const std::string& name, const std::string& value); 453 | StringReply get(const std::string& name); 454 | // TODO: mget 455 | StringReply getSet(const std::string& name, const std::string& value); 456 | BoolReply setNX(const std::string& name, const std::string& value); 457 | VoidReply setEx(const std::string& name, int time, const std::string& value); 458 | 459 | // TODO: mset 460 | // TODO: msetnx 461 | 462 | IntReply incr(const std::string& name); 463 | IntReply incrBy(const std::string& name, int value); 464 | 465 | IntReply decr(const std::string& name); 466 | IntReply decrBy(const std::string& name, int value); 467 | 468 | IntReply append(const std::string& name, const std::string& value); 469 | StringReply subStr(const std::string& name, int start, int end); 470 | 471 | IntReply rpush(const std::string& key, const std::string& value); 472 | IntReply lpush(const std::string& key, const std::string& value); 473 | IntReply llen(const std::string& key); 474 | MultiBulkEnumerator lrange(const std::string& key, int start, int end); 475 | VoidReply ltrim(const std::string& key, int start, int end); 476 | StringReply lindex(const std::string& key, int index); 477 | VoidReply lset(const std::string& key, int index, const std::string& value); 478 | IntReply lrem(const std::string& key, int count, const std::string& value); 479 | StringReply lpop(const std::string& key); 480 | StringReply rpop(const std::string& key); 481 | MultiBulkEnumerator blpop(ArgList keys, int timeout); 482 | MultiBulkEnumerator brpop(ArgList keys, int timeout); 483 | StringReply rpopLpush(const std::string& src, const std::string& dest); 484 | 485 | BoolReply sadd(const std::string& key, const std::string& member); 486 | BoolReply srem(const std::string& key, const std::string& member); 487 | StringReply spop(const std::string& key); 488 | BoolReply smove(const std::string& src, const std::string& dest, 489 | const std::string& member); 490 | IntReply scard(const std::string& key); 491 | BoolReply sisMember(const std::string& key, const std::string& member); 492 | MultiBulkEnumerator sinter(const ArgList& keys); 493 | IntReply sinterStore(const std::string& key, const ArgList& keys); 494 | MultiBulkEnumerator sunion(const ArgList& keys); 495 | IntReply sunionStore(const std::string& key, const ArgList& keys); 496 | MultiBulkEnumerator sdiff(const ArgList& keys); 497 | IntReply sdiffStore(const std::string& key, const ArgList& keys); 498 | MultiBulkEnumerator smembers(const std::string& key); 499 | StringReply srandMember(const std::string& key); 500 | 501 | // TODO: all Z* functions 502 | 503 | BoolReply hset(const std::string& key, const std::string& field, 504 | const std::string& value); 505 | StringReply hget(const std::string& key, const std::string& field); 506 | BoolReply hsetNX(const std::string& key, const std::string& field, 507 | const std::string& value); 508 | MultiBulkEnumerator hmget(const std::string& key, 509 | const std::list& fields); 510 | VoidReply hmset(const std::string& key, 511 | const std::list>& fields); 512 | IntReply hincrBy(const std::string& key, const std::string& field, int value); 513 | BoolReply hexists(const std::string& key, const std::string& field); 514 | BoolReply hdel(const std::string& key, const std::string& field); 515 | IntReply hlen(const std::string& key); 516 | MultiBulkEnumerator hkeys(const std::string& key); 517 | MultiBulkEnumerator hvals(const std::string& key); 518 | MultiBulkEnumerator hgetAll(const std::string& key); 519 | 520 | MultiBulkEnumerator scriptExists(const ArgList& script); 521 | VoidReply scriptFlush(); 522 | VoidReply scriptKill(); 523 | StringReply scriptLoad(const std::string& script); 524 | 525 | MultiBulkEnumerator eval(const std::string& script, const ArgList& keys, 526 | const ArgList& args); 527 | MultiBulkEnumerator evalSha(const std::string& sha, const ArgList& keys, 528 | const ArgList& args); 529 | 530 | VoidReply save(); 531 | VoidReply bgSave(); 532 | VoidReply bgReWriteAOF(); 533 | IntReply lastSave(); 534 | void shutdown(); 535 | StringReply info(); 536 | 537 | void subscribe(const std::string& channel); 538 | void unsubscribe(const std::string& channel); 539 | void psubscribe(const std::string& channel); 540 | void punsubscribe(const std::string& channel); 541 | IntReply publish(const std::string& channel, const std::string& message); 542 | 543 | private: 544 | char statusCode(); 545 | void readErrorReply(); 546 | void readStatusCodeReply(std::string* out); 547 | std::string readStatusCodeReply(); 548 | int64_t readIntegerReply(); 549 | void readBulkReply(boost::optional& out); 550 | boost::optional readBulkReply(); 551 | 552 | std::unique_ptr connection; 553 | std::unique_ptr ioStream; 554 | std::unique_ptr buffer; 555 | ReplyList outstandingReplies; 556 | Transaction* transaction; 557 | 558 | DEFINE_COMMAND(Quit, 0); 559 | DEFINE_COMMAND(Auth, 1); 560 | DEFINE_COMMAND(Exists, 1); 561 | DEFINE_COMMAND(Del, 1); 562 | DEFINE_COMMAND(Type, 1); 563 | DEFINE_COMMAND(Keys, 1); 564 | DEFINE_COMMAND(RandomKey, 0); 565 | DEFINE_COMMAND(Rename, 2); 566 | DEFINE_COMMAND(RenameNX, 2); 567 | DEFINE_COMMAND(DbSize, 0); 568 | DEFINE_COMMAND(Expire, 2); 569 | DEFINE_COMMAND(ExpireAt, 2); 570 | DEFINE_COMMAND(Persist, 1); 571 | DEFINE_COMMAND(Ttl, 1); 572 | DEFINE_COMMAND(Select, 1); 573 | DEFINE_COMMAND(Move, 2); 574 | DEFINE_COMMAND(FlushDb, 0); 575 | DEFINE_COMMAND(FlushAll, 0); 576 | DEFINE_COMMAND(Set, 2); 577 | DEFINE_COMMAND(Get, 1); 578 | DEFINE_COMMAND(GetSet, 2); 579 | DEFINE_COMMAND(SetNX, 2); 580 | DEFINE_COMMAND(SetEx, 3); 581 | DEFINE_COMMAND(Incr, 1); 582 | DEFINE_COMMAND(IncrBy, 2); 583 | DEFINE_COMMAND(Decr, 1); 584 | DEFINE_COMMAND(DecrBy, 2); 585 | DEFINE_COMMAND(Append, 2); 586 | DEFINE_COMMAND(SubStr, 3); 587 | 588 | DEFINE_COMMAND(RPush, 2); 589 | DEFINE_COMMAND(LPush, 2); 590 | DEFINE_COMMAND(LLen, 1); 591 | DEFINE_COMMAND(LRange, 3); 592 | DEFINE_COMMAND(LTrim, 3); 593 | DEFINE_COMMAND(LIndex, 2); 594 | DEFINE_COMMAND(LSet, 3); 595 | DEFINE_COMMAND(LRem, 3); 596 | DEFINE_COMMAND(LPop, 1); 597 | DEFINE_COMMAND(RPop, 1); 598 | DEFINE_COMMAND(BLPop, 2); 599 | DEFINE_COMMAND(BRPop, 2); 600 | DEFINE_COMMAND(RPopLPush, 2); 601 | // TODO: sort 602 | 603 | DEFINE_COMMAND(SAdd, 2); 604 | DEFINE_COMMAND(SRem, 2); 605 | DEFINE_COMMAND(SPop, 1); 606 | DEFINE_COMMAND(SMove, 3); 607 | DEFINE_COMMAND(SCard, 1); 608 | DEFINE_COMMAND(SIsMember, 2); 609 | DEFINE_COMMAND(SInter, 1); 610 | DEFINE_COMMAND(SInterStore, 2); 611 | DEFINE_COMMAND(SUnion, 1); 612 | DEFINE_COMMAND(SUnionStore, 2); 613 | DEFINE_COMMAND(SDiff, 1); 614 | DEFINE_COMMAND(SDiffStore, 2); 615 | DEFINE_COMMAND(SMembers, 1); 616 | DEFINE_COMMAND(SRandMember, 1); 617 | 618 | DEFINE_COMMAND(ZAdd, 2); 619 | DEFINE_COMMAND(ZRem, 2); 620 | DEFINE_COMMAND(ZIncrBy, 3); 621 | DEFINE_COMMAND(ZRank, 2); 622 | DEFINE_COMMAND(ZRevRank, 2); 623 | DEFINE_COMMAND(ZRange, 3); 624 | DEFINE_COMMAND(ZRevRange, 3); 625 | DEFINE_COMMAND(ZRangeByScore, 3); 626 | DEFINE_COMMAND(ZCount, 3); 627 | DEFINE_COMMAND(ZRemRangeByRank, 3); 628 | DEFINE_COMMAND(ZRemRangeByScore, 3); 629 | DEFINE_COMMAND(ZCard, 1); 630 | DEFINE_COMMAND(ZScore, 2); 631 | // TODO: zunionstore 632 | // TODO: zinterstore 633 | 634 | DEFINE_COMMAND(HSet, 3); 635 | DEFINE_COMMAND(HSetNX, 3); 636 | DEFINE_COMMAND(HGet, 2); 637 | DEFINE_COMMAND(HMGet, 2); 638 | DEFINE_COMMAND(HMSet, 2); 639 | DEFINE_COMMAND(HIncrBy, 3); 640 | DEFINE_COMMAND(HExists, 2); 641 | DEFINE_COMMAND(HDel, 2); 642 | DEFINE_COMMAND(HLen, 1); 643 | DEFINE_COMMAND(HKeys, 1); 644 | DEFINE_COMMAND(HVals, 1); 645 | DEFINE_COMMAND(HGetAll, 1); 646 | 647 | DEFINE_COMMAND(Script, 2); 648 | DEFINE_COMMAND(Eval, 3); 649 | DEFINE_COMMAND(EvalSha, 3); 650 | 651 | DEFINE_COMMAND(Save, 0); 652 | DEFINE_COMMAND(BgSave, 0); 653 | DEFINE_COMMAND(LastSave, 0); 654 | DEFINE_COMMAND(Shutdown, 0); 655 | DEFINE_COMMAND(BgReWriteAOF, 0); 656 | DEFINE_COMMAND(Info, 0); 657 | 658 | DEFINE_COMMAND(Subscribe, 1); 659 | DEFINE_COMMAND(Unsubscribe, 1); 660 | DEFINE_COMMAND(PSubscribe, 1); 661 | DEFINE_COMMAND(PUnsubscribe, 1); 662 | DEFINE_COMMAND(Publish, 2); 663 | 664 | // TODO: watch 665 | // TODO: unwatch 666 | 667 | DEFINE_COMMAND(Multi, 0); 668 | DEFINE_COMMAND(Exec, 0); 669 | DEFINE_COMMAND(Discard, 0); 670 | 671 | void multi(); 672 | void exec(); 673 | void discard(); 674 | }; 675 | }; 676 | -------------------------------------------------------------------------------- /test/Jamfile: -------------------------------------------------------------------------------- 1 | project redispptest 2 | ; 3 | 4 | using testing ; 5 | 6 | run test.cpp /redispp ; 7 | 8 | run perf.cpp /redispp ; 9 | 10 | run multi.cpp /redispp ; 11 | 12 | run trans.cpp /redispp ; 13 | -------------------------------------------------------------------------------- /test/multi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #ifdef _WIN32 5 | #include 6 | #endif 7 | 8 | using namespace redispp; 9 | using namespace std; 10 | 11 | const char* TEST_PORT = "0"; 12 | const char* TEST_HOST = "127.0.0.1"; 13 | const char* TEST_UNIX_DOMAIN_SOCKET = "/tmp/redis.sock"; 14 | 15 | int main(int argc, char* argv[]) { 16 | #ifdef _WIN32 17 | WSADATA wsaData; 18 | WORD version; 19 | version = MAKEWORD(2, 0); 20 | WSAStartup(version, &wsaData); 21 | #endif 22 | 23 | #ifdef UNIX_DOMAIN_SOCKET 24 | Connection conn(TEST_UNIX_DOMAIN_SOCKET, ""); 25 | #else 26 | Connection conn(TEST_HOST, TEST_PORT, ""); 27 | #endif 28 | 29 | int length = conn.llen("list"); 30 | printf("Length: %d\n", length); 31 | if (argc > 1) { 32 | int targetLength = atoi(argv[1]); 33 | const size_t chunkFactor = 256; 34 | IntReply replies[chunkFactor]; 35 | string key = "list"; 36 | string value = "abcdefghijklmnopqrstuvwxyz"; 37 | while (value.length() < 1400) { 38 | value = value + value; 39 | } 40 | for (int i = 0; length < targetLength; ++i, ++length) { 41 | replies[i & (chunkFactor - 1)] = conn.lpush(key, value); 42 | } 43 | length = conn.llen("list"); 44 | printf("New Length: %d\n", length); 45 | } 46 | 47 | MultiBulkEnumerator result = conn.lrange("list", length - 10000, -1); 48 | 49 | string data; 50 | while (result.next(&data)) { 51 | printf("Data: %s\n", data.c_str()); 52 | } 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /test/perf.cpp: -------------------------------------------------------------------------------- 1 | #include "redispp.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef _WIN32 7 | #include 8 | #endif 9 | using namespace redispp; 10 | using namespace boost::posix_time; 11 | 12 | void runFunc(const char* arg, size_t count) { 13 | #ifdef UNIX_DOMAIN_SOCKET 14 | Connection conn(arg, ""); 15 | #else 16 | Connection conn("localhost", arg, ""); 17 | #endif 18 | 19 | std::string key = "somemediumkey2"; 20 | std::string value = "somemediumvalue"; 21 | 22 | // Write benchmark 23 | { 24 | const size_t chunkFactor = 256; 25 | VoidReply replies[chunkFactor]; 26 | 27 | ptime begin(microsec_clock::local_time()); 28 | 29 | for (size_t i = 0; i < count; ++i) { 30 | const size_t index = i & (chunkFactor - 1); 31 | replies[index] = conn.set(key, value); 32 | if (index == (chunkFactor - 1)) { 33 | for (size_t j = 0; j < chunkFactor; ++j) { 34 | replies[j].result(); 35 | } 36 | } 37 | } 38 | 39 | ptime end(microsec_clock::local_time()); 40 | 41 | std::cout << count << " writes in " << (end - begin).total_microseconds() 42 | << " usecs ~= " 43 | << (double)count * 1000000.0 / 44 | (double)(end - begin).total_microseconds() 45 | << " requests per second" << std::endl; 46 | } 47 | 48 | // Read benchmark 49 | { 50 | const size_t chunkFactor = 256; 51 | StringReply replies[chunkFactor]; 52 | 53 | ptime begin(microsec_clock::local_time()); 54 | 55 | for (size_t i = 0; i < count; ++i) { 56 | const size_t index = i & (chunkFactor - 1); 57 | replies[index] = conn.get(key); 58 | if (index == (chunkFactor - 1)) { 59 | for (size_t j = 0; j < chunkFactor; ++j) { 60 | replies[j].result(); 61 | } 62 | } 63 | } 64 | 65 | ptime end(microsec_clock::local_time()); 66 | 67 | std::cout << count << " reads in " << (end - begin).total_microseconds() 68 | << " usecs ~= " 69 | << (double)count * 1000000.0 / 70 | (double)(end - begin).total_microseconds() 71 | << " requests per second" << std::endl; 72 | } 73 | } 74 | 75 | int main(int argc, char* argv[]) { 76 | #ifdef _WIN32 77 | WSADATA wsaData; 78 | WORD version; 79 | version = MAKEWORD(2, 0); 80 | WSAStartup(version, &wsaData); 81 | #endif 82 | 83 | if (argc <= 1) { 84 | #ifdef UNIX_DOMAIN_SOCKET 85 | std::cout << "usage: ./perf [count]" << std::endl; 86 | #else 87 | std::cout << "usage: ./perf [count]" << std::endl; 88 | #endif 89 | return 1; 90 | } 91 | int count = argc > 2 ? atoi(argv[2]) : 100000; 92 | runFunc(argv[1], count); 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_ALTERNATIVE_INIT_API 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef _WIN32 7 | #include 8 | void sleep(size_t seconds) { Sleep(seconds * 1000); } 9 | #endif 10 | 11 | using namespace redispp; 12 | 13 | const char* TEST_PORT = "6379"; 14 | const char* TEST_HOST = "127.0.0.1"; 15 | const char* TEST_UNIX_DOMAIN_SOCKET = "/tmp/redis.sock"; 16 | 17 | bool init_unit_test() { 18 | #ifdef _WIN32 19 | WSADATA wsaData; 20 | WORD version; 21 | version = MAKEWORD(2, 0); 22 | WSAStartup(version, &wsaData); 23 | #endif 24 | return true; 25 | } 26 | 27 | #ifdef UNIX_DOMAIN_SOCKET 28 | struct F { 29 | F() : conn(TEST_UNIX_DOMAIN_SOCKET, "password") { 30 | BOOST_TEST_MESSAGE("Set up fixture"); 31 | } 32 | ~F() { BOOST_TEST_MESSAGE("Tore down fixture"); } 33 | Connection conn; 34 | }; 35 | #else 36 | struct F { 37 | F() : conn(TEST_HOST, TEST_PORT, "password") { 38 | BOOST_TEST_MESSAGE("Set up fixture"); 39 | } 40 | ~F() { BOOST_TEST_MESSAGE("Tore down fixture"); } 41 | Connection conn; 42 | }; 43 | #endif 44 | 45 | BOOST_FIXTURE_TEST_SUITE(s, F) 46 | 47 | BOOST_AUTO_TEST_CASE(set_get_exists_del) { 48 | conn.set("hello", "world"); 49 | StringReply stringReply = conn.get("hello"); 50 | BOOST_CHECK(stringReply.result().is_initialized()); 51 | BOOST_CHECK_EQUAL((std::string)conn.get("hello"), "world"); 52 | BOOST_CHECK((bool)conn.exists("hello")); 53 | BOOST_CHECK((bool)conn.del("hello")); 54 | BOOST_CHECK(!conn.exists("hello")); 55 | BOOST_CHECK(!conn.del("hello")); 56 | } 57 | 58 | BOOST_AUTO_TEST_CASE(nullreplies) { 59 | BOOST_CHECK_THROW((std::string)conn.get("nonexistant"), NullReplyException); 60 | StringReply nullReply = conn.get("nonexistant"); 61 | BOOST_CHECK_EQUAL(false, nullReply.result().is_initialized()); 62 | 63 | // Connection still in good state: 64 | conn.set("one", "1"); 65 | BOOST_CHECK_EQUAL((std::string)conn.get("one"), "1"); 66 | 67 | MultiBulkEnumerator result = 68 | conn.blpop(boost::assign::list_of("notalist"), 1); 69 | std::string str; 70 | BOOST_CHECK(!result.next(&str)); 71 | 72 | // Connection still in good state: 73 | BOOST_CHECK_EQUAL((std::string)conn.get("one"), "1"); 74 | } 75 | 76 | BOOST_AUTO_TEST_CASE(type) { 77 | conn.set("hello", "world"); 78 | BOOST_CHECK(conn.type("hello") == String); 79 | } 80 | 81 | BOOST_AUTO_TEST_CASE(keys) { 82 | conn.set("hello", "world"); 83 | MultiBulkEnumerator response = conn.keys("h?llo"); 84 | std::string key; 85 | bool found = false; 86 | while (response.next(&key)) { 87 | if (key == "hello") 88 | found = true; 89 | } 90 | BOOST_CHECK(found); 91 | } 92 | 93 | BOOST_AUTO_TEST_CASE(randomkey) { 94 | conn.set("hello", "world"); 95 | BOOST_CHECK((bool)conn.exists(conn.randomKey())); 96 | } 97 | 98 | BOOST_AUTO_TEST_CASE(rename_) { 99 | conn.set("hello", "world"); 100 | conn.rename("hello", "hello1"); 101 | BOOST_CHECK((std::string)conn.get("hello1") == "world"); 102 | conn.set("hello2", "one"); 103 | BOOST_CHECK(!conn.renameNX("hello1", "hello2")); 104 | BOOST_CHECK((bool)conn.renameNX("hello1", "hello3")); 105 | } 106 | 107 | BOOST_AUTO_TEST_CASE(dbsize) { 108 | conn.set("hello", "world"); 109 | conn.del("hello1"); 110 | int size = conn.dbSize(); 111 | BOOST_CHECK(size >= 1); 112 | conn.set("hello1", "one"); 113 | BOOST_CHECK(conn.dbSize() == size + 1); 114 | } 115 | 116 | BOOST_AUTO_TEST_CASE(expire_ttl) { 117 | conn.set("hello", "world"); 118 | conn.set("hello1", "world"); 119 | time_t now = time(NULL); 120 | BOOST_CHECK((bool)conn.expire("hello", 5)); 121 | BOOST_CHECK((bool)conn.expireAt("hello1", now + 5)); 122 | BOOST_CHECK(conn.ttl("hello") <= 5); 123 | BOOST_CHECK(conn.ttl("hello1") <= 5); 124 | sleep(6); 125 | BOOST_CHECK(!conn.exists("hello")); 126 | BOOST_CHECK(!conn.exists("hello1")); 127 | } 128 | 129 | BOOST_AUTO_TEST_CASE(select_move) { 130 | conn.select(0); 131 | conn.set("hello", "world"); 132 | BOOST_CHECK((bool)conn.exists("hello")); 133 | conn.select(1); 134 | BOOST_CHECK(!conn.exists("hello")); 135 | conn.select(0); 136 | conn.move("hello", 1); 137 | BOOST_CHECK(!conn.exists("hello")); 138 | conn.select(1); 139 | BOOST_CHECK((bool)conn.exists("hello")); 140 | conn.select(0); 141 | } 142 | 143 | BOOST_AUTO_TEST_CASE(flush) { 144 | conn.set("hello", "world"); 145 | BOOST_CHECK(conn.dbSize() > 0); 146 | conn.flushDb(); 147 | BOOST_CHECK(conn.dbSize() == 0); 148 | conn.set("hello", "world"); 149 | BOOST_CHECK(conn.dbSize() > 0); 150 | conn.select(1); 151 | conn.set("hello", "world"); 152 | BOOST_CHECK(conn.dbSize() > 0); 153 | conn.flushAll(); 154 | BOOST_CHECK(conn.dbSize() == 0); 155 | conn.select(0); 156 | BOOST_CHECK(conn.dbSize() == 0); 157 | } 158 | 159 | BOOST_AUTO_TEST_CASE(getset) { 160 | conn.set("hello", "world"); 161 | BOOST_CHECK((std::string)conn.getSet("hello", "one") == "world"); 162 | BOOST_CHECK((std::string)conn.get("hello") == "one"); 163 | } 164 | 165 | BOOST_AUTO_TEST_CASE(setnx) { 166 | conn.set("hello", "world"); 167 | BOOST_CHECK(!conn.setNX("hello", "one")); 168 | conn.del("hello"); 169 | BOOST_CHECK((bool)conn.setNX("hello", "one")); 170 | } 171 | 172 | BOOST_AUTO_TEST_CASE(setex) { 173 | conn.setEx("hello", 5, "world"); 174 | BOOST_CHECK(conn.ttl("hello") <= 5); 175 | } 176 | 177 | BOOST_AUTO_TEST_CASE(incrdecr) { 178 | conn.set("hello", "5"); 179 | BOOST_CHECK(conn.incr("hello") == 6); 180 | BOOST_CHECK(conn.incrBy("hello", 2) == 8); 181 | BOOST_CHECK(conn.decr("hello") == 7); 182 | BOOST_CHECK(conn.decrBy("hello", 2) == 5); 183 | } 184 | 185 | BOOST_AUTO_TEST_CASE(append) { 186 | conn.set("hello", "world"); 187 | BOOST_CHECK(conn.append("hello", "one") == 8); 188 | BOOST_CHECK((std::string)conn.get("hello") == "worldone"); 189 | } 190 | 191 | BOOST_AUTO_TEST_CASE(substr) { 192 | conn.set("hello", "world"); 193 | BOOST_CHECK((std::string)conn.subStr("hello", 1, 3) == "orl"); 194 | } 195 | 196 | BOOST_AUTO_TEST_CASE(lists) { 197 | conn.del("hello"); 198 | BOOST_CHECK(conn.lpush("hello", "c") == 1); 199 | BOOST_CHECK(conn.lpush("hello", "d") == 2); 200 | BOOST_CHECK(conn.rpush("hello", "b") == 3); 201 | BOOST_CHECK(conn.rpush("hello", "a") == 4); 202 | BOOST_CHECK(conn.llen("hello") == 4); 203 | MultiBulkEnumerator result = conn.lrange("hello", 1, 3); 204 | std::string str1; 205 | BOOST_CHECK(result.next(&str1)); 206 | BOOST_CHECK(str1 == "c"); 207 | BOOST_CHECK(result.next(&str1)); 208 | BOOST_CHECK(str1 == "b"); 209 | BOOST_CHECK(result.next(&str1)); 210 | BOOST_CHECK(str1 == "a"); 211 | conn.ltrim("hello", 0, 1); 212 | result = conn.lrange("hello", 0, 10); 213 | BOOST_CHECK(result.next(&str1)); 214 | BOOST_CHECK(str1 == "d"); 215 | BOOST_CHECK(result.next(&str1)); 216 | BOOST_CHECK(str1 == "c"); 217 | BOOST_CHECK((std::string)conn.lindex("hello", 0) == "d"); 218 | BOOST_CHECK((std::string)conn.lindex("hello", 1) == "c"); 219 | conn.lset("hello", 1, "f"); 220 | BOOST_CHECK((std::string)conn.lindex("hello", 1) == "f"); 221 | conn.lpush("hello", "f"); 222 | conn.lpush("hello", "f"); 223 | conn.lpush("hello", "f"); 224 | BOOST_CHECK(conn.lrem("hello", 2, "f") == 2); 225 | BOOST_CHECK(conn.llen("hello") == 3); 226 | BOOST_CHECK((std::string)conn.lpop("hello") == "f"); 227 | BOOST_CHECK(conn.llen("hello") == 2); 228 | conn.rpush("hello", "x"); 229 | BOOST_CHECK((std::string)conn.rpop("hello") == "x"); 230 | conn.rpush("hello", "z"); 231 | BOOST_CHECK((std::string)conn.rpopLpush("hello", "hello") == "z"); 232 | conn.lpush("list1", "a"); 233 | conn.lpush("list1", "b"); 234 | conn.lpush("list2", "c"); 235 | result = conn.blpop(boost::assign::list_of("list1")("list2"), 0); 236 | std::string str2; 237 | BOOST_CHECK(result.next(&str1)); 238 | BOOST_CHECK(result.next(&str2)); 239 | BOOST_CHECK(str1 == "list1"); 240 | BOOST_CHECK(str2 == "b"); 241 | result = conn.blpop(boost::assign::list_of("list1")("list2"), 0); 242 | BOOST_CHECK(result.next(&str1)); 243 | BOOST_CHECK(result.next(&str2)); 244 | BOOST_CHECK(str1 == "list1"); 245 | BOOST_CHECK(str2 == "a"); 246 | result = conn.blpop(boost::assign::list_of("list1")("list2"), 0); 247 | BOOST_CHECK(result.next(&str1)); 248 | BOOST_CHECK(result.next(&str2)); 249 | BOOST_CHECK(str1 == "list2"); 250 | BOOST_CHECK(str2 == "c"); 251 | result = conn.blpop(boost::assign::list_of("list1")("list2"), 1); 252 | BOOST_CHECK(!result.next(&str1)); 253 | conn.lpush("list1", "a"); 254 | conn.lpush("list1", "b"); 255 | conn.lpush("list2", "c"); 256 | result = conn.brpop(boost::assign::list_of("list1")("list2"), 0); 257 | BOOST_CHECK(result.next(&str1)); 258 | BOOST_CHECK(result.next(&str2)); 259 | BOOST_CHECK(str1 == "list1"); 260 | BOOST_CHECK(str2 == "a"); 261 | result = conn.brpop(boost::assign::list_of("list1")("list2"), 0); 262 | BOOST_CHECK(result.next(&str1)); 263 | BOOST_CHECK(result.next(&str2)); 264 | BOOST_CHECK(str1 == "list1"); 265 | BOOST_CHECK(str2 == "b"); 266 | result = conn.brpop(boost::assign::list_of("list1")("list2"), 0); 267 | BOOST_CHECK(result.next(&str1)); 268 | BOOST_CHECK(result.next(&str2)); 269 | BOOST_CHECK(str1 == "list2"); 270 | BOOST_CHECK(str2 == "c"); 271 | result = conn.brpop(boost::assign::list_of("list1")("list2"), 1); 272 | BOOST_CHECK(!result.next(&str1)); 273 | } 274 | 275 | BOOST_AUTO_TEST_CASE(sets) { 276 | conn.del("hello"); 277 | BOOST_CHECK((bool)conn.sadd("hello", "world")); 278 | BOOST_CHECK((bool)conn.sisMember("hello", "world")); 279 | BOOST_CHECK(!conn.sisMember("hello", "mars")); 280 | BOOST_CHECK(conn.scard("hello") == 1); 281 | BOOST_CHECK((bool)conn.sadd("hello", "mars")); 282 | BOOST_CHECK(conn.scard("hello") == 2); 283 | MultiBulkEnumerator result = conn.smembers("hello"); 284 | std::string str1; 285 | std::string str2; 286 | BOOST_CHECK(result.next(&str1)); 287 | BOOST_CHECK(result.next(&str2)); 288 | BOOST_CHECK((str1 == "world" && str2 == "mars") || 289 | (str2 == "world" && str1 == "mars")); 290 | std::string randomMember = conn.srandMember("hello"); 291 | BOOST_CHECK(randomMember == "world" || randomMember == "mars"); 292 | BOOST_CHECK((bool)conn.srem("hello", "mars")); 293 | BOOST_CHECK(conn.scard("hello") == 1); 294 | BOOST_CHECK((std::string)conn.spop("hello") == "world"); 295 | BOOST_CHECK(conn.scard("hello") == 0); 296 | conn.del("hello1"); 297 | BOOST_CHECK((bool)conn.sadd("hello", "world")); 298 | BOOST_CHECK(conn.scard("hello") == 1); 299 | BOOST_CHECK((bool)conn.smove("hello", "hello1", "world")); 300 | BOOST_CHECK(conn.scard("hello") == 0); 301 | BOOST_CHECK(conn.scard("hello1") == 1); 302 | conn.sadd("set1", "a"); 303 | conn.sadd("set1", "b"); 304 | conn.sadd("set2", "b"); 305 | conn.sadd("set2", "c"); 306 | result = conn.sinter(boost::assign::list_of("set1")("set2")); 307 | BOOST_CHECK(result.next(&str1)); 308 | BOOST_CHECK(str1 == "b"); 309 | BOOST_CHECK(!result.next(&str2)); 310 | BOOST_CHECK(conn.sinterStore("res", boost::assign::list_of("set1")("set2")) == 311 | 1); 312 | BOOST_CHECK(conn.scard("res") == 1); 313 | result = conn.sunion(boost::assign::list_of("set1")("set2")); 314 | BOOST_CHECK(result.next(&str1)); 315 | BOOST_CHECK(str1 == "a" || str1 == "b" || str1 == "c"); 316 | BOOST_CHECK(result.next(&str1)); 317 | BOOST_CHECK(str1 == "a" || str1 == "b" || str1 == "c"); 318 | BOOST_CHECK(result.next(&str1)); 319 | BOOST_CHECK(str1 == "a" || str1 == "b" || str1 == "c"); 320 | BOOST_CHECK(!result.next(&str2)); 321 | BOOST_CHECK(conn.sunionStore("res", boost::assign::list_of("set1")("set2")) == 322 | 3); 323 | BOOST_CHECK(conn.scard("res") == 3); 324 | result = conn.sdiff(boost::assign::list_of("set1")("set2")); 325 | BOOST_CHECK(result.next(&str1)); 326 | BOOST_CHECK(str1 == "a"); 327 | BOOST_CHECK(!result.next(&str2)); 328 | BOOST_CHECK(conn.sdiffStore("res", boost::assign::list_of("set1")("set2")) == 329 | 1); 330 | BOOST_CHECK(conn.scard("res") == 1); 331 | } 332 | 333 | BOOST_AUTO_TEST_CASE(hashes) { 334 | conn.del("hello"); 335 | BOOST_CHECK((bool)conn.hset("hello", "world", "one")); 336 | BOOST_CHECK((bool)conn.hset("hello", "mars", "two")); 337 | BOOST_CHECK((std::string)conn.hget("hello", "world") == "one"); 338 | BOOST_CHECK(!conn.hsetNX("hello", "mars", "two")); 339 | BOOST_CHECK((bool)conn.hsetNX("hello", "venus", "1")); 340 | BOOST_CHECK(conn.hincrBy("hello", "venus", 3) == 4); 341 | BOOST_CHECK((bool)conn.hexists("hello", "venus")); 342 | BOOST_CHECK((bool)conn.hdel("hello", "venus")); 343 | BOOST_CHECK(!conn.hexists("hello", "venus")); 344 | BOOST_CHECK(conn.hlen("hello") == 2); 345 | MultiBulkEnumerator result = conn.hkeys("hello"); 346 | std::string str1; 347 | std::string str2; 348 | BOOST_CHECK(result.next(&str1)); 349 | BOOST_CHECK(result.next(&str2)); 350 | BOOST_CHECK((str1 == "world" && str2 == "mars") || 351 | (str2 == "world" && str1 == "mars")); 352 | result = conn.hvals("hello"); 353 | BOOST_CHECK(result.next(&str1)); 354 | BOOST_CHECK(result.next(&str2)); 355 | BOOST_CHECK((str1 == "one" && str2 == "two") || 356 | (str2 == "one" && str1 == "two")); 357 | result = conn.hgetAll("hello"); 358 | std::string str3; 359 | std::string str4; 360 | BOOST_CHECK(result.next(&str1)); 361 | BOOST_CHECK(result.next(&str2)); 362 | BOOST_CHECK(result.next(&str3)); 363 | BOOST_CHECK(result.next(&str4)); 364 | BOOST_CHECK( 365 | (str1 == "world" && str2 == "one" && str3 == "mars" && str4 == "two") || 366 | (str1 == "mars" && str2 == "two" && str3 == "world" && str4 == "one")); 367 | KeyValueList fields = boost::assign::list_of( 368 | std::make_pair("venus", "three"))(std::make_pair("jupiter", "four")); 369 | BOOST_CHECK((bool)conn.hmset("hello", fields)); 370 | ArgList fieldNames = boost::assign::list_of("venus")("jupiter"); 371 | result = conn.hmget("hello", fieldNames); 372 | BOOST_CHECK(result.next(&str1)); 373 | BOOST_CHECK(result.next(&str2)); 374 | BOOST_CHECK(str1 == "three" && str2 == "four"); 375 | } 376 | 377 | BOOST_AUTO_TEST_CASE(scripts) { 378 | std::string script = "return {KEYS[1], KEYS[2], false, ARGV[1], ARGV[2]}"; 379 | BOOST_CHECK(true); 380 | std::string sha = conn.scriptLoad(script); 381 | BOOST_CHECK(!sha.empty()); 382 | 383 | MultiBulkEnumerator result = 384 | conn.scriptExists(boost::assign::list_of(sha)("notascript")); 385 | std::string str; 386 | BOOST_CHECK(result.next(&str)); 387 | BOOST_CHECK(str == "1"); 388 | BOOST_CHECK(result.next(&str)); 389 | BOOST_CHECK(str == "0"); 390 | BOOST_CHECK(!result.next(&str)); 391 | 392 | result = conn.evalSha(sha, boost::assign::list_of("a")("b"), 393 | boost::assign::list_of("c")("d")); 394 | BOOST_CHECK(result.next(&str)); 395 | BOOST_CHECK(str == "a"); 396 | BOOST_CHECK(result.next(&str)); 397 | BOOST_CHECK(str == "b"); 398 | BOOST_CHECK_THROW(result.next(&str), NullReplyException); 399 | BOOST_CHECK(result.next(&str)); 400 | BOOST_CHECK(str == "c"); 401 | BOOST_CHECK(result.next(&str)); 402 | BOOST_CHECK(str == "d"); 403 | BOOST_CHECK(!result.next(&str)); 404 | 405 | conn.scriptFlush(); 406 | result = conn.scriptExists(boost::assign::list_of(sha)); 407 | BOOST_CHECK(result.next(&str)); 408 | BOOST_CHECK(str == "0"); 409 | 410 | result = conn.eval(script, boost::assign::list_of("a")("b"), 411 | boost::assign::list_of("c")("d")); 412 | BOOST_CHECK(result.next(&str)); 413 | BOOST_CHECK(str == "a"); 414 | BOOST_CHECK(result.next(&str)); 415 | BOOST_CHECK(str == "b"); 416 | BOOST_CHECK_THROW(result.next(&str), NullReplyException); 417 | BOOST_CHECK(result.next(&str)); 418 | BOOST_CHECK(str == "c"); 419 | BOOST_CHECK(result.next(&str)); 420 | BOOST_CHECK(str == "d"); 421 | BOOST_CHECK(!result.next(&str)); 422 | 423 | conn.scriptKill(); 424 | } 425 | 426 | BOOST_AUTO_TEST_CASE(misc) { 427 | time_t now = time(NULL); 428 | ::sleep(2); 429 | conn.save(); 430 | BOOST_CHECK(conn.lastSave() > now); 431 | conn.bgSave(); 432 | conn.bgReWriteAOF(); 433 | BOOST_CHECK(((std::string)conn.info()).length() > 0); 434 | } 435 | 436 | // TODO: test for pipelined requests 437 | 438 | BOOST_AUTO_TEST_CASE(pipelined) { 439 | { 440 | VoidReply a = conn.set("one", "a"); 441 | StringReply readA = conn.get("one"); 442 | { 443 | BoolReply b = conn.hset("two", "two", "b"); 444 | VoidReply c = conn.set("three", "c"); 445 | } 446 | BOOST_CHECK((std::string)readA == "a"); 447 | } 448 | 449 | BOOST_CHECK((std::string)conn.get("one") == "a"); 450 | BOOST_CHECK((std::string)conn.hget("two", "two") == "b"); 451 | BOOST_CHECK((std::string)conn.get("three") == "c"); 452 | 453 | { 454 | conn.del("hello"); 455 | IntReply c = conn.lpush("hello", "c"); 456 | IntReply d = conn.lpush("hello", "d"); 457 | IntReply b = conn.rpush("hello", "b"); 458 | IntReply a = conn.rpush("hello", "a"); 459 | { 460 | MultiBulkEnumerator result = conn.lrange("hello", 1, 3); 461 | IntReply c = conn.lpush("hello", "c"); 462 | IntReply d = conn.lpush("hello", "d"); 463 | IntReply b = conn.rpush("hello", "b"); 464 | IntReply a = conn.rpush("hello", "a"); 465 | BOOST_CHECK((int)a == 8); 466 | BOOST_CHECK((int)b == 7); 467 | BOOST_CHECK((int)d == 6); 468 | BOOST_CHECK((int)c == 5); 469 | std::string str; 470 | BOOST_CHECK(result.next(&str)); 471 | BOOST_CHECK(str == "c"); 472 | BOOST_CHECK(result.next(&str)); 473 | BOOST_CHECK(str == "b"); 474 | BOOST_CHECK(result.next(&str)); 475 | BOOST_CHECK(str == "a"); 476 | } 477 | IntReply len = conn.llen("hello"); 478 | BOOST_CHECK((int)a == 4); 479 | BOOST_CHECK((int)b == 3); 480 | BOOST_CHECK((int)d == 2); 481 | BOOST_CHECK((int)c == 1); 482 | } 483 | } 484 | 485 | BOOST_AUTO_TEST_SUITE_END() 486 | -------------------------------------------------------------------------------- /test/trans.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #ifdef _WIN32 5 | #include 6 | #endif 7 | 8 | using namespace redispp; 9 | using namespace std; 10 | 11 | const char* TEST_PORT = "0"; 12 | const char* TEST_HOST = "127.0.0.1"; 13 | const char* TEST_UNIX_DOMAIN_SOCKET = "/tmp/redis.sock"; 14 | 15 | int main(int argc, char* argv[]) { 16 | #ifdef _WIN32 17 | WSADATA wsaData; 18 | WORD version; 19 | version = MAKEWORD(2, 0); 20 | WSAStartup(version, &wsaData); 21 | #endif 22 | 23 | #ifdef UNIX_DOMAIN_SOCKET 24 | Connection conn(TEST_UNIX_DOMAIN_SOCKET, ""); 25 | #else 26 | Connection conn(TEST_HOST, TEST_PORT, ""); 27 | #endif 28 | 29 | conn.set("x", "a"); 30 | conn.set("y", "b"); 31 | 32 | Transaction trans(&conn); 33 | VoidReply one = conn.set("x", "1"); 34 | VoidReply two = conn.set("y", "21"); 35 | StringReply resOne = conn.get("x"); 36 | trans.commit(); 37 | 38 | std::string res1 = resOne; 39 | std::string res2 = conn.get("x"); 40 | if (res2 != res1) 41 | std::cout << "did not match: " << res1 << " " << res2 << std::endl; 42 | else 43 | std::cout << "they match: " << res1 << " " << res2 << std::endl; 44 | return 0; 45 | } 46 | --------------------------------------------------------------------------------