├── .github └── workflows │ └── Testing.yaml ├── .gitignore ├── .gitmodules ├── .mailmap ├── LICENSE ├── Makefile ├── README.org ├── debian ├── changelog ├── compat ├── control ├── rules └── source │ └── format ├── src ├── Makefile ├── rl.cpp ├── rl.h ├── rl_compdata.h ├── rl_connection.cpp ├── rl_connection.h ├── rl_hash.cpp ├── rl_kv.cpp ├── rl_list.cpp ├── rl_request.cpp ├── rl_request.h ├── rl_server.cpp ├── rl_server.h ├── rl_set.cpp ├── rl_util.cpp └── rl_util.h └── t ├── 001-kv.t ├── 002-set.t ├── 003-hash.t ├── 004-list.t ├── 005-tx.t ├── 006-db.t ├── 007-srv.t └── Tester.pm /.github/workflows/Testing.yaml: -------------------------------------------------------------------------------- 1 | name: Redis-LevelDB Testing 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: 13 | - ubuntu-latest 14 | - macOS-latest 15 | arch: 16 | - x64 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | with: 21 | submodules: recursive 22 | # - name: Checkout submodules 23 | # run: git submodule update --init --recursive 24 | - name: Install Dependencies 25 | run: | 26 | if [ `uname` = "Linux" ]; then 27 | sudo apt-get update -qq -y; 28 | sudo apt-get install libsnappy-dev libev-dev libgmp-dev cpanminus perl -y; 29 | sudo cpanm --quiet --notest --skip-satisfied --force Redis; 30 | elif [ `uname` = "Darwin" ]; then 31 | brew update; 32 | brew install snappy; 33 | brew install libev; 34 | brew install gmp; 35 | brew install cpanminus; 36 | sudo cpanm --quiet --notest --skip-satisfied --force Redis; 37 | fi 38 | - name: Build 39 | run: make 40 | - name: Unit Testing 41 | run: | 42 | make test 43 | echo "Ready for packaging..." 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | redis-leveldb 2 | *.a 3 | *.o 4 | redis.db 5 | tmp_* 6 | tmp.* 7 | *.db 8 | .DS_Store 9 | data/ 10 | *~ 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/leveldb"] 2 | path = vendor/leveldb 3 | url = https://github.com/google/leveldb.git 4 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | KDr2 ZHUO QL 2 | KDr2 ZHUO QL 3 | KDr2 ZHUO QL 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2021 Killian Q. Zhuo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Top level makefile, the real shit is at src/Makefile 2 | 3 | default: all 4 | 5 | 6 | .DEFAULT: 7 | cd src && $(MAKE) $@ 8 | 9 | deb: 10 | dpkg-buildpackage 11 | 12 | .PHONY: test 13 | test: 14 | @prove -I t/ 15 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | # -*- mode: org; mode: auto-fill -*- 2 | #+TITLE: Redis-Leveldb 3 | #+AUTHOR: KDr2 4 | 5 | * Introduction 6 | 7 | !!! Notice !!!: This project is looking for a new maintainer, if you 8 | are interested, please fix some known issues then contact me, I will 9 | transfer the project to you. Thank you. 10 | 11 | Redis-LevelDB is a redis-protocol compatible frontend to google's 12 | leveldb: Use leveldb as a Redis-Server. 13 | 14 | #+BEGIN_HTML 15 | 16 | CI BUILD STATUS 17 | 18 | #+END_HTML 19 | 20 | Current Version: 1.6 21 | 22 | * Redis COMMAND Supported 23 | 24 | ** key-value commands: 25 | - incr/incrby 26 | - get/set 27 | - mget/mset 28 | 29 | ** hash commands(New): 30 | - hget 31 | - hset 32 | - hsetnx 33 | - hdel 34 | - hexists 35 | - hgetall 36 | - hkeys 37 | - hvals 38 | - hlen 39 | 40 | ** set commands(New): 41 | - sadd 42 | - srem 43 | - scard 44 | - smembers 45 | - sismemeber 46 | 47 | ** transaction commands: 48 | - multi 49 | - exec 50 | - discard 51 | 52 | ** connection commans: 53 | - select: select db (when redis-leveldb run in multi-db mode, with 54 | argument =-M =) 55 | 56 | ** server commands: 57 | - keys 58 | - info: Different to redis, this info command accepts a flag argument, 59 | eg =info=, =info k=, =info t=, =info kt= 60 | * default: show leveldb.stat info 61 | * k: show the count of all keys 62 | * t: show leveldb.sstables info 63 | 64 | * Dependencies 65 | - libev(>=1.4): 66 | install with apt-get or port please. 67 | - gmp(http://gmplib.org/): 68 | install with apt-get or port please. 69 | - libsnappy 70 | - leveldb: 71 | * git clone git://github.com/KDr2/redis-leveldb.git 72 | * cd redis-leveldb 73 | * git submodule init 74 | * git submodule update 75 | 76 | * Compile 77 | #+BEGIN_SRC sh 78 | [LIBEV=LIBEV_PREFIX GMP=GMP_PREFIX DEBUG=1] make 79 | #+END_SRC 80 | 81 | * Run 82 | 83 | #+BEGIN_SRC sh 84 | ./redis-leveldb -h 85 | #+END_SRC 86 | 87 | ** options: 88 | - -d: run redis-level as a daemon process 89 | - -H : host addr to listen on(eg: 127.0.0.1) 90 | - -P : port to listen on(default 8323) 91 | - -D : leveldb data dir(default "redis.db" under your 92 | work directory) 93 | - -M : run in multi-db mode and set its db count to , 94 | each db in the server is a separatly leveldb database and its data 95 | directory is a directory named =db-= under the directory you 96 | specified with the option =-D=; you can use command =select= to 97 | switch db on the client side while redis-leveldb is running in this 98 | mode. 99 | 100 | * Test Suite 101 | - dependencies: perl5(>=v5.10) with Redis.pm 102 | - run test: ~make test~ 103 | * Known Issues 104 | - [[https://github.com/KDr2/redis-leveldb/issues/13][issue 13]]: signle data package size limitation. 105 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | redis-leveldb (0.02) unstable; urgency=low 2 | 3 | * Initial release. 4 | 5 | -- piotr Thu, 30 Jan 2014 18:49:19 +0100 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: redis-leveldb 2 | Section: misc 3 | Priority: extra 4 | Maintainer: Pedro Larroy 5 | Build-Depends: debhelper (>= 8.0.0), g++, make 6 | Standards-Version: 3.9.2 7 | Homepage: https://github.com/larroy/redis-leveldb 8 | #Vcs-Git: https://github.com/larroy/redis-leveldb.git 9 | #Vcs-Browser: https://github.com/larroy/redis-leveldb 10 | 11 | Package: redis-leveldb 12 | Architecture: amd64 13 | Depends: 14 | Description: A redis-protocol compatible persistent storage server using leveldb as a storage engine 15 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # 5 | # This file was originally written by Joey Hess and Craig Small. 6 | # As a special exception, when this file is copied by dh-make into a 7 | # dh-make output file, you may use that output file without restriction. 8 | # This special exception was added by Craig Small in version 0.37 of dh-make. 9 | # 10 | # Modified to make a template file for a multi-binary package with separated 11 | # build-arch and build-indep targets by Bill Allombert 2001 12 | 13 | # Uncomment this to turn on verbose mode. 14 | export DH_VERBOSE=1 15 | 16 | export DEB_CXXFLAGS_SET='' 17 | # This has to be exported to make some magic below work. 18 | export DH_OPTIONS 19 | 20 | pkg_base = "debian/redis-leveldb" 21 | src_base = "./" 22 | 23 | clean: 24 | make -C $(src_base) clean 25 | rm -rf $(pkg_base) 26 | 27 | build: 28 | make -C $(src_base) -j10 29 | mkdir -p $(pkg_base)/usr/local/bin 30 | mkdir -p $(pkg_base)/etc/redis-leveldb 31 | install $(src_base)redis-leveldb $(pkg_base)/usr/bin 32 | 33 | install: 34 | 35 | binary: 36 | dh_testdir -A 37 | dh_testroot -A 38 | dh_md5sums -A 39 | dh_gencontrol -A 40 | dh_builddeb -A 41 | 42 | binary-arch: 43 | 44 | binary-indep: 45 | 46 | %: 47 | echo $@ 48 | # dh $@ 49 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 3 | 4 | ifeq ($(uname_S),Darwin) 5 | LIBEV?=/usr/local 6 | GMP?=/usr/local 7 | else 8 | LIBEV?=/usr 9 | GMP?=/usr 10 | endif 11 | 12 | CFLAGS += -Wall -I$(LIBEV)/include -I$(GMP)/include -I../vendor/leveldb/include -std=c99 13 | CXXFLAGS += -Wall -I$(LIBEV)/include -I$(GMP)/include -I../vendor/leveldb/include 14 | LDFLAGS += ../vendor/libleveldb.a -lm -L$(LIBEV)/lib -lev -L$(GMP)/lib -lgmp -lsnappy 15 | 16 | ifeq ($(uname_S),Linux) 17 | LDFLAGS += -lpthread 18 | endif 19 | 20 | ifeq ($(DEBUG),1) 21 | CXXFLAGS += -g -DDEBUG 22 | endif 23 | 24 | all: redis-leveldb 25 | 26 | OBJS = rl_util.o rl_server.o rl_connection.o rl.o \ 27 | rl_request.o rl_kv.o rl_set.o rl_hash.o rl_list.o 28 | 29 | rl_util.o: rl_util.h rl_server.h rl_util.cpp 30 | rl_server.o: rl_util.h rl_server.h rl_connection.h rl_server.cpp 31 | rl_connection.o: rl_util.h rl_server.h rl_connection.h rl_request.h rl_connection.cpp 32 | rl_request.o: rl.h rl_util.h rl_server.h rl_connection.h rl_compdata.h rl_request.h rl_request.cpp 33 | rl_kv.o: rl.h rl_util.h rl_server.h rl_connection.h rl_request.h rl_kv.cpp 34 | rl_set.o: rl.h rl_util.h rl_server.h rl_connection.h rl_compdata.h rl_request.h rl_set.cpp 35 | rl_hash.o: rl.h rl_util.h rl_server.h rl_connection.h rl_compdata.h rl_request.h rl_hash.cpp 36 | rl_list.o: rl.h rl_util.h rl_server.h rl_connection.h rl_request.h rl_list.cpp 37 | rl.o: rl_util.h rl_server.h rl_connection.h rl_request.h rl.cpp 38 | 39 | 40 | redis-leveldb: $(OBJS) ../vendor/libleveldb.a 41 | $(CXX) $^ $(LIBS) $(LDFLAGS) -o ../$@ 42 | 43 | clean: 44 | -rm -f ../redis-leveldb 45 | -rm -f *.o 46 | 47 | distclean: clean 48 | -rm -f ../vendor/*.a 49 | cd ../vendor/leveldb; make clean 50 | 51 | ../vendor/libleveldb.a: 52 | cd ../vendor/leveldb; make && cp out-static/libleveldb.a .. 53 | -------------------------------------------------------------------------------- /src/rl.cpp: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_request.cpp 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include "rl_util.h" 19 | #include "rl_server.h" 20 | #include "rl_connection.h" 21 | #include "rl_request.h" 22 | 23 | extern char *optarg; 24 | 25 | RLServer *server=NULL; 26 | 27 | int main(int argc, char** argv) { 28 | int daemon_flag = 0, ch; 29 | 30 | int opt_host=0; 31 | char hostaddr[64]; 32 | memset(hostaddr,0,64); 33 | 34 | int port=8323; 35 | int db_num=0; 36 | 37 | RLRequest::init_cmd_map(); 38 | 39 | char data_dir[128]; 40 | memset(data_dir,0,128); 41 | strncpy(data_dir,"redis.db",8); 42 | 43 | while ((ch = getopt(argc, argv, "hdr:H:P:D:M:")) != -1) { 44 | switch (ch) { 45 | case 'h': 46 | printf("Usage:\n\t./redis-leveldb [options]\n"); 47 | printf("Options:\n"); 48 | printf("\t-d:\t\t run in daemon mode\n"); 49 | printf("\t-r db-path:\t repair db\n"); 50 | printf("\t-H host-ip:\t listen host\n"); 51 | printf("\t-P port:\t listen port\n"); 52 | printf("\t-D data-dir:\t data dir\n"); 53 | printf("\t-M number:\t DB count(run in multi-db mode)\n"); 54 | exit(0); 55 | case 'd': 56 | daemon_flag = 1; 57 | break; 58 | case 'r': 59 | leveldb_repair(optarg); 60 | exit(0); 61 | break; 62 | case 'H': 63 | strcpy(hostaddr,optarg); 64 | opt_host=1; 65 | break; 66 | case 'P': 67 | port=(int)strtol(optarg, (char **)NULL, 10); 68 | if(!port){ 69 | printf("Bad port(-P) value\n"); 70 | exit(1); 71 | } 72 | break; 73 | case 'D': 74 | strcpy(data_dir,optarg); 75 | break; 76 | case 'M': 77 | if(std::find_if(optarg,optarg+strlen(optarg), 78 | std::not1(std::ptr_fun(isdigit)))!=optarg+strlen(optarg)){ 79 | printf("Bad DB count(-M) value(must be a num in range [1,%d])\n", MAX_DBCOUNT); 80 | exit(1); 81 | } 82 | db_num=strtol(optarg,NULL,10); 83 | if(db_num<1 || db_num>MAX_DBCOUNT){ 84 | printf("Bad DB count(-M) value(must be a num in range [1,%d])\n", MAX_DBCOUNT); 85 | exit(1); 86 | } 87 | break; 88 | case '?': 89 | //if(optopt=='H' || optopt=='P' || optopt=='D' || optopt=='M') 90 | exit(1); 91 | break; 92 | default: 93 | break; 94 | } 95 | } 96 | 97 | if(daemon_flag){ 98 | if(daemon_init() == -1) { 99 | printf("can't run as daemon\n"); 100 | exit(1); 101 | } 102 | } 103 | 104 | signal(SIGINT, sig_term); 105 | signal(SIGTERM, sig_term); 106 | signal(SIGQUIT, sig_term); 107 | signal(SIGPIPE, SIG_IGN); 108 | atexit(&exit_hook); 109 | 110 | if(opt_host){ 111 | server = new RLServer(data_dir, hostaddr, port, db_num); 112 | server->start(); 113 | }else{ 114 | server = new RLServer(data_dir, "", port, db_num); 115 | server->start(); 116 | } 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /src/rl.h: -------------------------------------------------------------------------------- 1 | //-*- c++ -*- 2 | /* 3 | * author : KDr2 4 | */ 5 | 6 | #ifndef _REDIS_LEVELDB_H_INCLUDED 7 | #define _REDIS_LEVELDB_H_INCLUDED 8 | 9 | #define MAX_DBCOUNT 256 10 | #define MAX_CONNECTIONS 1024 11 | #define READ_BUFFER 81920 12 | #define VERSION_STR "1.6" 13 | 14 | #endif /* _REDIS_LEVELDB_H_INCLUDED */ 15 | -------------------------------------------------------------------------------- /src/rl_compdata.h: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_compdata.h 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #include 9 | 10 | using std::string; 11 | 12 | struct CompDataType{ 13 | enum COM_DATA_TYPE{ 14 | NORMAL = 1, // for k-v, not in use now 15 | SIZE = 2, 16 | SET = 3, 17 | ZSET = 4, 18 | HASH = 5, 19 | LIST = 6, 20 | }; 21 | }; 22 | 23 | inline string _encode_compdata_size_key(const string &name, CompDataType::COM_DATA_TYPE t){ 24 | string ret; 25 | ret.append(1, (char)CompDataType::SIZE); 26 | ret.append(1, (char)t); 27 | ret.append("size", 4); 28 | ret.append(1, (uint8_t)name.size()); 29 | ret.append(name.data(), name.size()); 30 | return ret; 31 | } 32 | 33 | inline string _encode_compdata_key(const string &cname, const string &memname, CompDataType::COM_DATA_TYPE t){ 34 | string ret; 35 | ret.append(1, (char)t); 36 | ret.append(1, (uint8_t)cname.size()); 37 | ret.append(cname.data(), cname.size()); 38 | if(memname.size()<1){ 39 | return ret; 40 | } 41 | ret.append(1, (uint8_t)memname.size()); 42 | ret.append(memname.data(), memname.size()); 43 | return ret; 44 | } 45 | 46 | inline string _encode_set_key(const string &setname, const string &memname){ 47 | return _encode_compdata_key(setname, memname, CompDataType::SET); 48 | } 49 | 50 | inline string _encode_list_key(const string &listname, const string &memname){ 51 | return _encode_compdata_key(listname, memname, CompDataType::LIST); 52 | } 53 | 54 | inline string _encode_hash_key(const string &hname, const string &memname){ 55 | return _encode_compdata_key(hname, memname, CompDataType::HASH); 56 | } 57 | 58 | inline string _decode_key(const string key){ 59 | uint8_t com_size = (uint8_t)key[1] +3; 60 | if(key.size() < com_size) return ""; 61 | return key.substr(com_size); 62 | } 63 | -------------------------------------------------------------------------------- /src/rl_connection.cpp: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_connection.cpp 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "rl_util.h" 19 | #include "rl_server.h" 20 | #include "rl_connection.h" 21 | #include "rl_request.h" 22 | 23 | #define CHECK_BUFFER(N) do{ \ 24 | if(next_idx+(N)>(read_buffer+buffered_data)){ \ 25 | next_idx=old_ni; \ 26 | memmove(read_buffer,next_idx,buffered_data-(next_idx-read_buffer)); \ 27 | buffered_data-=(next_idx-read_buffer);next_idx=read_buffer; \ 28 | read_buffer[buffered_data]=0; \ 29 | return 0;}}while(0) 30 | 31 | #define START_WRITER() do{if(!writer_started){writer_started=true;ev_io_start(server->loop, &write_watcher);}}while(0) 32 | 33 | RLConnection::RLConnection(RLServer *s, int fd): 34 | db_index(0), fd(fd), server(s), buffered_data(0),writer_started(false) 35 | { 36 | 37 | next_idx = read_buffer; 38 | ev_init(&read_watcher, RLConnection::on_readable); 39 | read_watcher.data = this; 40 | 41 | ev_init(&write_watcher, RLConnection::on_writable); 42 | write_watcher.data = this; 43 | 44 | timeout_watcher.data = this; 45 | 46 | set_nonblock(fd); 47 | open=true; 48 | current_request=NULL; 49 | transaction=NULL; 50 | server->clients_num++; 51 | //memcpy(sockaddr, &addr, addr_len); 52 | //ip = inet_ntoa(sockaddr.sin_addr); 53 | } 54 | 55 | RLConnection::~RLConnection() 56 | { 57 | if(open){ 58 | ev_io_stop(server->loop, &read_watcher); 59 | if(writer_started){ 60 | ev_io_stop(server->loop, &write_watcher); 61 | } 62 | close(fd); 63 | } 64 | server->clients_num--; 65 | } 66 | 67 | void RLConnection::start() 68 | { 69 | ev_io_set(&write_watcher, fd, EV_WRITE); 70 | 71 | ev_io_set(&read_watcher, fd, EV_READ); 72 | ev_io_start(server->loop, &read_watcher); 73 | } 74 | 75 | 76 | size_t RLConnection::get_int() { 77 | char *b = next_idx; 78 | size_t val = 0; 79 | while(*b != '\r') { 80 | val *= 10; 81 | val += (*b++ - '0'); 82 | } 83 | if(b<=(read_buffer+buffered_data-1)){ 84 | b += 2; 85 | next_idx = b; 86 | return val; 87 | } 88 | return -1; 89 | } 90 | 91 | void RLConnection::do_request(){ 92 | if(current_request && current_request->completed()){ 93 | current_request->run(); 94 | if(current_request){ 95 | delete current_request; 96 | current_request=NULL; 97 | } 98 | } 99 | } 100 | 101 | int RLConnection::do_read(){ 102 | char *old_ni=next_idx; 103 | while(next_idx<(read_buffer+buffered_data)){ 104 | old_ni=next_idx; 105 | if(!current_request)current_request=new RLRequest(this); 106 | // 1. read the arg count: 107 | if(current_request->arg_count<0){ 108 | CHECK_BUFFER(4); 109 | if(*next_idx++ != '*') return -1; 110 | current_request->arg_count=get_int(); 111 | current_request->arg_count--; 112 | old_ni=next_idx; 113 | } 114 | // 2. read the request name 115 | if(current_request->arg_count>=0 && current_request->name.empty()){ 116 | CHECK_BUFFER(4); 117 | if(*next_idx++ != '$') return -1; 118 | int len=get_int(); 119 | CHECK_BUFFER(len+2); 120 | current_request->name=std::string(next_idx,len); 121 | std::transform(current_request->name.begin(), current_request->name.end(), 122 | current_request->name.begin(), ::tolower); 123 | next_idx+=len+2; 124 | old_ni=next_idx; 125 | } 126 | // 3. read a arg 127 | if(current_request->arg_count>=0 && 128 | current_request->arg_count - current_request->args.size()>0){ 129 | CHECK_BUFFER(4); 130 | if(*next_idx++ != '$') return -1; 131 | int len=get_int(); 132 | CHECK_BUFFER(len+2); 133 | current_request->append_arg(std::string(next_idx,len)); 134 | next_idx+=len+2; 135 | old_ni=next_idx; 136 | } 137 | // 4. do the request 138 | if(current_request->arg_count>=0 && 139 | current_request->arg_count - current_request->args.size()==0){ 140 | do_request(); 141 | if(next_idx>=(read_buffer+buffered_data)){ 142 | buffered_data=0; 143 | next_idx=read_buffer; 144 | old_ni=next_idx; 145 | return 1; 146 | } 147 | } 148 | } 149 | old_ni=next_idx; 150 | CHECK_BUFFER(1); 151 | // 5. done 152 | return 1; 153 | } 154 | 155 | void RLConnection::on_readable(struct ev_loop *loop, ev_io *watcher, int revents) 156 | { 157 | RLConnection *connection = static_cast(watcher->data); 158 | size_t offset = connection->buffered_data; 159 | int left = READ_BUFFER - offset; 160 | char* recv_buffer = connection->read_buffer + offset; 161 | ssize_t recved; 162 | 163 | //assert(ev_is_active(&connection->timeout_watcher)); 164 | assert(watcher == &connection->read_watcher); 165 | 166 | // No more buffer space. 167 | if(left == 0) return; 168 | 169 | if(EV_ERROR & revents) { 170 | puts("on_readable() got error event, closing connection."); 171 | return; 172 | } 173 | 174 | recved = recv(connection->fd, recv_buffer, left, 0); 175 | 176 | if(recved == 0) { 177 | delete connection; 178 | return; 179 | } 180 | 181 | if(recved <= 0) return; 182 | 183 | recv_buffer[recved] = 0; 184 | 185 | connection->buffered_data += recved; 186 | 187 | int ret = connection->do_read(); 188 | switch(ret) { 189 | case -1: 190 | puts("bad protocol error"); 191 | // fallthrough 192 | break; 193 | case 1: 194 | connection->buffered_data = 0; 195 | break; 196 | case 0: 197 | // more data needed, leave the buffer. 198 | //TODO 199 | break; 200 | default: 201 | puts("unknown return error"); 202 | break; 203 | } 204 | 205 | /* rl_connection_reset_timeout(connection); */ 206 | 207 | return; 208 | 209 | /* error: */ 210 | /* rl_connection_schedule_close(connection); */ 211 | } 212 | 213 | 214 | int RLConnection::do_write(){ 215 | size_t nleft=write_buffer.size(); 216 | ssize_t nwritten=0; 217 | const char *ptr=write_buffer.c_str(); 218 | 219 | if ((nwritten = write(fd, ptr, nleft)) < 0) { 220 | if (nwritten < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){ 221 | return 0; 222 | }else{ 223 | perror("Write Error Msg"); 224 | writer_started=false; 225 | ev_io_stop(server->loop, &write_watcher); 226 | return -1; 227 | } 228 | } 229 | write_buffer.erase(0,nwritten); 230 | if(write_buffer.size()<=0){ 231 | writer_started=false; 232 | ev_io_stop(server->loop, &write_watcher); 233 | } 234 | return 1; 235 | } 236 | 237 | 238 | void RLConnection::on_writable(struct ev_loop *loop, ev_io *watcher, int revents) 239 | { 240 | RLConnection *connection = static_cast(watcher->data); 241 | int ret = connection->do_write(); 242 | switch(ret) { 243 | case -1: 244 | puts("write error"); 245 | break; 246 | case 0: 247 | //unwritable 248 | break; 249 | case 1: 250 | //done 251 | break; 252 | default: 253 | puts("unknown return error"); 254 | break; 255 | } 256 | } 257 | 258 | void RLConnection::write_nil(){ 259 | write_buffer+="$-1\r\n"; 260 | START_WRITER(); 261 | } 262 | 263 | void RLConnection::write_error(const char* msg){ 264 | write_buffer+="-"; 265 | write_buffer+=std::string(msg,strlen(msg)); 266 | write_buffer+="\r\n"; 267 | START_WRITER(); 268 | } 269 | 270 | void RLConnection::write_status(const char* msg){ 271 | write_buffer+="+"; 272 | write_buffer+=std::string(msg,strlen(msg)); 273 | write_buffer+="\r\n"; 274 | START_WRITER(); 275 | } 276 | 277 | void RLConnection::write_integer(const char *out, size_t out_size){ 278 | write_buffer+=":"; 279 | write_buffer+=std::string(out,out_size); 280 | write_buffer+="\r\n"; 281 | START_WRITER(); 282 | } 283 | 284 | void RLConnection::write_bulk(const char *out, size_t out_size){ 285 | char buf[32]; 286 | int count = sprintf(buf, "%ld", out_size); 287 | write_buffer+="$"; 288 | write_buffer+=std::string(buf,count); 289 | write_buffer+="\r\n"; 290 | write_buffer+=std::string(out,out_size); 291 | write_buffer+="\r\n"; 292 | START_WRITER(); 293 | } 294 | 295 | void RLConnection::write_bulk(const std::string &out){ 296 | write_bulk(out.c_str(), out.size()); 297 | START_WRITER(); 298 | } 299 | 300 | void RLConnection::write_mbulk_header(int n){ 301 | char buf[32]; 302 | int count = sprintf(buf, "%d", n); 303 | write_buffer+="*"; 304 | write_buffer+=std::string(buf,count); 305 | write_buffer+="\r\n"; 306 | START_WRITER(); 307 | } 308 | -------------------------------------------------------------------------------- /src/rl_connection.h: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_connection.h 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #ifndef _RL_CONNECTION_H_INCLUDED 9 | #define _RL_CONNECTION_H_INCLUDED 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include "rl.h" 18 | 19 | class RLServer; 20 | class RLRequest; 21 | 22 | 23 | class RLConnection{ 24 | 25 | public: 26 | bool open; 27 | int db_index; 28 | int fd; 29 | RLServer *server; 30 | RLRequest *current_request; 31 | RLRequest *transaction; 32 | 33 | char *next_idx; /* next val to handle*/ 34 | int buffered_data; /* data has been read */ 35 | char read_buffer[READ_BUFFER]; 36 | bool writer_started; 37 | std::string write_buffer; 38 | 39 | ev_io write_watcher; 40 | ev_io read_watcher; 41 | ev_timer timeout_watcher; 42 | ev_timer goodbye_watcher; 43 | 44 | /*** methods ***/ 45 | 46 | RLConnection(RLServer *s, int fd); 47 | ~RLConnection(); 48 | 49 | static void on_readable(struct ev_loop *loop, ev_io *watcher, int revents); 50 | static void on_writable(struct ev_loop *loop, ev_io *watcher, int revents); 51 | 52 | void start(); 53 | size_t get_int(); 54 | int do_read(); 55 | int do_write(); 56 | void do_request(); 57 | 58 | void write_nil(); 59 | void write_error(const char* msg); 60 | void write_status(const char* msg); 61 | void write_integer(const char *out, size_t out_size); 62 | void write_bulk(const char *out, size_t out_size); 63 | void write_bulk(const std::string &out); 64 | void write_mbulk_header(int n); 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/rl_hash.cpp: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_hash.cpp 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include 24 | 25 | #include "rl.h" 26 | #include "rl_util.h" 27 | #include "rl_server.h" 28 | #include "rl_connection.h" 29 | #include "rl_request.h" 30 | #include "rl_compdata.h" 31 | 32 | void RLRequest::rl_hget(){ 33 | if(args.size()!=2){ 34 | connection->write_error("ERR wrong number of arguments for 'hget' command"); 35 | return; 36 | } 37 | string &hname = args[0]; 38 | 39 | string out; 40 | leveldb::Status status; 41 | 42 | string key = _encode_hash_key(hname, args[1]); 43 | 44 | status = connection->server->db[connection->db_index]->Get( 45 | connection->server->read_options, key, &out); 46 | 47 | if(status.IsNotFound()) { 48 | connection->write_nil(); 49 | } else if(status.ok()){ 50 | connection->write_bulk(out.data(), out.size()); 51 | } else { 52 | connection->write_error("HGET ERROR 1"); 53 | } 54 | } 55 | 56 | 57 | 58 | void RLRequest::rl_hset(){ 59 | if(args.size()!=3){ 60 | connection->write_error("ERR wrong number of arguments for 'hset' command"); 61 | return; 62 | } 63 | string &hname = args[0]; 64 | uint32_t new_mem = 0; 65 | 66 | string sizekey = _encode_compdata_size_key(hname, CompDataType::HASH); 67 | string key = _encode_hash_key(hname, args[1]); 68 | 69 | string out; 70 | leveldb::Status status; 71 | leveldb::WriteBatch write_batch; 72 | 73 | status = connection->server->db[connection->db_index]->Get( 74 | connection->server->read_options, key, &out); 75 | 76 | // set even exists 77 | write_batch.Put(key, args[2]); 78 | 79 | if(status.IsNotFound()){ 80 | ++new_mem; 81 | }else if(!status.ok()) { 82 | connection->write_error("HSET ERROR 0"); 83 | return; 84 | } 85 | 86 | if(new_mem == 0){ 87 | status = connection->server->db[connection->db_index]->Write( 88 | connection->server->write_options, &write_batch); 89 | if(!status.ok()){ 90 | connection->write_error("HSET ERROR 1"); 91 | }else{ 92 | connection->write_integer("0", 1); 93 | } 94 | return; 95 | } 96 | // update size! 97 | status = connection->server->db[connection->db_index]->Get( 98 | connection->server->read_options, sizekey, &out); 99 | 100 | char *str_oldv=NULL; 101 | if(status.IsNotFound()){ 102 | str_oldv = strdup("0"); 103 | }else if(status.ok()){ 104 | str_oldv=(char*)malloc(out.size()+1); 105 | memcpy(str_oldv, out.data(), out.size()); 106 | str_oldv[out.size()]=0; 107 | }else{ 108 | connection->write_error("HSET ERROR 2"); 109 | return; 110 | } 111 | 112 | mpz_t delta; 113 | mpz_init(delta); 114 | mpz_set_ui(delta, new_mem); 115 | 116 | mpz_t old_v; 117 | mpz_init(old_v); 118 | mpz_set_str(old_v, str_oldv, 10); 119 | free(str_oldv); 120 | mpz_add(old_v, old_v, delta); 121 | char *str_newv=mpz_get_str(NULL, 10, old_v); 122 | char *str_delta=mpz_get_str(NULL, 10, delta); 123 | mpz_clear(delta); 124 | mpz_clear(old_v); 125 | 126 | write_batch.Put(sizekey, str_newv); 127 | status = connection->server->db[connection->db_index]->Write( 128 | connection->server->write_options, &write_batch); 129 | 130 | if(!status.ok()) { 131 | connection->write_error("HSET ERROR 3"); 132 | } else { 133 | connection->write_integer(str_delta, strlen(str_delta)); 134 | } 135 | free(str_newv); 136 | free(str_delta); 137 | } 138 | 139 | 140 | void RLRequest::rl_hsetnx(){ 141 | if(args.size()!=3){ 142 | connection->write_error("ERR wrong number of arguments for 'hsetnx' command"); 143 | return; 144 | } 145 | string &hname = args[0]; 146 | uint32_t new_mem = 0; 147 | 148 | string out; 149 | leveldb::Status status; 150 | leveldb::WriteBatch write_batch; 151 | 152 | string sizekey = _encode_compdata_size_key(hname, CompDataType::HASH); 153 | string key = _encode_hash_key(hname, args[1]); 154 | 155 | status = connection->server->db[connection->db_index]->Get( 156 | connection->server->read_options, key, &out); 157 | if(!status.ok() && !status.IsNotFound()) { 158 | connection->write_error("HSETNX ERROR 1"); 159 | return; 160 | } 161 | 162 | if(status.IsNotFound()) { 163 | // set value 164 | write_batch.Put(key, args[2]); 165 | ++new_mem; 166 | } 167 | 168 | if(new_mem == 0){ 169 | connection->write_integer("0", 1); 170 | return; 171 | } 172 | // update size! 173 | status = connection->server->db[connection->db_index]->Get( 174 | connection->server->read_options, sizekey, &out); 175 | 176 | char *str_oldv=NULL; 177 | if(status.IsNotFound()){ 178 | str_oldv = strdup("0"); 179 | }else if(status.ok()){ 180 | str_oldv=(char*)malloc(out.size()+1); 181 | memcpy(str_oldv, out.data(), out.size()); 182 | str_oldv[out.size()]=0; 183 | }else { 184 | connection->write_error("HSETNX ERROR 2"); 185 | } 186 | 187 | mpz_t delta; 188 | mpz_init(delta); 189 | mpz_set_ui(delta, new_mem); 190 | 191 | mpz_t old_v; 192 | mpz_init(old_v); 193 | mpz_set_str(old_v, str_oldv, 10); 194 | free(str_oldv); 195 | mpz_add(old_v, old_v, delta); 196 | char *str_newv=mpz_get_str(NULL, 10, old_v); 197 | char *str_delta=mpz_get_str(NULL, 10, delta); 198 | mpz_clear(delta); 199 | mpz_clear(old_v); 200 | 201 | write_batch.Put(sizekey, str_newv); 202 | status = connection->server->db[connection->db_index]->Write( 203 | connection->server->write_options, &write_batch); 204 | 205 | 206 | if(!status.ok()) { 207 | connection->write_error("HSETNX ERROR 3"); 208 | } else { 209 | connection->write_integer(str_delta, strlen(str_delta)); 210 | } 211 | free(str_newv); 212 | free(str_delta); 213 | } 214 | 215 | void RLRequest::rl_hdel(){ 216 | if(args.size()<2){ 217 | connection->write_error("ERR wrong number of arguments for 'hdel' command"); 218 | return; 219 | } 220 | 221 | string &hname = args[0]; 222 | string sizekey = _encode_compdata_size_key(hname, CompDataType::HASH); 223 | uint32_t del_mem = 0; 224 | 225 | string out; 226 | leveldb::Status status; 227 | 228 | for(uint32_t i=1; iserver->db[connection->db_index]->Get( 232 | connection->server->read_options, key, &out); 233 | 234 | if(status.IsNotFound()) { 235 | // not exist 236 | } else if(status.ok()) { 237 | // delete value 238 | status = connection->server->db[connection->db_index]->Delete( 239 | connection->server->write_options, key); 240 | if(status.ok()) { 241 | ++del_mem; 242 | } 243 | } 244 | } 245 | if(del_mem == 0){ 246 | connection->write_integer("0", 1); 247 | return; 248 | } 249 | // update size! 250 | status = connection->server->db[connection->db_index]->Get( 251 | connection->server->read_options, sizekey, &out); 252 | 253 | char *str_oldv=NULL; 254 | if(status.ok()){ 255 | str_oldv=(char*)malloc(out.size()+1); 256 | memcpy(str_oldv, out.data(), out.size()); 257 | str_oldv[out.size()]=0; 258 | }else{ 259 | connection->write_error("HDEL ERROR 1"); 260 | return; 261 | } 262 | 263 | mpz_t delta; 264 | mpz_init(delta); 265 | mpz_set_ui(delta, del_mem); 266 | 267 | mpz_t old_v; 268 | mpz_init(old_v); 269 | mpz_set_str(old_v, str_oldv, 10); 270 | free(str_oldv); 271 | mpz_sub(old_v, old_v, delta); 272 | char *str_newv=mpz_get_str(NULL, 10, old_v); 273 | char *str_delta=mpz_get_str(NULL, 10, delta); 274 | mpz_clear(delta); 275 | mpz_clear(old_v); 276 | 277 | status = connection->server->db[connection->db_index]->Put( 278 | connection->server->write_options, sizekey, str_newv); 279 | 280 | if(!status.ok()) { 281 | connection->write_error("HDEL ERROR 2"); 282 | } else { 283 | connection->write_integer(str_delta, strlen(str_delta)); 284 | } 285 | 286 | free(str_newv); 287 | free(str_delta); 288 | } 289 | 290 | void RLRequest::rl_hexists(){ 291 | if(args.size()<2){ 292 | connection->write_error("ERR wrong number of arguments for 'hexists' command"); 293 | return; 294 | } 295 | 296 | string &hname = args[0]; 297 | string key = _encode_hash_key(hname, args[1]); 298 | 299 | string out; 300 | leveldb::Status status; 301 | 302 | status = connection->server->db[connection->db_index]->Get( 303 | connection->server->read_options, key, &out); 304 | 305 | if(status.IsNotFound()) { 306 | // not a member 307 | connection->write_integer("0", 1); 308 | } else if(status.ok()) { 309 | // is a member 310 | connection->write_integer("1", 1); 311 | } else { 312 | connection->write_error("HEXISTS ERROR 1"); 313 | } 314 | } 315 | 316 | void RLRequest::rl_hgetall(){ 317 | if(args.size()!=1){ 318 | connection->write_error("ERR wrong number of arguments for 'hgetall' command"); 319 | return; 320 | } 321 | 322 | std::vector kvs; 323 | leveldb::Slice key, val; 324 | 325 | string hash_begin = _encode_hash_key(args[0], ""); 326 | leveldb::Iterator *kit = connection->server->db[connection->db_index]->NewIterator( 327 | connection->server->read_options); 328 | 329 | kit->Seek(hash_begin); 330 | 331 | while(kit->Valid()) { 332 | key = kit->key(); 333 | val = kit->value(); 334 | if(strncmp(hash_begin.data(), key.data(), hash_begin.size()) == 0){ 335 | const string &k = _decode_key(key.ToString()); 336 | if(k.size()>0){ 337 | kvs.push_back(k); 338 | kvs.push_back(val.ToString()); 339 | } 340 | kit->Next(); 341 | }else{ 342 | break; 343 | } 344 | } 345 | 346 | delete kit; 347 | 348 | connection->write_mbulk_header(kvs.size()); 349 | std::vector::iterator it=kvs.begin(); 350 | while(it!=kvs.end())connection->write_bulk(*it++); 351 | } 352 | 353 | void RLRequest::rl_hkeys(){ 354 | if(args.size()!=1){ 355 | connection->write_error("ERR wrong number of arguments for 'hkeys' command"); 356 | return; 357 | } 358 | 359 | std::vector keys; 360 | leveldb::Slice key; 361 | 362 | string hash_begin = _encode_hash_key(args[0], ""); 363 | leveldb::Iterator *kit = connection->server->db[connection->db_index]->NewIterator( 364 | connection->server->read_options); 365 | 366 | kit->Seek(hash_begin); 367 | 368 | while(kit->Valid()) { 369 | key = kit->key(); 370 | if(strncmp(hash_begin.data(), key.data(), hash_begin.size()) == 0){ 371 | const string &k = _decode_key(key.ToString()); 372 | if(k.size()>0){ 373 | keys.push_back(k); 374 | } 375 | kit->Next(); 376 | }else{ 377 | break; 378 | } 379 | } 380 | delete kit; 381 | 382 | connection->write_mbulk_header(keys.size()); 383 | std::vector::iterator it=keys.begin(); 384 | while(it!=keys.end())connection->write_bulk(*it++); 385 | } 386 | 387 | void RLRequest::rl_hvals(){ 388 | if(args.size()!=1){ 389 | connection->write_error("ERR wrong number of arguments for 'hvals' command"); 390 | return; 391 | } 392 | 393 | std::vector vals; 394 | leveldb::Slice key, val; 395 | 396 | string hash_begin = _encode_hash_key(args[0], ""); 397 | leveldb::Iterator *kit = connection->server->db[connection->db_index]->NewIterator( 398 | connection->server->read_options); 399 | kit->Seek(hash_begin); 400 | 401 | while(kit->Valid()) { 402 | key = kit->key(); 403 | val = kit->value(); 404 | if(strncmp(hash_begin.data(), key.data(), hash_begin.size()) == 0){ 405 | //const string &k = _decode_key(key.ToString()); 406 | if(key.size()>0){ 407 | //vals.push_back(k); 408 | vals.push_back(val.ToString()); 409 | } 410 | kit->Next(); 411 | }else{ 412 | break; 413 | } 414 | } 415 | delete kit; 416 | 417 | connection->write_mbulk_header(vals.size()); 418 | std::vector::iterator it=vals.begin(); 419 | while(it!=vals.end())connection->write_bulk(*it++); 420 | } 421 | 422 | void RLRequest::rl_hlen(){ 423 | if(args.size()!=1){ 424 | connection->write_error("ERR wrong number of arguments for 'hlen' command"); 425 | return; 426 | } 427 | 428 | string sizekey = _encode_compdata_size_key(args[0], CompDataType::HASH); 429 | 430 | string out; 431 | leveldb::Status status = connection->server->db[connection->db_index]->Get( 432 | connection->server->read_options, sizekey, &out); 433 | 434 | if(!status.ok()) { 435 | connection->write_error("HLEN ERROR 1"); 436 | } else { 437 | connection->write_integer(out.data(), out.size()); 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /src/rl_kv.cpp: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_set.cpp 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include 24 | 25 | #include "rl.h" 26 | #include "rl_util.h" 27 | #include "rl_server.h" 28 | #include "rl_connection.h" 29 | #include "rl_request.h" 30 | 31 | 32 | void RLRequest::rl_incr(){ 33 | 34 | if(args.size()!=1){ 35 | connection->write_error("ERR wrong number of arguments for 'incr' command"); 36 | return; 37 | } 38 | 39 | std::string out; 40 | leveldb::Status status; 41 | 42 | status = connection->server->db[connection->db_index]->Get(connection->server->read_options, 43 | args[0], &out); 44 | 45 | 46 | char *str_oldv=NULL; 47 | if(status.IsNotFound()){ 48 | str_oldv = strdup("0"); 49 | }else if(status.ok()){ 50 | str_oldv=(char*)malloc(out.size()+1); 51 | memcpy(str_oldv,out.data(),out.size()); 52 | str_oldv[out.size()]=0; 53 | }else{ 54 | connection->write_error("INCR ERROR 1"); 55 | return; 56 | } 57 | 58 | mpz_t old_v; 59 | mpz_init(old_v); 60 | mpz_set_str(old_v,str_oldv,10); 61 | free(str_oldv); 62 | mpz_add_ui(old_v,old_v,1); 63 | char *str_newv=mpz_get_str(NULL,10,old_v); 64 | mpz_clear(old_v); 65 | 66 | status = connection->server->db[connection->db_index]->Put(connection->server->write_options, 67 | args[0], leveldb::Slice(str_newv, strlen(str_newv))); 68 | 69 | if(!status.ok()) { 70 | connection->write_error("INCR ERROR 2"); 71 | } else { 72 | connection->write_integer(str_newv, strlen(str_newv)); 73 | } 74 | free(str_newv); 75 | } 76 | 77 | 78 | void RLRequest::rl_incrby(){ 79 | 80 | if(args.size()!=2){ 81 | connection->write_error("ERR wrong number of arguments for 'incrby' command"); 82 | return; 83 | } 84 | 85 | std::string out; 86 | leveldb::Status status; 87 | status = connection->server->db[connection->db_index]->Get(connection->server->read_options, 88 | args[0], &out); 89 | 90 | mpz_t delta; 91 | mpz_init(delta); 92 | mpz_set_str(delta,args[1].c_str(),10); 93 | 94 | char *str_oldv=NULL; 95 | if(status.IsNotFound()){ 96 | str_oldv = strdup("0"); 97 | }else if(status.ok()){ 98 | str_oldv=(char*)malloc(out.size()+1); 99 | memcpy(str_oldv,out.data(),out.size()); 100 | str_oldv[out.size()]=0; 101 | }else{ 102 | connection->write_error("INCRBY ERROR 1"); 103 | return; 104 | } 105 | 106 | mpz_t old_v; 107 | mpz_init(old_v); 108 | mpz_set_str(old_v,str_oldv,10); 109 | free(str_oldv); 110 | mpz_add(old_v,old_v,delta); 111 | char *str_newv=mpz_get_str(NULL,10,old_v); 112 | mpz_clear(delta); 113 | mpz_clear(old_v); 114 | 115 | status = connection->server->db[connection->db_index]->Put(connection->server->write_options, 116 | args[0], leveldb::Slice(str_newv, strlen(str_newv))); 117 | 118 | if(!status.ok()) { 119 | connection->write_error("INCRBY ERROR 2"); 120 | }else{ 121 | connection->write_integer(str_newv, strlen(str_newv)); 122 | } 123 | free(str_newv); 124 | } 125 | 126 | 127 | void RLRequest::rl_get(){ 128 | 129 | if(args.size()!=1){ 130 | connection->write_error("ERR wrong number of arguments for 'get' command"); 131 | return; 132 | } 133 | 134 | std::string out; 135 | leveldb::Status status; 136 | status = connection->server->db[connection->db_index]->Get(connection->server->read_options, 137 | args[0], &out); 138 | 139 | if(status.IsNotFound()) { 140 | connection->write_nil(); 141 | }else if(status.ok()) { 142 | connection->write_bulk(out); 143 | } else { 144 | connection->write_error("GET ERROR 1"); 145 | } 146 | } 147 | 148 | 149 | void RLRequest::rl_set(){ 150 | 151 | if(args.size()!=2){ 152 | connection->write_error("ERR wrong number of arguments for 'set' command"); 153 | return; 154 | } 155 | 156 | leveldb::Status status; 157 | status = connection->server->db[connection->db_index]->Put(connection->server->write_options, 158 | args[0], args[1]); 159 | 160 | if(!status.ok()) { 161 | connection->write_error("SET ERROR 1"); 162 | } else { 163 | connection->write_status("OK"); 164 | } 165 | } 166 | 167 | void RLRequest::rl_del(){ 168 | 169 | if(args.size()!=1){ 170 | connection->write_error("ERR wrong number of arguments for 'del' command"); 171 | return; 172 | } 173 | 174 | std::string out; 175 | leveldb::Status status; 176 | 177 | status = connection->server->db[connection->db_index]->Get(connection->server->read_options, 178 | args[0], &out); 179 | 180 | if(status.IsNotFound()) { 181 | connection->write_integer("0", 1); 182 | } else if(status.ok()) { 183 | status = connection->server->db[connection->db_index]->Delete(connection->server->write_options, args[0]); 184 | if(!status.ok()){ 185 | connection->write_error("DELETE ERROR"); 186 | }else{ 187 | connection->write_integer("1", 1); 188 | } 189 | } 190 | } 191 | 192 | 193 | void RLRequest::rl_mget(){ 194 | 195 | if(args.size()<1){ 196 | connection->write_error("ERR wrong number of arguments for 'mget' command"); 197 | return; 198 | } 199 | 200 | std::string out; 201 | leveldb::Status status; 202 | 203 | connection->write_mbulk_header(args.size()); 204 | 205 | std::vector::iterator it=args.begin(); 206 | 207 | for(;it!=args.end();it++){ 208 | status = connection->server->db[connection->db_index]->Get(connection->server->read_options, 209 | *it, &out); 210 | 211 | if(!status.ok()) { 212 | connection->write_nil(); 213 | } else { 214 | connection->write_bulk(out); 215 | } 216 | } 217 | } 218 | 219 | 220 | void RLRequest::rl_mset(){ 221 | 222 | if(args.size()<2 || args.size()%2!=0){ 223 | connection->write_error("ERR wrong number of arguments for 'mset' command"); 224 | return; 225 | } 226 | 227 | leveldb::WriteBatch write_batch; 228 | 229 | for(uint32_t i=0;iserver->db[connection->db_index]->Write( 234 | connection->server->write_options, &write_batch); 235 | if(!status.ok()) { 236 | connection->write_error("MSET ERROR 1"); 237 | } else { 238 | connection->write_status("OK"); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/rl_list.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * rl_list.cpp 3 | * 4 | * Created on: 2013-5-19 5 | * Author: imessi 6 | * Author: KDr2 7 | */ 8 | 9 | #define __STDC_FORMAT_MACROS 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "rl.h" 20 | #include "rl_util.h" 21 | #include "rl_server.h" 22 | #include "rl_connection.h" 23 | #include "rl_request.h" 24 | #include "rl_compdata.h" 25 | 26 | #define RIGHT_FLAG "right" 27 | #define LEFT_FLAG "left" 28 | 29 | void RLRequest::rl_lpush(){ 30 | if(args.size()<2){ 31 | connection->write_error("ERR wrong number of arguments for 'lpush' command"); 32 | return; 33 | } 34 | 35 | string &lname = args[0]; 36 | int64_t flag_index; 37 | char flag_index_s[32]; 38 | string flag_key = _encode_list_key(lname, LEFT_FLAG); 39 | size_t args_size = args.size(); 40 | 41 | string out; 42 | leveldb::Status status; 43 | leveldb::WriteBatch write_batch; 44 | 45 | //get list left 46 | status = connection->server->db[connection->db_index]->Get( 47 | connection->server->read_options, flag_key, &out); 48 | 49 | if(status.IsNotFound()) { 50 | string key = _encode_list_key(lname, RIGHT_FLAG); 51 | write_batch.Put(key, "0"); 52 | flag_index = 0; 53 | } else if(status.ok()){ 54 | char *temp = (char *)malloc(out.size() + 1); 55 | memcpy(temp, out.data(), out.size()); 56 | temp[out.size()] = 0; 57 | flag_index = atoll(temp) - 1; 58 | free(temp); 59 | } else { 60 | connection->write_error("LPUSH ERROR 1"); 61 | return; 62 | } 63 | 64 | // push list members 65 | for (uint32_t i = 1; i < args_size; i++){ 66 | sprintf(flag_index_s, "%"PRId64, flag_index); 67 | string key = _encode_list_key(lname, flag_index_s); 68 | write_batch.Put(key, args[i]); 69 | 70 | if(i + 1 < args_size) { 71 | flag_index--; 72 | } 73 | } 74 | 75 | // update left flag 76 | sprintf(flag_index_s, "%"PRId64, flag_index); 77 | write_batch.Put(flag_key, flag_index_s); 78 | 79 | status = connection->server->db[connection->db_index]->Write( 80 | connection->server->write_options, &write_batch); 81 | 82 | if (!status.ok()) { 83 | connection->write_error("LPUSH ERROR 2"); 84 | } else { 85 | sprintf(flag_index_s, "%"PRId64, (args_size - 1)); 86 | connection->write_integer(flag_index_s, strlen(flag_index_s)); 87 | } 88 | } 89 | 90 | void RLRequest::rl_rpush(){ 91 | if(args.size() < 2){ 92 | connection->write_error("ERR wrong number of arguments for 'rpush' command"); 93 | return; 94 | } 95 | 96 | string &lname = args[0]; 97 | int64_t flag_index; 98 | char flag_index_s[32]; 99 | string flag_key = _encode_list_key(lname, RIGHT_FLAG); 100 | size_t args_size = args.size(); 101 | 102 | string out; 103 | leveldb::Status status; 104 | leveldb::WriteBatch write_batch; 105 | 106 | //get list left 107 | status = connection->server->db[connection->db_index]->Get( 108 | connection->server->read_options, flag_key, &out); 109 | 110 | if(status.IsNotFound()) { 111 | string key = _encode_list_key(lname, LEFT_FLAG); 112 | write_batch.Put(key, "0"); 113 | flag_index = 0; 114 | } else if(status.ok()) { 115 | char *temp = (char *)malloc(out.size() + 1); 116 | memcpy(temp, out.data(), out.size()); 117 | temp[out.size()] = 0; 118 | flag_index = atoll(temp) + 1; 119 | free(temp); 120 | } else { 121 | connection->write_error("RPUSH ERROR 1"); 122 | return; 123 | } 124 | 125 | // push list members 126 | for (uint32_t i = 1; i < args_size; i++){ 127 | sprintf(flag_index_s, "%"PRId64, flag_index); 128 | string key = _encode_list_key(lname, flag_index_s); 129 | write_batch.Put(key, args[i]); 130 | if(i + 1 < args_size) { 131 | flag_index++; 132 | } 133 | } 134 | 135 | // update right flag 136 | sprintf(flag_index_s, "%"PRId64, flag_index); 137 | write_batch.Put(flag_key, flag_index_s); 138 | 139 | status = connection->server->db[connection->db_index]->Write( 140 | connection->server->write_options, &write_batch); 141 | 142 | if (!status.ok()) { 143 | connection->write_error("RPUSH ERROR 2"); 144 | } else { 145 | sprintf(flag_index_s, "%lu", (unsigned long)(args_size - 1)); 146 | connection->write_integer(flag_index_s, strlen(flag_index_s)); 147 | } 148 | } 149 | 150 | void RLRequest::rl_lpop(){ 151 | if(args.size() != 1){ 152 | connection->write_error("ERR wrong number of arguments for 'lpop' command"); 153 | return; 154 | } 155 | 156 | string &lname = args[0]; 157 | int64_t flag_index; 158 | char flag_index_s[32]; 159 | string flag_key = _encode_list_key(lname, LEFT_FLAG); 160 | 161 | string key; 162 | string out; 163 | leveldb::Status status; 164 | 165 | //get list left index 166 | status = connection->server->db[connection->db_index]->Get( 167 | connection->server->read_options, flag_key, &out); 168 | 169 | if(status.IsNotFound()) { 170 | connection->write_nil(); 171 | return; 172 | } else if(status.ok()) { 173 | char *temp = (char*)malloc(out.size() + 1); 174 | memcpy(temp, out.data(), out.size()); 175 | temp[out.size()] = 0; 176 | flag_index = atoll(temp); 177 | free(temp); 178 | } else { 179 | connection->write_error("LPOP ERROR 1"); 180 | return; 181 | } 182 | 183 | sprintf(flag_index_s, "%"PRId64, flag_index); 184 | key = _encode_list_key(lname, flag_index_s); 185 | 186 | status = connection->server->db[connection->db_index]->Get( 187 | connection->server->read_options, key, &out); 188 | 189 | if(!status.ok()) { 190 | connection->write_error("LPOP ERROR 2"); 191 | return; 192 | } 193 | 194 | leveldb::WriteBatch write_batch; 195 | // delete this member and update left index 196 | if(status.ok()) { 197 | string _out; 198 | write_batch.Delete(key); 199 | 200 | sprintf(flag_index_s, "%"PRId64, flag_index + 1); 201 | key = _encode_list_key(lname, flag_index_s); 202 | 203 | status = connection->server->db[connection->db_index]->Get( 204 | connection->server->read_options, key, &_out); 205 | 206 | if (status.ok()) { 207 | write_batch.Put(flag_key, flag_index_s); 208 | } else { 209 | // clear the list 210 | write_batch.Delete(flag_key); 211 | flag_key = _encode_list_key(lname, RIGHT_FLAG); 212 | write_batch.Delete(flag_key); 213 | } 214 | 215 | status = connection->server->db[connection->db_index]->Write( 216 | connection->server->write_options, &write_batch); 217 | if (!status.ok()) { 218 | connection->write_error("LPOP ERROR 3"); 219 | } else { 220 | connection->write_bulk(out.data(), out.size()); 221 | } 222 | 223 | } else { 224 | connection->write_error("LPOP ERROR 4"); 225 | } 226 | } 227 | 228 | void RLRequest::rl_rpop(){ 229 | if(args.size() != 1){ 230 | connection->write_error("ERR wrong number of arguments for 'rpop' command"); 231 | return; 232 | } 233 | 234 | string &lname = args[0]; 235 | int64_t flag_index; 236 | char flag_index_s[32]; 237 | string flag_key = _encode_list_key(lname, RIGHT_FLAG); 238 | string key; 239 | 240 | string out; 241 | leveldb::Status status; 242 | 243 | //get list right index 244 | status = connection->server->db[connection->db_index]->Get( 245 | connection->server->read_options, flag_key, &out); 246 | 247 | if(status.IsNotFound()) { 248 | connection->write_nil(); 249 | return; 250 | } else if(status.ok()) { 251 | char *temp = (char*)malloc(out.size() + 1); 252 | memcpy(temp, out.data(), out.size()); 253 | temp[out.size()] = 0; 254 | flag_index = atoll(temp); 255 | free(temp); 256 | } else { 257 | connection->write_error("RPOP ERROR 1"); 258 | } 259 | 260 | sprintf(flag_index_s, "%"PRId64, flag_index); 261 | key = _encode_list_key(lname, flag_index_s); 262 | status = connection->server->db[connection->db_index]->Get( 263 | connection->server->read_options, key, &out); 264 | 265 | if(!status.ok()) { 266 | connection->write_error("RPOP ERROR 2"); 267 | return; 268 | } 269 | 270 | leveldb::WriteBatch write_batch; 271 | 272 | // delete this member and update right index 273 | if(status.ok()) { 274 | string _out; 275 | write_batch.Delete(key); 276 | 277 | sprintf(flag_index_s, "%"PRId64, flag_index - 1); 278 | key = _encode_list_key(lname, flag_index_s); 279 | status = connection->server->db[connection->db_index]->Get( 280 | connection->server->read_options, key, &_out); 281 | 282 | if (status.ok()) { 283 | write_batch.Put(flag_key, flag_index_s); 284 | } else { 285 | write_batch.Delete(flag_key); 286 | flag_key = _encode_list_key(lname, LEFT_FLAG); 287 | write_batch.Delete(flag_key); 288 | } 289 | 290 | status = connection->server->db[connection->db_index]->Write( 291 | connection->server->write_options, &write_batch); 292 | if (!status.ok()) { 293 | connection->write_error("RPOP ERROR 3"); 294 | } else { 295 | connection->write_bulk(out.data(), out.size()); 296 | } 297 | 298 | } else { 299 | connection->write_error("RPOP ERROR 4"); 300 | } 301 | } 302 | 303 | 304 | void RLRequest::rl_llen(){ 305 | if(args.size() != 1){ 306 | connection->write_error("ERR wrong number of arguments for 'llen' command"); 307 | return; 308 | } 309 | 310 | string &lname = args[0]; 311 | int64_t right_index = 0; 312 | int64_t left_index = 0; 313 | char len[32]; 314 | 315 | string out; 316 | leveldb::Status status; 317 | 318 | string flag_key = _encode_list_key(lname, RIGHT_FLAG); 319 | 320 | status = connection->server->db[connection->db_index]->Get( 321 | connection->server->read_options, flag_key, &out); 322 | 323 | if(status.IsNotFound()) { 324 | connection->write_integer("0", 1); 325 | return; 326 | } else if(status.ok()) { 327 | char *temp = (char*)malloc(out.size() + 1); 328 | memcpy(temp, out.data(), out.size()); 329 | temp[out.size()] = 0; 330 | right_index = atoll(temp); 331 | free(temp); 332 | 333 | flag_key = _encode_list_key(lname, LEFT_FLAG); 334 | status = connection->server->db[connection->db_index]->Get( 335 | connection->server->read_options, flag_key, &out); 336 | 337 | if (!status.ok()) { 338 | connection->write_error("LLEN ERROR 2"); 339 | return; 340 | } else { 341 | char *temp = (char*)malloc(out.size() + 1); 342 | memcpy(temp, out.data(), out.size()); 343 | temp[out.size()] = 0; 344 | left_index = atoll(temp); 345 | free(temp); 346 | } 347 | } else { 348 | connection->write_error("LLEN ERROR 1"); 349 | return; 350 | } 351 | 352 | sprintf(len, "%"PRId64, right_index - left_index + 1); 353 | connection->write_integer(len, strlen(len)); 354 | } 355 | -------------------------------------------------------------------------------- /src/rl_request.cpp: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_request.cpp 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | 22 | #include 23 | 24 | #include "rl.h" 25 | #include "rl_util.h" 26 | #include "rl_server.h" 27 | #include "rl_connection.h" 28 | #include "rl_request.h" 29 | #include "rl_compdata.h" 30 | 31 | std::map RLRequest::cmd_map; 32 | 33 | void RLRequest::init_cmd_map() 34 | { 35 | /* sys commands */ 36 | RLRequest::cmd_map["info"] = &RLRequest::rl_info; 37 | RLRequest::cmd_map["keys"] = &RLRequest::rl_keys; 38 | RLRequest::cmd_map["select"] = &RLRequest::rl_select; 39 | RLRequest::cmd_map["shutdown"] = &RLRequest::rl_shutdown; 40 | RLRequest::cmd_map["multi"] = &RLRequest::rl_multi; 41 | RLRequest::cmd_map["exec"] = &RLRequest::rl_exec; 42 | RLRequest::cmd_map["discard"] = &RLRequest::rl_discard; 43 | 44 | /* kv commands */ 45 | RLRequest::cmd_map["incr"] = &RLRequest::rl_incr; 46 | RLRequest::cmd_map["incrby"] = &RLRequest::rl_incrby; 47 | RLRequest::cmd_map["get"] = &RLRequest::rl_get; 48 | RLRequest::cmd_map["set"] = &RLRequest::rl_set; 49 | RLRequest::cmd_map["del"] = &RLRequest::rl_del; 50 | RLRequest::cmd_map["mget"] = &RLRequest::rl_mget; 51 | RLRequest::cmd_map["mset"] = &RLRequest::rl_mset; 52 | 53 | /* set commands */ 54 | RLRequest::cmd_map["sadd"] = &RLRequest::rl_sadd; 55 | RLRequest::cmd_map["srem"] = &RLRequest::rl_srem; 56 | RLRequest::cmd_map["scard"] = &RLRequest::rl_scard; 57 | RLRequest::cmd_map["smembers"] = &RLRequest::rl_smembers; 58 | RLRequest::cmd_map["sismember"] = &RLRequest::rl_sismember; 59 | 60 | /* hash commands */ 61 | RLRequest::cmd_map["hget"] = &RLRequest::rl_hget; 62 | RLRequest::cmd_map["hset"] = &RLRequest::rl_hset; 63 | RLRequest::cmd_map["hsetnx"] = &RLRequest::rl_hsetnx; 64 | RLRequest::cmd_map["hdel"] = &RLRequest::rl_hdel; 65 | RLRequest::cmd_map["hexists"] = &RLRequest::rl_hexists; 66 | RLRequest::cmd_map["hgetall"] = &RLRequest::rl_hgetall; 67 | RLRequest::cmd_map["hkeys"] = &RLRequest::rl_hkeys; 68 | RLRequest::cmd_map["hvals"] = &RLRequest::rl_hvals; 69 | RLRequest::cmd_map["hlen"] = &RLRequest::rl_hlen; 70 | 71 | /* list commands */ 72 | RLRequest::cmd_map["lpush"] = &RLRequest::rl_lpush; 73 | RLRequest::cmd_map["lpop"] = &RLRequest::rl_lpop; 74 | RLRequest::cmd_map["rpush"] = &RLRequest::rl_rpush; 75 | RLRequest::cmd_map["rpop"] = &RLRequest::rl_rpop; 76 | RLRequest::cmd_map["llen"] = &RLRequest::rl_llen; 77 | } 78 | 79 | 80 | RLRequest::RLRequest(RLConnection *c): 81 | connection(c), arg_count(-1), name("") 82 | { 83 | } 84 | 85 | RLRequest::~RLRequest() 86 | { 87 | std::vector::iterator it=subrequest.begin(); 88 | for(;it!=subrequest.end();it++) 89 | delete (*it); 90 | } 91 | 92 | 93 | void RLRequest::append_arg(std::string arg) 94 | { 95 | args.push_back(arg); 96 | } 97 | 98 | void RLRequest::_run() 99 | { 100 | 101 | #ifdef DEBUG 102 | printf("Request Name:%s\n",name.c_str()); 103 | for(std::vector::iterator it=args.begin();it!=args.end();it++) 104 | printf("Request arg:%s\n",it->c_str()); 105 | #endif 106 | 107 | std::map::iterator it=cmd_map.find(name); 108 | if(it!=cmd_map.end()){ 109 | (this->*(it->second))(); 110 | }else{ 111 | rl_dummy(); 112 | } 113 | 114 | } 115 | 116 | void RLRequest::run() 117 | { 118 | if(name=="multi"){ 119 | _run(); 120 | return; 121 | } 122 | if(name=="exec"||name=="discard"){ 123 | 124 | #ifdef DEBUG 125 | printf("Subrequest Number in this Transaction:%ld\n", 126 | connection->transaction->subrequest.size()); 127 | #endif 128 | 129 | if(connection->transaction){ 130 | _run(); 131 | }else{ 132 | connection->write_error("ERR EXEC/DISCARD without MULTI"); 133 | } 134 | return; 135 | } 136 | 137 | if(connection->transaction){ 138 | connection->transaction->subrequest.push_back(this); 139 | connection->current_request=NULL; 140 | connection->write_status("QUEUED"); 141 | }else{ 142 | _run(); 143 | } 144 | } 145 | 146 | void RLRequest::rl_dummy(){ 147 | connection->write_error("ERR unknown command"); 148 | } 149 | 150 | 151 | void RLRequest::rl_select(){ 152 | if(connection->server->db_num < 1){ 153 | connection->write_error("ERR redis-leveldb is running in single-db mode"); 154 | return; 155 | } 156 | if(args.size()!=1){ 157 | connection->write_error("ERR wrong number of arguments for 'select' command"); 158 | return; 159 | } 160 | if(std::find_if(args[0].begin(),args[0].end(), 161 | std::not1(std::ptr_fun(isdigit)))!=args[0].end()){ 162 | connection->write_error("ERR argument for 'select' must be a number"); 163 | return; 164 | } 165 | int target=strtol(args[0].c_str(),NULL,10); 166 | if(target<0||target >= connection->server->db_num){ 167 | connection->write_error("ERR invalid DB index"); 168 | return; 169 | } 170 | connection->db_index=target; 171 | connection->write_status("OK"); 172 | } 173 | 174 | void RLRequest::rl_multi(){ 175 | if(connection->transaction){ 176 | connection->write_error("ERR MULTI calls can not be nested"); 177 | return; 178 | } 179 | connection->transaction=this; 180 | connection->current_request=NULL; 181 | connection->write_status("OK"); 182 | } 183 | 184 | void RLRequest::rl_exec(){ 185 | if(!connection->transaction){ 186 | connection->write_error("ERR EXEC without MULTI"); 187 | return; 188 | } 189 | 190 | std::vector tsub=connection->transaction->subrequest; 191 | 192 | connection->write_mbulk_header(tsub.size()); 193 | std::for_each(tsub.begin(),tsub.end(),std::mem_fun(&RLRequest::_run)); 194 | delete connection->transaction; 195 | connection->transaction=NULL; 196 | } 197 | 198 | void RLRequest::rl_discard(){ 199 | if(!connection->transaction){ 200 | connection->write_error("ERR DISCARD without MULTI"); 201 | return; 202 | } 203 | 204 | delete connection->transaction; 205 | connection->transaction=NULL; 206 | connection->write_status("OK"); 207 | } 208 | 209 | 210 | void RLRequest::rl_keys(){ 211 | 212 | if(args.size()!=1){ 213 | connection->write_error("ERR wrong number of arguments for 'keys' command"); 214 | return; 215 | } 216 | 217 | std::vector keys; 218 | size_t arg_len=args[0].size(); 219 | bool allkeys = (arg_len==1 && args[0][0]=='*'); 220 | if(arg_len>0){ 221 | leveldb::Iterator *kit = connection->server->db[connection->db_index]->NewIterator( 222 | connection->server->read_options); 223 | leveldb::Slice key; 224 | kit->SeekToFirst(); 225 | while(kit->Valid()) { 226 | key = kit->key(); 227 | if(allkeys || stringmatchlen(args[0].c_str(), arg_len, key.data(), key.size(), 0)){ 228 | keys.push_back(key.ToString()); 229 | } 230 | kit->Next(); 231 | } 232 | delete kit; 233 | }else{ 234 | keys.push_back(""); 235 | } 236 | 237 | connection->write_mbulk_header(keys.size()); 238 | //std::for_each(keys.begin(), keys.end(), std::bind1st(std::mem_fun(&RLConnection::write_bulk),connection)); 239 | std::vector::iterator it=keys.begin(); 240 | while(it!=keys.end())connection->write_bulk(*it++); 241 | } 242 | 243 | 244 | 245 | void RLRequest::rl_info(){ 246 | 247 | if(args.size()>1){ 248 | connection->write_error("ERR wrong number of arguments for 'info' command"); 249 | return; 250 | } 251 | 252 | std::ostringstream info; 253 | string out; 254 | 255 | info << "redis_version: redis-leveldb " VERSION_STR "\r\n"; 256 | info << "mode: "; 257 | if(connection->server->db_num<1){ 258 | info << "single\r\n"; 259 | }else{ 260 | info << "multi = " << connection->server->db_num; 261 | info << "," << connection->db_index << "\r\n"; 262 | } 263 | char *cwd=getcwd(NULL,0); 264 | info << "work_dir: " << cwd << "\r\n"; 265 | free(cwd); 266 | info << "data_path: " << connection->server->db_path << "\r\n"; 267 | info << "clients_num: " << connection->server->clients_num << "\r\n"; 268 | 269 | bool r = connection->server->db[connection->db_index]->GetProperty("leveldb.stats", &out); 270 | if(r){ 271 | info<< "stats: " << out <<"\r\n"; 272 | } 273 | 274 | /* kyes num */ 275 | if(args.size()>0 && args[0].find('k')!=std::string::npos){ 276 | uint64_t key_num=0; 277 | leveldb::Iterator *kit = connection->server->db[connection->db_index]->NewIterator( 278 | connection->server->read_options); 279 | 280 | kit->SeekToFirst(); 281 | while(kit->Valid()){ 282 | key_num++; 283 | kit->Next(); 284 | } 285 | delete kit; 286 | info<< "keys: " << key_num <<"\r\n"; 287 | } 288 | 289 | /** sstables info */ 290 | if(args.size()>0 && args[0].find('t')!=std::string::npos){ 291 | r = connection->server->db[connection->db_index]->GetProperty("leveldb.sstables", &out); 292 | if(r){ 293 | info<< "sstables:\r\n" << out <<"\r\n"; 294 | } 295 | } 296 | connection->write_bulk(info.str()); 297 | } 298 | 299 | 300 | void RLRequest::rl_shutdown(){ 301 | extern RLServer *server; 302 | server=NULL; 303 | delete connection->server; 304 | fprintf(stderr, "SHUTDOWN by client\n"); 305 | exit(0); 306 | } 307 | -------------------------------------------------------------------------------- /src/rl_request.h: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_request.h 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #ifndef _RL_REUQEST_H_INCLUDED 9 | #define _RL_REQUEST_H_INCLUDED 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class RLConnection; 16 | 17 | class RLRequest{ 18 | public: 19 | 20 | typedef void (RLRequest::*COMMAND)(); 21 | 22 | RLConnection *connection; 23 | int32_t arg_count; 24 | std::string name; 25 | std::vector args; 26 | std::vector subrequest; /* for MULTI */ 27 | 28 | /**method**/ 29 | RLRequest(RLConnection *c); 30 | ~RLRequest(); 31 | void append_arg(std::string arg); 32 | void _run(); 33 | void run(); 34 | bool completed(){return arg_count>=0 && arg_count-args.size()==0;} 35 | 36 | static std::map cmd_map; 37 | static void init_cmd_map(); 38 | 39 | /** sys commands **/ 40 | void rl_dummy(); 41 | void rl_select(); 42 | void rl_keys(); 43 | void rl_info(); 44 | void rl_shutdown(); 45 | 46 | void rl_multi(); 47 | void rl_exec(); 48 | void rl_discard(); 49 | 50 | /* kv commands */ 51 | void rl_get(); 52 | void rl_set(); 53 | void rl_del(); 54 | 55 | void rl_mget(); 56 | void rl_mset(); 57 | 58 | void rl_incr(); 59 | void rl_incrby(); 60 | 61 | /* set commands */ 62 | void rl_sadd(); 63 | void rl_srem(); 64 | void rl_scard(); 65 | void rl_smembers(); 66 | void rl_sismember(); 67 | 68 | /* hash commands */ 69 | void rl_hget(); 70 | void rl_hset(); 71 | void rl_hsetnx(); 72 | void rl_hdel(); 73 | void rl_hexists(); 74 | void rl_hgetall(); 75 | void rl_hkeys(); 76 | void rl_hvals(); 77 | void rl_hlen(); 78 | 79 | /* list commands */ 80 | void rl_lpush(); 81 | void rl_lpop(); 82 | void rl_rpush(); 83 | void rl_rpop(); 84 | void rl_llen(); 85 | }; 86 | 87 | #define RL_GET(key_data, key_size, out_size, err) leveldb_get(connection->server->db[connection->db_index], \ 88 | connection->server->read_options,key_data, key_size, &out_size, &err) 89 | 90 | #define RL_SET(key_data, key_size, value_data, value_size, err) leveldb_put(connection->server->db[connection->db_index], \ 91 | connection->server->write_options, key_data, key_size, value_data, value_size, &err) 92 | 93 | #define RL_DEL(key_data, key_size, err) leveldb_delete(connection->server->db[connection->db_index], \ 94 | connection->server->write_options, key_data, key_size, &err) 95 | #endif 96 | -------------------------------------------------------------------------------- /src/rl_server.cpp: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_server.cpp 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include /* TCP_NODELAY */ 15 | #include /* inet_ntoa */ 16 | #include /* inet_ntoa */ 17 | 18 | #include 19 | 20 | #include "rl_util.h" 21 | #include "rl_server.h" 22 | #include "rl_connection.h" 23 | 24 | #define EVBACKEND EVFLAG_AUTO 25 | 26 | #ifdef __linux 27 | #undef EVBACKEND 28 | #define EVBACKEND EVBACKEND_EPOLL 29 | #endif 30 | 31 | #ifdef __APPLE__ 32 | #undef EVBACKEND 33 | #define EVBACKEND EVBACKEND_KQUEUE 34 | #endif 35 | 36 | RLServer::RLServer(const char *_db_path, const char *_hostaddr, int _port, int dbn): 37 | db_num(dbn), db_path(_db_path), hostaddr(_hostaddr), port(_port), 38 | fd(-1), clients_num(0) 39 | { 40 | 41 | printf("HOSTADDR:(%s)\n",hostaddr.c_str()); 42 | printf("DB_PATH:(%s)\n",db_path.c_str()); 43 | printf("PORT:(%d)\n",port); 44 | 45 | leveldb::Status status; 46 | 47 | if(db_num<1){ 48 | options = new leveldb::Options[1]; 49 | options[0].create_if_missing = true; 50 | options[0].filter_policy = leveldb::NewBloomFilterPolicy(10); 51 | 52 | db=new leveldb::DB*[1]; 53 | status = leveldb::DB::Open(options[0], db_path.c_str(), &db[0]); 54 | if(!status.ok()) { 55 | puts("leveldb open error"); 56 | exit(1); 57 | } 58 | }else{ 59 | options = new leveldb::Options[db_num]; 60 | 61 | db=new leveldb::DB*[db_num]; 62 | char buf[16]; 63 | for(int i=0;i 0) close(fd); 141 | exit(1); 142 | } 143 | 144 | if(listen(fd, MAX_CONNECTIONS) < 0) { 145 | perror("listen()"); 146 | exit(1); 147 | } 148 | 149 | set_nonblock(fd); 150 | 151 | ev_init(&connection_watcher, RLServer::on_connection); 152 | 153 | ev_io_set(&connection_watcher, fd, EV_READ); 154 | ev_io_start(loop, &connection_watcher); 155 | 156 | printf("Server running successfully\n"); 157 | ev_run(loop, 0); 158 | } 159 | 160 | void RLServer::on_connection(struct ev_loop *loop, ev_io *watcher, int revents) 161 | { 162 | RLServer *s = static_cast(watcher->data); 163 | if(EV_ERROR & revents) { 164 | puts("on_connection() got error event, closing server."); 165 | return; 166 | } 167 | 168 | struct sockaddr_in addr; // connector's address information 169 | socklen_t addr_len = sizeof(addr); 170 | int fd = accept(s->fd, (struct sockaddr*)&addr, &addr_len); 171 | 172 | if(fd < 0) { 173 | perror("accept()"); 174 | return; 175 | } 176 | 177 | RLConnection *connection = new RLConnection(s, fd); 178 | 179 | if(connection == NULL) { 180 | close(fd); 181 | return; 182 | } 183 | connection->start(); 184 | 185 | } 186 | -------------------------------------------------------------------------------- /src/rl_server.h: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_server.h 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #ifndef _RL_SERVER_H_INCLUDED 9 | #define _RL_SERVER_H_INCLUDED 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | class RLServer{ 19 | 20 | public: 21 | int db_num; 22 | std::string db_path; 23 | std::string hostaddr; 24 | int port; 25 | int fd; 26 | int clients_num; 27 | struct ev_loop* loop; 28 | ev_io connection_watcher; 29 | leveldb::Options *options; 30 | leveldb::ReadOptions read_options; 31 | leveldb::WriteOptions write_options; 32 | leveldb::DB **db; 33 | 34 | RLServer(const char *db_path, const char *hostaddr, int port, int dbn=0); 35 | ~RLServer(); 36 | void start(); 37 | static void on_connection(struct ev_loop *loop, ev_io *watcher, int revents); 38 | }; 39 | 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/rl_set.cpp: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_set.cpp 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include 24 | 25 | #include "rl.h" 26 | #include "rl_util.h" 27 | #include "rl_server.h" 28 | #include "rl_connection.h" 29 | #include "rl_request.h" 30 | #include "rl_compdata.h" 31 | 32 | void RLRequest::rl_sadd(){ 33 | if(args.size()<2){ 34 | connection->write_error("ERR wrong number of arguments for 'sadd' command"); 35 | return; 36 | } 37 | string &sname = args[0]; 38 | string sizekey = _encode_compdata_size_key(sname, CompDataType::SET); 39 | uint32_t new_mem = 0; 40 | 41 | std::string out; 42 | leveldb::WriteBatch write_batch; 43 | leveldb::Status status; 44 | 45 | for(uint32_t i=1; iserver->db[connection->db_index]->Get( 49 | connection->server->read_options, key, &out); 50 | 51 | if(status.IsNotFound()) { 52 | // set value 53 | write_batch.Put(key, "\1"); 54 | ++new_mem; 55 | } 56 | } 57 | if(new_mem == 0){ 58 | connection->write_integer("0", 1); 59 | return; 60 | } 61 | // update size! 62 | status = connection->server->db[connection->db_index]->Get( 63 | connection->server->read_options, sizekey, &out); 64 | 65 | mpz_t delta; 66 | mpz_init(delta); 67 | mpz_set_ui(delta, new_mem); 68 | 69 | char *str_oldv = NULL; 70 | if(status.IsNotFound()){ 71 | str_oldv = strdup("0"); 72 | }else if(status.ok()){ 73 | str_oldv = (char*)malloc(out.size()+1); 74 | memcpy(str_oldv, out.data(), out.size()); 75 | str_oldv[out.size()] = 0; 76 | }else{ 77 | connection->write_error("SADD ERROR 1"); 78 | return; 79 | } 80 | 81 | mpz_t old_v; 82 | mpz_init(old_v); 83 | mpz_set_str(old_v, str_oldv, 10); 84 | free(str_oldv); 85 | mpz_add(old_v, old_v, delta); 86 | char *str_newv=mpz_get_str(NULL, 10, old_v); 87 | char *str_delta=mpz_get_str(NULL, 10, delta); 88 | mpz_clear(delta); 89 | mpz_clear(old_v); 90 | 91 | write_batch.Put(sizekey, str_newv); 92 | 93 | status = connection->server->db[connection->db_index]->Write(connection->server->write_options, &write_batch); 94 | 95 | if(!status.ok()) { 96 | connection->write_error("SADD ERROR 2"); 97 | }else{ 98 | connection->write_integer(str_delta, strlen(str_delta)); 99 | } 100 | free(str_newv); 101 | free(str_delta); 102 | } 103 | 104 | void RLRequest::rl_srem(){ 105 | if(args.size()<2){ 106 | connection->write_error("ERR wrong number of arguments for 'srem' command"); 107 | return; 108 | } 109 | 110 | string &sname = args[0]; 111 | string sizekey = _encode_compdata_size_key(sname, CompDataType::SET); 112 | uint32_t del_mem = 0; 113 | 114 | std::string out; 115 | leveldb::Status status; 116 | 117 | for(uint32_t i=1; iserver->db[connection->db_index]->Get( 121 | connection->server->read_options, key, &out); 122 | 123 | if(status.IsNotFound()) { 124 | // not exist 125 | } else if(status.ok()){ 126 | 127 | // delete value 128 | status = connection->server->db[connection->db_index]->Delete( 129 | connection->server->write_options, key); 130 | if(status.ok()){ 131 | ++del_mem; 132 | } 133 | } 134 | } 135 | if(del_mem == 0){ 136 | connection->write_integer("0", 1); 137 | return; 138 | } 139 | // update size! 140 | status = connection->server->db[connection->db_index]->Get( 141 | connection->server->read_options, sizekey, &out); 142 | 143 | char *str_oldv=NULL; 144 | if(status.IsNotFound()){ 145 | str_oldv = strdup("0"); 146 | }else if(status.ok()){ 147 | str_oldv=(char*)malloc(out.size()+1); 148 | memcpy(str_oldv, out.data(), out.size()); 149 | str_oldv[out.size()]=0; 150 | }else{ 151 | //TODO 152 | } 153 | 154 | mpz_t delta; 155 | mpz_init(delta); 156 | mpz_set_ui(delta, del_mem); 157 | 158 | mpz_t old_v; 159 | mpz_init(old_v); 160 | mpz_set_str(old_v, str_oldv, 10); 161 | free(str_oldv); 162 | mpz_sub(old_v, old_v, delta); 163 | char *str_newv=mpz_get_str(NULL, 10, old_v); 164 | char *str_delta=mpz_get_str(NULL, 10, delta); 165 | mpz_clear(delta); 166 | mpz_clear(old_v); 167 | 168 | status = connection->server->db[connection->db_index]->Put( 169 | connection->server->write_options, 170 | sizekey, str_newv); 171 | 172 | if(!status.ok()) { 173 | connection->write_error("SREM ERROR 1"); 174 | }else{ 175 | connection->write_integer(str_delta, strlen(str_delta)); 176 | } 177 | free(str_newv); 178 | free(str_delta); 179 | } 180 | 181 | void RLRequest::rl_scard(){ 182 | if(args.size()!=1){ 183 | connection->write_error("ERR wrong number of arguments for 'scard' command"); 184 | return; 185 | } 186 | 187 | string sizekey = _encode_compdata_size_key(args[0], CompDataType::SET); 188 | 189 | std::string out; 190 | 191 | leveldb::Status status = connection->server->db[connection->db_index]->Get( 192 | connection->server->read_options, 193 | sizekey, &out); 194 | 195 | if(status.IsNotFound()) { 196 | connection->write_integer("0", 1); 197 | } else if(status.ok()){ 198 | connection->write_integer(out.data(), out.size()); 199 | } else { 200 | connection->write_error("SCARD ERROR 1"); 201 | } 202 | } 203 | 204 | void RLRequest::rl_smembers(){ 205 | if(args.size()!=1){ 206 | connection->write_error("ERR wrong number of arguments for 'smembers' command"); 207 | return; 208 | } 209 | 210 | std::vector keys; 211 | leveldb::Slice key; 212 | 213 | string set_begin = _encode_set_key(args[0], ""); 214 | leveldb::Iterator *kit = connection->server->db[connection->db_index]->NewIterator( 215 | connection->server->read_options); 216 | 217 | kit->Seek(set_begin); 218 | 219 | while(kit->Valid()) { 220 | key = kit->key(); 221 | if(strncmp(set_begin.data(), key.data(), set_begin.size()) == 0){ 222 | const string &v = _decode_key(key.ToString()); 223 | if(v.size()>0)keys.push_back(v); 224 | kit->Next(); 225 | }else{ 226 | break; 227 | } 228 | } 229 | delete kit; 230 | 231 | connection->write_mbulk_header(keys.size()); 232 | //std::for_each(keys.begin(), keys.end(), std::bind1st(std::mem_fun(&RLConnection::write_bulk),connection)); 233 | std::vector::iterator it=keys.begin(); 234 | while(it!=keys.end())connection->write_bulk(*it++); 235 | } 236 | 237 | void RLRequest::rl_sismember(){ 238 | if(args.size()<2){ 239 | connection->write_error("ERR wrong number of arguments for 'sismember' command"); 240 | return; 241 | } 242 | 243 | string &sname = args[0]; 244 | string key = _encode_set_key(sname, args[1]); 245 | 246 | string out; 247 | leveldb::Status status; 248 | 249 | status = connection->server->db[connection->db_index]->Get( 250 | connection->server->read_options, key, &out); 251 | 252 | if(status.IsNotFound()) { 253 | // not a member 254 | connection->write_integer("0", 1); 255 | } else if(status.ok()){ 256 | // is a member 257 | connection->write_integer("1", 1); 258 | }else { 259 | connection->write_error("SISMEMBER ERROR 1"); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/rl_util.cpp: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_util.cpp 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "rl_util.h" 22 | #include "rl_server.h" 23 | 24 | void set_nonblock (int fd) { 25 | int flags = fcntl(fd, F_GETFL, 0); 26 | int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); 27 | assert(0 <= r && "Setting socket non-block failed!"); 28 | } 29 | 30 | int daemon_init(void) { 31 | pid_t pid; 32 | if((pid = fork()) < 0) { 33 | return -1; 34 | } else if(pid != 0) { 35 | exit(0); //parent exit 36 | } 37 | 38 | /* child continues */ 39 | setsid(); /* become session leader */ 40 | //chdir("/"); /* change working directory */ 41 | 42 | umask(0); /* clear file mode creation mask */ 43 | close(0); /* close stdin */ 44 | close(1); /* close stdout */ 45 | close(2); /* close stderr */ 46 | 47 | return 0; 48 | } 49 | 50 | extern RLServer *server; 51 | 52 | void leveldb_repair(const char *db_path){ 53 | struct stat dbstat; 54 | leveldb::Options options; 55 | options.create_if_missing = false; 56 | 57 | if(stat(db_path, &dbstat)){ 58 | perror("REPAIR DB ERROR:"); 59 | exit(-1); 60 | } 61 | if(!S_ISDIR(dbstat.st_mode)){ 62 | fprintf(stderr, "REPAIR DB ERROR: %s is not a directory\n", db_path); 63 | exit(-1); 64 | } 65 | 66 | leveldb::Status st = leveldb::RepairDB(db_path, options); 67 | if(!st.ok()){ 68 | fprintf(stderr, "REPAIR DB ERROR :(\n"); 69 | }else{ 70 | fprintf(stderr, "REPAIR DB FINISHED\n"); 71 | } 72 | } 73 | 74 | void exit_hook(){ 75 | printf("exiting...\n"); 76 | if(server){ 77 | delete server; 78 | server=NULL; 79 | } 80 | } 81 | 82 | void sig_term(int signo) { 83 | if(signo==SIGINT || 84 | signo == SIGTERM || 85 | signo == SIGQUIT) { 86 | exit_hook(); 87 | exit(0); 88 | } 89 | } 90 | 91 | 92 | 93 | /* Glob-style pattern matching, from redis */ 94 | int stringmatchlen(const char *pattern, int patternLen, 95 | const char *string, int stringLen, int nocase) 96 | { 97 | while(patternLen) { 98 | switch(pattern[0]) { 99 | case '*': 100 | while (pattern[1] == '*') { 101 | pattern++; 102 | patternLen--; 103 | } 104 | if (patternLen == 1) 105 | return 1; /* match */ 106 | while(stringLen) { 107 | if (stringmatchlen(pattern+1, patternLen-1, 108 | string, stringLen, nocase)) 109 | return 1; /* match */ 110 | string++; 111 | stringLen--; 112 | } 113 | return 0; /* no match */ 114 | break; 115 | case '?': 116 | if (stringLen == 0) 117 | return 0; /* no match */ 118 | string++; 119 | stringLen--; 120 | break; 121 | case '[': 122 | { 123 | int not_, match; 124 | 125 | pattern++; 126 | patternLen--; 127 | not_ = pattern[0] == '^'; 128 | if (not_) { 129 | pattern++; 130 | patternLen--; 131 | } 132 | match = 0; 133 | while(1) { 134 | if (pattern[0] == '\\') { 135 | pattern++; 136 | patternLen--; 137 | if (pattern[0] == string[0]) 138 | match = 1; 139 | } else if (pattern[0] == ']') { 140 | break; 141 | } else if (patternLen == 0) { 142 | pattern--; 143 | patternLen++; 144 | break; 145 | } else if (pattern[1] == '-' && patternLen >= 3) { 146 | int start = pattern[0]; 147 | int end = pattern[2]; 148 | int c = string[0]; 149 | if (start > end) { 150 | int t = start; 151 | start = end; 152 | end = t; 153 | } 154 | if (nocase) { 155 | start = tolower(start); 156 | end = tolower(end); 157 | c = tolower(c); 158 | } 159 | pattern += 2; 160 | patternLen -= 2; 161 | if (c >= start && c <= end) 162 | match = 1; 163 | } else { 164 | if (!nocase) { 165 | if (pattern[0] == string[0]) 166 | match = 1; 167 | } else { 168 | if (tolower((int)pattern[0]) == tolower((int)string[0])) 169 | match = 1; 170 | } 171 | } 172 | pattern++; 173 | patternLen--; 174 | } 175 | if (not_) 176 | match = !match; 177 | if (!match) 178 | return 0; /* no match */ 179 | string++; 180 | stringLen--; 181 | break; 182 | } 183 | case '\\': 184 | if (patternLen >= 2) { 185 | pattern++; 186 | patternLen--; 187 | } 188 | /* fall through */ 189 | default: 190 | if (!nocase) { 191 | if (pattern[0] != string[0]) 192 | return 0; /* no match */ 193 | } else { 194 | if (tolower((int)pattern[0]) != tolower((int)string[0])) 195 | return 0; /* no match */ 196 | } 197 | string++; 198 | stringLen--; 199 | break; 200 | } 201 | pattern++; 202 | patternLen--; 203 | if (stringLen == 0) { 204 | while(*pattern == '*') { 205 | pattern++; 206 | patternLen--; 207 | } 208 | break; 209 | } 210 | } 211 | if (patternLen == 0 && stringLen == 0) 212 | return 1; 213 | return 0; 214 | } 215 | 216 | int stringmatch(const char *pattern, const char *string, int nocase) { 217 | return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase); 218 | } 219 | -------------------------------------------------------------------------------- /src/rl_util.h: -------------------------------------------------------------------------------- 1 | /*-*- c++ -*- 2 | * 3 | * rl_util.h 4 | * author : KDr2 5 | * 6 | */ 7 | 8 | #ifndef _RL_UTIL_H_INCLUDED 9 | #define _RL_UTIL_H_INCLUDED 10 | 11 | void set_nonblock(int fd); 12 | 13 | int daemon_init(void); 14 | 15 | void leveldb_repair(const char *db_path); 16 | 17 | void exit_hook(void); 18 | void sig_term(int signo); 19 | 20 | int stringmatchlen(const char *pattern, int patternLen, 21 | const char *string, int stringLen, int nocase); 22 | int stringmatch(const char *pattern, const char *string, int nocase); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /t/001-kv.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # -*- perl; coding: utf-8 -*- 3 | 4 | use 5.010; 5 | use Test::More; 6 | use Tester; 7 | 8 | my $tester = Tester->new; 9 | $tester->start(); 10 | 11 | foreach (1..20) { 12 | $tester->client->set("key-$_" => "value-$_"); 13 | } 14 | 15 | foreach (1..20) { 16 | ok($tester->client->get("key-$_") eq "value-$_", "command get/set"); 17 | } 18 | 19 | $tester->client->set('test-inc-0' => 1); 20 | $tester->client->incr('test-inc-0'); 21 | ok($tester->client->get('test-inc-0') == 2, 'command incr'); 22 | 23 | $tester->client->set('test-inc-1' => 1024); 24 | $tester->client->incrby('test-inc-1', 1024); 25 | ok($tester->client->get('test-inc-1') == 2048, 'command incrby'); 26 | 27 | $tester->client->mset('mset_k1' => 1, 'mset_k2'=>2, 'mset_k3'=>3); 28 | is_deeply([$tester->client->mget('mset_k1', 'mset_k2', 'mset_k3')], [1, 2, 3], 'command mset/mget'); 29 | 30 | $tester->stop; 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/002-set.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # -*- perl; coding: utf-8 -*- 3 | 4 | use 5.010; 5 | use Test::More; 6 | use Tester; 7 | 8 | our $tester = Tester->new; 9 | $tester->start(); 10 | 11 | our $set_name = "test-set"; 12 | 13 | $tester->client->sadd($set_name, map { "set-element-$_" } 1..20); 14 | ok($tester->client->scard($set_name) == 20, 'command sadd/scard'); 15 | 16 | $tester->client->srem($set_name, "set-element-7"); 17 | ok($tester->client->scard($set_name) == 19, 'command srem/scard'); 18 | 19 | our @members_in_db = $tester->client->smembers($set_name); 20 | our @members_expected = map { "set-element-$_" } (grep { $_ != 7 } 1..20); 21 | is_deeply(\@members_in_db, \@members_expected, "command smembers"); 22 | ok($tester->client->sismember($set_name, "set-element-1"), "command sismember"); 23 | ok(!$tester->client->sismember($set_name, "set-element-7"), "command sismemeber"); 24 | 25 | $tester->stop; 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/003-hash.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # -*- perl; coding: utf-8 -*- 3 | 4 | use 5.010; 5 | use Test::More; 6 | use Tester; 7 | 8 | my $tester = Tester->new; 9 | $tester->start(); 10 | 11 | my $hash_name = "test-hash"; 12 | 13 | for ( 1..20 ) { 14 | $tester->try( 15 | "hset", $hash_name, "key-$_", "val-$_" 16 | ); 17 | } 18 | 19 | ok($tester->try("hlen", $hash_name) == 20, 'command hset/hlen'); 20 | ok($tester->try("hget", $hash_name, "key-7") eq "val-7", 'command hget'); 21 | 22 | is($tester->try("hsetnx", $hash_name, "key-7", "val-7"), 0, 'command hsetnx 1'); 23 | is($tester->try("hsetnx", $hash_name, "key-21", "val-21"), 1, 'command hsetnx 2'); 24 | 25 | is($tester->try("hdel", $hash_name, "key-21"), 1, 'command hdel 1'); 26 | ok($tester->try("hlen", $hash_name) == 20, 'command hdel 2'); 27 | 28 | ok($tester->try("hexists", $hash_name, "key-7"), 'command hexists'); 29 | 30 | my %items_in_db = $tester->try("hgetall", $hash_name); 31 | my %items_expected = map {; "key-$_" => "val-$_" } 1..20; 32 | is_deeply(\%items_in_db, \%items_expected, "command hgetall"); 33 | 34 | my %keys_in_db = $tester->try("hkeys", $hash_name); 35 | my %keys_expected = map {; "key-$_" } 1..20; 36 | is_deeply(\%keys_in_db, \%keys_expected, "command hkeys"); 37 | 38 | my %vals_in_db = $tester->try("hvals", $hash_name); 39 | my %vals_expected = map {; "val-$_" } 1..20; 40 | is_deeply(\%vals_in_db, \%vals_expected, "command hvals"); 41 | 42 | $tester->stop; 43 | done_testing; 44 | -------------------------------------------------------------------------------- /t/004-list.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # -*- perl; coding: utf-8 -*- 3 | 4 | use 5.010; 5 | use Test::More; 6 | use Tester; 7 | 8 | my $tester = Tester->new; 9 | $tester->start(); 10 | 11 | my $list_name = "test-list"; 12 | 13 | for ( 1..5 ) { 14 | $tester->try( 15 | "lpush", $list_name, "elm-l-$_" 16 | ); 17 | $tester->try( 18 | "rpush", $list_name, "elm-r-$_" 19 | ); 20 | } 21 | 22 | is($tester->try("llen", $list_name), 10, "command lpush/rpush/llen"); 23 | ok($tester->try("lpop", $list_name) == "elm-l-5", "command lpop"); 24 | ok($tester->try("rpop", $list_name) == "elm-r-5", "command rpop"); 25 | is($tester->try("llen", $list_name), 8, "command llen"); 26 | 27 | $tester->stop; 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/005-tx.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # -*- perl; coding: utf-8 -*- 3 | 4 | use 5.010; 5 | use Test::More; 6 | use Tester; 7 | 8 | my $tester = Tester->new; 9 | $tester->start(); 10 | 11 | my $list_name = "test-list"; 12 | my $key_name = "test-key"; 13 | 14 | # multi, exec 15 | is($tester->try("multi"), "OK", "command multi"); 16 | 17 | for ( 1..20 ) { 18 | is($tester->try("lpush", $list_name, "elm-l-$_"), "QUEUED", "queued command"); 19 | is($tester->try("incr", $key_name), "QUEUED", "queued command"); 20 | } 21 | 22 | my $result = $tester->try("exec"); 23 | is_deeply(\@$result, \@{[(map +("1", "$_"), 1..20)]}, "command exec"); 24 | is($tester->try("llen", $list_name), 20, "command llen"); 25 | ok($tester->try("get", $key_name) == 20, "command incr"); 26 | 27 | # multi, discard 28 | is($tester->try("multi"), "OK", "command multi"); 29 | for ( 1..20 ) { 30 | is($tester->try("lpush", $list_name, "elm-l-$_"), "QUEUED", "queued command"); 31 | is($tester->try("incr", $key_name), "QUEUED", "queued command"); 32 | } 33 | 34 | my $result = $tester->try("discard"); 35 | ok($result == "OK", "command exec"); 36 | is($tester->try("llen", $list_name), 20, "command llen"); 37 | ok($tester->try("get", $key_name) == 20, "command incr"); 38 | 39 | 40 | $tester->stop; 41 | done_testing; 42 | -------------------------------------------------------------------------------- /t/006-db.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # -*- perl; coding: utf-8 -*- 3 | 4 | use 5.010; 5 | use Test::More; 6 | use Tester; 7 | 8 | my $tester = Tester->new(db_number=>4); 9 | $tester->start(); 10 | 11 | my $key_name = "test-key"; 12 | 13 | for ( 0..3 ) { 14 | is($tester->try("select", $_), "OK", "command select"); 15 | is($tester->try("set", $key_name, $_), "OK", "command set"); 16 | } 17 | 18 | for ( 0..3 ) { 19 | is($tester->try("select", $_), "OK", "command select"); 20 | is($tester->try("get", $key_name), $_, "command get"); 21 | } 22 | 23 | $tester->stop; 24 | done_testing; 25 | -------------------------------------------------------------------------------- /t/007-srv.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # -*- perl; coding: utf-8 -*- 3 | 4 | use 5.010; 5 | use Test::More; 6 | use Tester; 7 | 8 | my $tester = Tester->new(); 9 | $tester->start(); 10 | 11 | # command keys 12 | my @keys = ("t1-key", "t2-key", "t3-key"); 13 | 14 | for my $key (@keys) { 15 | for ( 0..3 ) { 16 | is($tester->try("set", "${key}-$_", $_), "OK", "command set"); 17 | } 18 | } 19 | 20 | for my $key (@keys) { 21 | my $expected_keys = [ map {; $key . "-$_" } 0..3 ]; 22 | my @keys_in_db = $tester->try("keys", $key . "-*"); 23 | is_deeply(\@keys_in_db, $expected_keys, "command keys"); 24 | } 25 | 26 | my @expected_keys = (); 27 | for my $key (@keys) { 28 | push @expected_keys, (map {; $key . "-$_" } 0..3); 29 | } 30 | my @keys_in_db = $tester->try("keys", "*"); 31 | is_deeply(\@keys_in_db, \@expected_keys, "command keys"); 32 | 33 | # command info 34 | my $info = $tester->try("info", "k"); 35 | 36 | like($info->{keys}, qr/\s*12\s*/, "command info keys"); 37 | like($info->{mode}, qr/\s*single\s*/, "command info mode"); 38 | like($info->{clients_num}, qr/\s*1\s*/, "command info clients_num"); 39 | 40 | # command shutdown 41 | my $shutdown_status = $tester->try("shutdown"); 42 | is($shutdown_status, 1, "command shutdown"); 43 | $tester->stop if (!$shutdown_status); 44 | $tester->clean_data; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/Tester.pm: -------------------------------------------------------------------------------- 1 | package Tester; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Cwd qw(cwd); 7 | use POSIX; 8 | use Redis; 9 | 10 | sub new { 11 | my $class = shift; 12 | my $option = {data_dir => "./tmp.test-db-dir", @_}; 13 | return bless {option => $option}, $class; 14 | } 15 | 16 | sub start { 17 | my $self = shift; 18 | my ($client, $retry_times) =(undef, 30); 19 | my $child_pid = fork(); 20 | 21 | qx(rm -fr $self->{option}{data_dir}); 22 | 23 | if ($child_pid) { 24 | $self->{'pid'} = $child_pid; 25 | } else { 26 | my $null_fd = POSIX::open("/dev/null", &POSIX::O_WRONLY); 27 | POSIX::dup2($null_fd, 1); 28 | POSIX::dup2($null_fd, 2); 29 | qx(mkdir -p $self->{option}{data_dir}); 30 | my @cmd = (cwd() . '/redis-leveldb', '-D', $self->{option}{data_dir}); 31 | push @cmd, "-M", $self->{option}{db_number} if defined($self->{option}{db_number}); 32 | exec @cmd or die "# can not start the server instance!"; 33 | } 34 | 35 | while (!$client && $retry_times > 0) { 36 | eval { 37 | $client = Redis->new(server => "localhost:8323"); 38 | }; 39 | select(undef, undef, undef, 0.2); 40 | $retry_times--; 41 | } 42 | $client or die "# can not connect to server"; 43 | $self->{'client'} = $client; 44 | } 45 | 46 | sub stop { 47 | my $self = shift; 48 | my $stop_cmd = "kill -INT " . $self->{'pid'}; 49 | qx($stop_cmd) and die "# server stop error!"; 50 | qx(rm -fr $self->{option}{data_dir}); 51 | } 52 | 53 | sub clean_data { 54 | my $self = shift; 55 | qx(rm -fr $self->{option}{data_dir}); 56 | } 57 | 58 | sub client { 59 | my $self = shift; 60 | return $self->{'client'}; 61 | } 62 | 63 | sub try { 64 | my $self = shift; 65 | my $cmd = shift; 66 | return eval { 67 | $self->{'client'}->$cmd(@_); 68 | }; 69 | } 70 | 71 | 1; 72 | --------------------------------------------------------------------------------