diff --git a/LICENSE b/LICENSE index 234a477..234076e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013 José Carlos Nieto, http://xiam.menteslibres.org/ +Copyright (c) 2013-2014 José Carlos Nieto, https://menteslibres.net/xiam Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/_hiredis/CHANGELOG.md b/_hiredis/CHANGELOG.md deleted file mode 100644 index 268b15c..0000000 --- a/_hiredis/CHANGELOG.md +++ /dev/null @@ -1,24 +0,0 @@ -### 0.11.0 - -* Increase the maximum multi-bulk reply depth to 7. - -* Increase the read buffer size from 2k to 16k. - -* Use poll(2) instead of select(2) to support large fds (>= 1024). - -### 0.10.1 - -* Makefile overhaul. Important to check out if you override one or more - variables using environment variables or via arguments to the "make" tool. - -* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements - being created by the default reply object functions. - -* Issue #43: Don't crash in an asynchronous context when Redis returns an error - reply after the connection has been made (this happens when the maximum - number of connections is reached). - -### 0.10.0 - -* See commit log. - diff --git a/_hiredis/COPYING b/_hiredis/COPYING deleted file mode 100644 index a5fc973..0000000 --- a/_hiredis/COPYING +++ /dev/null @@ -1,29 +0,0 @@ -Copyright (c) 2009-2011, Salvatore Sanfilippo -Copyright (c) 2010-2011, Pieter Noordhuis - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of Redis nor the names of its contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/_hiredis/Makefile b/_hiredis/Makefile deleted file mode 100644 index c8632b4..0000000 --- a/_hiredis/Makefile +++ /dev/null @@ -1,160 +0,0 @@ -# Hiredis Makefile -# Copyright (C) 2010-2011 Salvatore Sanfilippo -# Copyright (C) 2010-2011 Pieter Noordhuis -# This file is released under the BSD license, see the COPYING file - -OBJ=net.o hiredis.o sds.o async.o -EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev -TESTS=hiredis-test -LIBNAME=libhiredis - -HIREDIS_MAJOR=0 -HIREDIS_MINOR=10 - -# Fallback to gcc when $CC is not in $PATH. -CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc') -OPTIMIZATION?=-O3 -WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -DEBUG?= -g -ggdb -REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH) -REAL_LDFLAGS=$(LDFLAGS) $(ARCH) - -DYLIBSUFFIX=so -STLIBSUFFIX=a -DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR) -DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) -DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) -DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) -STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) -STLIB_MAKE_CMD=ar rcs $(STLIBNAME) - -# Platform-specific overrides -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -ifeq ($(uname_S),SunOS) - REAL_LDFLAGS+= -ldl -lnsl -lsocket - DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) - INSTALL= cp -r -endif -ifeq ($(uname_S),Darwin) - DYLIBSUFFIX=dylib - DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX) - DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX) - DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) -endif - -all: $(DYLIBNAME) - -# Deps (use make dep to generate this) -net.o: net.c fmacros.h net.h hiredis.h -async.o: async.c async.h hiredis.h sds.h dict.c dict.h -hiredis.o: hiredis.c fmacros.h hiredis.h net.h sds.h -sds.o: sds.c sds.h -test.o: test.c hiredis.h - -$(DYLIBNAME): $(OBJ) - $(DYLIB_MAKE_CMD) $(OBJ) - -$(STLIBNAME): $(OBJ) - $(STLIB_MAKE_CMD) $(OBJ) - -dynamic: $(DYLIBNAME) -static: $(STLIBNAME) - -# Binaries: -hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) - -hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) - -ifndef AE_DIR -hiredis-example-ae: - @echo "Please specify AE_DIR (e.g. /src)" - @false -else -hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) -endif - -ifndef LIBUV_DIR -hiredis-example-libuv: - @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" - @false -else -hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME) -endif - -hiredis-example: examples/example.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) - -examples: $(EXAMPLES) - -hiredis-test: test.o $(STLIBNAME) - $(CC) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) - -test: hiredis-test - ./hiredis-test - -check: hiredis-test - echo \ - "daemonize yes\n" \ - "pidfile /tmp/hiredis-test-redis.pid\n" \ - "port 56379\n" \ - "bind 127.0.0.1\n" \ - "unixsocket /tmp/hiredis-test-redis.sock" \ - | redis-server - - ./hiredis-test -h 127.0.0.1 -p 56379 -s /tmp/hiredis-test-redis.sock || \ - ( kill `cat /tmp/hiredis-test-redis.pid` && false ) - kill `cat /tmp/hiredis-test-redis.pid` - -.c.o: - $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< - -clean: - rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) examples/hiredis-example* *.o *.gcda *.gcno *.gcov - -dep: - $(CC) -MM *.c - -# Installation related variables and target -PREFIX?=/usr/local -INSTALL_INCLUDE_PATH= $(PREFIX)/include/hiredis -INSTALL_LIBRARY_PATH= $(PREFIX)/lib - -ifeq ($(uname_S),SunOS) - INSTALL?= cp -r -endif - -INSTALL?= cp -a - -install: $(DYLIBNAME) $(STLIBNAME) - mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) - $(INSTALL) hiredis.h async.h adapters $(INSTALL_INCLUDE_PATH) - $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) - cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) - cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME) - $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) - -32bit: - @echo "" - @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" - @echo "" - $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" - -gprof: - $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" - -gcov: - $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" - -coverage: gcov - make check - mkdir -p tmp/lcov - lcov -d . -c -o tmp/lcov/hiredis.info - genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info - -noopt: - $(MAKE) OPTIMIZATION="" - -.PHONY: all test check clean dep install 32bit gprof gcov noopt diff --git a/_hiredis/README.md b/_hiredis/README.md deleted file mode 100644 index dba4a8c..0000000 --- a/_hiredis/README.md +++ /dev/null @@ -1,384 +0,0 @@ -[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) - -# HIREDIS - -Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. - -It is minimalistic because it just adds minimal support for the protocol, but -at the same time it uses an high level printf-alike API in order to make it -much higher level than otherwise suggested by its minimal code base and the -lack of explicit bindings for every Redis command. - -Apart from supporting sending commands and receiving replies, it comes with -a reply parser that is decoupled from the I/O layer. It -is a stream parser designed for easy reusability, which can for instance be used -in higher level language bindings for efficient reply parsing. - -Hiredis only supports the binary-safe Redis protocol, so you can use it with any -Redis version >= 1.2.0. - -The library comes with multiple APIs. There is the -*synchronous API*, the *asynchronous API* and the *reply parsing API*. - -## UPGRADING - -Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing -code using hiredis should not be a big pain. The key thing to keep in mind when -upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to -the stateless 0.0.1 that only has a file descriptor to work with. - -## Synchronous API - -To consume the synchronous API, there are only a few function calls that need to be introduced: - - redisContext *redisConnect(const char *ip, int port); - void *redisCommand(redisContext *c, const char *format, ...); - void freeReplyObject(void *reply); - -### Connecting - -The function `redisConnect` is used to create a so-called `redisContext`. The -context is where Hiredis holds state for a connection. The `redisContext` -struct has an integer `err` field that is non-zero when an the connection is in -an error state. The field `errstr` will contain a string with a description of -the error. More information on errors can be found in the **Errors** section. -After trying to connect to Redis using `redisConnect` you should -check the `err` field to see if establishing the connection was successful: - - redisContext *c = redisConnect("127.0.0.1", 6379); - if (c != NULL && c->err) { - printf("Error: %s\n", c->errstr); - // handle error - } - -### Sending commands - -There are several ways to issue commands to Redis. The first that will be introduced is -`redisCommand`. This function takes a format similar to printf. In the simplest form, -it is used like this: - - reply = redisCommand(context, "SET foo bar"); - -The specifier `%s` interpolates a string in the command, and uses `strlen` to -determine the length of the string: - - reply = redisCommand(context, "SET foo %s", value); - -When you need to pass binary safe strings in a command, the `%b` specifier can be -used. Together with a pointer to the string, it requires a `size_t` length argument -of the string: - - reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); - -Internally, Hiredis splits the command in different arguments and will -convert it to the protocol used to communicate with Redis. -One or more spaces separates arguments, so you can use the specifiers -anywhere in an argument: - - reply = redisCommand(context, "SET key:%s %s", myid, value); - -### Using replies - -The return value of `redisCommand` holds a reply when the command was -successfully executed. When an error occurs, the return value is `NULL` and -the `err` field in the context will be set (see section on **Errors**). -Once an error is returned the context cannot be reused and you should set up -a new connection. - -The standard replies that `redisCommand` are of the type `redisReply`. The -`type` field in the `redisReply` should be used to test what kind of reply -was received: - -* **`REDIS_REPLY_STATUS`**: - * The command replied with a status reply. The status string can be accessed using `reply->str`. - The length of this string can be accessed using `reply->len`. - -* **`REDIS_REPLY_ERROR`**: - * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. - -* **`REDIS_REPLY_INTEGER`**: - * The command replied with an integer. The integer value can be accessed using the - `reply->integer` field of type `long long`. - -* **`REDIS_REPLY_NIL`**: - * The command replied with a **nil** object. There is no data to access. - -* **`REDIS_REPLY_STRING`**: - * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. - The length of this string can be accessed using `reply->len`. - -* **`REDIS_REPLY_ARRAY`**: - * A multi bulk reply. The number of elements in the multi bulk reply is stored in - `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well - and can be accessed via `reply->element[..index..]`. - Redis may reply with nested arrays but this is fully supported. - -Replies should be freed using the `freeReplyObject()` function. -Note that this function will take care of freeing sub-replies objects -contained in arrays and nested arrays, so there is no need for the user to -free the sub replies (it is actually harmful and will corrupt the memory). - -**Important:** the current version of hiredis (0.10.0) free's replies when the -asynchronous API is used. This means you should not call `freeReplyObject` when -you use this API. The reply is cleaned up by hiredis _after_ the callback -returns. This behavior will probably change in future releases, so make sure to -keep an eye on the changelog when upgrading (see issue #39). - -### Cleaning up - -To disconnect and free the context the following function can be used: - - void redisFree(redisContext *c); - -This function immediately closes the socket and then free's the allocations done in -creating the context. - -### Sending commands (cont'd) - -Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. -It has the following prototype: - - void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); - -It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the -arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will -use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments -need to be binary safe, the entire array of lengths `argvlen` should be provided. - -The return value has the same semantic as `redisCommand`. - -### Pipelining - -To explain how Hiredis supports pipelining in a blocking connection, there needs to be -understanding of the internal execution flow. - -When any of the functions in the `redisCommand` family is called, Hiredis first formats the -command according to the Redis protocol. The formatted command is then put in the output buffer -of the context. This output buffer is dynamic, so it can hold any number of commands. -After the command is put in the output buffer, `redisGetReply` is called. This function has the -following two execution paths: - -1. The input buffer is non-empty: - * Try to parse a single reply from the input buffer and return it - * If no reply could be parsed, continue at *2* -2. The input buffer is empty: - * Write the **entire** output buffer to the socket - * Read from the socket until a single reply could be parsed - -The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply -is expected on the socket. To pipeline commands, the only things that needs to be done is -filling up the output buffer. For this cause, two commands can be used that are identical -to the `redisCommand` family, apart from not returning a reply: - - void redisAppendCommand(redisContext *c, const char *format, ...); - void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); - -After calling either function one or more times, `redisGetReply` can be used to receive the -subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where -the latter means an error occurred while reading a reply. Just as with the other commands, -the `err` field in the context can be used to find out what the cause of this error is. - -The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and -a single call to `read(2)`): - - redisReply *reply; - redisAppendCommand(context,"SET foo bar"); - redisAppendCommand(context,"GET foo"); - redisGetReply(context,&reply); // reply for SET - freeReplyObject(reply); - redisGetReply(context,&reply); // reply for GET - freeReplyObject(reply); - -This API can also be used to implement a blocking subscriber: - - reply = redisCommand(context,"SUBSCRIBE foo"); - freeReplyObject(reply); - while(redisGetReply(context,&reply) == REDIS_OK) { - // consume message - freeReplyObject(reply); - } - -### Errors - -When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is -returned. The `err` field inside the context will be non-zero and set to one of the -following constants: - -* **`REDIS_ERR_IO`**: - There was an I/O error while creating the connection, trying to write - to the socket or read from the socket. If you included `errno.h` in your - application, you can use the global `errno` variable to find out what is - wrong. - -* **`REDIS_ERR_EOF`**: - The server closed the connection which resulted in an empty read. - -* **`REDIS_ERR_PROTOCOL`**: - There was an error while parsing the protocol. - -* **`REDIS_ERR_OTHER`**: - Any other error. Currently, it is only used when a specified hostname to connect - to cannot be resolved. - -In every case, the `errstr` field in the context will be set to hold a string representation -of the error. - -## Asynchronous API - -Hiredis comes with an asynchronous API that works easily with any event library. -Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) -and [libevent](http://monkey.org/~provos/libevent/). - -### Connecting - -The function `redisAsyncConnect` can be used to establish a non-blocking connection to -Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field -should be checked after creation to see if there were errors creating the connection. -Because the connection that will be created is non-blocking, the kernel is not able to -instantly return if the specified host and port is able to accept a connection. - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - printf("Error: %s\n", c->errstr); - // handle error - } - -The asynchronous context can hold a disconnect callback function that is called when the -connection is disconnected (either because of an error or per user request). This function should -have the following prototype: - - void(const redisAsyncContext *c, int status); - -On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the -user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` -field in the context can be accessed to find out the cause of the error. - -The context object is always free'd after the disconnect callback fired. When a reconnect is needed, -the disconnect callback is a good point to do so. - -Setting the disconnect callback can only be done once per context. For subsequent calls it will -return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: - - int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); - -### Sending commands and their callbacks - -In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. -Therefore, unlike the synchronous API, there is only a single way to send commands. -Because commands are sent to Redis asynchronously, issuing a command requires a callback function -that is called when the reply is received. Reply callbacks should have the following prototype: - - void(redisAsyncContext *c, void *reply, void *privdata); - -The `privdata` argument can be used to curry arbitrary data to the callback from the point where -the command is initially queued for execution. - -The functions that can be used to issue commands in an asynchronous context are: - - int redisAsyncCommand( - redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, - const char *format, ...); - int redisAsyncCommandArgv( - redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, - int argc, const char **argv, const size_t *argvlen); - -Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command -was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection -is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is -returned on calls to the `redisAsyncCommand` family. - -If the reply for a command with a `NULL` callback is read, it is immediately free'd. When the callback -for a command is non-`NULL`, the memory is free'd immediately following the callback: the reply is only -valid for the duration of the callback. - -All pending callbacks are called with a `NULL` reply when the context encountered an error. - -### Disconnecting - -An asynchronous connection can be terminated using: - - void redisAsyncDisconnect(redisAsyncContext *ac); - -When this function is called, the connection is **not** immediately terminated. Instead, new -commands are no longer accepted and the connection is only terminated when all pending commands -have been written to the socket, their respective replies have been read and their respective -callbacks have been executed. After this, the disconnection callback is executed with the -`REDIS_OK` status and the context object is free'd. - -### Hooking it up to event library *X* - -There are a few hooks that need to be set on the context object after it is created. -See the `adapters/` directory for bindings to *libev* and *libevent*. - -## Reply parsing API - -Hiredis comes with a reply parsing API that makes it easy for writing higher -level language bindings. - -The reply parsing API consists of the following functions: - - redisReader *redisReaderCreate(void); - void redisReaderFree(redisReader *reader); - int redisReaderFeed(redisReader *reader, const char *buf, size_t len); - int redisReaderGetReply(redisReader *reader, void **reply); - -The same set of functions are used internally by hiredis when creating a -normal Redis context, the above API just exposes it to the user for a direct -usage. - -### Usage - -The function `redisReaderCreate` creates a `redisReader` structure that holds a -buffer with unparsed data and state for the protocol parser. - -Incoming data -- most likely from a socket -- can be placed in the internal -buffer of the `redisReader` using `redisReaderFeed`. This function will make a -copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed -when `redisReaderGetReply` is called. This function returns an integer status -and a reply object (as described above) via `void **reply`. The returned status -can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went -wrong (either a protocol error, or an out of memory error). - -The parser limits the level of nesting for multi bulk payloads to 7. If the -multi bulk nesting level is higher than this, the parser returns an error. - -### Customizing replies - -The function `redisReaderGetReply` creates `redisReply` and makes the function -argument `reply` point to the created `redisReply` variable. For instance, if -the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` -will hold the status as a vanilla C string. However, the functions that are -responsible for creating instances of the `redisReply` can be customized by -setting the `fn` field on the `redisReader` struct. This should be done -immediately after creating the `redisReader`. - -For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) -uses customized reply object functions to create Ruby objects. - -### Reader max buffer - -Both when using the Reader API directly or when using it indirectly via a -normal Redis context, the redisReader structure uses a buffer in order to -accumulate data from the server. -Usually this buffer is destroyed when it is empty and is larger than 16 -kb in order to avoid wasting memory in unused buffers - -However when working with very big payloads destroying the buffer may slow -down performances considerably, so it is possible to modify the max size of -an idle buffer changing the value of the `maxbuf` field of the reader structure -to the desired value. The special value of 0 means that there is no maximum -value for an idle buffer, so the buffer will never get freed. - -For instance if you have a normal Redis context you can set the maximum idle -buffer to zero (unlimited) just with: - - context->reader->maxbuf = 0; - -This should be done only in order to maximize performances when working with -large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again -as soon as possible in order to prevent allocation of useless memory. - -## AUTHORS - -Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and -Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. diff --git a/_hiredis/async.c b/_hiredis/async.c deleted file mode 100644 index dcfb9e2..0000000 --- a/_hiredis/async.c +++ /dev/null @@ -1,663 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include "async.h" -#include "net.h" -#include "dict.c" -#include "sds.h" - -#define _EL_ADD_READ(ctx) do { \ - if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ - } while(0) -#define _EL_DEL_READ(ctx) do { \ - if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ - } while(0) -#define _EL_ADD_WRITE(ctx) do { \ - if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ - } while(0) -#define _EL_DEL_WRITE(ctx) do { \ - if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ - } while(0) -#define _EL_CLEANUP(ctx) do { \ - if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ - } while(0); - -/* Forward declaration of function in hiredis.c */ -#ifdef CGO -int __redisAppendCommand(redisContext *c, char *cmd, size_t len); -#else -void __redisAppendCommand(redisContext *c, char *cmd, size_t len); -#endif - -/* Functions managing dictionary of callbacks for pub/sub. */ -static unsigned int callbackHash(const void *key) { - return dictGenHashFunction((const unsigned char *)key, - sdslen((const sds)key)); -} - -static void *callbackValDup(void *privdata, const void *src) { - ((void) privdata); - redisCallback *dup = malloc(sizeof(*dup)); - memcpy(dup,src,sizeof(*dup)); - return dup; -} - -static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { - int l1, l2; - ((void) privdata); - - l1 = sdslen((const sds)key1); - l2 = sdslen((const sds)key2); - if (l1 != l2) return 0; - return memcmp(key1,key2,l1) == 0; -} - -static void callbackKeyDestructor(void *privdata, void *key) { - ((void) privdata); - sdsfree((sds)key); -} - -static void callbackValDestructor(void *privdata, void *val) { - ((void) privdata); - free(val); -} - -static dictType callbackDict = { - callbackHash, - NULL, - callbackValDup, - callbackKeyCompare, - callbackKeyDestructor, - callbackValDestructor -}; - -#ifdef CGO -redisAsyncContext *redisAsyncInitialize(redisContext *c) { -#else -static redisAsyncContext *redisAsyncInitialize(redisContext *c) { -#endif - redisAsyncContext *ac; - - ac = realloc(c,sizeof(redisAsyncContext)); - if (ac == NULL) - return NULL; - - c = &(ac->c); - - /* The regular connect functions will always set the flag REDIS_CONNECTED. - * For the async API, we want to wait until the first write event is - * received up before setting this flag, so reset it here. */ - c->flags &= ~REDIS_CONNECTED; - - ac->err = 0; - ac->errstr = NULL; - ac->data = NULL; - - ac->ev.data = NULL; - ac->ev.addRead = NULL; - ac->ev.delRead = NULL; - ac->ev.addWrite = NULL; - ac->ev.delWrite = NULL; - ac->ev.cleanup = NULL; - - ac->onConnect = NULL; - ac->onDisconnect = NULL; - - ac->replies.head = NULL; - ac->replies.tail = NULL; - ac->sub.invalid.head = NULL; - ac->sub.invalid.tail = NULL; - ac->sub.channels = dictCreate(&callbackDict,NULL); - ac->sub.patterns = dictCreate(&callbackDict,NULL); - return ac; -} - -/* We want the error field to be accessible directly instead of requiring - * an indirection to the redisContext struct. */ -#ifdef CGO -void __redisAsyncCopyError(redisAsyncContext *ac) { -#else -static void __redisAsyncCopyError(redisAsyncContext *ac) { -#endif - redisContext *c = &(ac->c); - ac->err = c->err; - ac->errstr = c->errstr; -} - -redisAsyncContext *redisAsyncConnect(const char *ip, int port) { - redisContext *c; - redisAsyncContext *ac; - - c = redisConnectNonBlock(ip,port); - if (c == NULL) - return NULL; - - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnectUnix(const char *path) { - redisContext *c; - redisAsyncContext *ac; - - c = redisConnectUnixNonBlock(path); - if (c == NULL) - return NULL; - - ac = redisAsyncInitialize(c); - __redisAsyncCopyError(ac); - return ac; -} - -int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { - if (ac->onConnect == NULL) { - ac->onConnect = fn; - - /* The common way to detect an established connection is to wait for - * the first write event to be fired. This assumes the related event - * library functions are already set. */ - _EL_ADD_WRITE(ac); - return REDIS_OK; - } - return REDIS_ERR; -} - -int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { - if (ac->onDisconnect == NULL) { - ac->onDisconnect = fn; - return REDIS_OK; - } - return REDIS_ERR; -} - -/* Helper functions to push/shift callbacks */ -static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { - redisCallback *cb; - - /* Copy callback from stack to heap */ - cb = malloc(sizeof(*cb)); - if (cb == NULL) - return REDIS_ERR_OOM; - - if (source != NULL) { - memcpy(cb,source,sizeof(*cb)); - cb->next = NULL; - } - - /* Store callback in list */ - if (list->head == NULL) - list->head = cb; - if (list->tail != NULL) - list->tail->next = cb; - list->tail = cb; - return REDIS_OK; -} - -static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { - redisCallback *cb = list->head; - if (cb != NULL) { - list->head = cb->next; - if (cb == list->tail) - list->tail = NULL; - - /* Copy callback from heap to stack */ - if (target != NULL) - memcpy(target,cb,sizeof(*cb)); - free(cb); - return REDIS_OK; - } - return REDIS_ERR; -} - -static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { - redisContext *c = &(ac->c); - if (cb->fn != NULL) { - c->flags |= REDIS_IN_CALLBACK; - cb->fn(ac,reply,cb->privdata); - c->flags &= ~REDIS_IN_CALLBACK; - } -} - -/* Helper function to free the context. */ -static void __redisAsyncFree(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb; - dictIterator *it; - dictEntry *de; - - /* Execute pending callbacks with NULL reply. */ - while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) - __redisRunCallback(ac,&cb,NULL); - - /* Execute callbacks for invalid commands */ - while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) - __redisRunCallback(ac,&cb,NULL); - - /* Run subscription callbacks callbacks with NULL reply */ - it = dictGetIterator(ac->sub.channels); - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - dictRelease(ac->sub.channels); - - it = dictGetIterator(ac->sub.patterns); - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - dictRelease(ac->sub.patterns); - - /* Signal event lib to clean up */ - _EL_CLEANUP(ac); - - /* Execute disconnect callback. When redisAsyncFree() initiated destroying - * this context, the status will always be REDIS_OK. */ - if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { - if (c->flags & REDIS_FREEING) { - ac->onDisconnect(ac,REDIS_OK); - } else { - ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); - } - } - - /* Cleanup self */ - redisFree(c); -} - -/* Free the async context. When this function is called from a callback, - * control needs to be returned to redisProcessCallbacks() before actual - * free'ing. To do so, a flag is set on the context which is picked up by - * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ -void redisAsyncFree(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - c->flags |= REDIS_FREEING; - if (!(c->flags & REDIS_IN_CALLBACK)) - __redisAsyncFree(ac); -} - -/* Helper function to make the disconnect happen and clean up. */ -static void __redisAsyncDisconnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - /* Make sure error is accessible if there is any */ - __redisAsyncCopyError(ac); - - if (ac->err == 0) { - /* For clean disconnects, there should be no pending callbacks. */ - assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); - } else { - /* Disconnection is caused by an error, make sure that pending - * callbacks cannot call new commands. */ - c->flags |= REDIS_DISCONNECTING; - } - - /* For non-clean disconnects, __redisAsyncFree() will execute pending - * callbacks with a NULL-reply. */ - __redisAsyncFree(ac); -} - -/* Tries to do a clean disconnect from Redis, meaning it stops new commands - * from being issued, but tries to flush the output buffer and execute - * callbacks for all remaining replies. When this function is called from a - * callback, there might be more replies and we can safely defer disconnecting - * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately - * when there are no pending callbacks. */ -void redisAsyncDisconnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - c->flags |= REDIS_DISCONNECTING; - if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) - __redisAsyncDisconnect(ac); -} - -static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { - redisContext *c = &(ac->c); - dict *callbacks; - dictEntry *de; - int pvariant; - char *stype; - sds sname; - - /* Custom reply functions are not supported for pub/sub. This will fail - * very hard when they are used... */ - if (reply->type == REDIS_REPLY_ARRAY) { - assert(reply->elements >= 2); - assert(reply->element[0]->type == REDIS_REPLY_STRING); - stype = reply->element[0]->str; - pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; - - if (pvariant) - callbacks = ac->sub.patterns; - else - callbacks = ac->sub.channels; - - /* Locate the right callback */ - assert(reply->element[1]->type == REDIS_REPLY_STRING); - sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); - de = dictFind(callbacks,sname); - if (de != NULL) { - memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); - - /* If this is an unsubscribe message, remove it. */ - if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { - dictDelete(callbacks,sname); - - /* If this was the last unsubscribe message, revert to - * non-subscribe mode. */ - assert(reply->element[2]->type == REDIS_REPLY_INTEGER); - if (reply->element[2]->integer == 0) - c->flags &= ~REDIS_SUBSCRIBED; - } - } - sdsfree(sname); - } else { - /* Shift callback for invalid commands. */ - __redisShiftCallback(&ac->sub.invalid,dstcb); - } - return REDIS_OK; -} - -void redisProcessCallbacks(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb; - void *reply = NULL; - int status; - - while((status = redisGetReply(c,&reply)) == REDIS_OK) { - if (reply == NULL) { - /* When the connection is being disconnected and there are - * no more replies, this is the cue to really disconnect. */ - if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { - __redisAsyncDisconnect(ac); - return; - } - - /* If monitor mode, repush callback */ - if(c->flags & REDIS_MONITORING) { - __redisPushCallback(&ac->replies,&cb); - } - - /* When the connection is not being disconnected, simply stop - * trying to get replies and wait for the next loop tick. */ - break; - } - - /* Even if the context is subscribed, pending regular callbacks will - * get a reply before pub/sub messages arrive. */ - if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { - /* - * A spontaneous reply in a not-subscribed context can be the error - * reply that is sent when a new connection exceeds the maximum - * number of allowed connections on the server side. - * - * This is seen as an error instead of a regular reply because the - * server closes the connection after sending it. - * - * To prevent the error from being overwritten by an EOF error the - * connection is closed here. See issue #43. - * - * Another possibility is that the server is loading its dataset. - * In this case we also want to close the connection, and have the - * user wait until the server is ready to take our request. - */ - if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { - c->err = REDIS_ERR_OTHER; - snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); - __redisAsyncDisconnect(ac); - return; - } - /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ - assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); - if(c->flags & REDIS_SUBSCRIBED) - __redisGetSubscribeCallback(ac,reply,&cb); - } - - if (cb.fn != NULL) { - __redisRunCallback(ac,&cb,reply); -#ifndef CGO - // This object is freed by gosexy/redis. - c->reader->fn->freeObject(reply); -#endif - - /* Proceed with free'ing when redisAsyncFree() was called. */ - if (c->flags & REDIS_FREEING) { - __redisAsyncFree(ac); - return; - } - } else { - /* No callback for this reply. This can either be a NULL callback, - * or there were no callbacks to begin with. Either way, don't - * abort with an error, but simply ignore it because the client - * doesn't know what the server will spit out over the wire. */ - c->reader->fn->freeObject(reply); - } - } - - /* Disconnect when there was an error reading the reply */ - if (status != REDIS_OK) - __redisAsyncDisconnect(ac); -} - -/* Internal helper function to detect socket status the first time a read or - * write event fires. When connecting was not succesful, the connect callback - * is called with a REDIS_ERR status and the context is free'd. */ -static int __redisAsyncHandleConnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (redisCheckSocketError(c,c->fd) == REDIS_ERR) { - /* Try again later when connect(2) is still in progress. */ - if (errno == EINPROGRESS) - return REDIS_OK; - - if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); - __redisAsyncDisconnect(ac); - return REDIS_ERR; - } - - /* Mark context as connected. */ - c->flags |= REDIS_CONNECTED; - if (ac->onConnect) ac->onConnect(ac,REDIS_OK); - return REDIS_OK; -} - -/* This function should be called when the socket is readable. - * It processes all replies that can be read and executes their callbacks. - */ -void redisAsyncHandleRead(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (!(c->flags & REDIS_CONNECTED)) { - /* Abort connect was not successful. */ - if (__redisAsyncHandleConnect(ac) != REDIS_OK) - return; - /* Try again later when the context is still not connected. */ - if (!(c->flags & REDIS_CONNECTED)) - return; - } - - if (redisBufferRead(c) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Always re-schedule reads */ - _EL_ADD_READ(ac); - redisProcessCallbacks(ac); - } -} - -void redisAsyncHandleWrite(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - int done = 0; - - if (!(c->flags & REDIS_CONNECTED)) { - /* Abort connect was not successful. */ - if (__redisAsyncHandleConnect(ac) != REDIS_OK) - return; - /* Try again later when the context is still not connected. */ - if (!(c->flags & REDIS_CONNECTED)) - return; - } - - if (redisBufferWrite(c,&done) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Continue writing when not done, stop writing otherwise */ - if (!done) - _EL_ADD_WRITE(ac); - else - _EL_DEL_WRITE(ac); - - /* Always schedule reads after writes */ - _EL_ADD_READ(ac); - } -} - -/* Sets a pointer to the first argument and its length starting at p. Returns - * the number of bytes to skip to get to the following argument. */ -static char *nextArgument(char *start, char **str, size_t *len) { - char *p = start; - if (p[0] != '$') { - p = strchr(p,'$'); - if (p == NULL) return NULL; - } - - *len = (int)strtol(p+1,NULL,10); - p = strchr(p,'\r'); - assert(p); - *str = p+2; - return p+2+(*len)+2; -} - -/* Helper function for the redisAsyncCommand* family of functions. Writes a - * formatted command to the output buffer and registers the provided callback - * function with the context. */ -static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) { - redisContext *c = &(ac->c); - redisCallback cb; - int pvariant, hasnext; - char *cstr, *astr; - size_t clen, alen; - char *p; - sds sname; - - /* Don't accept new commands when the connection is about to be closed. */ - if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; - - /* Setup callback */ - cb.fn = fn; - cb.privdata = privdata; - - /* Find out which command will be appended. */ - p = nextArgument(cmd,&cstr,&clen); - assert(p != NULL); - hasnext = (p[0] == '$'); - pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; - cstr += pvariant; - clen -= pvariant; - - if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { - c->flags |= REDIS_SUBSCRIBED; - - /* Add every channel/pattern to the list of subscription callbacks. */ - while ((p = nextArgument(p,&astr,&alen)) != NULL) { - sname = sdsnewlen(astr,alen); - if (pvariant) - dictReplace(ac->sub.patterns,sname,&cb); - else - dictReplace(ac->sub.channels,sname,&cb); - } - } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { - /* It is only useful to call (P)UNSUBSCRIBE when the context is - * subscribed to one or more channels or patterns. */ - if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; - - /* (P)UNSUBSCRIBE does not have its own response: every channel or - * pattern that is unsubscribed will receive a message. This means we - * should not append a callback function for this command. */ - } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { - /* Set monitor flag and push callback */ - c->flags |= REDIS_MONITORING; - __redisPushCallback(&ac->replies,&cb); - } else { - if (c->flags & REDIS_SUBSCRIBED) - /* This will likely result in an error reply, but it needs to be - * received and passed to the callback. */ - __redisPushCallback(&ac->sub.invalid,&cb); - else - __redisPushCallback(&ac->replies,&cb); - } - - __redisAppendCommand(c,cmd,len); - - /* Always schedule a write when the write buffer is non-empty */ - _EL_ADD_WRITE(ac); - - return REDIS_OK; -} - -int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { - char *cmd; - int len; - int status; - len = redisvFormatCommand(&cmd,format,ap); - status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - free(cmd); - return status; -} - -int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { - va_list ap; - int status; - va_start(ap,format); - status = redisvAsyncCommand(ac,fn,privdata,format,ap); - va_end(ap); - return status; -} - -int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - char *cmd; - int len; - int status; - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - free(cmd); - return status; -} diff --git a/_hiredis/async.h b/_hiredis/async.h deleted file mode 100644 index 925a5d6..0000000 --- a/_hiredis/async.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __HIREDIS_ASYNC_H -#define __HIREDIS_ASYNC_H -#include "hiredis.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ -struct dict; /* dictionary header is included in async.c */ - -/* Reply callback prototype and container */ -typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); -typedef struct redisCallback { - struct redisCallback *next; /* simple singly linked list */ - redisCallbackFn *fn; - void *privdata; -} redisCallback; - -/* List of callbacks for either regular replies or pub/sub */ -typedef struct redisCallbackList { - redisCallback *head, *tail; -} redisCallbackList; - -/* Connection callback prototypes */ -typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); -typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); - -/* Context for an async connection to Redis */ -typedef struct redisAsyncContext { - /* Hold the regular context, so it can be realloc'ed. */ - redisContext c; - - /* Setup error flags so they can be used directly. */ - int err; - char *errstr; - - /* Not used by hiredis */ - void *data; - - /* Event library data and hooks */ - struct { - void *data; - - /* Hooks that are called when the library expects to start - * reading/writing. These functions should be idempotent. */ - void (*addRead)(void *privdata); - void (*delRead)(void *privdata); - void (*addWrite)(void *privdata); - void (*delWrite)(void *privdata); - void (*cleanup)(void *privdata); - } ev; - - /* Called when either the connection is terminated due to an error or per - * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ - redisDisconnectCallback *onDisconnect; - - /* Called when the first write event was received. */ - redisConnectCallback *onConnect; - - /* Regular command callbacks */ - redisCallbackList replies; - - /* Subscription callbacks */ - struct { - redisCallbackList invalid; - struct dict *channels; - struct dict *patterns; - } sub; -} redisAsyncContext; - -/* Functions that proxy to hiredis */ -redisAsyncContext *redisAsyncConnect(const char *ip, int port); -redisAsyncContext *redisAsyncConnectUnix(const char *path); -int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); -int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); -void redisAsyncDisconnect(redisAsyncContext *ac); -void redisAsyncFree(redisAsyncContext *ac); - -/* Handle read/write events */ -void redisAsyncHandleRead(redisAsyncContext *ac); -void redisAsyncHandleWrite(redisAsyncContext *ac); - -/* Command functions for an async context. Write the command to the - * output buffer and register the provided callback. */ -int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); -int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); -int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); - -#ifdef CGO -redisAsyncContext *redisAsyncInitialize(redisContext *c); -void __redisAsyncCopyError(redisAsyncContext *ac); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/_hiredis/dict.c b/_hiredis/dict.c deleted file mode 100644 index 79b1041..0000000 --- a/_hiredis/dict.c +++ /dev/null @@ -1,338 +0,0 @@ -/* Hash table implementation. - * - * This file implements in memory hash tables with insert/del/replace/find/ - * get-random-element operations. Hash tables will auto resize if needed - * tables of power of two in size are used, collisions are handled by - * chaining. See the source code for more information... :) - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include "dict.h" - -/* -------------------------- private prototypes ---------------------------- */ - -static int _dictExpandIfNeeded(dict *ht); -static unsigned long _dictNextPower(unsigned long size); -static int _dictKeyIndex(dict *ht, const void *key); -static int _dictInit(dict *ht, dictType *type, void *privDataPtr); - -/* -------------------------- hash functions -------------------------------- */ - -/* Generic hash function (a popular one from Bernstein). - * I tested a few and this was the best. */ -static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { - unsigned int hash = 5381; - - while (len--) - hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ - return hash; -} - -/* ----------------------------- API implementation ------------------------- */ - -/* Reset an hashtable already initialized with ht_init(). - * NOTE: This function should only called by ht_destroy(). */ -static void _dictReset(dict *ht) { - ht->table = NULL; - ht->size = 0; - ht->sizemask = 0; - ht->used = 0; -} - -/* Create a new hash table */ -static dict *dictCreate(dictType *type, void *privDataPtr) { - dict *ht = malloc(sizeof(*ht)); - _dictInit(ht,type,privDataPtr); - return ht; -} - -/* Initialize the hash table */ -static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { - _dictReset(ht); - ht->type = type; - ht->privdata = privDataPtr; - return DICT_OK; -} - -/* Expand or create the hashtable */ -static int dictExpand(dict *ht, unsigned long size) { - dict n; /* the new hashtable */ - unsigned long realsize = _dictNextPower(size), i; - - /* the size is invalid if it is smaller than the number of - * elements already inside the hashtable */ - if (ht->used > size) - return DICT_ERR; - - _dictInit(&n, ht->type, ht->privdata); - n.size = realsize; - n.sizemask = realsize-1; - n.table = calloc(realsize,sizeof(dictEntry*)); - - /* Copy all the elements from the old to the new table: - * note that if the old hash table is empty ht->size is zero, - * so dictExpand just creates an hash table. */ - n.used = ht->used; - for (i = 0; i < ht->size && ht->used > 0; i++) { - dictEntry *he, *nextHe; - - if (ht->table[i] == NULL) continue; - - /* For each hash entry on this slot... */ - he = ht->table[i]; - while(he) { - unsigned int h; - - nextHe = he->next; - /* Get the new element index */ - h = dictHashKey(ht, he->key) & n.sizemask; - he->next = n.table[h]; - n.table[h] = he; - ht->used--; - /* Pass to the next element */ - he = nextHe; - } - } - assert(ht->used == 0); - free(ht->table); - - /* Remap the new hashtable in the old */ - *ht = n; - return DICT_OK; -} - -/* Add an element to the target hash table */ -static int dictAdd(dict *ht, void *key, void *val) { - int index; - dictEntry *entry; - - /* Get the index of the new element, or -1 if - * the element already exists. */ - if ((index = _dictKeyIndex(ht, key)) == -1) - return DICT_ERR; - - /* Allocates the memory and stores key */ - entry = malloc(sizeof(*entry)); - entry->next = ht->table[index]; - ht->table[index] = entry; - - /* Set the hash entry fields. */ - dictSetHashKey(ht, entry, key); - dictSetHashVal(ht, entry, val); - ht->used++; - return DICT_OK; -} - -/* Add an element, discarding the old if the key already exists. - * Return 1 if the key was added from scratch, 0 if there was already an - * element with such key and dictReplace() just performed a value update - * operation. */ -static int dictReplace(dict *ht, void *key, void *val) { - dictEntry *entry, auxentry; - - /* Try to add the element. If the key - * does not exists dictAdd will suceed. */ - if (dictAdd(ht, key, val) == DICT_OK) - return 1; - /* It already exists, get the entry */ - entry = dictFind(ht, key); - /* Free the old value and set the new one */ - /* Set the new value and free the old one. Note that it is important - * to do that in this order, as the value may just be exactly the same - * as the previous one. In this context, think to reference counting, - * you want to increment (set), and then decrement (free), and not the - * reverse. */ - auxentry = *entry; - dictSetHashVal(ht, entry, val); - dictFreeEntryVal(ht, &auxentry); - return 0; -} - -/* Search and remove an element */ -static int dictDelete(dict *ht, const void *key) { - unsigned int h; - dictEntry *de, *prevde; - - if (ht->size == 0) - return DICT_ERR; - h = dictHashKey(ht, key) & ht->sizemask; - de = ht->table[h]; - - prevde = NULL; - while(de) { - if (dictCompareHashKeys(ht,key,de->key)) { - /* Unlink the element from the list */ - if (prevde) - prevde->next = de->next; - else - ht->table[h] = de->next; - - dictFreeEntryKey(ht,de); - dictFreeEntryVal(ht,de); - free(de); - ht->used--; - return DICT_OK; - } - prevde = de; - de = de->next; - } - return DICT_ERR; /* not found */ -} - -/* Destroy an entire hash table */ -static int _dictClear(dict *ht) { - unsigned long i; - - /* Free all the elements */ - for (i = 0; i < ht->size && ht->used > 0; i++) { - dictEntry *he, *nextHe; - - if ((he = ht->table[i]) == NULL) continue; - while(he) { - nextHe = he->next; - dictFreeEntryKey(ht, he); - dictFreeEntryVal(ht, he); - free(he); - ht->used--; - he = nextHe; - } - } - /* Free the table and the allocated cache structure */ - free(ht->table); - /* Re-initialize the table */ - _dictReset(ht); - return DICT_OK; /* never fails */ -} - -/* Clear & Release the hash table */ -static void dictRelease(dict *ht) { - _dictClear(ht); - free(ht); -} - -static dictEntry *dictFind(dict *ht, const void *key) { - dictEntry *he; - unsigned int h; - - if (ht->size == 0) return NULL; - h = dictHashKey(ht, key) & ht->sizemask; - he = ht->table[h]; - while(he) { - if (dictCompareHashKeys(ht, key, he->key)) - return he; - he = he->next; - } - return NULL; -} - -static dictIterator *dictGetIterator(dict *ht) { - dictIterator *iter = malloc(sizeof(*iter)); - - iter->ht = ht; - iter->index = -1; - iter->entry = NULL; - iter->nextEntry = NULL; - return iter; -} - -static dictEntry *dictNext(dictIterator *iter) { - while (1) { - if (iter->entry == NULL) { - iter->index++; - if (iter->index >= - (signed)iter->ht->size) break; - iter->entry = iter->ht->table[iter->index]; - } else { - iter->entry = iter->nextEntry; - } - if (iter->entry) { - /* We need to save the 'next' here, the iterator user - * may delete the entry we are returning. */ - iter->nextEntry = iter->entry->next; - return iter->entry; - } - } - return NULL; -} - -static void dictReleaseIterator(dictIterator *iter) { - free(iter); -} - -/* ------------------------- private functions ------------------------------ */ - -/* Expand the hash table if needed */ -static int _dictExpandIfNeeded(dict *ht) { - /* If the hash table is empty expand it to the intial size, - * if the table is "full" dobule its size. */ - if (ht->size == 0) - return dictExpand(ht, DICT_HT_INITIAL_SIZE); - if (ht->used == ht->size) - return dictExpand(ht, ht->size*2); - return DICT_OK; -} - -/* Our hash table capability is a power of two */ -static unsigned long _dictNextPower(unsigned long size) { - unsigned long i = DICT_HT_INITIAL_SIZE; - - if (size >= LONG_MAX) return LONG_MAX; - while(1) { - if (i >= size) - return i; - i *= 2; - } -} - -/* Returns the index of a free slot that can be populated with - * an hash entry for the given 'key'. - * If the key already exists, -1 is returned. */ -static int _dictKeyIndex(dict *ht, const void *key) { - unsigned int h; - dictEntry *he; - - /* Expand the hashtable if needed */ - if (_dictExpandIfNeeded(ht) == DICT_ERR) - return -1; - /* Compute the key hash value */ - h = dictHashKey(ht, key) & ht->sizemask; - /* Search if this slot does not already contain the given key */ - he = ht->table[h]; - while(he) { - if (dictCompareHashKeys(ht, key, he->key)) - return -1; - he = he->next; - } - return h; -} - diff --git a/_hiredis/dict.h b/_hiredis/dict.h deleted file mode 100644 index 95fcd28..0000000 --- a/_hiredis/dict.h +++ /dev/null @@ -1,126 +0,0 @@ -/* Hash table implementation. - * - * This file implements in memory hash tables with insert/del/replace/find/ - * get-random-element operations. Hash tables will auto resize if needed - * tables of power of two in size are used, collisions are handled by - * chaining. See the source code for more information... :) - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __DICT_H -#define __DICT_H - -#define DICT_OK 0 -#define DICT_ERR 1 - -/* Unused arguments generate annoying warnings... */ -#define DICT_NOTUSED(V) ((void) V) - -typedef struct dictEntry { - void *key; - void *val; - struct dictEntry *next; -} dictEntry; - -typedef struct dictType { - unsigned int (*hashFunction)(const void *key); - void *(*keyDup)(void *privdata, const void *key); - void *(*valDup)(void *privdata, const void *obj); - int (*keyCompare)(void *privdata, const void *key1, const void *key2); - void (*keyDestructor)(void *privdata, void *key); - void (*valDestructor)(void *privdata, void *obj); -} dictType; - -typedef struct dict { - dictEntry **table; - dictType *type; - unsigned long size; - unsigned long sizemask; - unsigned long used; - void *privdata; -} dict; - -typedef struct dictIterator { - dict *ht; - int index; - dictEntry *entry, *nextEntry; -} dictIterator; - -/* This is the initial size of every hash table */ -#define DICT_HT_INITIAL_SIZE 4 - -/* ------------------------------- Macros ------------------------------------*/ -#define dictFreeEntryVal(ht, entry) \ - if ((ht)->type->valDestructor) \ - (ht)->type->valDestructor((ht)->privdata, (entry)->val) - -#define dictSetHashVal(ht, entry, _val_) do { \ - if ((ht)->type->valDup) \ - entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ - else \ - entry->val = (_val_); \ -} while(0) - -#define dictFreeEntryKey(ht, entry) \ - if ((ht)->type->keyDestructor) \ - (ht)->type->keyDestructor((ht)->privdata, (entry)->key) - -#define dictSetHashKey(ht, entry, _key_) do { \ - if ((ht)->type->keyDup) \ - entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ - else \ - entry->key = (_key_); \ -} while(0) - -#define dictCompareHashKeys(ht, key1, key2) \ - (((ht)->type->keyCompare) ? \ - (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ - (key1) == (key2)) - -#define dictHashKey(ht, key) (ht)->type->hashFunction(key) - -#define dictGetEntryKey(he) ((he)->key) -#define dictGetEntryVal(he) ((he)->val) -#define dictSlots(ht) ((ht)->size) -#define dictSize(ht) ((ht)->used) - -/* API */ -static unsigned int dictGenHashFunction(const unsigned char *buf, int len); -static dict *dictCreate(dictType *type, void *privDataPtr); -static int dictExpand(dict *ht, unsigned long size); -static int dictAdd(dict *ht, void *key, void *val); -static int dictReplace(dict *ht, void *key, void *val); -static int dictDelete(dict *ht, const void *key); -static void dictRelease(dict *ht); -static dictEntry * dictFind(dict *ht, const void *key); -static dictIterator *dictGetIterator(dict *ht); -static dictEntry *dictNext(dictIterator *iter); -static void dictReleaseIterator(dictIterator *iter); - -#endif /* __DICT_H */ diff --git a/_hiredis/fmacros.h b/_hiredis/fmacros.h deleted file mode 100644 index 799c12c..0000000 --- a/_hiredis/fmacros.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef __HIREDIS_FMACRO_H -#define __HIREDIS_FMACRO_H - -#if !defined(_BSD_SOURCE) -#define _BSD_SOURCE -#endif - -#if defined(__sun__) -#define _POSIX_C_SOURCE 200112L -#elif defined(__linux__) -#define _XOPEN_SOURCE 600 -#else -#define _XOPEN_SOURCE -#endif - -#if __APPLE__ && __MACH__ -#define _OSX -#endif - -#endif diff --git a/_hiredis/hiredis.c b/_hiredis/hiredis.c deleted file mode 100644 index 9b74b5b..0000000 --- a/_hiredis/hiredis.c +++ /dev/null @@ -1,1322 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include -#include - -#include "hiredis.h" -#include "net.h" -#include "sds.h" - -static redisReply *createReplyObject(int type); -static void *createStringObject(const redisReadTask *task, char *str, size_t len); -static void *createArrayObject(const redisReadTask *task, int elements); -static void *createIntegerObject(const redisReadTask *task, long long value); -static void *createNilObject(const redisReadTask *task); - -/* Default set of functions to build the reply. Keep in mind that such a - * function returning NULL is interpreted as OOM. */ -static redisReplyObjectFunctions defaultFunctions = { - createStringObject, - createArrayObject, - createIntegerObject, - createNilObject, - freeReplyObject -}; - -/* Create a reply object */ -static redisReply *createReplyObject(int type) { - redisReply *r = calloc(1,sizeof(*r)); - - if (r == NULL) - return NULL; - - r->type = type; - return r; -} - -/* Free a reply object */ -void freeReplyObject(void *reply) { - redisReply *r = reply; - size_t j; - - switch(r->type) { - case REDIS_REPLY_INTEGER: - break; /* Nothing to free */ - case REDIS_REPLY_ARRAY: - if (r->element != NULL) { - for (j = 0; j < r->elements; j++) - if (r->element[j] != NULL) - freeReplyObject(r->element[j]); - free(r->element); - } - break; - case REDIS_REPLY_ERROR: - case REDIS_REPLY_STATUS: - case REDIS_REPLY_STRING: - if (r->str != NULL) - free(r->str); - break; - } - free(r); -} - -static void *createStringObject(const redisReadTask *task, char *str, size_t len) { - redisReply *r, *parent; - char *buf; - - r = createReplyObject(task->type); - if (r == NULL) - return NULL; - - buf = malloc(len+1); - if (buf == NULL) { - freeReplyObject(r); - return NULL; - } - - assert(task->type == REDIS_REPLY_ERROR || - task->type == REDIS_REPLY_STATUS || - task->type == REDIS_REPLY_STRING); - - /* Copy string value */ - memcpy(buf,str,len); - buf[len] = '\0'; - r->str = buf; - r->len = len; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createArrayObject(const redisReadTask *task, int elements) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_ARRAY); - if (r == NULL) - return NULL; - - if (elements > 0) { - r->element = calloc(elements,sizeof(redisReply*)); - if (r->element == NULL) { - freeReplyObject(r); - return NULL; - } - } - - r->elements = elements; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createIntegerObject(const redisReadTask *task, long long value) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_INTEGER); - if (r == NULL) - return NULL; - - r->integer = value; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void *createNilObject(const redisReadTask *task) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_NIL); - if (r == NULL) - return NULL; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); - parent->element[task->idx] = r; - } - return r; -} - -static void __redisReaderSetError(redisReader *r, int type, const char *str) { - size_t len; - - if (r->reply != NULL && r->fn && r->fn->freeObject) { - r->fn->freeObject(r->reply); - r->reply = NULL; - } - - /* Clear input buffer on errors. */ - if (r->buf != NULL) { - sdsfree(r->buf); - r->buf = NULL; - r->pos = r->len = 0; - } - - /* Reset task stack. */ - r->ridx = -1; - - /* Set error. */ - r->err = type; - len = strlen(str); - len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); - memcpy(r->errstr,str,len); - r->errstr[len] = '\0'; -} - -static size_t chrtos(char *buf, size_t size, char byte) { - size_t len = 0; - - switch(byte) { - case '\\': - case '"': - len = snprintf(buf,size,"\"\\%c\"",byte); - break; - case '\n': len = snprintf(buf,size,"\"\\n\""); break; - case '\r': len = snprintf(buf,size,"\"\\r\""); break; - case '\t': len = snprintf(buf,size,"\"\\t\""); break; - case '\a': len = snprintf(buf,size,"\"\\a\""); break; - case '\b': len = snprintf(buf,size,"\"\\b\""); break; - default: - if (isprint(byte)) - len = snprintf(buf,size,"\"%c\"",byte); - else - len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); - break; - } - - return len; -} - -static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { - char cbuf[8], sbuf[128]; - - chrtos(cbuf,sizeof(cbuf),byte); - snprintf(sbuf,sizeof(sbuf), - "Protocol error, got %s as reply type byte", cbuf); - __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); -} - -static void __redisReaderSetErrorOOM(redisReader *r) { - __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); -} - -static char *readBytes(redisReader *r, unsigned int bytes) { - char *p; - if (r->len-r->pos >= bytes) { - p = r->buf+r->pos; - r->pos += bytes; - return p; - } - return NULL; -} - -/* Find pointer to \r\n. */ -static char *seekNewline(char *s, size_t len) { - int pos = 0; - int _len = len-1; - - /* Position should be < len-1 because the character at "pos" should be - * followed by a \n. Note that strchr cannot be used because it doesn't - * allow to search a limited length and the buffer that is being searched - * might not have a trailing NULL character. */ - while (pos < _len) { - while(pos < _len && s[pos] != '\r') pos++; - if (s[pos] != '\r') { - /* Not found. */ - return NULL; - } else { - if (s[pos+1] == '\n') { - /* Found. */ - return s+pos; - } else { - /* Continue searching. */ - pos++; - } - } - } - return NULL; -} - -/* Read a long long value starting at *s, under the assumption that it will be - * terminated by \r\n. Ambiguously returns -1 for unexpected input. */ -static long long readLongLong(char *s) { - long long v = 0; - int dec, mult = 1; - char c; - - if (*s == '-') { - mult = -1; - s++; - } else if (*s == '+') { - mult = 1; - s++; - } - - while ((c = *(s++)) != '\r') { - dec = c - '0'; - if (dec >= 0 && dec < 10) { - v *= 10; - v += dec; - } else { - /* Should not happen... */ - return -1; - } - } - - return mult*v; -} - -static char *readLine(redisReader *r, int *_len) { - char *p, *s; - int len; - - p = r->buf+r->pos; - s = seekNewline(p,(r->len-r->pos)); - if (s != NULL) { - len = s-(r->buf+r->pos); - r->pos += len+2; /* skip \r\n */ - if (_len) *_len = len; - return p; - } - return NULL; -} - -static void moveToNextTask(redisReader *r) { - redisReadTask *cur, *prv; - while (r->ridx >= 0) { - /* Return a.s.a.p. when the stack is now empty. */ - if (r->ridx == 0) { - r->ridx--; - return; - } - - cur = &(r->rstack[r->ridx]); - prv = &(r->rstack[r->ridx-1]); - assert(prv->type == REDIS_REPLY_ARRAY); - if (cur->idx == prv->elements-1) { - r->ridx--; - } else { - /* Reset the type because the next item can be anything */ - assert(cur->idx < prv->elements); - cur->type = -1; - cur->elements = -1; - cur->idx++; - return; - } - } -} - -static int processLineItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj; - char *p; - int len; - - if ((p = readLine(r,&len)) != NULL) { - if (cur->type == REDIS_REPLY_INTEGER) { - if (r->fn && r->fn->createInteger) - obj = r->fn->createInteger(cur,readLongLong(p)); - else - obj = (void*)REDIS_REPLY_INTEGER; - } else { - /* Type will be error or status. */ - if (r->fn && r->fn->createString) - obj = r->fn->createString(cur,p,len); - else - obj = (void*)(size_t)(cur->type); - } - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - /* Set reply if this is the root object. */ - if (r->ridx == 0) r->reply = obj; - moveToNextTask(r); - return REDIS_OK; - } - - return REDIS_ERR; -} - -static int processBulkItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj = NULL; - char *p, *s; - long len; - unsigned long bytelen; - int success = 0; - - p = r->buf+r->pos; - s = seekNewline(p,r->len-r->pos); - if (s != NULL) { - p = r->buf+r->pos; - bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ - len = readLongLong(p); - - if (len < 0) { - /* The nil object can always be created. */ - if (r->fn && r->fn->createNil) - obj = r->fn->createNil(cur); - else - obj = (void*)REDIS_REPLY_NIL; - success = 1; - } else { - /* Only continue when the buffer contains the entire bulk item. */ - bytelen += len+2; /* include \r\n */ - if (r->pos+bytelen <= r->len) { - if (r->fn && r->fn->createString) - obj = r->fn->createString(cur,s+2,len); - else - obj = (void*)REDIS_REPLY_STRING; - success = 1; - } - } - - /* Proceed when obj was created. */ - if (success) { - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - r->pos += bytelen; - - /* Set reply if this is the root object. */ - if (r->ridx == 0) r->reply = obj; - moveToNextTask(r); - return REDIS_OK; - } - } - - return REDIS_ERR; -} - -static int processMultiBulkItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj; - char *p; - long elements; - int root = 0; - - /* Set error for nested multi bulks with depth > 7 */ - if (r->ridx == 8) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "No support for nested multi bulk replies with depth > 7"); - return REDIS_ERR; - } - - if ((p = readLine(r,NULL)) != NULL) { - elements = readLongLong(p); - root = (r->ridx == 0); - - if (elements == -1) { - if (r->fn && r->fn->createNil) - obj = r->fn->createNil(cur); - else - obj = (void*)REDIS_REPLY_NIL; - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - moveToNextTask(r); - } else { - if (r->fn && r->fn->createArray) - obj = r->fn->createArray(cur,elements); - else - obj = (void*)REDIS_REPLY_ARRAY; - - if (obj == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - /* Modify task stack when there are more than 0 elements. */ - if (elements > 0) { - cur->elements = elements; - cur->obj = obj; - r->ridx++; - r->rstack[r->ridx].type = -1; - r->rstack[r->ridx].elements = -1; - r->rstack[r->ridx].idx = 0; - r->rstack[r->ridx].obj = NULL; - r->rstack[r->ridx].parent = cur; - r->rstack[r->ridx].privdata = r->privdata; - } else { - moveToNextTask(r); - } - } - - /* Set reply if this is the root object. */ - if (root) r->reply = obj; - return REDIS_OK; - } - - return REDIS_ERR; -} - -static int processItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - char *p; - - /* check if we need to read type */ - if (cur->type < 0) { - if ((p = readBytes(r,1)) != NULL) { - switch (p[0]) { - case '-': - cur->type = REDIS_REPLY_ERROR; - break; - case '+': - cur->type = REDIS_REPLY_STATUS; - break; - case ':': - cur->type = REDIS_REPLY_INTEGER; - break; - case '$': - cur->type = REDIS_REPLY_STRING; - break; - case '*': - cur->type = REDIS_REPLY_ARRAY; - break; - default: - __redisReaderSetErrorProtocolByte(r,*p); - return REDIS_ERR; - } - } else { - /* could not consume 1 byte */ - return REDIS_ERR; - } - } - - /* process typed item */ - switch(cur->type) { - case REDIS_REPLY_ERROR: - case REDIS_REPLY_STATUS: - case REDIS_REPLY_INTEGER: - return processLineItem(r); - case REDIS_REPLY_STRING: - return processBulkItem(r); - case REDIS_REPLY_ARRAY: - return processMultiBulkItem(r); - default: - assert(NULL); - return REDIS_ERR; /* Avoid warning. */ - } -} - -redisReader *redisReaderCreate(void) { - redisReader *r; - - r = calloc(sizeof(redisReader),1); - if (r == NULL) - return NULL; - - r->err = 0; - r->errstr[0] = '\0'; - r->fn = &defaultFunctions; - r->buf = sdsempty(); - r->maxbuf = REDIS_READER_MAX_BUF; - if (r->buf == NULL) { - free(r); - return NULL; - } - - r->ridx = -1; - return r; -} - -void redisReaderFree(redisReader *r) { - if (r->reply != NULL && r->fn && r->fn->freeObject) - r->fn->freeObject(r->reply); - if (r->buf != NULL) - sdsfree(r->buf); - free(r); -} - -int redisReaderFeed(redisReader *r, const char *buf, size_t len) { - sds newbuf; - - /* Return early when this reader is in an erroneous state. */ - if (r->err) - return REDIS_ERR; - - /* Copy the provided buffer. */ - if (buf != NULL && len >= 1) { - /* Destroy internal buffer when it is empty and is quite large. */ - if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { - sdsfree(r->buf); - r->buf = sdsempty(); - r->pos = 0; - - /* r->buf should not be NULL since we just free'd a larger one. */ - assert(r->buf != NULL); - } - - newbuf = sdscatlen(r->buf,buf,len); - if (newbuf == NULL) { - __redisReaderSetErrorOOM(r); - return REDIS_ERR; - } - - r->buf = newbuf; - r->len = sdslen(r->buf); - } - - return REDIS_OK; -} - -int redisReaderGetReply(redisReader *r, void **reply) { - /* Default target pointer to NULL. */ - if (reply != NULL) - *reply = NULL; - - /* Return early when this reader is in an erroneous state. */ - if (r->err) - return REDIS_ERR; - - /* When the buffer is empty, there will never be a reply. */ - if (r->len == 0) - return REDIS_OK; - - /* Set first item to process when the stack is empty. */ - if (r->ridx == -1) { - r->rstack[0].type = -1; - r->rstack[0].elements = -1; - r->rstack[0].idx = -1; - r->rstack[0].obj = NULL; - r->rstack[0].parent = NULL; - r->rstack[0].privdata = r->privdata; - r->ridx = 0; - } - - /* Process items in reply. */ - while (r->ridx >= 0) - if (processItem(r) != REDIS_OK) - break; - - /* Return ASAP when an error occurred. */ - if (r->err) - return REDIS_ERR; - - /* Discard part of the buffer when we've consumed at least 1k, to avoid - * doing unnecessary calls to memmove() in sds.c. */ - if (r->pos >= 1024) { - r->buf = sdsrange(r->buf,r->pos,-1); - r->pos = 0; - r->len = sdslen(r->buf); - } - - /* Emit a reply when there is one. */ - if (r->ridx == -1) { - if (reply != NULL) - *reply = r->reply; - r->reply = NULL; - } - return REDIS_OK; -} - -/* Calculate the number of bytes needed to represent an integer as string. */ -static int intlen(int i) { - int len = 0; - if (i < 0) { - len++; - i = -i; - } - do { - len++; - i /= 10; - } while(i); - return len; -} - -/* Helper that calculates the bulk length given a certain string length. */ -static size_t bulklen(size_t len) { - return 1+intlen(len)+2+len+2; -} - -int redisvFormatCommand(char **target, const char *format, va_list ap) { - const char *c = format; - char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - sds curarg, newarg; /* current argument */ - int touched = 0; /* was the current argument touched? */ - char **curargv = NULL, **newargv = NULL; - int argc = 0; - int totlen = 0; - int j; - - /* Abort if there is not target to set */ - if (target == NULL) - return -1; - - /* Build the command string accordingly to protocol */ - curarg = sdsempty(); - if (curarg == NULL) - return -1; - - while(*c != '\0') { - if (*c != '%' || c[1] == '\0') { - if (*c == ' ') { - if (touched) { - newargv = realloc(curargv,sizeof(char*)*(argc+1)); - if (newargv == NULL) goto err; - curargv = newargv; - curargv[argc++] = curarg; - totlen += bulklen(sdslen(curarg)); - - /* curarg is put in argv so it can be overwritten. */ - curarg = sdsempty(); - if (curarg == NULL) goto err; - touched = 0; - } - } else { - newarg = sdscatlen(curarg,c,1); - if (newarg == NULL) goto err; - curarg = newarg; - touched = 1; - } - } else { - char *arg; - size_t size; - - /* Set newarg so it can be checked even if it is not touched. */ - newarg = curarg; - - switch(c[1]) { - case 's': - arg = va_arg(ap,char*); - size = strlen(arg); - if (size > 0) - newarg = sdscatlen(curarg,arg,size); - break; - case 'b': - arg = va_arg(ap,char*); - size = va_arg(ap,size_t); - if (size > 0) - newarg = sdscatlen(curarg,arg,size); - break; - case '%': - newarg = sdscat(curarg,"%"); - break; - default: - /* Try to detect printf format */ - { - static const char intfmts[] = "diouxX"; - char _format[16]; - const char *_p = c+1; - size_t _l = 0; - va_list _cpy; - - /* Flags */ - if (*_p != '\0' && *_p == '#') _p++; - if (*_p != '\0' && *_p == '0') _p++; - if (*_p != '\0' && *_p == '-') _p++; - if (*_p != '\0' && *_p == ' ') _p++; - if (*_p != '\0' && *_p == '+') _p++; - - /* Field width */ - while (*_p != '\0' && isdigit(*_p)) _p++; - - /* Precision */ - if (*_p == '.') { - _p++; - while (*_p != '\0' && isdigit(*_p)) _p++; - } - - /* Copy va_list before consuming with va_arg */ - va_copy(_cpy,ap); - - /* Integer conversion (without modifiers) */ - if (strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); - goto fmt_valid; - } - - /* Double conversion (without modifiers) */ - if (strchr("eEfFgGaA",*_p) != NULL) { - va_arg(ap,double); - goto fmt_valid; - } - - /* Size: char */ - if (_p[0] == 'h' && _p[1] == 'h') { - _p += 2; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); /* char gets promoted to int */ - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: short */ - if (_p[0] == 'h') { - _p += 1; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); /* short gets promoted to int */ - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: long long */ - if (_p[0] == 'l' && _p[1] == 'l') { - _p += 2; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,long long); - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: long */ - if (_p[0] == 'l') { - _p += 1; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,long); - goto fmt_valid; - } - goto fmt_invalid; - } - - fmt_invalid: - va_end(_cpy); - goto err; - - fmt_valid: - _l = (_p+1)-c; - if (_l < sizeof(_format)-2) { - memcpy(_format,c,_l); - _format[_l] = '\0'; - newarg = sdscatvprintf(curarg,_format,_cpy); - - /* Update current position (note: outer blocks - * increment c twice so compensate here) */ - c = _p-1; - } - - va_end(_cpy); - break; - } - } - - if (newarg == NULL) goto err; - curarg = newarg; - - touched = 1; - c++; - } - c++; - } - - /* Add the last argument if needed */ - if (touched) { - newargv = realloc(curargv,sizeof(char*)*(argc+1)); - if (newargv == NULL) goto err; - curargv = newargv; - curargv[argc++] = curarg; - totlen += bulklen(sdslen(curarg)); - } else { - sdsfree(curarg); - } - - /* Clear curarg because it was put in curargv or was free'd. */ - curarg = NULL; - - /* Add bytes needed to hold multi bulk count */ - totlen += 1+intlen(argc)+2; - - /* Build the command at protocol level */ - cmd = malloc(totlen+1); - if (cmd == NULL) goto err; - - pos = sprintf(cmd,"*%d\r\n",argc); - for (j = 0; j < argc; j++) { - pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); - memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); - pos += sdslen(curargv[j]); - sdsfree(curargv[j]); - cmd[pos++] = '\r'; - cmd[pos++] = '\n'; - } - assert(pos == totlen); - cmd[pos] = '\0'; - - free(curargv); - *target = cmd; - return totlen; - -err: - while(argc--) - sdsfree(curargv[argc]); - free(curargv); - - if (curarg != NULL) - sdsfree(curarg); - - /* No need to check cmd since it is the last statement that can fail, - * but do it anyway to be as defensive as possible. */ - if (cmd != NULL) - free(cmd); - - return -1; -} - -/* Format a command according to the Redis protocol. This function - * takes a format similar to printf: - * - * %s represents a C null terminated string you want to interpolate - * %b represents a binary safe string - * - * When using %b you need to provide both the pointer to the string - * and the length in bytes as a size_t. Examples: - * - * len = redisFormatCommand(target, "GET %s", mykey); - * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); - */ -int redisFormatCommand(char **target, const char *format, ...) { - va_list ap; - int len; - va_start(ap,format); - len = redisvFormatCommand(target,format,ap); - va_end(ap); - return len; -} - -/* Format a command according to the Redis protocol. This function takes the - * number of arguments, an array with arguments and an array with their - * lengths. If the latter is set to NULL, strlen will be used to compute the - * argument lengths. - */ -int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { - char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - size_t len; - int totlen, j; - - /* Calculate number of bytes needed for the command */ - totlen = 1+intlen(argc)+2; - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - totlen += bulklen(len); - } - - /* Build the command at protocol level */ - cmd = malloc(totlen+1); - if (cmd == NULL) - return -1; - - pos = sprintf(cmd,"*%d\r\n",argc); - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - pos += sprintf(cmd+pos,"$%zu\r\n",len); - memcpy(cmd+pos,argv[j],len); - pos += len; - cmd[pos++] = '\r'; - cmd[pos++] = '\n'; - } - assert(pos == totlen); - cmd[pos] = '\0'; - - *target = cmd; - return totlen; -} - -void __redisSetError(redisContext *c, int type, const char *str) { - size_t len; - - c->err = type; - if (str != NULL) { - len = strlen(str); - len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); - memcpy(c->errstr,str,len); - c->errstr[len] = '\0'; - } else { - /* Only REDIS_ERR_IO may lack a description! */ - assert(type == REDIS_ERR_IO); - strerror_r(errno,c->errstr,sizeof(c->errstr)); - } -} - -static redisContext *redisContextInit(void) { - redisContext *c; - - c = calloc(1,sizeof(redisContext)); - if (c == NULL) - return NULL; - - c->err = 0; - c->errstr[0] = '\0'; - c->obuf = sdsempty(); - c->reader = redisReaderCreate(); - return c; -} - -void redisFree(redisContext *c) { - if (c->fd > 0) - close(c->fd); - if (c->obuf != NULL) - sdsfree(c->obuf); - if (c->reader != NULL) - redisReaderFree(c->reader); - free(c); -} - -/* Connect to a Redis instance. On error the field error in the returned - * context will be set to the return value of the error function. - * When no set of reply functions is given, the default set will be used. */ -redisContext *redisConnect(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; -} - -redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,&tv); - return c; -} - -redisContext *redisConnectNonBlock(const char *ip, int port) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectTcp(c,ip,port,NULL); - return c; -} - -redisContext *redisConnectUnix(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; -} - -redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,&tv); - return c; -} - -redisContext *redisConnectUnixNonBlock(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; -} - -/* Set read/write timeout on a blocking socket. */ -int redisSetTimeout(redisContext *c, const struct timeval tv) { - if (c->flags & REDIS_BLOCK) - return redisContextSetTimeout(c,tv); - return REDIS_ERR; -} - -/* Enable connection KeepAlive. */ -int redisEnableKeepAlive(redisContext *c) { - if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) - return REDIS_ERR; - return REDIS_OK; -} - -/* Use this function to handle a read event on the descriptor. It will try - * and read some bytes from the socket and feed them to the reply parser. - * - * After this function is called, you may use redisContextReadReply to - * see if there is a reply available. */ -int redisBufferRead(redisContext *c) { - char buf[1024*16]; - int nread; - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - nread = read(c->fd,buf,sizeof(buf)); - if (nread == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - } else if (nread == 0) { - __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); - return REDIS_ERR; - } else { - if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - } - return REDIS_OK; -} - -/* Write the output buffer to the socket. - * - * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was - * succesfully written to the socket. When the buffer is empty after the - * write operation, "done" is set to 1 (if given). - * - * Returns REDIS_ERR if an error occured trying to write and sets - * c->errstr to hold the appropriate error string. - */ -int redisBufferWrite(redisContext *c, int *done) { - int nwritten; - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - if (sdslen(c->obuf) > 0) { - nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); - if (nwritten == -1) { - if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - } else if (nwritten > 0) { - if (nwritten == (signed)sdslen(c->obuf)) { - sdsfree(c->obuf); - c->obuf = sdsempty(); - } else { - c->obuf = sdsrange(c->obuf,nwritten,-1); - } - } - } - if (done != NULL) *done = (sdslen(c->obuf) == 0); - return REDIS_OK; -} - -/* Internal helper function to try and get a reply from the reader, - * or set an error in the context otherwise. */ -int redisGetReplyFromReader(redisContext *c, void **reply) { - if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - return REDIS_OK; -} - -int redisGetReply(redisContext *c, void **reply) { - int wdone = 0; - void *aux = NULL; - - /* Try to read pending replies */ - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - - /* For the blocking context, flush output buffer and read reply */ - if (aux == NULL && c->flags & REDIS_BLOCK) { - /* Write until done */ - do { - if (redisBufferWrite(c,&wdone) == REDIS_ERR) - return REDIS_ERR; - } while (!wdone); - - /* Read until there is a reply */ - do { - if (redisBufferRead(c) == REDIS_ERR) - return REDIS_ERR; - if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - } while (aux == NULL); - } - - /* Set reply object */ - if (reply != NULL) *reply = aux; - return REDIS_OK; -} - - -/* Helper function for the redisAppendCommand* family of functions. - * - * Write a formatted command to the output buffer. When this family - * is used, you need to call redisGetReply yourself to retrieve - * the reply (or replies in pub/sub). - */ -int __redisAppendCommand(redisContext *c, char *cmd, size_t len) { - sds newbuf; - - newbuf = sdscatlen(c->obuf,cmd,len); - if (newbuf == NULL) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - c->obuf = newbuf; - return REDIS_OK; -} - -int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { - char *cmd; - int len; - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - free(cmd); - return REDIS_ERR; - } - - free(cmd); - return REDIS_OK; -} - -int redisAppendCommand(redisContext *c, const char *format, ...) { - va_list ap; - int ret; - - va_start(ap,format); - ret = redisvAppendCommand(c,format,ap); - va_end(ap); - return ret; -} - -int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - char *cmd; - int len; - - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - free(cmd); - return REDIS_ERR; - } - - free(cmd); - return REDIS_OK; -} - -/* Helper function for the redisCommand* family of functions. - * - * Write a formatted command to the output buffer. If the given context is - * blocking, immediately read the reply into the "reply" pointer. When the - * context is non-blocking, the "reply" pointer will not be used and the - * command is simply appended to the write buffer. - * - * Returns the reply when a reply was succesfully retrieved. Returns NULL - * otherwise. When NULL is returned in a blocking context, the error field - * in the context will be set. - */ -static void *__redisBlockForReply(redisContext *c) { - void *reply; - - if (c->flags & REDIS_BLOCK) { - if (redisGetReply(c,&reply) != REDIS_OK) - return NULL; - return reply; - } - return NULL; -} - -void *redisvCommand(redisContext *c, const char *format, va_list ap) { - if (redisvAppendCommand(c,format,ap) != REDIS_OK) - return NULL; - return __redisBlockForReply(c); -} - -void *redisCommand(redisContext *c, const char *format, ...) { - va_list ap; - void *reply = NULL; - va_start(ap,format); - reply = redisvCommand(c,format,ap); - va_end(ap); - return reply; -} - -void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) - return NULL; - return __redisBlockForReply(c); -} diff --git a/_hiredis/hiredis.h b/_hiredis/hiredis.h deleted file mode 100644 index c65098b..0000000 --- a/_hiredis/hiredis.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __HIREDIS_H -#define __HIREDIS_H -#include /* for size_t */ -#include /* for va_list */ -#include /* for struct timeval */ - -#define HIREDIS_MAJOR 0 -#define HIREDIS_MINOR 11 -#define HIREDIS_PATCH 0 - -#define REDIS_ERR -1 -#define REDIS_OK 0 - -/* When an error occurs, the err flag in a context is set to hold the type of - * error that occured. REDIS_ERR_IO means there was an I/O error and you - * should use the "errno" variable to find out what is wrong. - * For other values, the "errstr" field will hold a description. */ -#define REDIS_ERR_IO 1 /* Error in read or write */ -#define REDIS_ERR_EOF 3 /* End of file */ -#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ -#define REDIS_ERR_OOM 5 /* Out of memory */ -#define REDIS_ERR_OTHER 2 /* Everything else... */ - -/* Connection type can be blocking or non-blocking and is set in the - * least significant bit of the flags field in redisContext. */ -#define REDIS_BLOCK 0x1 - -/* Connection may be disconnected before being free'd. The second bit - * in the flags field is set when the context is connected. */ -#define REDIS_CONNECTED 0x2 - -/* The async API might try to disconnect cleanly and flush the output - * buffer and read all subsequent replies before disconnecting. - * This flag means no new commands can come in and the connection - * should be terminated once all replies have been read. */ -#define REDIS_DISCONNECTING 0x4 - -/* Flag specific to the async API which means that the context should be clean - * up as soon as possible. */ -#define REDIS_FREEING 0x8 - -/* Flag that is set when an async callback is executed. */ -#define REDIS_IN_CALLBACK 0x10 - -/* Flag that is set when the async context has one or more subscriptions. */ -#define REDIS_SUBSCRIBED 0x20 - -/* Flag that is set when monitor mode is active */ -#define REDIS_MONITORING 0x40 - -#define REDIS_REPLY_STRING 1 -#define REDIS_REPLY_ARRAY 2 -#define REDIS_REPLY_INTEGER 3 -#define REDIS_REPLY_NIL 4 -#define REDIS_REPLY_STATUS 5 -#define REDIS_REPLY_ERROR 6 - -#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ - -#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ - -#ifdef __cplusplus -extern "C" { -#endif - -/* This is the reply object returned by redisCommand() */ -typedef struct redisReply { - int type; /* REDIS_REPLY_* */ - long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ - int len; /* Length of string */ - char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ - size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ - struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ -} redisReply; - -typedef struct redisReadTask { - int type; - int elements; /* number of elements in multibulk container */ - int idx; /* index in parent (array) object */ - void *obj; /* holds user-generated value for a read task */ - struct redisReadTask *parent; /* parent task */ - void *privdata; /* user-settable arbitrary field */ -} redisReadTask; - -typedef struct redisReplyObjectFunctions { - void *(*createString)(const redisReadTask*, char*, size_t); - void *(*createArray)(const redisReadTask*, int); - void *(*createInteger)(const redisReadTask*, long long); - void *(*createNil)(const redisReadTask*); - void (*freeObject)(void*); -} redisReplyObjectFunctions; - -/* State for the protocol parser */ -typedef struct redisReader { - int err; /* Error flags, 0 when there is no error */ - char errstr[128]; /* String representation of error when applicable */ - - char *buf; /* Read buffer */ - size_t pos; /* Buffer cursor */ - size_t len; /* Buffer length */ - size_t maxbuf; /* Max length of unused buffer */ - - redisReadTask rstack[9]; - int ridx; /* Index of current read task */ - void *reply; /* Temporary reply pointer */ - - redisReplyObjectFunctions *fn; - void *privdata; -} redisReader; - -/* Public API for the protocol parser. */ -redisReader *redisReaderCreate(void); -void redisReaderFree(redisReader *r); -int redisReaderFeed(redisReader *r, const char *buf, size_t len); -int redisReaderGetReply(redisReader *r, void **reply); - -/* Backwards compatibility, can be removed on big version bump. */ -#define redisReplyReaderCreate redisReaderCreate -#define redisReplyReaderFree redisReaderFree -#define redisReplyReaderFeed redisReaderFeed -#define redisReplyReaderGetReply redisReaderGetReply -#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) -#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) -#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) - -/* Function to free the reply objects hiredis returns by default. */ -void freeReplyObject(void *reply); - -/* Functions to format a command according to the protocol. */ -int redisvFormatCommand(char **target, const char *format, va_list ap); -int redisFormatCommand(char **target, const char *format, ...); -int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); - -/* Context for a connection to Redis */ -typedef struct redisContext { - int err; /* Error flags, 0 when there is no error */ - char errstr[128]; /* String representation of error when applicable */ - int fd; - int flags; - char *obuf; /* Write buffer */ - redisReader *reader; /* Protocol reader */ -} redisContext; - -redisContext *redisConnect(const char *ip, int port); -redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); -redisContext *redisConnectNonBlock(const char *ip, int port); -redisContext *redisConnectUnix(const char *path); -redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); -redisContext *redisConnectUnixNonBlock(const char *path); -int redisSetTimeout(redisContext *c, const struct timeval tv); -int redisEnableKeepAlive(redisContext *c); -void redisFree(redisContext *c); -int redisBufferRead(redisContext *c); -int redisBufferWrite(redisContext *c, int *done); - -/* In a blocking context, this function first checks if there are unconsumed - * replies to return and returns one if so. Otherwise, it flushes the output - * buffer to the socket and reads until it has a reply. In a non-blocking - * context, it will return unconsumed replies until there are no more. */ -int redisGetReply(redisContext *c, void **reply); -int redisGetReplyFromReader(redisContext *c, void **reply); - -/* Write a command to the output buffer. Use these functions in blocking mode - * to get a pipeline of commands. */ -int redisvAppendCommand(redisContext *c, const char *format, va_list ap); -int redisAppendCommand(redisContext *c, const char *format, ...); -int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); - -/* Issue a command to Redis. In a blocking context, it is identical to calling - * redisAppendCommand, followed by redisGetReply. The function will return - * NULL if there was an error in performing the request, otherwise it will - * return the reply. In a non-blocking context, it is identical to calling - * only redisAppendCommand and will always return NULL. */ -void *redisvCommand(redisContext *c, const char *format, va_list ap); -void *redisCommand(redisContext *c, const char *format, ...); -void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/_hiredis/net.c b/_hiredis/net.c deleted file mode 100644 index 603699c..0000000 --- a/_hiredis/net.c +++ /dev/null @@ -1,339 +0,0 @@ -/* Extracted from anet.c to work properly with Hiredis error reporting. - * - * Copyright (c) 2006-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "net.h" -#include "sds.h" - -/* Defined in hiredis.c */ -void __redisSetError(redisContext *c, int type, const char *str); - -static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { - char buf[128] = { 0 }; - size_t len = 0; - - if (prefix != NULL) - len = snprintf(buf,sizeof(buf),"%s: ",prefix); - strerror_r(errno,buf+len,sizeof(buf)-len); - __redisSetError(c,type,buf); -} - -static int redisSetReuseAddr(redisContext *c, int fd) { - int on = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - close(fd); - return REDIS_ERR; - } - return REDIS_OK; -} - -static int redisCreateSocket(redisContext *c, int type) { - int s; - if ((s = socket(type, SOCK_STREAM, 0)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - if (type == AF_INET) { - if (redisSetReuseAddr(c,s) == REDIS_ERR) { - return REDIS_ERR; - } - } - return s; -} - -static int redisSetBlocking(redisContext *c, int fd, int blocking) { - int flags; - - /* Set the socket nonblocking. - * Note that fcntl(2) for F_GETFL and F_SETFL can't be - * interrupted by a signal. */ - if ((flags = fcntl(fd, F_GETFL)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); - close(fd); - return REDIS_ERR; - } - - if (blocking) - flags &= ~O_NONBLOCK; - else - flags |= O_NONBLOCK; - - if (fcntl(fd, F_SETFL, flags) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); - close(fd); - return REDIS_ERR; - } - return REDIS_OK; -} - -int redisKeepAlive(redisContext *c, int interval) { - int val = 1; - int fd = c->fd; - - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - -#ifdef _OSX - val = interval; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } -#else - val = interval; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - - val = interval/3; - if (val == 0) val = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } - - val = 3; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } -#endif - - return REDIS_OK; -} - -static int redisSetTcpNoDelay(redisContext *c, int fd) { - int yes = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); - close(fd); - return REDIS_ERR; - } - return REDIS_OK; -} - -#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) - -static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) { - struct pollfd wfd[1]; - long msec; - - msec = -1; - wfd[0].fd = fd; - wfd[0].events = POLLOUT; - - /* Only use timeout when not NULL. */ - if (timeout != NULL) { - if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - close(fd); - return REDIS_ERR; - } - - msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); - - if (msec < 0 || msec > INT_MAX) { - msec = INT_MAX; - } - } - - if (errno == EINPROGRESS) { - int res; - - if ((res = poll(wfd, 1, msec)) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); - close(fd); - return REDIS_ERR; - } else if (res == 0) { - errno = ETIMEDOUT; - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - close(fd); - return REDIS_ERR; - } - - if (redisCheckSocketError(c, fd) != REDIS_OK) - return REDIS_ERR; - - return REDIS_OK; - } - - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - close(fd); - return REDIS_ERR; -} - -int redisCheckSocketError(redisContext *c, int fd) { - int err = 0; - socklen_t errlen = sizeof(err); - - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); - close(fd); - return REDIS_ERR; - } - - if (err) { - errno = err; - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - close(fd); - return REDIS_ERR; - } - - return REDIS_OK; -} - -int redisContextSetTimeout(redisContext *c, const struct timeval tv) { - if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); - return REDIS_ERR; - } - if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); - return REDIS_ERR; - } - return REDIS_OK; -} - -int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout) { - int s, rv; - char _port[6]; /* strlen("65535"); */ - struct addrinfo hints, *servinfo, *p; - int blocking = (c->flags & REDIS_BLOCK); - - snprintf(_port, 6, "%d", port); - memset(&hints,0,sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - /* Try with IPv6 if no IPv4 address was found. We do it in this order since - * in a Redis client you can't afford to test if you have IPv6 connectivity - * as this would add latency to every connect. Otherwise a more sensible - * route could be: Use IPv6 if both addresses are available and there is IPv6 - * connectivity. */ - if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { - hints.ai_family = AF_INET6; - if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { - __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); - return REDIS_ERR; - } - } - for (p = servinfo; p != NULL; p = p->ai_next) { - if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) - continue; - - if (redisSetBlocking(c,s,0) != REDIS_OK) - goto error; - if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { - if (errno == EHOSTUNREACH) { - close(s); - continue; - } else if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ - } else { - if (redisContextWaitReady(c,s,timeout) != REDIS_OK) - goto error; - } - } - if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) - goto error; - if (redisSetTcpNoDelay(c,s) != REDIS_OK) - goto error; - - c->fd = s; - c->flags |= REDIS_CONNECTED; - rv = REDIS_OK; - goto end; - } - if (p == NULL) { - char buf[128]; - snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); - __redisSetError(c,REDIS_ERR_OTHER,buf); - goto error; - } - -error: - rv = REDIS_ERR; -end: - freeaddrinfo(servinfo); - return rv; // Need to return REDIS_OK if alright -} - -int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { - int s; - int blocking = (c->flags & REDIS_BLOCK); - struct sockaddr_un sa; - - if ((s = redisCreateSocket(c,AF_LOCAL)) < 0) - return REDIS_ERR; - if (redisSetBlocking(c,s,0) != REDIS_OK) - return REDIS_ERR; - - sa.sun_family = AF_LOCAL; - strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); - if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { - if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ - } else { - if (redisContextWaitReady(c,s,timeout) != REDIS_OK) - return REDIS_ERR; - } - } - - /* Reset socket to be blocking after connect(2). */ - if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) - return REDIS_ERR; - - c->fd = s; - c->flags |= REDIS_CONNECTED; - return REDIS_OK; -} diff --git a/_hiredis/net.h b/_hiredis/net.h deleted file mode 100644 index 94b76f5..0000000 --- a/_hiredis/net.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Extracted from anet.c to work properly with Hiredis error reporting. - * - * Copyright (c) 2006-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __NET_H -#define __NET_H - -#include "hiredis.h" - -#if defined(__sun) -#define AF_LOCAL AF_UNIX -#endif - -int redisCheckSocketError(redisContext *c, int fd); -int redisContextSetTimeout(redisContext *c, const struct timeval tv); -int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); -int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); -int redisKeepAlive(redisContext *c, int interval); - -#endif diff --git a/_hiredis/sds.c b/_hiredis/sds.c deleted file mode 100644 index 9226799..0000000 --- a/_hiredis/sds.c +++ /dev/null @@ -1,606 +0,0 @@ -/* SDSLib, A C dynamic strings library - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include "sds.h" - -#ifdef SDS_ABORT_ON_OOM -static void sdsOomAbort(void) { - fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n"); - abort(); -} -#endif - -sds sdsnewlen(const void *init, size_t initlen) { - struct sdshdr *sh; - - sh = malloc(sizeof(struct sdshdr)+initlen+1); -#ifdef SDS_ABORT_ON_OOM - if (sh == NULL) sdsOomAbort(); -#else - if (sh == NULL) return NULL; -#endif - sh->len = initlen; - sh->free = 0; - if (initlen) { - if (init) memcpy(sh->buf, init, initlen); - else memset(sh->buf,0,initlen); - } - sh->buf[initlen] = '\0'; - return (char*)sh->buf; -} - -sds sdsempty(void) { - return sdsnewlen("",0); -} - -sds sdsnew(const char *init) { - size_t initlen = (init == NULL) ? 0 : strlen(init); - return sdsnewlen(init, initlen); -} - -sds sdsdup(const sds s) { - return sdsnewlen(s, sdslen(s)); -} - -void sdsfree(sds s) { - if (s == NULL) return; - free(s-sizeof(struct sdshdr)); -} - -void sdsupdatelen(sds s) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); - int reallen = strlen(s); - sh->free += (sh->len-reallen); - sh->len = reallen; -} - -static sds sdsMakeRoomFor(sds s, size_t addlen) { - struct sdshdr *sh, *newsh; - size_t free = sdsavail(s); - size_t len, newlen; - - if (free >= addlen) return s; - len = sdslen(s); - sh = (void*) (s-(sizeof(struct sdshdr))); - newlen = (len+addlen)*2; - newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1); -#ifdef SDS_ABORT_ON_OOM - if (newsh == NULL) sdsOomAbort(); -#else - if (newsh == NULL) return NULL; -#endif - - newsh->free = newlen - len; - return newsh->buf; -} - -/* Grow the sds to have the specified length. Bytes that were not part of - * the original length of the sds will be set to zero. */ -sds sdsgrowzero(sds s, size_t len) { - struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); - size_t totlen, curlen = sh->len; - - if (len <= curlen) return s; - s = sdsMakeRoomFor(s,len-curlen); - if (s == NULL) return NULL; - - /* Make sure added region doesn't contain garbage */ - sh = (void*)(s-(sizeof(struct sdshdr))); - memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ - totlen = sh->len+sh->free; - sh->len = len; - sh->free = totlen-sh->len; - return s; -} - -sds sdscatlen(sds s, const void *t, size_t len) { - struct sdshdr *sh; - size_t curlen = sdslen(s); - - s = sdsMakeRoomFor(s,len); - if (s == NULL) return NULL; - sh = (void*) (s-(sizeof(struct sdshdr))); - memcpy(s+curlen, t, len); - sh->len = curlen+len; - sh->free = sh->free-len; - s[curlen+len] = '\0'; - return s; -} - -sds sdscat(sds s, const char *t) { - return sdscatlen(s, t, strlen(t)); -} - -sds sdscpylen(sds s, char *t, size_t len) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); - size_t totlen = sh->free+sh->len; - - if (totlen < len) { - s = sdsMakeRoomFor(s,len-sh->len); - if (s == NULL) return NULL; - sh = (void*) (s-(sizeof(struct sdshdr))); - totlen = sh->free+sh->len; - } - memcpy(s, t, len); - s[len] = '\0'; - sh->len = len; - sh->free = totlen-len; - return s; -} - -sds sdscpy(sds s, char *t) { - return sdscpylen(s, t, strlen(t)); -} - -sds sdscatvprintf(sds s, const char *fmt, va_list ap) { - va_list cpy; - char *buf, *t; - size_t buflen = 16; - - while(1) { - buf = malloc(buflen); -#ifdef SDS_ABORT_ON_OOM - if (buf == NULL) sdsOomAbort(); -#else - if (buf == NULL) return NULL; -#endif - buf[buflen-2] = '\0'; - va_copy(cpy,ap); - vsnprintf(buf, buflen, fmt, cpy); - va_end(cpy); - if (buf[buflen-2] != '\0') { - free(buf); - buflen *= 2; - continue; - } - break; - } - t = sdscat(s, buf); - free(buf); - return t; -} - -sds sdscatprintf(sds s, const char *fmt, ...) { - va_list ap; - char *t; - va_start(ap, fmt); - t = sdscatvprintf(s,fmt,ap); - va_end(ap); - return t; -} - -sds sdstrim(sds s, const char *cset) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); - char *start, *end, *sp, *ep; - size_t len; - - sp = start = s; - ep = end = s+sdslen(s)-1; - while(sp <= end && strchr(cset, *sp)) sp++; - while(ep > start && strchr(cset, *ep)) ep--; - len = (sp > ep) ? 0 : ((ep-sp)+1); - if (sh->buf != sp) memmove(sh->buf, sp, len); - sh->buf[len] = '\0'; - sh->free = sh->free+(sh->len-len); - sh->len = len; - return s; -} - -sds sdsrange(sds s, int start, int end) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); - size_t newlen, len = sdslen(s); - - if (len == 0) return s; - if (start < 0) { - start = len+start; - if (start < 0) start = 0; - } - if (end < 0) { - end = len+end; - if (end < 0) end = 0; - } - newlen = (start > end) ? 0 : (end-start)+1; - if (newlen != 0) { - if (start >= (signed)len) { - newlen = 0; - } else if (end >= (signed)len) { - end = len-1; - newlen = (start > end) ? 0 : (end-start)+1; - } - } else { - start = 0; - } - if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); - sh->buf[newlen] = 0; - sh->free = sh->free+(sh->len-newlen); - sh->len = newlen; - return s; -} - -void sdstolower(sds s) { - int len = sdslen(s), j; - - for (j = 0; j < len; j++) s[j] = tolower(s[j]); -} - -void sdstoupper(sds s) { - int len = sdslen(s), j; - - for (j = 0; j < len; j++) s[j] = toupper(s[j]); -} - -int sdscmp(sds s1, sds s2) { - size_t l1, l2, minlen; - int cmp; - - l1 = sdslen(s1); - l2 = sdslen(s2); - minlen = (l1 < l2) ? l1 : l2; - cmp = memcmp(s1,s2,minlen); - if (cmp == 0) return l1-l2; - return cmp; -} - -/* Split 's' with separator in 'sep'. An array - * of sds strings is returned. *count will be set - * by reference to the number of tokens returned. - * - * On out of memory, zero length string, zero length - * separator, NULL is returned. - * - * Note that 'sep' is able to split a string using - * a multi-character separator. For example - * sdssplit("foo_-_bar","_-_"); will return two - * elements "foo" and "bar". - * - * This version of the function is binary-safe but - * requires length arguments. sdssplit() is just the - * same function but for zero-terminated strings. - */ -sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) { - int elements = 0, slots = 5, start = 0, j; - - sds *tokens = malloc(sizeof(sds)*slots); -#ifdef SDS_ABORT_ON_OOM - if (tokens == NULL) sdsOomAbort(); -#endif - if (seplen < 1 || len < 0 || tokens == NULL) return NULL; - if (len == 0) { - *count = 0; - return tokens; - } - for (j = 0; j < (len-(seplen-1)); j++) { - /* make sure there is room for the next element and the final one */ - if (slots < elements+2) { - sds *newtokens; - - slots *= 2; - newtokens = realloc(tokens,sizeof(sds)*slots); - if (newtokens == NULL) { -#ifdef SDS_ABORT_ON_OOM - sdsOomAbort(); -#else - goto cleanup; -#endif - } - tokens = newtokens; - } - /* search the separator */ - if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { - tokens[elements] = sdsnewlen(s+start,j-start); - if (tokens[elements] == NULL) { -#ifdef SDS_ABORT_ON_OOM - sdsOomAbort(); -#else - goto cleanup; -#endif - } - elements++; - start = j+seplen; - j = j+seplen-1; /* skip the separator */ - } - } - /* Add the final element. We are sure there is room in the tokens array. */ - tokens[elements] = sdsnewlen(s+start,len-start); - if (tokens[elements] == NULL) { -#ifdef SDS_ABORT_ON_OOM - sdsOomAbort(); -#else - goto cleanup; -#endif - } - elements++; - *count = elements; - return tokens; - -#ifndef SDS_ABORT_ON_OOM -cleanup: - { - int i; - for (i = 0; i < elements; i++) sdsfree(tokens[i]); - free(tokens); - return NULL; - } -#endif -} - -void sdsfreesplitres(sds *tokens, int count) { - if (!tokens) return; - while(count--) - sdsfree(tokens[count]); - free(tokens); -} - -sds sdsfromlonglong(long long value) { - char buf[32], *p; - unsigned long long v; - - v = (value < 0) ? -value : value; - p = buf+31; /* point to the last character */ - do { - *p-- = '0'+(v%10); - v /= 10; - } while(v); - if (value < 0) *p-- = '-'; - p++; - return sdsnewlen(p,32-(p-buf)); -} - -sds sdscatrepr(sds s, char *p, size_t len) { - s = sdscatlen(s,"\"",1); - if (s == NULL) return NULL; - - while(len--) { - switch(*p) { - case '\\': - case '"': - s = sdscatprintf(s,"\\%c",*p); - break; - case '\n': s = sdscatlen(s,"\\n",2); break; - case '\r': s = sdscatlen(s,"\\r",2); break; - case '\t': s = sdscatlen(s,"\\t",2); break; - case '\a': s = sdscatlen(s,"\\a",2); break; - case '\b': s = sdscatlen(s,"\\b",2); break; - default: - if (isprint(*p)) - s = sdscatprintf(s,"%c",*p); - else - s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); - break; - } - p++; - if (s == NULL) return NULL; - } - return sdscatlen(s,"\"",1); -} - -/* Split a line into arguments, where every argument can be in the - * following programming-language REPL-alike form: - * - * foo bar "newline are supported\n" and "\xff\x00otherstuff" - * - * The number of arguments is stored into *argc, and an array - * of sds is returned. The caller should sdsfree() all the returned - * strings and finally free() the array itself. - * - * Note that sdscatrepr() is able to convert back a string into - * a quoted string in the same format sdssplitargs() is able to parse. - */ -sds *sdssplitargs(char *line, int *argc) { - char *p = line; - char *current = NULL; - char **vector = NULL, **_vector = NULL; - - *argc = 0; - while(1) { - /* skip blanks */ - while(*p && isspace(*p)) p++; - if (*p) { - /* get a token */ - int inq=0; /* set to 1 if we are in "quotes" */ - int done=0; - - if (current == NULL) { - current = sdsempty(); - if (current == NULL) goto err; - } - - while(!done) { - if (inq) { - if (*p == '\\' && *(p+1)) { - char c; - - p++; - switch(*p) { - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - case 'b': c = '\b'; break; - case 'a': c = '\a'; break; - default: c = *p; break; - } - current = sdscatlen(current,&c,1); - } else if (*p == '"') { - /* closing quote must be followed by a space */ - if (*(p+1) && !isspace(*(p+1))) goto err; - done=1; - } else if (!*p) { - /* unterminated quotes */ - goto err; - } else { - current = sdscatlen(current,p,1); - } - } else { - switch(*p) { - case ' ': - case '\n': - case '\r': - case '\t': - case '\0': - done=1; - break; - case '"': - inq=1; - break; - default: - current = sdscatlen(current,p,1); - break; - } - } - if (*p) p++; - if (current == NULL) goto err; - } - /* add the token to the vector */ - _vector = realloc(vector,((*argc)+1)*sizeof(char*)); - if (_vector == NULL) goto err; - - vector = _vector; - vector[*argc] = current; - (*argc)++; - current = NULL; - } else { - return vector; - } - } - -err: - while((*argc)--) - sdsfree(vector[*argc]); - if (vector != NULL) free(vector); - if (current != NULL) sdsfree(current); - return NULL; -} - -#ifdef SDS_TEST_MAIN -#include - -int __failed_tests = 0; -int __test_num = 0; -#define test_cond(descr,_c) do { \ - __test_num++; printf("%d - %s: ", __test_num, descr); \ - if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \ -} while(0); -#define test_report() do { \ - printf("%d tests, %d passed, %d failed\n", __test_num, \ - __test_num-__failed_tests, __failed_tests); \ - if (__failed_tests) { \ - printf("=== WARNING === We have failed tests here...\n"); \ - } \ -} while(0); - -int main(void) { - { - sds x = sdsnew("foo"), y; - - test_cond("Create a string and obtain the length", - sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) - - sdsfree(x); - x = sdsnewlen("foo",2); - test_cond("Create a string with specified length", - sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) - - x = sdscat(x,"bar"); - test_cond("Strings concatenation", - sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); - - x = sdscpy(x,"a"); - test_cond("sdscpy() against an originally longer string", - sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) - - x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); - test_cond("sdscpy() against an originally shorter string", - sdslen(x) == 33 && - memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) - - sdsfree(x); - x = sdscatprintf(sdsempty(),"%d",123); - test_cond("sdscatprintf() seems working in the base case", - sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) - - sdsfree(x); - x = sdstrim(sdsnew("xxciaoyyy"),"xy"); - test_cond("sdstrim() correctly trims characters", - sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) - - y = sdsrange(sdsdup(x),1,1); - test_cond("sdsrange(...,1,1)", - sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) - - sdsfree(y); - y = sdsrange(sdsdup(x),1,-1); - test_cond("sdsrange(...,1,-1)", - sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - sdsfree(y); - y = sdsrange(sdsdup(x),-2,-1); - test_cond("sdsrange(...,-2,-1)", - sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) - - sdsfree(y); - y = sdsrange(sdsdup(x),2,1); - test_cond("sdsrange(...,2,1)", - sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - sdsfree(y); - y = sdsrange(sdsdup(x),1,100); - test_cond("sdsrange(...,1,100)", - sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - sdsfree(y); - y = sdsrange(sdsdup(x),100,100); - test_cond("sdsrange(...,100,100)", - sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("foo"); - y = sdsnew("foa"); - test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("bar"); - y = sdsnew("bar"); - test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) - - sdsfree(y); - sdsfree(x); - x = sdsnew("aar"); - y = sdsnew("bar"); - test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) - } - test_report() -} -#endif diff --git a/_hiredis/sds.h b/_hiredis/sds.h deleted file mode 100644 index 94f5871..0000000 --- a/_hiredis/sds.h +++ /dev/null @@ -1,88 +0,0 @@ -/* SDSLib, A C dynamic strings library - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __SDS_H -#define __SDS_H - -#include -#include - -typedef char *sds; - -struct sdshdr { - int len; - int free; - char buf[]; -}; - -static inline size_t sdslen(const sds s) { - struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); - return sh->len; -} - -static inline size_t sdsavail(const sds s) { - struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); - return sh->free; -} - -sds sdsnewlen(const void *init, size_t initlen); -sds sdsnew(const char *init); -sds sdsempty(void); -size_t sdslen(const sds s); -sds sdsdup(const sds s); -void sdsfree(sds s); -size_t sdsavail(sds s); -sds sdsgrowzero(sds s, size_t len); -sds sdscatlen(sds s, const void *t, size_t len); -sds sdscat(sds s, const char *t); -sds sdscpylen(sds s, char *t, size_t len); -sds sdscpy(sds s, char *t); - -sds sdscatvprintf(sds s, const char *fmt, va_list ap); -#ifdef __GNUC__ -sds sdscatprintf(sds s, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else -sds sdscatprintf(sds s, const char *fmt, ...); -#endif - -sds sdstrim(sds s, const char *cset); -sds sdsrange(sds s, int start, int end); -void sdsupdatelen(sds s); -int sdscmp(sds s1, sds s2); -sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count); -void sdsfreesplitres(sds *tokens, int count); -void sdstolower(sds s); -void sdstoupper(sds s); -sds sdsfromlonglong(long long value); -sds sdscatrepr(sds s, char *p, size_t len); -sds *sdssplitargs(char *line, int *argc); - -#endif diff --git a/_hiredis/test.c b/_hiredis/test.c deleted file mode 100644 index b3b806f..0000000 --- a/_hiredis/test.c +++ /dev/null @@ -1,688 +0,0 @@ -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "hiredis.h" - -enum connection_type { - CONN_TCP, - CONN_UNIX -}; - -struct config { - enum connection_type type; - - struct { - const char *host; - int port; - struct timeval timeout; - } tcp; - - struct { - const char *path; - } unix; -}; - -/* The following lines make up our testing "framework" :) */ -static int tests = 0, fails = 0; -#define test(_s) { printf("#%02d ", ++tests); printf(_s); } -#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} - -static long long usec(void) { - struct timeval tv; - gettimeofday(&tv,NULL); - return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; -} - -static redisContext *select_database(redisContext *c) { - redisReply *reply; - - /* Switch to DB 9 for testing, now that we know we can chat. */ - reply = redisCommand(c,"SELECT 9"); - assert(reply != NULL); - freeReplyObject(reply); - - /* Make sure the DB is emtpy */ - reply = redisCommand(c,"DBSIZE"); - assert(reply != NULL); - if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { - /* Awesome, DB 9 is empty and we can continue. */ - freeReplyObject(reply); - } else { - printf("Database #9 is not empty, test can not continue\n"); - exit(1); - } - - return c; -} - -static void disconnect(redisContext *c) { - redisReply *reply; - - /* Make sure we're on DB 9. */ - reply = redisCommand(c,"SELECT 9"); - assert(reply != NULL); - freeReplyObject(reply); - reply = redisCommand(c,"FLUSHDB"); - assert(reply != NULL); - freeReplyObject(reply); - - /* Free the context as well. */ - redisFree(c); -} - -static redisContext *connect(struct config config) { - redisContext *c = NULL; - - if (config.type == CONN_TCP) { - c = redisConnect(config.tcp.host, config.tcp.port); - } else if (config.type == CONN_UNIX) { - c = redisConnectUnix(config.unix.path); - } else { - assert(NULL); - } - - if (c == NULL) { - printf("Connection error: can't allocate redis context\n"); - exit(1); - } else if (c->err) { - printf("Connection error: %s\n", c->errstr); - exit(1); - } - - return select_database(c); -} - -static void test_format_commands(void) { - char *cmd; - int len; - - test("Format command without interpolation: "); - len = redisFormatCommand(&cmd,"SET foo bar"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%s string interpolation: "); - len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%s and an empty string: "); - len = redisFormatCommand(&cmd,"SET %s %s","foo",""); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(0+2)); - free(cmd); - - test("Format command with an empty string in between proper interpolations: "); - len = redisFormatCommand(&cmd,"SET %s %s","","foo"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && - len == 4+4+(3+2)+4+(0+2)+4+(3+2)); - free(cmd); - - test("Format command with %%b string interpolation: "); - len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command with %%b and an empty string: "); - len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(0+2)); - free(cmd); - - test("Format command with literal %%: "); - len = redisFormatCommand(&cmd,"SET %% %%"); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && - len == 4+4+(3+2)+4+(1+2)+4+(1+2)); - free(cmd); - - /* Vararg width depends on the type. These tests make sure that the - * width is correctly determined using the format and subsequent varargs - * can correctly be interpolated. */ -#define INTEGER_WIDTH_TEST(fmt, type) do { \ - type value = 123; \ - test("Format command with printf-delegation (" #type "): "); \ - len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ - test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ - len == 4+5+(12+2)+4+(9+2)); \ - free(cmd); \ -} while(0) - -#define FLOAT_WIDTH_TEST(type) do { \ - type value = 123.0; \ - test("Format command with printf-delegation (" #type "): "); \ - len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ - test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ - len == 4+5+(12+2)+4+(9+2)); \ - free(cmd); \ -} while(0) - - INTEGER_WIDTH_TEST("d", int); - INTEGER_WIDTH_TEST("hhd", char); - INTEGER_WIDTH_TEST("hd", short); - INTEGER_WIDTH_TEST("ld", long); - INTEGER_WIDTH_TEST("lld", long long); - INTEGER_WIDTH_TEST("u", unsigned int); - INTEGER_WIDTH_TEST("hhu", unsigned char); - INTEGER_WIDTH_TEST("hu", unsigned short); - INTEGER_WIDTH_TEST("lu", unsigned long); - INTEGER_WIDTH_TEST("llu", unsigned long long); - FLOAT_WIDTH_TEST(float); - FLOAT_WIDTH_TEST(double); - - test("Format command with invalid printf format: "); - len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); - test_cond(len == -1); - - const char *argv[3]; - argv[0] = "SET"; - argv[1] = "foo\0xxx"; - argv[2] = "bar"; - size_t lens[3] = { 3, 7, 3 }; - int argc = 3; - - test("Format command by passing argc/argv without lengths: "); - len = redisFormatCommandArgv(&cmd,argc,argv,NULL); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(3+2)+4+(3+2)); - free(cmd); - - test("Format command by passing argc/argv with lengths: "); - len = redisFormatCommandArgv(&cmd,argc,argv,lens); - test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && - len == 4+4+(3+2)+4+(7+2)+4+(3+2)); - free(cmd); -} - -static void test_reply_reader(void) { - redisReader *reader; - void *reply; - int ret; - int i; - - test("Error handling in reply parser: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"@foo\r\n",6); - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); - redisReaderFree(reader); - - /* when the reply already contains multiple items, they must be free'd - * on an error. valgrind will bark when this doesn't happen. */ - test("Memory cleanup in reply parser: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"*2\r\n",4); - redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); - redisReaderFeed(reader,(char*)"@foo\r\n",6); - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); - redisReaderFree(reader); - - test("Set error on nested multi bulks with depth > 7: "); - reader = redisReaderCreate(); - - for (i = 0; i < 9; i++) { - redisReaderFeed(reader,(char*)"*1\r\n",4); - } - - ret = redisReaderGetReply(reader,NULL); - test_cond(ret == REDIS_ERR && - strncasecmp(reader->errstr,"No support for",14) == 0); - redisReaderFree(reader); - - test("Works with NULL functions for reply: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"+OK\r\n",5); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReaderFree(reader); - - test("Works when a single newline (\\r\\n) covers two calls to feed: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"+OK\r",4); - ret = redisReaderGetReply(reader,&reply); - assert(ret == REDIS_OK && reply == NULL); - redisReaderFeed(reader,(char*)"\n",1); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReaderFree(reader); - - test("Don't reset state after protocol error: "); - reader = redisReaderCreate(); - reader->fn = NULL; - redisReaderFeed(reader,(char*)"x",1); - ret = redisReaderGetReply(reader,&reply); - assert(ret == REDIS_ERR); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && reply == NULL); - redisReaderFree(reader); - - /* Regression test for issue #45 on GitHub. */ - test("Don't do empty allocation for empty multi bulk: "); - reader = redisReaderCreate(); - redisReaderFeed(reader,(char*)"*0\r\n",4); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && - ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && - ((redisReply*)reply)->elements == 0); - freeReplyObject(reply); - redisReaderFree(reader); -} - -static void test_blocking_connection_errors(void) { - redisContext *c; - - test("Returns error when host cannot be resolved: "); - c = redisConnect((char*)"idontexist.local", 6379); - test_cond(c->err == REDIS_ERR_OTHER && - (strcmp(c->errstr,"Name or service not known") == 0 || - strcmp(c->errstr,"Can't resolve: idontexist.local") == 0 || - strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || - strcmp(c->errstr,"no address associated with name") == 0)); - redisFree(c); - - test("Returns error when the port is not open: "); - c = redisConnect((char*)"localhost", 1); - test_cond(c->err == REDIS_ERR_IO && - strcmp(c->errstr,"Connection refused") == 0); - redisFree(c); - - test("Returns error when the unix socket path doesn't accept connections: "); - c = redisConnectUnix((char*)"/tmp/idontexist.sock"); - test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ - redisFree(c); -} - -static void test_blocking_connection(struct config config) { - redisContext *c; - redisReply *reply; - - c = connect(config); - - test("Is able to deliver commands: "); - reply = redisCommand(c,"PING"); - test_cond(reply->type == REDIS_REPLY_STATUS && - strcasecmp(reply->str,"pong") == 0) - freeReplyObject(reply); - - test("Is a able to send commands verbatim: "); - reply = redisCommand(c,"SET foo bar"); - test_cond (reply->type == REDIS_REPLY_STATUS && - strcasecmp(reply->str,"ok") == 0) - freeReplyObject(reply); - - test("%%s String interpolation works: "); - reply = redisCommand(c,"SET %s %s","foo","hello world"); - freeReplyObject(reply); - reply = redisCommand(c,"GET foo"); - test_cond(reply->type == REDIS_REPLY_STRING && - strcmp(reply->str,"hello world") == 0); - freeReplyObject(reply); - - test("%%b String interpolation works: "); - reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); - freeReplyObject(reply); - reply = redisCommand(c,"GET foo"); - test_cond(reply->type == REDIS_REPLY_STRING && - memcmp(reply->str,"hello\x00world",11) == 0) - - test("Binary reply length is correct: "); - test_cond(reply->len == 11) - freeReplyObject(reply); - - test("Can parse nil replies: "); - reply = redisCommand(c,"GET nokey"); - test_cond(reply->type == REDIS_REPLY_NIL) - freeReplyObject(reply); - - /* test 7 */ - test("Can parse integer replies: "); - reply = redisCommand(c,"INCR mycounter"); - test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) - freeReplyObject(reply); - - test("Can parse multi bulk replies: "); - freeReplyObject(redisCommand(c,"LPUSH mylist foo")); - freeReplyObject(redisCommand(c,"LPUSH mylist bar")); - reply = redisCommand(c,"LRANGE mylist 0 -1"); - test_cond(reply->type == REDIS_REPLY_ARRAY && - reply->elements == 2 && - !memcmp(reply->element[0]->str,"bar",3) && - !memcmp(reply->element[1]->str,"foo",3)) - freeReplyObject(reply); - - /* m/e with multi bulk reply *before* other reply. - * specifically test ordering of reply items to parse. */ - test("Can handle nested multi bulk replies: "); - freeReplyObject(redisCommand(c,"MULTI")); - freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); - freeReplyObject(redisCommand(c,"PING")); - reply = (redisCommand(c,"EXEC")); - test_cond(reply->type == REDIS_REPLY_ARRAY && - reply->elements == 2 && - reply->element[0]->type == REDIS_REPLY_ARRAY && - reply->element[0]->elements == 2 && - !memcmp(reply->element[0]->element[0]->str,"bar",3) && - !memcmp(reply->element[0]->element[1]->str,"foo",3) && - reply->element[1]->type == REDIS_REPLY_STATUS && - strcasecmp(reply->element[1]->str,"pong") == 0); - freeReplyObject(reply); - - disconnect(c); -} - -static void test_blocking_io_errors(struct config config) { - redisContext *c; - redisReply *reply; - void *_reply; - int major, minor; - - /* Connect to target given by config. */ - c = connect(config); - { - /* Find out Redis version to determine the path for the next test */ - const char *field = "redis_version:"; - char *p, *eptr; - - reply = redisCommand(c,"INFO"); - p = strstr(reply->str,field); - major = strtol(p+strlen(field),&eptr,10); - p = eptr+1; /* char next to the first "." */ - minor = strtol(p,&eptr,10); - freeReplyObject(reply); - } - - test("Returns I/O error when the connection is lost: "); - reply = redisCommand(c,"QUIT"); - if (major >= 2 && minor > 0) { - /* > 2.0 returns OK on QUIT and read() should be issued once more - * to know the descriptor is at EOF. */ - test_cond(strcasecmp(reply->str,"OK") == 0 && - redisGetReply(c,&_reply) == REDIS_ERR); - freeReplyObject(reply); - } else { - test_cond(reply == NULL); - } - - /* On 2.0, QUIT will cause the connection to be closed immediately and - * the read(2) for the reply on QUIT will set the error to EOF. - * On >2.0, QUIT will return with OK and another read(2) needed to be - * issued to find out the socket was closed by the server. In both - * conditions, the error will be set to EOF. */ - assert(c->err == REDIS_ERR_EOF && - strcmp(c->errstr,"Server closed the connection") == 0); - redisFree(c); - - c = connect(config); - test("Returns I/O error on socket timeout: "); - struct timeval tv = { 0, 1000 }; - assert(redisSetTimeout(c,tv) == REDIS_OK); - test_cond(redisGetReply(c,&_reply) == REDIS_ERR && - c->err == REDIS_ERR_IO && errno == EAGAIN); - redisFree(c); -} - -static void test_invalid_timeout_errors(struct config config) { - redisContext *c; - - test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); - - config.tcp.timeout.tv_sec = 0; - config.tcp.timeout.tv_usec = 10000001; - - c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); - - test_cond(c->err == REDIS_ERR_IO); - - test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); - - config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; - config.tcp.timeout.tv_usec = 0; - - c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); - - test_cond(c->err == REDIS_ERR_IO); - - redisFree(c); -} - -static void test_throughput(struct config config) { - redisContext *c = connect(config); - redisReply **replies; - int i, num; - long long t1, t2; - - test("Throughput:\n"); - for (i = 0; i < 500; i++) - freeReplyObject(redisCommand(c,"LPUSH mylist foo")); - - num = 1000; - replies = malloc(sizeof(redisReply*)*num); - t1 = usec(); - for (i = 0; i < num; i++) { - replies[i] = redisCommand(c,"PING"); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); - - replies = malloc(sizeof(redisReply*)*num); - t1 = usec(); - for (i = 0; i < num; i++) { - replies[i] = redisCommand(c,"LRANGE mylist 0 499"); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); - assert(replies[i] != NULL && replies[i]->elements == 500); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); - - num = 10000; - replies = malloc(sizeof(redisReply*)*num); - for (i = 0; i < num; i++) - redisAppendCommand(c,"PING"); - t1 = usec(); - for (i = 0; i < num; i++) { - assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); - - replies = malloc(sizeof(redisReply*)*num); - for (i = 0; i < num; i++) - redisAppendCommand(c,"LRANGE mylist 0 499"); - t1 = usec(); - for (i = 0; i < num; i++) { - assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); - assert(replies[i] != NULL && replies[i]->elements == 500); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); - - disconnect(c); -} - -// static long __test_callback_flags = 0; -// static void __test_callback(redisContext *c, void *privdata) { -// ((void)c); -// /* Shift to detect execution order */ -// __test_callback_flags <<= 8; -// __test_callback_flags |= (long)privdata; -// } -// -// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { -// ((void)c); -// /* Shift to detect execution order */ -// __test_callback_flags <<= 8; -// __test_callback_flags |= (long)privdata; -// if (reply) freeReplyObject(reply); -// } -// -// static redisContext *__connect_nonblock() { -// /* Reset callback flags */ -// __test_callback_flags = 0; -// return redisConnectNonBlock("127.0.0.1", port, NULL); -// } -// -// static void test_nonblocking_connection() { -// redisContext *c; -// int wdone = 0; -// -// test("Calls command callback when command is issued: "); -// c = __connect_nonblock(); -// redisSetCommandCallback(c,__test_callback,(void*)1); -// redisCommand(c,"PING"); -// test_cond(__test_callback_flags == 1); -// redisFree(c); -// -// test("Calls disconnect callback on redisDisconnect: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)2); -// redisDisconnect(c); -// test_cond(__test_callback_flags == 2); -// redisFree(c); -// -// test("Calls disconnect callback and free callback on redisFree: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)2); -// redisSetFreeCallback(c,__test_callback,(void*)4); -// redisFree(c); -// test_cond(__test_callback_flags == ((2 << 8) | 4)); -// -// test("redisBufferWrite against empty write buffer: "); -// c = __connect_nonblock(); -// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); -// redisFree(c); -// -// test("redisBufferWrite against not yet connected fd: "); -// c = __connect_nonblock(); -// redisCommand(c,"PING"); -// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && -// strncmp(c->error,"write:",6) == 0); -// redisFree(c); -// -// test("redisBufferWrite against closed fd: "); -// c = __connect_nonblock(); -// redisCommand(c,"PING"); -// redisDisconnect(c); -// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && -// strncmp(c->error,"write:",6) == 0); -// redisFree(c); -// -// test("Process callbacks in the right sequence: "); -// c = __connect_nonblock(); -// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); -// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); -// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); -// -// /* Write output buffer */ -// wdone = 0; -// while(!wdone) { -// usleep(500); -// redisBufferWrite(c,&wdone); -// } -// -// /* Read until at least one callback is executed (the 3 replies will -// * arrive in a single packet, causing all callbacks to be executed in -// * a single pass). */ -// while(__test_callback_flags == 0) { -// assert(redisBufferRead(c) == REDIS_OK); -// redisProcessCallbacks(c); -// } -// test_cond(__test_callback_flags == 0x010203); -// redisFree(c); -// -// test("redisDisconnect executes pending callbacks with NULL reply: "); -// c = __connect_nonblock(); -// redisSetDisconnectCallback(c,__test_callback,(void*)1); -// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); -// redisDisconnect(c); -// test_cond(__test_callback_flags == 0x0201); -// redisFree(c); -// } - -int main(int argc, char **argv) { - struct config cfg = { - .tcp = { - .host = "127.0.0.1", - .port = 6379 - }, - .unix = { - .path = "/tmp/redis.sock" - } - }; - int throughput = 1; - - /* Ignore broken pipe signal (for I/O error tests). */ - signal(SIGPIPE, SIG_IGN); - - /* Parse command line options. */ - argv++; argc--; - while (argc) { - if (argc >= 2 && !strcmp(argv[0],"-h")) { - argv++; argc--; - cfg.tcp.host = argv[0]; - } else if (argc >= 2 && !strcmp(argv[0],"-p")) { - argv++; argc--; - cfg.tcp.port = atoi(argv[0]); - } else if (argc >= 2 && !strcmp(argv[0],"-s")) { - argv++; argc--; - cfg.unix.path = argv[0]; - } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { - throughput = 0; - } else { - fprintf(stderr, "Invalid argument: %s\n", argv[0]); - exit(1); - } - argv++; argc--; - } - - test_format_commands(); - test_reply_reader(); - test_blocking_connection_errors(); - - printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); - cfg.type = CONN_TCP; - test_blocking_connection(cfg); - test_blocking_io_errors(cfg); - test_invalid_timeout_errors(cfg); - if (throughput) test_throughput(cfg); - - printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); - cfg.type = CONN_UNIX; - test_blocking_connection(cfg); - test_blocking_io_errors(cfg); - if (throughput) test_throughput(cfg); - - if (fails) { - printf("*** %d TESTS FAILED ***\n", fails); - return 1; - } - - printf("ALL TESTS PASSED\n"); - return 0; -} diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..769983f --- /dev/null +++ b/commands.go @@ -0,0 +1,2939 @@ +// Copyright (c) 2013-2014 José Carlos Nieto, https://menteslibres.net/xiam +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package redis + +import ( + "errors" + "fmt" + "menteslibres.net/gosexy/to" + "strings" +) + +// If key already exists and is a string, this command appends the value at the +// end of the string. If key does not exist it is created and set as an empty +// string, so APPEND will be similar to SET in this special case. +// +// http://redis.io/commands/append +func (self *Client) Append(key string, value interface{}) (int64, error) { + var ret int64 + err := self.command( + &ret, + []byte("APPEND"), + []byte(key), + to.Bytes(value), + ) + return ret, err +} + +/* +Request for authentication in a password-protected Redis server. Redis can be +instructed to require a password before allowing clients to execute commands. +This is done using the requirepass directive in the configuration file. + +http://redis.io/commands/auth +*/ +func (self *Client) Auth(password string) (string, error) { + var ret string + err := self.command( + &ret, + []byte("AUTH"), + []byte(password), + ) + return ret, err +} + +/* +Instruct Redis to start an Append Only File rewrite process. The rewrite will +create a small optimized version of the current Append Only File. + +http://redis.io/commands/bgwriteaof +*/ +func (self *Client) BgRewriteAOF() (string, error) { + var ret string + err := self.command( + &ret, + []byte("BGREWRITEAOF"), + ) + return ret, err +} + +/* +Save the DB in background. The OK code is immediately returned. Redis forks, the +parent continues to serve the clients, the child saves the DB on disk then exits. + +A client my be able to check if the operation succeeded using the LASTSAVE +command. + +http://redis.io/commands/bgsave +*/ +func (self *Client) BgSave() (string, error) { + var ret string + err := self.command( + &ret, + []byte("BGSAVE"), + ) + return ret, err +} + +/* +Count the number of set bits (population counting) in a string. + +http://redis.io/commands/bitcount +*/ +func (self *Client) BitCount(key string, params ...int64) (int64, error) { + var ret int64 + args := make([][]byte, len(params)+2) + args[0] = []byte("BITCOUNT") + args[1] = []byte(key) + for i, _ := range params { + args[2+i] = to.Bytes(params[i]) + } + err := self.command(&ret, args...) + return ret, err +} + +/* +Perform a bitwise operation between multiple keys (containing string values) +and store the result in the destination key. + +http://redis.io/commands/bitop +*/ +func (self *Client) BitOp(op string, dest string, keys ...string) (int64, error) { + var ret int64 + args := make([][]byte, len(keys)+3) + args[0] = []byte("BITOP") + args[1] = []byte(op) + args[2] = []byte(dest) + for i, _ := range keys { + args[3+i] = to.Bytes(keys[i]) + } + err := self.command(&ret, args...) + return ret, err +} + +/* +BLPOP is a blocking list pop primitive. It is the blocking version of LPOP +because it blocks the connection when there are no elements to pop from any of +the given lists. An element is popped from the head of the first list that is +non-empty, with the given keys being checked in the order that they are given. + +http://redis.io/commands/blpop +*/ +func (self *Client) BLPop(timeout uint64, keys ...string) ([]string, error) { + var ret []string + args := make([][]byte, len(keys)+2) + args[0] = []byte("BLPOP") + + i := 0 + + for _, key := range keys { + i += 1 + args[i] = to.Bytes(key) + } + + args[1+i] = to.Bytes(timeout) + + err := self.command(&ret, args...) + return ret, err +} + +/* +BRPOP is a blocking list pop primitive. It is the blocking version of RPOP +because it blocks the connection when there are no elements to pop from any of +the given lists. An element is popped from the tail of the first list that is +non-empty, with the given keys being checked in the order that they are given. + +http://redis.io/commands/brpop +*/ +func (self *Client) BRPop(timeout uint64, keys ...string) ([]string, error) { + var ret []string + + args := make([][]byte, len(keys)+2) + args[0] = []byte("BRPOP") + + i := 0 + + for _, key := range keys { + i += 1 + args[i] = to.Bytes(key) + } + args[1+i] = to.Bytes(timeout) + + err := self.command(&ret, args...) + return ret, err +} + +/* +BRPOPLPUSH is the blocking variant of RPOPLPUSH. When source contains elements, +this command behaves exactly like RPOPLPUSH. When source is empty, Redis will +block the connection until another client pushes to it or until timeout is +reached. A timeout of zero can be used to block indefinitely. + +http://redis.io/commands/brpoplpush +*/ +func (self *Client) BRPopLPush(source string, destination string, timeout int64) (string, error) { + var ret string + err := self.command( + &ret, + []byte("BRPOPLPUSH"), + []byte(source), + []byte(destination), + to.Bytes(timeout), + ) + return ret, err +} + +/* +The CLIENT KILL command closes a given client connection identified by ip:port. + +http://redis.io/commands/client-kill +*/ +func (self *Client) ClientKill(ip string, port uint) (string, error) { + var ret string + err := self.command( + &ret, + []byte("CLIENT"), + []byte("KILL"), + to.Bytes(fmt.Sprintf("%s:%d", ip, port)), + ) + return ret, err +} + +/* +The CLIENT LIST command returns information and statistics about the client +connections server in a mostly human readable format. + +http://redis.io/commands/client-list +*/ +func (self *Client) ClientList() ([]string, error) { + var ret []string + err := self.command( + &ret, + []byte("CLIENT"), + []byte("LIST"), + ) + return ret, err +} + +/* +The CLIENT GETNAME returns the name of the current connection as set by CLIENT +SETNAME. Since every new connection starts without an associated name, if no +name was assigned a null bulk reply is returned. + +http://redis.io/commands/client-getname +*/ +func (self *Client) ClientGetName() (string, error) { + var ret string + err := self.command( + &ret, + []byte("CLIENT"), + []byte("GETNAME"), + ) + return ret, err +} + +/* +The CLIENT SETNAME command assigns a name to the current connection. + +http://redis.io/commands/client-setname +*/ +func (self *Client) ClientSetName(connectionName string) (string, error) { + var ret string + err := self.command( + &ret, + []byte("CLIENT"), + []byte("SETNAME"), + to.Bytes(connectionName), + ) + return ret, err +} + +/* +The CONFIG GET command is used to read the configuration parameters of a running +Redis server. Not all the configuration parameters are supported in Redis 2.4, +while Redis 2.6 can read the whole configuration of a server using this command. + +http://redis.io/commands/config-get +*/ +func (self *Client) ConfigGet(parameter string) (string, error) { + var ret string + err := self.command( + &ret, + []byte("CONFIG"), + []byte("GET"), + []byte(parameter), + ) + return ret, err +} + +/* +The CONFIG SET command is used in order to reconfigure the server at run time +without the need to restart Redis. You can change both trivial parameters or +switch from one to another persistence option using this command. + +http://redis.io/commands/config-set +*/ +func (self *Client) ConfigSet(parameter string, value interface{}) (string, error) { + var ret string + err := self.command( + &ret, + []byte("CONFIG"), + []byte("SET"), + []byte(parameter), + to.Bytes(value), + ) + return ret, err +} + +/* +Resets the statistics reported by Redis using the INFO command. + +http://redis.io/commands/config-resetstat +*/ +func (self *Client) ConfigResetStat() (string, error) { + var ret string + err := self.command( + &ret, + []byte("CONFIG"), + []byte("RESETSTAT"), + ) + return ret, err +} + +/* +Return the number of keys in the currently-selected database. + +http://redis.io/commands/dbsize +*/ +func (self *Client) DbSize() (uint64, error) { + var ret uint64 + err := self.command( + &ret, + []byte("DBSIZE"), + ) + return ret, err +} + +/* +DEBUG OBJECT is a debugging command that should not be used by clients. Check +the OBJECT command instead. + +http://redis.io/commands/debug-object +*/ +func (self *Client) DebugObject(key string) (string, error) { + var ret string + err := self.command( + &ret, + []byte("DEBUG"), + []byte("OBJECT"), + to.Bytes(key), + ) + return ret, err +} + +/* +DEBUG SEGFAULT performs an invalid memory access that crashes Redis. It is used +to simulate bugs during the development. + +http://redis.io/commands/debug-segfault +*/ +func (self *Client) DebugSegfault() (string, error) { + var ret string + err := self.command( + &ret, + []byte("DEBUG"), + []byte("SEGFAULT"), + ) + return ret, err +} + +/* +Decrements the number stored at key by one. If the key does not exist, it is set +to 0 before performing the operation. An error is returned if the key contains a +value of the wrong type or contains a string that can not be represented as +integer. This operation is limited to 64 bit signed integers. + +http://redis.io/commands/decr +*/ +func (self *Client) Decr(key string) (int64, error) { + var ret int64 + err := self.command( + &ret, + []byte("DECR"), + []byte(key), + ) + return ret, err +} + +/* +Decrements the number stored at key by decrement. If the key does not exist, it +is set to 0 before performing the operation. An error is returned if the key +contains a value of the wrong type or contains a string that can not be +represented as integer. This operation is limited to 64 bit signed integers. + +http://redis.io/commands/decrby +*/ +func (self *Client) DecrBy(key string, decrement int64) (int64, error) { + var ret int64 + err := self.command( + &ret, + []byte("DECRBY"), + []byte(key), + to.Bytes(decrement), + ) + return ret, err +} + +/* +Removes the specified keys. A key is ignored if it does not exist. + +http://redis.io/commands/del +*/ + +func (self *Client) Del(keys ...string) (int64, error) { + var ret int64 + args := make([][]byte, len(keys)+1) + args[0] = []byte("DEL") + for i, key := range keys { + args[1+i] = to.Bytes(key) + } + err := self.command(&ret, args...) + return ret, err +} + +/* +Flushes all previously queued commands in a transaction and restores the +connection state to normal. + +http://redis.io/commands/discard +*/ +func (self *Client) Discard() (string, error) { + var ret string + err := self.command( + &ret, + []byte("DISCARD"), + ) + return ret, err +} + +/* +Serialize the value stored at key in a Redis-specific format and return it to +the user. The returned value can be synthesized back into a Redis key using the +RESTORE command. + +http://redis.io/commands/dump +*/ +func (self *Client) Dump(key string) (string, error) { + var ret string + err := self.command( + &ret, + []byte("DUMP"), + []byte(key), + ) + return ret, err +} + +/* +Returns message. + +http://redis.io/commands/echo +*/ +func (self *Client) Echo(message interface{}) (string, error) { + var ret string + err := self.command( + &ret, + []byte("ECHO"), + to.Bytes(message), + ) + return ret, err +} + +/* +Executes all previously queued commands in a transaction and restores the +connection state to normal. + +http://redis.io/commands/exec +*/ +func (self *Client) Exec() ([]interface{}, error) { + var ret []interface{} + err := self.command( + &ret, + []byte("EXEC"), + ) + return ret, err +} + +/* +Returns if key exists. + +http://redis.io/commands/exists +*/ +func (self *Client) Exists(key string) (bool, error) { + var ret bool + err := self.command( + &ret, + []byte("EXISTS"), + to.Bytes(key), + ) + return ret, err +} + +/* +Set a timeout on key. After the timeout has expired, the key will automatically +be deleted. A key with an associated timeout is often said to be volatile in +Redis terminology. + +http://redis.io/commands/expire +*/ +func (self *Client) Expire(key string, seconds uint64) (bool, error) { + var ret bool + err := self.command( + &ret, + []byte("EXPIRE"), + []byte(key), + to.Bytes(seconds), + ) + return ret, err +} + +/* +EXPIREAT has the same effect and semantic as EXPIRE, but instead of specifying +the number of seconds representing the TTL (time to live), it takes an absolute +Unix timestamp (seconds since January 1, 1970). + +http://redis.io/commands/expireat +*/ +func (self *Client) ExpireAt(key string, unixTime uint64) (bool, error) { + var ret bool + err := self.command( + &ret, + []byte("EXPIREAT"), + []byte(key), + to.Bytes(unixTime), + ) + return ret, err +} + +/* +Delete all the keys of all the existing databases, not just the currently +selected one. This command never fails. + +http://redis.io/commands/flushall +*/ +func (self *Client) FlushAll() (string, error) { + var ret string + err := self.command( + &ret, + []byte("FLUSHALL"), + ) + return ret, err +} + +/* +Delete all the keys of the currently selected DB. This command never fails. + +http://redis.io/commands/flushdb +*/ +func (self *Client) FlushDB() (string, error) { + var ret string + err := self.command( + &ret, + []byte("FLUSHDB"), + ) + return ret, err +} + +/* +Get the value of key. If the key does not exist the special value nil is +returned. An error is returned if the value stored at key is not a string, +because GET only handles string values. + +http://redis.io/commands/get +*/ +func (self *Client) Get(key string) (string, error) { + var s string + err := self.command(&s, + []byte("GET"), + []byte(key), + ) + return s, err +} + +/* +Returns the bit value at offset in the string value stored at key. + +http://redis.io/commands/getbit +*/ +func (self *Client) GetBit(key string, offset int64) (int64, error) { + var ret int64 + err := self.command( + &ret, + []byte("GETBIT"), + []byte(key), + to.Bytes(offset), + ) + return ret, err +} + +/* +Returns the substring of the string value stored at key, determined by the +offsets start and end (both are inclusive). Negative offsets can be used in +order to provide an offset starting from the end of the string. So -1 means +the last character, -2 the penultimate and so forth. + +http://redis.io/commands/getrange +*/ +func (self *Client) GetRange(key string, start int64, end int64) (string, error) { + var ret string + err := self.command(&ret, + []byte("GETRANGE"), + []byte(key), + to.Bytes(start), + to.Bytes(end), + ) + return ret, err +} + +/* +Atomically sets key to value and returns the old value stored at key. Returns an +error when key exists but does not hold a string value. + +http://redis.io/commands/getset +*/ +func (self *Client) GetSet(key string, value interface{}) (string, error) { + var ret string + err := self.command( + &ret, + []byte(string("GETSET")), + to.Bytes(key), + to.Bytes(value), + ) + return ret, err +} + +/* +Removes the specified fields from the hash stored at key. Specified fields that +do not exist within this hash are ignored. If key does not exist, it is treated +as an empty hash and this command returns 0. + +http://redis.io/commands/hdel +*/ +func (self *Client) HDel(key string, fields ...string) (int64, error) { + var ret int64 + + args := make([][]byte, len(fields)+2) + args[0] = []byte("HDEL") + args[1] = []byte(key) + + for i, _ := range fields { + args[2+i] = to.Bytes(fields[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns if field is an existing field in the hash stored at key. + +http://redis.io/commands/hexists +*/ +func (self *Client) HExists(key string, field string) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("HEXISTS"), + []byte(key), + []byte(field), + ) + + return ret, err +} + +/* +Returns the value associated with field in the hash stored at key. + +http://redis.io/commands/hget +*/ +func (self *Client) HGet(key string, field string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("HGET"), + []byte(key), + []byte(field), + ) + + return ret, err +} + +/* +Returns all fields and values of the hash stored at key. In the returned value, +every field name is followed by its value, so the length of the reply is twice +the size of the hash. + +http://redis.io/commands/hgetall +*/ +func (self *Client) HGetAll(key string) ([]string, error) { + var ret []string + + err := self.command( + &ret, + []byte("HGETALL"), + []byte(key), + ) + + return ret, err +} + +/* +Increments the number stored at field in the hash stored at key by increment. If +key does not exist, a new key holding a hash is created. If field does not exist +the value is set to 0 before the operation is performed. + +http://redis.io/commands/hincrby +*/ +func (self *Client) HIncrBy(key string, field string, increment interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("HINCRBY"), + []byte(key), + []byte(field), + to.Bytes(increment), + ) + + return ret, err +} + +/* +Increment the specified field of an hash stored at key, and representing a +floating point number, by the specified increment. If the field does not exist, +it is set to 0 before performing the operation. + +http://redis.io/commands/hincrbyfloat +*/ +func (self *Client) HIncrByFloat(key string, field string, increment float64) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("HINCRBYFLOAT"), + []byte(key), + []byte(field), + to.Bytes(increment), + ) + + return ret, err +} + +/* +Returns all field names in the hash stored at key. + +http://redis.io/commands/hkeys +*/ +func (self *Client) HKeys(key string) ([]string, error) { + var ret []string + + err := self.command( + &ret, + []byte("HKEYS"), + []byte(key), + ) + + return ret, err + +} + +/* +Returns the number of fields contained in the hash stored at key. + +http://redis.io/commands/hlen +*/ +func (self *Client) HLen(key string) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("HLEN"), + []byte(key), + ) + + return ret, err +} + +/* +Returns the values associated with the specified fields in the hash stored at key. + +http://redis.io/commands/hmget +*/ +func (self *Client) HMGet(key string, fields ...string) ([]string, error) { + var ret []string + + args := make([][]byte, len(fields)+2) + args[0] = []byte("HMGET") + args[1] = []byte(key) + + for i, field := range fields { + args[2+i] = to.Bytes(field) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Sets the specified fields to their respective values in the hash stored at key. +This command overwrites any existing fields in the hash. If key does not exist, +a new key holding a hash is created. + +http://redis.io/commands/hmset +*/ +func (self *Client) HMSet(key string, values ...interface{}) (string, error) { + var ret string + + if len(values)%2 != 0 { + return "", ErrExpectingPairs + } + + args := make([][]byte, len(values)+2) + args[0] = []byte("HMSET") + args[1] = []byte(key) + + for i, value := range values { + args[2+i] = to.Bytes(value) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Sets field in the hash stored at key to value. If key does not exist, a new key +holding a hash is created. If field already exists in the hash, it is +overwritten. + +http://redis.io/commands/hset +*/ +func (self *Client) HSet(key string, field string, value interface{}) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("HSET"), + []byte(key), + []byte(field), + to.Bytes(value), + ) + + return ret, err +} + +/* +Sets field in the hash stored at key to value, only if field does not yet exist. +If key does not exist, a new key holding a hash is created. If field already +exists, this operation has no effect. + +http://redis.io/commands/hsetnx +*/ +func (self *Client) HSetNX(key string, field string, value interface{}) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("HSETNX"), + []byte(key), + []byte(field), + to.Bytes(value), + ) + + return ret, err +} + +/* +Returns all values in the hash stored at key. + +http://redis.io/commands/hvals +*/ +func (self *Client) HVals(key string) ([]string, error) { + var ret []string + + err := self.command( + &ret, + []byte("HVALS"), + []byte(key), + ) + + return ret, err +} + +/* +Increments the number stored at key by one. If the key does not exist, it is set +to 0 before performing the operation. An error is returned if the key contains a +value of the wrong type or contains a string that can not be represented as +integer. This operation is limited to 64 bit signed integers. + +http://redis.io/commands/incr +*/ +func (self *Client) Incr(key string) (int64, error) { + var ret int64 + err := self.command( + &ret, + []byte("INCR"), + []byte(key), + ) + return ret, err +} + +/* +Increments the number stored at key by increment. If the key does not exist, it +is set to 0 before performing the operation. An error is returned if the key +contains a value of the wrong type or contains a string that can not be +represented as integer. This operation is limited to 64 bit signed integers. + +http://redis.io/commands/incrby +*/ +func (self *Client) IncrBy(key string, increment int64) (int64, error) { + var ret int64 + err := self.command( + &ret, + []byte("INCRBY"), + []byte(key), + to.Bytes(increment), + ) + return ret, err +} + +/* +Increment the string representing a floating point number stored at key by the +specified increment. If the key does not exist, it is set to 0 before +performing the operation. + +http://redis.io/commands/incrbyfloat +*/ +func (self *Client) IncrByFloat(key string, increment float64) (string, error) { + var ret string + err := self.command( + &ret, + []byte("INCRBYFLOAT"), + []byte(key), + to.Bytes(increment), + ) + return ret, err +} + +/* +The INFO command returns information and statistics about the server in a format +that is simple to parse by computers and easy to read by humans. + +http://redis.io/commands/info +*/ +func (self *Client) Info(section string) (ret string, err error) { + err = self.command( + &ret, + []byte("INFO"), + []byte(section), + ) + return ret, err +} + +/* +Returns all keys matching pattern. + +http://redis.io/commands/keys +*/ +func (self *Client) Keys(pattern string) ([]string, error) { + var ret []string + err := self.command( + &ret, + []byte("KEYS"), + []byte(pattern), + ) + return ret, err +} + +/* +Return the UNIX TIME of the last DB save executed with success. + +http://redis.io/commands/lastsave +*/ +func (self *Client) LastSave() (int64, error) { + var ret int64 + err := self.command( + &ret, + []byte("LASTSAVE"), + ) + return ret, err +} + +/* +Returns the element at index index in the list stored at key. The index is +zero-based, so 0 means the first element, 1 the second element and so on. +Negative indices can be used to designate elements starting at the tail of the +list. Here, -1 means the last element, -2 means the penultimate and so forth. + +http://redis.io/commands/lindex +*/ +func (self *Client) LIndex(key string, index int64) (string, error) { + var ret string + err := self.command( + &ret, + []byte("LINDEX"), + []byte(key), + to.Bytes(index), + ) + return ret, err +} + +/* +Inserts value in the list stored at key either before or after the reference +value pivot. + +http://redis.io/commands/linsert +*/ +func (self *Client) LInsert(key string, where string, pivot interface{}, value interface{}) (int64, error) { + var ret int64 + + where = strings.ToUpper(where) + + if where != "AFTER" && where != "BEFORE" { + return 0, errors.New(`The "where" value must be either BEFORE or AFTER.`) + } + + err := self.command( + &ret, + []byte("LINSERT"), + []byte(key), + []byte(where), + to.Bytes(pivot), + to.Bytes(value), + ) + + return ret, err +} + +/* +Returns the length of the list stored at key. If key does not exist, it is +interpreted as an empty list and 0 is returned. An error is returned when the +value stored at key is not a list. + +http://redis.io/commands/llen +*/ +func (self *Client) LLen(key string) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("LLEN"), + []byte(key), + ) + + return ret, err +} + +/* +Removes and returns the first element of the list stored at key. + +http://redis.io/commands/lpop +*/ +func (self *Client) LPop(key string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("LPOP"), + []byte(key), + ) + + return ret, err +} + +/* +Insert all the specified values at the head of the list stored at key. If key +does not exist, it is created as empty list before performing the push +operations. When key holds a value that is not a list, an error is returned. + +http://redis.io/commands/lpush +*/ +func (self *Client) LPush(key string, values ...interface{}) (int64, error) { + var ret int64 + + args := make([][]byte, len(values)+2) + args[0] = []byte("LPUSH") + args[1] = []byte(key) + + for i, _ := range values { + args[2+i] = to.Bytes(values[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Inserts value at the head of the list stored at key, only if key already exists +and holds a list. In contrary to LPUSH, no operation will be performed when key +does not yet exist. + +http://redis.io/commands/lpushx +*/ +func (self *Client) LPushX(key string, value interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("LPUSHX"), + []byte(key), + to.Bytes(value), + ) + + return ret, err +} + +/* +Returns the specified elements of the list stored at key. The offsets start and +stop are zero-based indexes, with 0 being the first element of the list (the +head of the list), 1 being the next element and so on. + +http://redis.io/commands/lrange +*/ +func (self *Client) LRange(key string, start int64, stop int64) ([]string, error) { + var ret []string + + err := self.command( + &ret, + []byte("LRANGE"), + []byte(key), + to.Bytes(start), + to.Bytes(stop), + ) + + return ret, err +} + +/* +Removes the first count occurrences of elements equal to value from the list +stored at key. The count argument influences the operation in the following +ways: + +http://redis.io/commands/lrem +*/ +func (self *Client) LRem(key string, count int64, value interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("LREM"), + []byte(key), + to.Bytes(count), + to.Bytes(value), + ) + + return ret, err +} + +/* +Sets the list element at index to value. For more information on the index +argument, see LINDEX. + +http://redis.io/commands/lset +*/ +func (self *Client) LSet(key string, index int64, value interface{}) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("LSET"), + []byte(key), + to.Bytes(index), + to.Bytes(value), + ) + + return ret, err +} + +/* +Trim an existing list so that it will contain only the specified range of +elements specified. Both start and stop are zero-based indexes, where 0 is the +first element of the list (the head), 1 the next element and so on. + +http://redis.io/commands/ltrim +*/ +func (self *Client) LTrim(key string, start int64, stop int64) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("LTRIM"), + []byte(key), + to.Bytes(start), + to.Bytes(stop), + ) + + return ret, err +} + +/* +Returns the values of all specified keys. For every key that does not hold a +string value or does not exist, the special value nil is returned. Because of +this, the operation never fails. + +*/ +func (self *Client) MGet(keys ...string) ([]string, error) { + var ret []string + + args := make([][]byte, len(keys)+1) + args[0] = []byte("MGET") + + for i, _ := range keys { + args[1+i] = []byte(keys[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Atomically transfer a key from a source Redis instance to a destination Redis +instance. On success the key is deleted from the original instance and is +guaranteed to exist in the target instance. + +http://redis.io/commands/migrate +*/ +func (self *Client) Migrate(host string, port uint, key string, destination string, timeout int64) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("MIGRATE"), + []byte(host), + to.Bytes(port), + []byte(key), + []byte(destination), + to.Bytes(timeout), + ) + + return ret, err +} + +/* +Move key from the currently selected database (see SELECT) to the specified +destination database. When key already exists in the destination database, or +it does not exist in the source database, it does nothing. It is possible to +use MOVE as a locking primitive because of this. + +http://redis.io/commands/move +*/ +func (self *Client) Move(key string, db string) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("MOVE"), + []byte(key), + []byte(db), + ) + + return ret, err +} + +/* +Sets the given keys to their respective values. MSET replaces existing values +with new values, just as regular SET. See MSETNX if you don't want to overwrite +existing values. + +http://redis.io/commands/mset +*/ +func (self *Client) MSet(values ...interface{}) (string, error) { + var ret string + + if len(values)%2 != 0 { + return "", ErrExpectingPairs + } + + args := make([][]byte, len(values)+1) + args[0] = []byte("MSET") + + for i, _ := range values { + args[1+i] = to.Bytes(values[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Sets the given keys to their respective values. MSETNX will not perform any +operation at all even if just a single key already exists. + +http://redis.io/commands/msetnx +*/ +func (self *Client) MSetNX(values ...interface{}) (bool, error) { + var ret bool + + if len(values)%2 != 0 { + return false, ErrExpectingPairs + } + + args := make([][]byte, len(values)+1) + args[0] = []byte("MSETNX") + + for i, _ := range values { + args[1+i] = to.Bytes(values[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Marks the start of a transaction block. Subsequent commands will be queued for +atomic execution using EXEC. + +http://redis.io/commands/multi +*/ +func (self *Client) Multi() (string, error) { + var ret string + + err := self.command( + &ret, + []byte("MULTI"), + ) + + return ret, err +} + +/* +The OBJECT command allows to inspect the internals of Redis Objects associated +with keys. It is useful for debugging or to understand if your keys are using +the specially encoded data types to save space. Your application may also use +the information reported by the OBJECT command to implement application level +key eviction policies when using Redis as a Cache. + +http://redis.io/commands/object +*/ +func (self *Client) Object(subcommand string, arguments ...interface{}) (string, error) { + var ret string + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("OBJECT") + args[1] = []byte(subcommand) + + for i, arg := range arguments { + args[2+i] = to.Bytes(arg) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Remove the existing timeout on key, turning the key from volatile (a key with an +expire set) to persistent (a key that will never expire as no timeout is +associated). + +http://redis.io/commands/persist +*/ +func (self *Client) Persist(key string) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("PERSIST"), + []byte(key), + ) + + return ret, err +} + +/* +This command works exactly like EXPIRE but the time to live of the key is +specified in milliseconds instead of seconds. + +http://redis.io/commands/pexpire +*/ +func (self *Client) PExpire(key string, milliseconds int64) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("PEXPIRE"), + []byte(key), + to.Bytes(milliseconds), + ) + + return ret, err +} + +/* +PEXPIREAT has the same effect and semantic as EXPIREAT, but the Unix time at +which the key will expire is specified in milliseconds instead of seconds. + +http://redis.io/commands/pexpireat +*/ +func (self *Client) PExpireAt(key string, milliseconds int64) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("PEXPIREAT"), + []byte(key), + to.Bytes(milliseconds), + ) + + return ret, err +} + +/* +Returns PONG. This command is often used to test if a connection is still alive, +or to measure latency. + +http://redis.io/commands/ping +*/ +func (self *Client) Ping() (string, error) { + var ret string + + err := self.command( + &ret, + []byte("PING"), + ) + + return ret, err +} + +/* +PSETEX works exactly like SETEX with the sole difference that the expire time is +specified in milliseconds instead of seconds. + +http://redis.io/commands/psetex +*/ +func (self *Client) PSetEx(key string, milliseconds int64, value interface{}) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("PSETEX"), + []byte(key), + to.Bytes(milliseconds), + to.Bytes(value), + ) + + return ret, err +} + +/* +Subscribes the client to the given patterns. + +http://redis.io/commands/psubscribe +*/ +func (self *Client) PSubscribe(c chan []string, channel ...string) error { + var ret []string + + args := make([][]byte, len(channel)+1) + args[0] = []byte("PSUBSCRIBE") + + for i, _ := range channel { + args[1+i] = to.Bytes(channel[i]) + } + + err := self.bcommand(c, &ret, args...) + + return err +} + +/* +The PUBSUB command is an introspection command that allows to inspect the state +of the Pub/Sub subsystem. It is composed of subcommands that are documented +separately. + +http://redis.io/commands/pubsub +*/ +func (self *Client) PubSub(subcommand string, arguments ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("PUBSUB") + args[1] = []byte(subcommand) + + for i, _ := range arguments { + args[2+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Like TTL this command returns the remaining time to live of a key that has an +expire set, with the sole difference that TTL returns the amount of remaining +time in seconds while PTTL returns it in milliseconds. + +http://redis.io/commands/pttl +*/ +func (self *Client) PTTL(key string) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("PTTL"), + []byte(key), + ) + + return ret, err +} + +/* +Posts a message to the given channel. + +http://redis.io/commands/publish +*/ +func (self *Client) Publish(channel string, message interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("PUBLISH"), + []byte(channel), + to.Bytes(message), + ) + + return ret, err +} + +/* +Unsubscribes the client from the given patterns, or from all of them if none is +given. + +http://redis.io/commands/punsubscribe +*/ +func (self *Client) PUnsubscribe(pattern ...string) (string, error) { + var ret string + + args := make([][]byte, len(pattern)+1) + args[0] = []byte("PUNSUBSCRIBE") + + for i, _ := range pattern { + args[1+i] = to.Bytes(pattern[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Ask the server to close the connection. The connection is closed as soon as all +pending replies have been written to the client. + +http://redis.io/commands/quit +*/ +func (self *Client) Quit() (s string, err error) { + self.redis.close() + return "", nil +} + +/* +Return a random key from the currently selected database. + +http://redis.io/commands/randomkey +*/ +func (self *Client) RandomKey() (string, error) { + var ret string + + err := self.command( + &ret, + []byte("RANDOMKEY"), + ) + + return ret, err +} + +/* +Renames key to newkey. It returns an error when the source and destination names +are the same, or when key does not exist. If newkey already exists it is +overwritten. + +http://redis.io/commands/rename +*/ +func (self *Client) Rename(key string, newkey string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("RENAME"), + []byte(key), + []byte(newkey), + ) + + return ret, err +} + +/* +Renames key to newkey if newkey does not yet exist. It returns an error under +the same conditions as RENAME. + +http://redis.io/commands/renamenx +*/ +func (self *Client) RenameNX(key string, newkey string) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("RENAMENX"), + []byte(key), + []byte(newkey), + ) + + return ret, err +} + +/* +Create a key associated with a value that is obtained by deserializing the +provided serialized value (obtained via DUMP). + +http://redis.io/commands/restore +*/ +func (self *Client) Restore(key string, ttl int64, serializedValue string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("RESTORE"), + []byte(key), + to.Bytes(ttl), + []byte(serializedValue), + ) + + return ret, err +} + +/* +Removes and returns the last element of the list stored at key. + +http://redis.io/commands/restore +*/ +func (self *Client) RPop(key string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("RPOP"), + []byte(key), + ) + + return ret, err +} + +/* +Atomically returns and removes the last element (tail) of the list stored at +source, and pushes the element at the first element (head) of the list stored +at destination. + +http://redis.io/commands/rpoplpush +*/ +func (self *Client) RPopLPush(source string, destination string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("RPOPLPUSH"), + []byte(source), + []byte(destination), + ) + + return ret, err +} + +/* +Insert all the specified values at the tail of the list stored at key. If key +does not exist, it is created as empty list before performing the push +operation. When key holds a value that is not a list, an error is returned. + +http://redis.io/commands/rpush +*/ +func (self *Client) RPush(key string, values ...interface{}) (int64, error) { + var ret int64 + + args := make([][]byte, len(values)+2) + args[0] = []byte("RPUSH") + args[1] = []byte(key) + + for i, _ := range values { + args[2+i] = to.Bytes(values[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Inserts value at the tail of the list stored at key, only if key already exists +and holds a list. In contrary to RPUSH, no operation will be performed when key +does not yet exist. + +http://redis.io/commands/rpushx +*/ +func (self *Client) RPushX(key string, value interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("RPUSHX"), + []byte(key), + to.Bytes(value), + ) + + return ret, err +} + +/* +Add the specified members to the set stored at key. Specified members that are +already a member of this set are ignored. If key does not exist, a new set is +created before adding the specified members. + +http://redis.io/commands/sadd +*/ +func (self *Client) SAdd(key string, member ...interface{}) (int64, error) { + var ret int64 + + args := make([][]byte, len(member)+2) + args[0] = []byte("SADD") + args[1] = []byte(key) + + for i, _ := range member { + args[2+i] = to.Bytes(member[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +The SAVE commands performs a synchronous save of the dataset producing a point +in time snapshot of all the data inside the Redis instance, in the form of an +RDB file. + +http://redis.io/commands/save +*/ +func (self *Client) Save() (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SAVE"), + ) + + return ret, err +} + +/* +Returns the set cardinality (number of elements) of the set stored at key. + +http://redis.io/commands/scard +*/ +func (self *Client) SCard(key string) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("SCARD"), + []byte(key), + ) + + return ret, err +} + +/* +Returns information about the existence of the scripts in the script cache. + +http://redis.io/commands/script-exists +*/ +func (self *Client) ScriptExists(script ...string) ([]string, error) { + var ret []string + + args := make([][]byte, len(script)+2) + args[0] = []byte("SCRIPT") + args[1] = []byte("EXISTS") + + for i, _ := range script { + args[2+i] = []byte(script[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Flush the Lua scripts cache. + +http://redis.io/commands/script-flush +*/ +func (self *Client) ScriptFlush() (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SCRIPT"), + []byte("FLUSH"), + ) + + return ret, err + +} + +/* +Kills the currently executing Lua script, assuming no write operation was yet +performed by the script. + +http://redis.io/commands/script-kill +*/ +func (self *Client) ScriptKill() (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SCRIPT"), + []byte("KILL"), + ) + + return ret, err +} + +/* +EVAL and EVALSHA are used to evaluate scripts using the Lua interpreter built +into Redis starting from version 2.6.0. + +http://redis.io/commands/eval +*/ +func (self *Client) Eval(script string, numkeys int64, arguments ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(arguments)+3) + args[0] = []byte("EVAL") + args[1] = []byte(script) + args[2] = to.Bytes(numkeys) + + for i, _ := range arguments { + args[3+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Evaluates a script cached on the server side by its SHA1 digest. Scripts are +cached on the server side using the SCRIPT LOAD command. The command is +otherwise identical to EVAL. + +http://redis.io/commands/evalsha +*/ +func (self *Client) EvalSHA(hash string, numkeys int64, arguments ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(arguments)+3) + args[0] = []byte("EVALSHA") + args[1] = []byte(hash) + args[2] = to.Bytes(numkeys) + + for i, _ := range arguments { + args[3+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Load a script into the scripts cache, without executing it. After the specified +command is loaded into the script cache it will be callable using EVALSHA with +the correct SHA1 digest of the script, exactly like after the first successful +invocation of EVAL. + +http://redis.io/commands/script-load +*/ +func (self *Client) ScriptLoad(script string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SCRIPT"), + []byte("LOAD"), + []byte(script), + ) + + return ret, err +} + +/* +Returns the members of the set resulting from the difference between the first +set and all the successive sets. + +http://redis.io/commands/sdiff +*/ +func (self *Client) SDiff(key ...string) ([]string, error) { + var ret []string + + args := make([][]byte, len(key)+1) + args[0] = []byte("SDIFF") + + for i, _ := range key { + args[1+i] = []byte(key[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +This command is equal to SDIFF, but instead of returning the resulting set, it +is stored in destination. + +http://redis.io/commands/sdiffstore +*/ +func (self *Client) SDiffStore(destination string, key ...string) (int64, error) { + var ret int64 + + args := make([][]byte, len(key)+2) + args[0] = []byte("SDIFFSTORE") + args[1] = []byte(destination) + + for i, _ := range key { + args[2+i] = []byte(key[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Select the DB with having the specified zero-based numeric index. New +connections always use DB 0. + +http://redis.io/commands/select +*/ +func (self *Client) Select(index int64) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SELECT"), + to.Bytes(index), + ) + + return ret, err +} + +/* +Set key to hold the string value. If key already holds a value, it is +overwritten, regardless of its type. + +http://redis.io/commands/set +*/ +func (self *Client) Set(key string, value interface{}) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SET"), + []byte(key), + to.Bytes(value), + ) + + return ret, err +} + +/* +Sets or clears the bit at offset in the string value stored at key. + +http://redis.io/commands/setbit +*/ +func (self *Client) SetBit(key string, offset int64, value int64) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("SETBIT"), + []byte(key), + to.Bytes(offset), + to.Bytes(value), + ) + + return ret, err +} + +/* +Set key to hold the string value and set key to timeout after a given number of +seconds. This command is equivalent to executing the following commands: + +http://redis.io/commands/setex +*/ +func (self *Client) SetEx(key string, seconds int64, value interface{}) (int, error) { + var ret int + + err := self.command( + &ret, + []byte("SETEX"), + []byte(key), + to.Bytes(seconds), + to.Bytes(value), + ) + + return ret, err +} + +/* +Set key to hold string value if key does not exist. In that case, it is equal to +SET. When key already holds a value, no operation is performed. SETNX is short +for "SET if N ot e X ists". + +http://redis.io/commands/setnx +*/ +func (self *Client) SetNX(key string, value interface{}) (bool, error) { + var ret int + + err := self.command( + &ret, + []byte("SETNX"), + []byte(key), + to.Bytes(value), + ) + + if ret == 0 { + return false, err + } + + return true, err +} + +/* +Overwrites part of the string stored at key, starting at the specified offset, +for the entire length of value. If the offset is larger than the current length +of the string at key, the string is padded with zero-bytes to make offset fit. +Non-existing keys are considered as empty strings, so this command will make +sure it holds a string large enough to be able to set value at offset. + +http://redis.io/commands/setrange +*/ +func (self *Client) SetRange(key string, offset int64, value interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("SETRANGE"), + []byte(key), + to.Bytes(offset), + to.Bytes(value), + ) + + return ret, err +} + +/* +Returns the members of the set resulting from the intersection of all the given sets. + +http://redis.io/commands/sinter +*/ +func (self *Client) SInter(key ...string) ([]string, error) { + var ret []string + + args := make([][]byte, len(key)+1) + args[0] = []byte("SINTER") + + for i, _ := range key { + args[1+i] = []byte(key[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +This command is equal to SINTER, but instead of returning the resulting set, it +is stored in destination. + +http://redis.io/commands/sinterstore +*/ +func (self *Client) SInterStore(destination string, key ...string) (int64, error) { + var ret int64 + + args := make([][]byte, len(key)+2) + args[0] = []byte("SINTERSTORE") + args[1] = []byte(destination) + + for i, _ := range key { + args[2+i] = []byte(key[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns if member is a member of the set stored at key. + +http://redis.io/commands/sismember +*/ +func (self *Client) SIsMember(key string, member interface{}) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("SISMEMBER"), + []byte(key), + to.Bytes(member), + ) + + return ret, err +} + +/* +The SLAVEOF command can change the replication settings of a slave on the fly. +If a Redis server is already acting as slave, the command SLAVEOF NO ONE will +turn off the replication, turning the Redis server into a MASTER. In the proper +form SLAVEOF hostname port will make the server a slave of another server +listening at the specified hostname and port. + +http://redis.io/commands/slaveof +*/ +func (self *Client) SlaveOf(key string, port uint) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SLAVEOF"), + []byte(key), + to.Bytes(port), + ) + + return ret, err +} + +/* +This command is used in order to read and reset the Redis slow queries log. + +http://redis.io/commands/slowlog +*/ +func (self *Client) SlowLog(subcommand string, argument interface{}) ([]string, error) { + var ret []string + + err := self.command( + &ret, + []byte("SLOWLOG"), + []byte(subcommand), + to.Bytes(argument), + ) + + return ret, err +} + +/* +Returns all the members of the set value stored at key. + +http://redis.io/commands/smembers +*/ +func (self *Client) SMembers(key string) ([]string, error) { + var ret []string + + err := self.command( + &ret, + []byte("SMEMBERS"), + []byte(key), + ) + + return ret, err +} + +/* +Move member from the set at source to the set at destination. This operation is +atomic. In every given moment the element will appear to be a member of source +or destination for other clients. + +http://redis.io/commands/smove +*/ +func (self *Client) SMove(source string, destination string, member interface{}) (bool, error) { + var ret bool + + err := self.command( + &ret, + []byte("SMOVE"), + []byte(source), + []byte(destination), + to.Bytes(member), + ) + + return ret, err +} + +/* +Returns or stores the elements contained in the list, set or sorted set at key. +By default, sorting is numeric and elements are compared by their value +interpreted as double precision floating point number. + +http://redis.io/commands/sort +*/ +func (self *Client) Sort(key string, arguments ...string) ([]string, error) { + var ret []string + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("SORT") + args[1] = []byte(key) + + for i, _ := range arguments { + args[2+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Removes and returns a random element from the set value stored at key. + +http://redis.io/commands/spop +*/ +func (self *Client) SPop(key string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SPOP"), + []byte(key), + ) + + return ret, err +} + +/* +When called with just the key argument, return a random element from the set +value stored at key. + +http://redis.io/commands/srandmember +*/ +func (self *Client) SRandMember(key string, count int64) ([]string, error) { + var ret []string + + err := self.command( + &ret, + []byte("SRANDMEMBER"), + []byte(key), + to.Bytes(count), + ) + + return ret, err +} + +/* +Remove the specified members from the set stored at key. Specified members that +are not a member of this set are ignored. If key does not exist, it is treated +as an empty set and this command returns 0. + +http://redis.io/commands/srem +*/ +func (self *Client) SRem(key string, members ...interface{}) (int64, error) { + var ret int64 + + args := make([][]byte, len(members)+2) + args[0] = []byte("SREM") + args[1] = []byte(key) + + for i, _ := range members { + args[2+i] = to.Bytes(members[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns the length of the string value stored at key. An error is returned when +key holds a non-string value. + +http://redis.io/commands/strlen +*/ +func (self *Client) Strlen(key string) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("STRLEN"), + []byte(key), + ) + + return ret, err +} + +/* +Subscribes the client to the specified channels. + +http://redis.io/commands/subscribe +*/ +func (self *Client) Subscribe(c chan []string, channel ...string) error { + var ret []string + + args := make([][]byte, len(channel)+1) + args[0] = []byte("SUBSCRIBE") + + for i, _ := range channel { + args[1+i] = to.Bytes(channel[i]) + } + + err := self.bcommand(c, &ret, args...) + + return err +} + +/* +Returns the members of the set resulting from the union of all the given sets. + +http://redis.io/commands/sunion +*/ +func (self *Client) SUnion(key ...string) ([]string, error) { + var ret []string + + args := make([][]byte, len(key)+1) + args[0] = []byte("SUNION") + + for i, _ := range key { + args[1+i] = []byte(key[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +This command is equal to SUNION, but instead of returning the resulting set, it +is stored in destination. + +http://redis.io/commands/sunionstore +*/ +func (self *Client) SUnionStore(destination string, key ...string) (int64, error) { + var ret int64 + + args := make([][]byte, len(key)+2) + args[0] = []byte("SUNIONSTORE") + args[1] = []byte(destination) + + for i, _ := range key { + args[2+i] = []byte(key[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +http://redis.io/commands/sync +*/ +func (self *Client) Sync() (string, error) { + var ret string + + err := self.command( + &ret, + []byte("SYNC"), + ) + + return ret, err +} + +/* +The TIME command returns the current server time as a two items lists: a Unix +timestamp and the amount of microseconds already elapsed in the current second. +Basically the interface is very similar to the one of the gettimeofday system +call. + +http://redis.io/commands/time +*/ +func (self *Client) Time() ([]uint64, error) { + var ret []uint64 + + err := self.command( + &ret, + []byte("TIME"), + ) + + return ret, err +} + +/* +Returns the remaining time to live of a key that has a timeout. This +introspection capability allows a Redis client to check how many seconds a given +key will continue to be part of the dataset. + +http://redis.io/commands/ttl +*/ +func (self *Client) TTL(key string) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("TTL"), + []byte(key), + ) + + return ret, err +} + +/* +Returns the string representation of the type of the value stored at key. The +different types that can be returned are: string, list, set, zset and hash. + +http://redis.io/commands/type +*/ +func (self *Client) Type(key string) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("TYPE"), + []byte(key), + ) + + return ret, err +} + +/* +Unsubscribes the client from the given channels, or from all of them if none is +given. + +http://redis.io/commands/unsubscribe +*/ +func (self *Client) Unsubscribe(channel ...string) error { + + args := make([][]byte, len(channel)+1) + args[0] = []byte("UNSUBSCRIBE") + + for i, _ := range channel { + args[1+i] = to.Bytes(channel[i]) + } + + err := self.command(nil, args...) + + return err +} + +/* +Flushes all the previously watched keys for a transaction. + +http://redis.io/commands/unwatch +*/ +func (self *Client) Unwatch() (string, error) { + var ret string + + err := self.command( + &ret, + []byte("UNWATCH"), + ) + + return ret, err +} + +/* +Marks the given keys to be watched for conditional execution of a transaction. + +http://redis.io/commands/watch +*/ +func (self *Client) Watch(key ...string) (string, error) { + var ret string + + args := make([][]byte, len(key)+1) + args[0] = []byte("WATCH") + + for i, _ := range key { + args[1+i] = to.Bytes(key[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Adds all the specified members with the specified scores to the sorted set +stored at key. It is possible to specify multiple score/member pairs. If a +specified member is already a member of the sorted set, the score is updated and +the element reinserted at the right position to ensure the correct ordering. If +key does not exist, a new sorted set with the specified members as sole members +is created, like if the sorted set was empty. If the key exists but does not +hold a sorted set, an error is returned. + +http://redis.io/commands/zadd +*/ +func (self *Client) ZAdd(key string, arguments ...interface{}) (int64, error) { + var ret int64 + + if len(arguments)%2 != 0 { + return 0, errors.New("Failed to relate SCORE -> MEMBER using the given arguments.") + } + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("ZADD") + args[1] = []byte(key) + + for i, _ := range arguments { + args[2+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns the sorted set cardinality (number of elements) of the sorted set stored at key. + +http://redis.io/commands/zcard +*/ +func (self *Client) ZCard(key string) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("ZCARD"), + []byte(key), + ) + + return ret, err +} + +/* +Returns the number of elements in the sorted set at key with a score between min +and max. + +http://redis.io/commands/zcount +*/ +func (self *Client) ZCount(key string, min interface{}, max interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("ZCOUNT"), + []byte(key), + to.Bytes(min), + to.Bytes(max), + ) + + return ret, err +} + +/* +Increments the score of member in the sorted set stored at key by increment. If +member does not exist in the sorted set, it is added with increment as its score +(as if its previous score was 0.0). If key does not exist, a new sorted set with +the specified member as its sole member is created. + +http://redis.io/commands/zincrby +*/ +func (self *Client) ZIncrBy(key string, increment int64, member interface{}) (string, error) { + var ret string + + err := self.command( + &ret, + []byte("ZINCRBY"), + []byte(key), + to.Bytes(increment), + to.Bytes(member), + ) + + return ret, err +} + +/* +Computes the intersection of numkeys sorted sets given by the specified keys, +and stores the result in destination. It is mandatory to provide the number of +input keys (numkeys) before passing the input keys and the other (optional) +arguments. + +http://redis.io/commands/zinterstore +*/ +func (self *Client) ZInterStore(destination string, numkeys int64, arguments ...interface{}) (int64, error) { + var ret int64 + + args := make([][]byte, len(arguments)+3) + args[0] = []byte("ZINTERSTORE") + args[1] = []byte(destination) + args[2] = to.Bytes(numkeys) + + for i, _ := range arguments { + args[3+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns the specified range of elements in the sorted set stored at key. The +elements are considered to be ordered from the lowest to the highest score. +Lexicographical order is used for elements with equal score. + +http://redis.io/commands/zrange +*/ +func (self *Client) ZRange(key string, values ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(values)+2) + args[0] = []byte("ZRANGE") + args[1] = []byte(key) + + for i, v := range values { + args[2+i] = to.Bytes(v) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns all the elements in the sorted set at key with a score between min and +max (including elements with score equal to min or max). The elements are +considered to be ordered from low to high scores. + +http://redis.io/commands/zrangebyscore +*/ +func (self *Client) ZRangeByScore(key string, values ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(values)+2) + args[0] = []byte("ZRANGEBYSCORE") + args[1] = []byte(key) + + for i, _ := range values { + args[2+i] = to.Bytes(values[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns the rank of member in the sorted set stored at key, with the scores +ordered from low to high. The rank (or index) is 0-based, which means that the +member with the lowest score has rank 0. + +http://redis.io/commands/zrank +*/ +func (self *Client) ZRank(key string, member interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("ZRANK"), + []byte(key), + to.Bytes(member), + ) + + return ret, err +} + +/* +Removes the specified members from the sorted set stored at key. Non existing +members are ignored. + +http://redis.io/commands/zrem +*/ +func (self *Client) ZRem(key string, arguments ...interface{}) (int64, error) { + var ret int64 + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("ZREM") + args[1] = []byte(key) + + for i, _ := range arguments { + args[2+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Removes all elements in the sorted set stored at key with rank between start and +stop. Both start and stop are 0 -based indexes with 0 being the element with the +lowest score. These indexes can be negative numbers, where they indicate offsets +starting at the element with the highest score. For example: -1 is the element +with the highest score, -2 the element with the second highest score and so +forth. + +http://redis.io/commands/zremrangebyrank +*/ +func (self *Client) ZRemRangeByRank(key string, start interface{}, stop interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("ZREMRANGEBYRANK"), + []byte(key), + to.Bytes(start), + to.Bytes(stop), + ) + + return ret, err +} + +/* +Removes all elements in the sorted set stored at key with a score between min +and max (inclusive). + +http://redis.io/commands/zremrangebyscore +*/ +func (self *Client) ZRemRangeByScore(key string, min interface{}, max interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("ZREMRANGEBYSCORE"), + []byte(key), + to.Bytes(min), + to.Bytes(max), + ) + + return ret, err +} + +/* +Returns the specified range of elements in the sorted set stored at key. The +elements are considered to be ordered from the highest to the lowest score. +Descending lexicographical order is used for elements with equal score. + +http://redis.io/commands/zrevrange +*/ +func (self *Client) ZRevRange(key string, start int64, stop int64, params ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(params)+4) + args[0] = []byte("ZREVRANGE") + args[1] = []byte(key) + args[2] = to.Bytes(start) + args[3] = to.Bytes(stop) + + for i, v := range params { + args[4+i] = to.Bytes(v) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns all the elements in the sorted set at key with a score between max and +min (including elements with score equal to max or min). In contrary to the +default ordering of sorted sets, for this command the elements are considered to +be ordered from high to low scores. + +http://redis.io/commands/zrevrangebyscore +*/ +func (self *Client) ZRevRangeByScore(key string, start interface{}, stop interface{}, params ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(params)+4) + args[0] = []byte("ZREVRANGEBYSCORE") + args[1] = []byte(key) + args[2] = to.Bytes(start) + args[3] = to.Bytes(stop) + + for i, _ := range params { + args[4+i] = to.Bytes(params[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +Returns the rank of member in the sorted set stored at key, with the scores +ordered from high to low. The rank (or index) is 0-based, which means that the +member with the highest score has rank 0. + +http://redis.io/commands/zrevrank +*/ +func (self *Client) ZRevRank(key string, member interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("ZREVRANK"), + []byte(key), + to.Bytes(member), + ) + + return ret, err +} + +/* +Returns the score of member in the sorted set at key. + +http://redis.io/commands/zscore +*/ +func (self *Client) ZScore(key string, member interface{}) (int64, error) { + var ret int64 + + err := self.command( + &ret, + []byte("ZSCORE"), + []byte(key), + to.Bytes(member), + ) + + return ret, err +} + +/* +Computes the union of numkeys sorted sets given by the specified keys, and +stores the result in destination. It is mandatory to provide the number of input +keys (numkeys) before passing the input keys and the other (optional) arguments. + +http://redis.io/commands/zunionstore +*/ +func (self *Client) ZUnionStore(destination string, numkeys int64, key string, params ...interface{}) (int64, error) { + var ret int64 + + args := make([][]byte, len(params)+4) + args[0] = []byte("ZUNIONSTORE") + args[1] = []byte(destination) + args[2] = to.Bytes(numkeys) + args[3] = []byte(key) + + for i, _ := range params { + args[4+i] = to.Bytes(params[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +SCAN iterates the set of keys in the currently selected Redis database. + +http://redis.io/commands/scan +*/ +func (self *Client) Scan(cursor int64, arguments ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("SCAN") + args[1] = to.Bytes(cursor) + + for i, _ := range arguments { + args[2+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +SSCAN iterates elements of Sets types. + +http://redis.io/commands/scan +*/ +func (self *Client) SScan(cursor int64, arguments ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("SSCAN") + args[1] = to.Bytes(cursor) + + for i, _ := range arguments { + args[2+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +HSCAN iterates fields of Hash types and their associated values. + +http://redis.io/commands/scan +*/ +func (self *Client) HScan(cursor int64, arguments ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("HSCAN") + args[1] = to.Bytes(cursor) + + for i, _ := range arguments { + args[2+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} + +/* +ZSCAN iterates elements of Sorted Set types and their associated scores. + +http://redis.io/commands/zscan +*/ +func (self *Client) ZScan(cursor int64, arguments ...interface{}) ([]string, error) { + var ret []string + + args := make([][]byte, len(arguments)+2) + args[0] = []byte("ZSCAN") + args[1] = to.Bytes(cursor) + + for i, _ := range arguments { + args[2+i] = to.Bytes(arguments[i]) + } + + err := self.command(&ret, args...) + + return ret, err +} diff --git a/conn.go b/conn.go index 93ee0b2..4c99362 100644 --- a/conn.go +++ b/conn.go @@ -5,7 +5,7 @@ import ( "bytes" "errors" "github.com/xiam/resp" - //"log" + "log" "net" "strconv" "sync" @@ -238,7 +238,7 @@ func (self *conn) readNextMessage() ([]byte, error) { return nil, err } - //log.Printf("peek: %#v\n", string(head)) + log.Printf("peek: %#v\n", string(head)) switch head[0] { case respStringByte: @@ -263,6 +263,10 @@ func (self *conn) readNextMessage() ([]byte, error) { return nil, err } + if size < 0 { + return nil, err + } + var data []byte for i := 0; i < size; i++ { if data, err = self.readNextMessage(); err != nil { @@ -271,7 +275,7 @@ func (self *conn) readNextMessage() ([]byte, error) { buf = append(buf, data...) } - //log.Printf("S(1): %#v\n", string(data)) + log.Printf("S(1): %#v\n", string(data)) return buf, nil case respBulkByte: @@ -289,6 +293,12 @@ func (self *conn) readNextMessage() ([]byte, error) { return nil, err } + // Nil + if size < 1 { + self.readNextLine() + return nil, err + } + size = len(buf) + size + 2 if self.reader.Buffered() < size { @@ -309,7 +319,7 @@ func (self *conn) readNextMessage() ([]byte, error) { //log.Printf("S(2): %#v\n", string(data)) } - //log.Printf("S: %#v\n", string(data)) + log.Printf("S: %#v\n", string(data)) return data, nil } @@ -331,7 +341,7 @@ func (self *conn) writeCommand(command ...interface{}) error { func (self *conn) write(data []byte) error { var err error - //log.Printf("C: %#v\n", string(data)) + log.Printf("C: %#v\n", string(data)) if _, err = self.conn.Write(data); err != nil { return err @@ -413,8 +423,8 @@ func (self *conn) command(dest interface{}, command ...interface{}) error { buf = <-nextLine - if dest == nil { - return nil + if dest == nil || len(buf) == 0 { + return ErrNilReply } return resp.Unmarshal(buf, dest) diff --git a/fd.go b/fd.go deleted file mode 100644 index 3e276f0..0000000 --- a/fd.go +++ /dev/null @@ -1,187 +0,0 @@ -// This file is based on src/pkg/net/fd.go. -// -// I removed most of the networking stuff only to leave a bare bones file -// descriptor watcher that could make use of already existing multi-OS code, -// such as fd_linux.go, fd_darwin.go, etc. -// -// -// -// Original net/fd.go's notice: -// -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin freebsd linux netbsd openbsd - -package redis - -import ( - "sync" - "syscall" - "time" -) - -// File descriptor. -type fileFD struct { - sysfd int - - rdeadline int64 - wdeadline int64 -} - -var fds = map[int]*fileFD{} - -type pollServer struct { - poll *pollster // low-level OS hooks - sync.Mutex // controls pending and deadline - pending map[int]*fileFD - deadline int64 // next deadline (nsec since 1970) -} - -func (s *pollServer) AddFD(fd *fileFD, mode int) error { - - intfd := fd.sysfd - - if intfd < 0 { - return nil - } - - var t int64 - key := intfd << 1 - if mode == 'r' { - t = fd.rdeadline - } else { - key++ - t = fd.wdeadline - } - s.pending[key] = fd - - if t > 0 && (s.deadline == 0 || t < s.deadline) { - s.deadline = t - } - - _, err := s.poll.AddFD(intfd, mode, false) - - if err != nil { - s.EvictWrite(intfd) - s.EvictRead(intfd) - return nil - } - - return nil -} - -func (s *pollServer) EvictWrite(fd int) { - if ffd, ok := s.pending[fd<<1|1]; ok == true { - s.poll.DelFD(ffd.sysfd, 'w') - delete(s.pending, ffd.sysfd<<1|1) - } -} - -func (s *pollServer) EvictRead(fd int) { - if ffd, ok := s.pending[fd<<1]; ok == true { - s.poll.DelFD(ffd.sysfd, 'r') - delete(s.pending, ffd.sysfd<<1) - } -} - -func (s *pollServer) Now() int64 { - return time.Now().UnixNano() -} - -func (s *pollServer) Run() { - - s.Lock() - - defer s.Unlock() - - for { - - var t = s.deadline - - if t > 0 { - t = t - s.Now() - if t <= 0 { - continue - } - } - - fd, m, err := s.poll.WaitFD(s, t) - - if err != nil { - print("pollServer WaitFD: ", err.Error(), "\n") - return - } - - if fd < 0 { - continue - } - - if fdev[m] != nil { - if fn, ok := fdev[m][fd]; ok == true { - fn() - } - } - - } -} - -func (s *pollServer) WaitRead(fd int) error { - var err error - - /* - var ok bool - if _, ok = s.pending[fd<<1]; ok == true { - s.EvictRead(fd) - } - */ - - ffd, err := newFD(fd) - if err != nil { - return err - } - err = s.AddFD(ffd, 'r') - return err -} - -func (s *pollServer) WaitWrite(fd int) error { - var err error - - /* - var ok bool - if _, ok = s.pending[fd<<1|1]; ok == true { - s.EvictWrite(fd) - } - */ - - ffd, err := newFD(fd) - if err != nil { - return err - } - err = s.AddFD(ffd, 'w') - return err -} - -// Network FD methods. -// All the network FDs use a single pollServer. - -var pollserver *pollServer - -func startServer() { - p, err := newPollServer() - if err != nil { - print("Start pollServer: ", err.Error(), "\n") - } - pollserver = p -} - -func newFD(fd int) (*fileFD, error) { - if err := syscall.SetNonblock(fd, true); err != nil { - return nil, err - } - ffd := &fileFD{ - sysfd: fd, - } - return ffd, nil -} diff --git a/fd_darwin.go b/fd_darwin.go deleted file mode 100644 index f1f6337..0000000 --- a/fd_darwin.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Waiting for FDs via kqueue/kevent. - -package redis - -import ( - "errors" - "os" - "syscall" -) - -type pollster struct { - kq int - eventbuf [10]syscall.Kevent_t - events []syscall.Kevent_t - - // An event buffer for AddFD/DelFD. - // Must hold pollServer lock. - kbuf [1]syscall.Kevent_t -} - -func newpollster() (p *pollster, err error) { - p = new(pollster) - if p.kq, err = syscall.Kqueue(); err != nil { - return nil, os.NewSyscallError("kqueue", err) - } - syscall.CloseOnExec(p.kq) - p.events = p.eventbuf[0:0] - return p, nil -} - -func (p *pollster) AddFD(fd int, mode int, repeat bool) (bool, error) { - // pollServer is locked. - - var kmode int - if mode == 'r' { - kmode = syscall.EVFILT_READ - } else { - kmode = syscall.EVFILT_WRITE - } - ev := &p.kbuf[0] - // EV_ADD - add event to kqueue list - // EV_RECEIPT - generate fake EV_ERROR as result of add, - // rather than waiting for real event - // EV_ONESHOT - delete the event the first time it triggers - flags := syscall.EV_ADD | syscall.EV_RECEIPT - if !repeat { - flags |= syscall.EV_ONESHOT - } - syscall.SetKevent(ev, fd, kmode, flags) - - n, err := syscall.Kevent(p.kq, p.kbuf[:], p.kbuf[:], nil) - if err != nil { - return false, os.NewSyscallError("kevent", err) - } - if n != 1 || (ev.Flags&syscall.EV_ERROR) == 0 || int(ev.Ident) != fd || int(ev.Filter) != kmode { - return false, errors.New("kqueue phase error") - } - if ev.Data != 0 { - return false, syscall.Errno(ev.Data) - } - return false, nil -} - -func (p *pollster) DelFD(fd int, mode int) { - // pollServer is locked. - - var kmode int - if mode == 'r' { - kmode = syscall.EVFILT_READ - } else { - kmode = syscall.EVFILT_WRITE - } - ev := &p.kbuf[0] - // EV_DELETE - delete event from kqueue list - // EV_RECEIPT - generate fake EV_ERROR as result of add, - // rather than waiting for real event - syscall.SetKevent(ev, fd, kmode, syscall.EV_DELETE|syscall.EV_RECEIPT) - syscall.Kevent(p.kq, p.kbuf[0:], p.kbuf[0:], nil) -} - -func (p *pollster) WaitFD(s *pollServer, nsec int64) (fd int, mode int, err error) { - var t *syscall.Timespec - for len(p.events) == 0 { - if nsec > 0 { - if t == nil { - t = new(syscall.Timespec) - } - *t = syscall.NsecToTimespec(nsec) - } - - s.Unlock() - n, err := syscall.Kevent(p.kq, nil, p.eventbuf[:], t) - s.Lock() - - if err != nil { - if err == syscall.EINTR { - continue - } - return -1, 0, os.NewSyscallError("kevent", nil) - } - if n == 0 { - return -1, 0, nil - } - p.events = p.eventbuf[:n] - } - ev := &p.events[0] - p.events = p.events[1:] - fd = int(ev.Ident) - if ev.Filter == syscall.EVFILT_READ { - mode = 'r' - } else { - mode = 'w' - } - return fd, mode, nil -} - -func (p *pollster) Close() error { return os.NewSyscallError("close", syscall.Close(p.kq)) } diff --git a/fd_freebsd.go b/fd_freebsd.go deleted file mode 100644 index 7b629ea..0000000 --- a/fd_freebsd.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Waiting for FDs via kqueue/kevent. - -package redis - -import ( - "os" - "syscall" -) - -type pollster struct { - kq int - eventbuf [10]syscall.Kevent_t - events []syscall.Kevent_t - - // An event buffer for AddFD/DelFD. - // Must hold pollServer lock. - kbuf [1]syscall.Kevent_t -} - -func newpollster() (p *pollster, err error) { - p = new(pollster) - if p.kq, err = syscall.Kqueue(); err != nil { - return nil, os.NewSyscallError("kqueue", err) - } - syscall.CloseOnExec(p.kq) - p.events = p.eventbuf[0:0] - return p, nil -} - -func (p *pollster) AddFD(fd int, mode int, repeat bool) (bool, error) { - // pollServer is locked. - - var kmode int - if mode == 'r' { - kmode = syscall.EVFILT_READ - } else { - kmode = syscall.EVFILT_WRITE - } - ev := &p.kbuf[0] - // EV_ADD - add event to kqueue list - // EV_ONESHOT - delete the event the first time it triggers - flags := syscall.EV_ADD - if !repeat { - flags |= syscall.EV_ONESHOT - } - syscall.SetKevent(ev, fd, kmode, flags) - - n, err := syscall.Kevent(p.kq, p.kbuf[:], nil, nil) - if err != nil { - return false, os.NewSyscallError("kevent", err) - } - if n != 1 || (ev.Flags&syscall.EV_ERROR) == 0 || int(ev.Ident) != fd || int(ev.Filter) != kmode { - return false, os.NewSyscallError("kqueue phase error", err) - } - if ev.Data != 0 { - return false, syscall.Errno(int(ev.Data)) - } - return false, nil -} - -func (p *pollster) DelFD(fd int, mode int) { - // pollServer is locked. - - var kmode int - if mode == 'r' { - kmode = syscall.EVFILT_READ - } else { - kmode = syscall.EVFILT_WRITE - } - ev := &p.kbuf[0] - // EV_DELETE - delete event from kqueue list - syscall.SetKevent(ev, fd, kmode, syscall.EV_DELETE) - syscall.Kevent(p.kq, p.kbuf[:], nil, nil) -} - -func (p *pollster) WaitFD(s *pollServer, nsec int64) (fd int, mode int, err error) { - var t *syscall.Timespec - for len(p.events) == 0 { - if nsec > 0 { - if t == nil { - t = new(syscall.Timespec) - } - *t = syscall.NsecToTimespec(nsec) - } - - s.Unlock() - n, err := syscall.Kevent(p.kq, nil, p.eventbuf[:], t) - s.Lock() - - if err != nil { - if err == syscall.EINTR { - continue - } - return -1, 0, os.NewSyscallError("kevent", err) - } - if n == 0 { - return -1, 0, nil - } - p.events = p.eventbuf[:n] - } - ev := &p.events[0] - p.events = p.events[1:] - fd = int(ev.Ident) - if ev.Filter == syscall.EVFILT_READ { - mode = 'r' - } else { - mode = 'w' - } - return fd, mode, nil -} - -func (p *pollster) Close() error { return os.NewSyscallError("close", syscall.Close(p.kq)) } diff --git a/fd_linux.go b/fd_linux.go deleted file mode 100644 index 6ecf153..0000000 --- a/fd_linux.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Waiting for FDs via epoll(7). - -package redis - -import ( - "os" - "syscall" -) - -const ( - readFlags = syscall.EPOLLIN | syscall.EPOLLRDHUP - writeFlags = syscall.EPOLLOUT -) - -type pollster struct { - epfd int - - // Events we're already waiting for - // Must hold pollServer lock - events map[int]uint32 - - // An event buffer for EpollWait. - // Used without a lock, may only be used by WaitFD. - waitEventBuf [10]syscall.EpollEvent - waitEvents []syscall.EpollEvent - - // An event buffer for EpollCtl, to avoid a malloc. - // Must hold pollServer lock. - ctlEvent syscall.EpollEvent -} - -func newpollster() (p *pollster, err error) { - p = new(pollster) - if p.epfd, err = syscall.EpollCreate1(syscall.EPOLL_CLOEXEC); err != nil { - if err != syscall.ENOSYS { - return nil, os.NewSyscallError("epoll_create1", err) - } - // The arg to epoll_create is a hint to the kernel - // about the number of FDs we will care about. - // We don't know, and since 2.6.8 the kernel ignores it anyhow. - if p.epfd, err = syscall.EpollCreate(16); err != nil { - return nil, os.NewSyscallError("epoll_create", err) - } - syscall.CloseOnExec(p.epfd) - } - p.events = make(map[int]uint32) - return p, nil -} - -func (p *pollster) AddFD(fd int, mode int, repeat bool) (bool, error) { - // pollServer is locked. - - var already bool - p.ctlEvent.Fd = int32(fd) - p.ctlEvent.Events, already = p.events[fd] - if !repeat { - p.ctlEvent.Events |= syscall.EPOLLONESHOT - } - if mode == 'r' { - p.ctlEvent.Events |= readFlags - } else { - p.ctlEvent.Events |= writeFlags - } - - var op int - if already { - op = syscall.EPOLL_CTL_MOD - } else { - op = syscall.EPOLL_CTL_ADD - } - if err := syscall.EpollCtl(p.epfd, op, fd, &p.ctlEvent); err != nil { - return false, os.NewSyscallError("epoll_ctl", err) - } - p.events[fd] = p.ctlEvent.Events - return false, nil -} - -func (p *pollster) StopWaiting(fd int, bits uint) { - // pollServer is locked. - - events, already := p.events[fd] - if !already { - // The fd returned by the kernel may have been - // cancelled already; return silently. - return - } - - // If syscall.EPOLLONESHOT is not set, the wait - // is a repeating wait, so don't change it. - if events&syscall.EPOLLONESHOT == 0 { - return - } - - // Disable the given bits. - // If we're still waiting for other events, modify the fd - // event in the kernel. Otherwise, delete it. - events &= ^uint32(bits) - if int32(events)&^syscall.EPOLLONESHOT != 0 { - p.ctlEvent.Fd = int32(fd) - p.ctlEvent.Events = events - if err := syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_MOD, fd, &p.ctlEvent); err != nil { - print("Epoll modify fd=", fd, ": ", err.Error(), "\n") - } - p.events[fd] = events - } else { - if err := syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_DEL, fd, nil); err != nil { - print("Epoll delete fd=", fd, ": ", err.Error(), "\n") - } - delete(p.events, fd) - } -} - -func (p *pollster) DelFD(fd int, mode int) { - // pollServer is locked. - - if mode == 'r' { - p.StopWaiting(fd, readFlags) - } else { - p.StopWaiting(fd, writeFlags) - } - - // Discard any queued up events. - i := 0 - for i < len(p.waitEvents) { - if fd == int(p.waitEvents[i].Fd) { - copy(p.waitEvents[i:], p.waitEvents[i+1:]) - p.waitEvents = p.waitEvents[:len(p.waitEvents)-1] - } else { - i++ - } - } -} - -func (p *pollster) WaitFD(s *pollServer, nsec int64) (fd int, mode int, err error) { - for len(p.waitEvents) == 0 { - var msec int = -1 - if nsec > 0 { - msec = int((nsec + 1e6 - 1) / 1e6) - } - - s.Unlock() - n, err := syscall.EpollWait(p.epfd, p.waitEventBuf[0:], msec) - s.Lock() - - if err != nil { - if err == syscall.EAGAIN || err == syscall.EINTR { - continue - } - return -1, 0, os.NewSyscallError("epoll_wait", err) - } - if n == 0 { - return -1, 0, nil - } - p.waitEvents = p.waitEventBuf[0:n] - } - - ev := &p.waitEvents[0] - p.waitEvents = p.waitEvents[1:] - - fd = int(ev.Fd) - - if ev.Events&writeFlags != 0 { - p.StopWaiting(fd, writeFlags) - return fd, 'w', nil - } - if ev.Events&readFlags != 0 { - p.StopWaiting(fd, readFlags) - return fd, 'r', nil - } - - // Other events are error conditions - wake whoever is waiting. - events, _ := p.events[fd] - if events&writeFlags != 0 { - p.StopWaiting(fd, writeFlags) - return fd, 'w', nil - } - p.StopWaiting(fd, readFlags) - return fd, 'r', nil -} - -func (p *pollster) Close() error { - return os.NewSyscallError("close", syscall.Close(p.epfd)) -} diff --git a/fd_netbsd.go b/fd_netbsd.go deleted file mode 100644 index 7b629ea..0000000 --- a/fd_netbsd.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Waiting for FDs via kqueue/kevent. - -package redis - -import ( - "os" - "syscall" -) - -type pollster struct { - kq int - eventbuf [10]syscall.Kevent_t - events []syscall.Kevent_t - - // An event buffer for AddFD/DelFD. - // Must hold pollServer lock. - kbuf [1]syscall.Kevent_t -} - -func newpollster() (p *pollster, err error) { - p = new(pollster) - if p.kq, err = syscall.Kqueue(); err != nil { - return nil, os.NewSyscallError("kqueue", err) - } - syscall.CloseOnExec(p.kq) - p.events = p.eventbuf[0:0] - return p, nil -} - -func (p *pollster) AddFD(fd int, mode int, repeat bool) (bool, error) { - // pollServer is locked. - - var kmode int - if mode == 'r' { - kmode = syscall.EVFILT_READ - } else { - kmode = syscall.EVFILT_WRITE - } - ev := &p.kbuf[0] - // EV_ADD - add event to kqueue list - // EV_ONESHOT - delete the event the first time it triggers - flags := syscall.EV_ADD - if !repeat { - flags |= syscall.EV_ONESHOT - } - syscall.SetKevent(ev, fd, kmode, flags) - - n, err := syscall.Kevent(p.kq, p.kbuf[:], nil, nil) - if err != nil { - return false, os.NewSyscallError("kevent", err) - } - if n != 1 || (ev.Flags&syscall.EV_ERROR) == 0 || int(ev.Ident) != fd || int(ev.Filter) != kmode { - return false, os.NewSyscallError("kqueue phase error", err) - } - if ev.Data != 0 { - return false, syscall.Errno(int(ev.Data)) - } - return false, nil -} - -func (p *pollster) DelFD(fd int, mode int) { - // pollServer is locked. - - var kmode int - if mode == 'r' { - kmode = syscall.EVFILT_READ - } else { - kmode = syscall.EVFILT_WRITE - } - ev := &p.kbuf[0] - // EV_DELETE - delete event from kqueue list - syscall.SetKevent(ev, fd, kmode, syscall.EV_DELETE) - syscall.Kevent(p.kq, p.kbuf[:], nil, nil) -} - -func (p *pollster) WaitFD(s *pollServer, nsec int64) (fd int, mode int, err error) { - var t *syscall.Timespec - for len(p.events) == 0 { - if nsec > 0 { - if t == nil { - t = new(syscall.Timespec) - } - *t = syscall.NsecToTimespec(nsec) - } - - s.Unlock() - n, err := syscall.Kevent(p.kq, nil, p.eventbuf[:], t) - s.Lock() - - if err != nil { - if err == syscall.EINTR { - continue - } - return -1, 0, os.NewSyscallError("kevent", err) - } - if n == 0 { - return -1, 0, nil - } - p.events = p.eventbuf[:n] - } - ev := &p.events[0] - p.events = p.events[1:] - fd = int(ev.Ident) - if ev.Filter == syscall.EVFILT_READ { - mode = 'r' - } else { - mode = 'w' - } - return fd, mode, nil -} - -func (p *pollster) Close() error { return os.NewSyscallError("close", syscall.Close(p.kq)) } diff --git a/fd_openbsd.go b/fd_openbsd.go deleted file mode 100644 index 7b629ea..0000000 --- a/fd_openbsd.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Waiting for FDs via kqueue/kevent. - -package redis - -import ( - "os" - "syscall" -) - -type pollster struct { - kq int - eventbuf [10]syscall.Kevent_t - events []syscall.Kevent_t - - // An event buffer for AddFD/DelFD. - // Must hold pollServer lock. - kbuf [1]syscall.Kevent_t -} - -func newpollster() (p *pollster, err error) { - p = new(pollster) - if p.kq, err = syscall.Kqueue(); err != nil { - return nil, os.NewSyscallError("kqueue", err) - } - syscall.CloseOnExec(p.kq) - p.events = p.eventbuf[0:0] - return p, nil -} - -func (p *pollster) AddFD(fd int, mode int, repeat bool) (bool, error) { - // pollServer is locked. - - var kmode int - if mode == 'r' { - kmode = syscall.EVFILT_READ - } else { - kmode = syscall.EVFILT_WRITE - } - ev := &p.kbuf[0] - // EV_ADD - add event to kqueue list - // EV_ONESHOT - delete the event the first time it triggers - flags := syscall.EV_ADD - if !repeat { - flags |= syscall.EV_ONESHOT - } - syscall.SetKevent(ev, fd, kmode, flags) - - n, err := syscall.Kevent(p.kq, p.kbuf[:], nil, nil) - if err != nil { - return false, os.NewSyscallError("kevent", err) - } - if n != 1 || (ev.Flags&syscall.EV_ERROR) == 0 || int(ev.Ident) != fd || int(ev.Filter) != kmode { - return false, os.NewSyscallError("kqueue phase error", err) - } - if ev.Data != 0 { - return false, syscall.Errno(int(ev.Data)) - } - return false, nil -} - -func (p *pollster) DelFD(fd int, mode int) { - // pollServer is locked. - - var kmode int - if mode == 'r' { - kmode = syscall.EVFILT_READ - } else { - kmode = syscall.EVFILT_WRITE - } - ev := &p.kbuf[0] - // EV_DELETE - delete event from kqueue list - syscall.SetKevent(ev, fd, kmode, syscall.EV_DELETE) - syscall.Kevent(p.kq, p.kbuf[:], nil, nil) -} - -func (p *pollster) WaitFD(s *pollServer, nsec int64) (fd int, mode int, err error) { - var t *syscall.Timespec - for len(p.events) == 0 { - if nsec > 0 { - if t == nil { - t = new(syscall.Timespec) - } - *t = syscall.NsecToTimespec(nsec) - } - - s.Unlock() - n, err := syscall.Kevent(p.kq, nil, p.eventbuf[:], t) - s.Lock() - - if err != nil { - if err == syscall.EINTR { - continue - } - return -1, 0, os.NewSyscallError("kevent", err) - } - if n == 0 { - return -1, 0, nil - } - p.events = p.eventbuf[:n] - } - ev := &p.events[0] - p.events = p.events[1:] - fd = int(ev.Ident) - if ev.Filter == syscall.EVFILT_READ { - mode = 'r' - } else { - mode = 'w' - } - return fd, mode, nil -} - -func (p *pollster) Close() error { return os.NewSyscallError("close", syscall.Close(p.kq)) } diff --git a/fd_windows.go b/fd_windows.go deleted file mode 100644 index fb7c44b..0000000 --- a/fd_windows.go +++ /dev/null @@ -1,589 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package redis - -import ( - "errors" - "io" - "os" - "runtime" - "sync" - "syscall" - "time" - "unsafe" -) - -var initErr error - -func init() { - var d syscall.WSAData - e := syscall.WSAStartup(uint32(0x202), &d) - if e != nil { - initErr = os.NewSyscallError("WSAStartup", e) - } -} - -func closesocket(s syscall.Handle) error { - return syscall.Closesocket(s) -} - -// Interface for all io operations. -type anOpIface interface { - Op() *anOp - Name() string - Submit() error -} - -// IO completion result parameters. -type ioResult struct { - qty uint32 - err error -} - -// anOp implements functionality common to all io operations. -type anOp struct { - // Used by IOCP interface, it must be first field - // of the struct, as our code rely on it. - o syscall.Overlapped - - resultc chan ioResult - errnoc chan error - fd *netFD -} - -func (o *anOp) Init(fd *netFD, mode int) { - o.fd = fd - var i int - if mode == 'r' { - i = 0 - } else { - i = 1 - } - if fd.resultc[i] == nil { - fd.resultc[i] = make(chan ioResult, 1) - } - o.resultc = fd.resultc[i] - if fd.errnoc[i] == nil { - fd.errnoc[i] = make(chan error) - } - o.errnoc = fd.errnoc[i] -} - -func (o *anOp) Op() *anOp { - return o -} - -// bufOp is used by io operations that read / write -// data from / to client buffer. -type bufOp struct { - anOp - buf syscall.WSABuf -} - -func (o *bufOp) Init(fd *netFD, buf []byte, mode int) { - o.anOp.Init(fd, mode) - o.buf.Len = uint32(len(buf)) - if len(buf) == 0 { - o.buf.Buf = nil - } else { - o.buf.Buf = (*byte)(unsafe.Pointer(&buf[0])) - } -} - -// resultSrv will retrieve all io completion results from -// iocp and send them to the correspondent waiting client -// goroutine via channel supplied in the request. -type resultSrv struct { - iocp syscall.Handle -} - -func (s *resultSrv) Run() { - var o *syscall.Overlapped - var key uint32 - var r ioResult - for { - r.err = syscall.GetQueuedCompletionStatus(s.iocp, &(r.qty), &key, &o, syscall.INFINITE) - switch { - case r.err == nil: - // Dequeued successfully completed io packet. - case r.err == syscall.Errno(syscall.WAIT_TIMEOUT) && o == nil: - // Wait has timed out (should not happen now, but might be used in the future). - panic("GetQueuedCompletionStatus timed out") - case o == nil: - // Failed to dequeue anything -> report the error. - panic("GetQueuedCompletionStatus failed " + r.err.Error()) - default: - // Dequeued failed io packet. - } - (*anOp)(unsafe.Pointer(o)).resultc <- r - } -} - -// ioSrv executes net io requests. -type ioSrv struct { - submchan chan anOpIface // submit io requests - canchan chan anOpIface // cancel io requests -} - -// ProcessRemoteIO will execute submit io requests on behalf -// of other goroutines, all on a single os thread, so it can -// cancel them later. Results of all operations will be sent -// back to their requesters via channel supplied in request. -func (s *ioSrv) ProcessRemoteIO() { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - for { - select { - case o := <-s.submchan: - o.Op().errnoc <- o.Submit() - case o := <-s.canchan: - o.Op().errnoc <- syscall.CancelIo(syscall.Handle(o.Op().fd.sysfd)) - } - } -} - -// ExecIO executes a single io operation. It either executes it -// inline, or, if a deadline is employed, passes the request onto -// a special goroutine and waits for completion or cancels request. -// deadline is unix nanos. -func (s *ioSrv) ExecIO(oi anOpIface, deadline int64) (int, error) { - var err error - o := oi.Op() - if deadline != 0 { - // Send request to a special dedicated thread, - // so it can stop the io with CancelIO later. - s.submchan <- oi - err = <-o.errnoc - } else { - err = oi.Submit() - } - switch err { - case nil: - // IO completed immediately, but we need to get our completion message anyway. - case syscall.ERROR_IO_PENDING: - // IO started, and we have to wait for its completion. - err = nil - default: - return 0, &OpError{oi.Name(), o.fd.net, o.fd.laddr, err} - } - // Wait for our request to complete. - var r ioResult - if deadline != 0 { - dt := deadline - time.Now().UnixNano() - if dt < 1 { - dt = 1 - } - timer := time.NewTimer(time.Duration(dt) * time.Nanosecond) - defer timer.Stop() - select { - case r = <-o.resultc: - case <-timer.C: - s.canchan <- oi - <-o.errnoc - r = <-o.resultc - if r.err == syscall.ERROR_OPERATION_ABORTED { // IO Canceled - r.err = syscall.EWOULDBLOCK - } - } - } else { - r = <-o.resultc - } - if r.err != nil { - err = &OpError{oi.Name(), o.fd.net, o.fd.laddr, r.err} - } - return int(r.qty), err -} - -// Start helper goroutines. -var resultsrv *resultSrv -var iosrv *ioSrv -var onceStartServer sync.Once - -func startServer() { - resultsrv = new(resultSrv) - var err error - resultsrv.iocp, err = syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 1) - if err != nil { - panic("CreateIoCompletionPort: " + err.Error()) - } - go resultsrv.Run() - - iosrv = new(ioSrv) - iosrv.submchan = make(chan anOpIface) - iosrv.canchan = make(chan anOpIface) - go iosrv.ProcessRemoteIO() -} - -// Network file descriptor. -type netFD struct { - // locking/lifetime of sysfd - sysmu sync.Mutex - sysref int - closing bool - - // immutable until Close - sysfd syscall.Handle - family int - sotype int - isConnected bool - net string - laddr Addr - raddr Addr - resultc [2]chan ioResult // read/write completion results - errnoc [2]chan error // read/write submit or cancel operation errors - - // owned by client - rdeadline int64 - rio sync.Mutex - wdeadline int64 - wio sync.Mutex -} - -func allocFD(fd syscall.Handle, family, sotype int, net string) *netFD { - netfd := &netFD{ - sysfd: fd, - family: family, - sotype: sotype, - net: net, - } - runtime.SetFinalizer(netfd, (*netFD).Close) - return netfd -} - -func newFD(fd syscall.Handle, family, proto int, net string) (*netFD, error) { - if initErr != nil { - return nil, initErr - } - onceStartServer.Do(startServer) - // Associate our socket with resultsrv.iocp. - if _, err := syscall.CreateIoCompletionPort(syscall.Handle(fd), resultsrv.iocp, 0, 0); err != nil { - return nil, err - } - return allocFD(fd, family, proto, net), nil -} - -func (fd *netFD) setAddr(laddr, raddr Addr) { - fd.laddr = laddr - fd.raddr = raddr -} - -func (fd *netFD) connect(ra syscall.Sockaddr) error { - return syscall.Connect(fd.sysfd, ra) -} - -var errClosing = errors.New("use of closed network connection") - -// Add a reference to this fd. -// If closing==true, mark the fd as closing. -// Returns an error if the fd cannot be used. -func (fd *netFD) incref(closing bool) error { - if fd == nil { - return errClosing - } - fd.sysmu.Lock() - if fd.closing { - fd.sysmu.Unlock() - return errClosing - } - fd.sysref++ - if closing { - fd.closing = true - } - closing = fd.closing - fd.sysmu.Unlock() - return nil -} - -// Remove a reference to this FD and close if we've been asked to do so (and -// there are no references left. -func (fd *netFD) decref() { - fd.sysmu.Lock() - fd.sysref-- - // NOTE(rsc): On Unix we check fd.sysref == 0 here before closing, - // but on Windows we have no way to wake up the blocked I/O other - // than closing the socket (or calling Shutdown, which breaks other - // programs that might have a reference to the socket). So there is - // a small race here that we might close fd.sysfd and then some other - // goroutine might start a read of fd.sysfd (having read it before we - // write InvalidHandle to it), which might refer to some other file - // if the specific handle value gets reused. I think handle values on - // Windows are not reused as aggressively as file descriptors on Unix, - // so this might be tolerable. - if fd.closing && fd.sysfd != syscall.InvalidHandle { - // In case the user has set linger, switch to blocking mode so - // the close blocks. As long as this doesn't happen often, we - // can handle the extra OS processes. Otherwise we'll need to - // use the resultsrv for Close too. Sigh. - syscall.SetNonblock(fd.sysfd, false) - closesocket(fd.sysfd) - fd.sysfd = syscall.InvalidHandle - // no need for a finalizer anymore - runtime.SetFinalizer(fd, nil) - } - fd.sysmu.Unlock() -} - -func (fd *netFD) Close() error { - if err := fd.incref(true); err != nil { - return err - } - fd.decref() - return nil -} - -func (fd *netFD) shutdown(how int) error { - if fd == nil || fd.sysfd == syscall.InvalidHandle { - return syscall.EINVAL - } - err := syscall.Shutdown(fd.sysfd, how) - if err != nil { - return &OpError{"shutdown", fd.net, fd.laddr, err} - } - return nil -} - -func (fd *netFD) CloseRead() error { - return fd.shutdown(syscall.SHUT_RD) -} - -func (fd *netFD) CloseWrite() error { - return fd.shutdown(syscall.SHUT_WR) -} - -// Read from network. - -type readOp struct { - bufOp -} - -func (o *readOp) Submit() error { - var d, f uint32 - return syscall.WSARecv(syscall.Handle(o.fd.sysfd), &o.buf, 1, &d, &f, &o.o, nil) -} - -func (o *readOp) Name() string { - return "WSARecv" -} - -func (fd *netFD) Read(buf []byte) (int, error) { - if fd == nil { - return 0, syscall.EINVAL - } - fd.rio.Lock() - defer fd.rio.Unlock() - if err := fd.incref(false); err != nil { - return 0, err - } - defer fd.decref() - if fd.sysfd == syscall.InvalidHandle { - return 0, syscall.EINVAL - } - var o readOp - o.Init(fd, buf, 'r') - n, err := iosrv.ExecIO(&o, fd.rdeadline) - if err == nil && n == 0 { - err = io.EOF - } - return n, err -} - -// ReadFrom from network. - -type readFromOp struct { - bufOp - rsa syscall.RawSockaddrAny - rsan int32 -} - -func (o *readFromOp) Submit() error { - var d, f uint32 - return syscall.WSARecvFrom(o.fd.sysfd, &o.buf, 1, &d, &f, &o.rsa, &o.rsan, &o.o, nil) -} - -func (o *readFromOp) Name() string { - return "WSARecvFrom" -} - -func (fd *netFD) ReadFrom(buf []byte) (n int, sa syscall.Sockaddr, err error) { - if fd == nil { - return 0, nil, syscall.EINVAL - } - if len(buf) == 0 { - return 0, nil, nil - } - fd.rio.Lock() - defer fd.rio.Unlock() - if err := fd.incref(false); err != nil { - return 0, nil, err - } - defer fd.decref() - var o readFromOp - o.Init(fd, buf, 'r') - o.rsan = int32(unsafe.Sizeof(o.rsa)) - n, err = iosrv.ExecIO(&o, fd.rdeadline) - if err != nil { - return 0, nil, err - } - sa, _ = o.rsa.Sockaddr() - return -} - -// Write to network. - -type writeOp struct { - bufOp -} - -func (o *writeOp) Submit() error { - var d uint32 - return syscall.WSASend(o.fd.sysfd, &o.buf, 1, &d, 0, &o.o, nil) -} - -func (o *writeOp) Name() string { - return "WSASend" -} - -func (fd *netFD) Write(buf []byte) (int, error) { - if fd == nil { - return 0, syscall.EINVAL - } - fd.wio.Lock() - defer fd.wio.Unlock() - if err := fd.incref(false); err != nil { - return 0, err - } - defer fd.decref() - var o writeOp - o.Init(fd, buf, 'w') - return iosrv.ExecIO(&o, fd.wdeadline) -} - -// WriteTo to network. - -type writeToOp struct { - bufOp - sa syscall.Sockaddr -} - -func (o *writeToOp) Submit() error { - var d uint32 - return syscall.WSASendto(o.fd.sysfd, &o.buf, 1, &d, 0, o.sa, &o.o, nil) -} - -func (o *writeToOp) Name() string { - return "WSASendto" -} - -func (fd *netFD) WriteTo(buf []byte, sa syscall.Sockaddr) (int, error) { - if fd == nil { - return 0, syscall.EINVAL - } - if len(buf) == 0 { - return 0, nil - } - fd.wio.Lock() - defer fd.wio.Unlock() - if err := fd.incref(false); err != nil { - return 0, err - } - defer fd.decref() - if fd.sysfd == syscall.InvalidHandle { - return 0, syscall.EINVAL - } - var o writeToOp - o.Init(fd, buf, 'w') - o.sa = sa - return iosrv.ExecIO(&o, fd.wdeadline) -} - -// Accept new network connections. - -type acceptOp struct { - anOp - newsock syscall.Handle - attrs [2]syscall.RawSockaddrAny // space for local and remote address only -} - -func (o *acceptOp) Submit() error { - var d uint32 - l := uint32(unsafe.Sizeof(o.attrs[0])) - return syscall.AcceptEx(o.fd.sysfd, o.newsock, - (*byte)(unsafe.Pointer(&o.attrs[0])), 0, l, l, &d, &o.o) -} - -func (o *acceptOp) Name() string { - return "AcceptEx" -} - -func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) { - if err := fd.incref(false); err != nil { - return nil, err - } - defer fd.decref() - - // Get new socket. - // See ../syscall/exec.go for description of ForkLock. - syscall.ForkLock.RLock() - s, err := syscall.Socket(fd.family, fd.sotype, 0) - if err != nil { - syscall.ForkLock.RUnlock() - return nil, err - } - syscall.CloseOnExec(s) - syscall.ForkLock.RUnlock() - - // Associate our new socket with IOCP. - onceStartServer.Do(startServer) - if _, err := syscall.CreateIoCompletionPort(s, resultsrv.iocp, 0, 0); err != nil { - return nil, &OpError{"CreateIoCompletionPort", fd.net, fd.laddr, err} - } - - // Submit accept request. - var o acceptOp - o.Init(fd, 'r') - o.newsock = s - _, err = iosrv.ExecIO(&o, 0) - if err != nil { - closesocket(s) - return nil, err - } - - // Inherit properties of the listening socket. - err = syscall.Setsockopt(s, syscall.SOL_SOCKET, syscall.SO_UPDATE_ACCEPT_CONTEXT, (*byte)(unsafe.Pointer(&fd.sysfd)), int32(unsafe.Sizeof(fd.sysfd))) - if err != nil { - closesocket(s) - return nil, err - } - - // Get local and peer addr out of AcceptEx buffer. - var lrsa, rrsa *syscall.RawSockaddrAny - var llen, rlen int32 - l := uint32(unsafe.Sizeof(*lrsa)) - syscall.GetAcceptExSockaddrs((*byte)(unsafe.Pointer(&o.attrs[0])), - 0, l, l, &lrsa, &llen, &rrsa, &rlen) - lsa, _ := lrsa.Sockaddr() - rsa, _ := rrsa.Sockaddr() - - netfd := allocFD(s, fd.family, fd.sotype, fd.net) - netfd.setAddr(toAddr(lsa), toAddr(rsa)) - return netfd, nil -} - -// Unimplemented functions. - -func (fd *netFD) dup() (*os.File, error) { - // TODO: Implement this - return nil, os.NewSyscallError("dup", syscall.EWINDOWS) -} - -var errNoSupport = errors.New("address family not supported") - -func (fd *netFD) ReadMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.Sockaddr, err error) { - return 0, 0, 0, nil, errNoSupport -} - -func (fd *netFD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) { - return 0, 0, errNoSupport -} diff --git a/main.go b/main.go index e068c25..b555a83 100644 --- a/main.go +++ b/main.go @@ -1,86 +1,34 @@ -/* - Copyright (c) 2013 José Carlos Nieto, https://menteslibres.net/xiam +// Copyright (c) 2013-2014 José Carlos Nieto, https://menteslibres.net/xiam +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - (Unofficial) bindings for the official C redis client (hiredis). - - Supports the complete set of commands for the 2.6 series. -*/ package redis -/* -#cgo CFLAGS: -I./_hiredis -DCGO=1 - -#include -#include - -#include "net.h" -#include "sds.h" -#include "hiredis.h" -#include "async.h" - -typedef struct redisEvent { - -} redisEvent; - -// Redis events with goroutines. -typedef struct redisGoroutineEvents { - redisAsyncContext *context; -} redisGoroutineEvents; - -int redisGetReplyType(redisReply *); - -struct timeval redisTimeVal(long, long); -void getCallback(redisAsyncContext *, void *, void *); - -int redisGetReplyType(redisReply *); -int redisAsyncCommandArgvWrapper(redisAsyncContext *, void *, int, const char **, const size_t *); -redisReply *redisReplyGetElement(redisReply *, int i); - -int redisGoroutineAttach(redisAsyncContext *, redisEvent *); -void redisGoroutineWriteEvent(void *); -void redisGoroutineReadEvent(void *); -void redisForceAsyncFree(redisAsyncContext *); -void redisAsyncSetCallbacks(redisAsyncContext *); - -*/ -import "C" - import ( "errors" "fmt" - "menteslibres.net/gosexy/to" - "reflect" - "strings" "time" - "unsafe" -) - -const ( - pollWait = 100 ) const ( + // Default port for connecting to redis. DefaultPort = 6379 ) @@ -103,3586 +51,72 @@ var ( ErrInvalidDestination = errors.New(`Destination must be a pointer.`) ) -var asyncClients map[*C.redisAsyncContext]*Client - -// Avoids creating reflect.TypeOf([]interface{}{}) in setReplyValue. -var interfaceSliceType = reflect.TypeOf([]interface{}{}) - -// Workaround for creating a C's struct timeval. -func cStructTimeval(timeout time.Duration) C.struct_timeval { - return C.redisTimeVal(C.long(int64(timeout/time.Second)), C.long(int64(timeout%time.Millisecond))) -} - // A redis client type Client struct { - ctx *C.redisContext - async *C.redisAsyncContext - ev *C.redisEvent - onConnect chan int - onDisconnect chan int - connected bool -} - -// File descriptor event map. -var fdev map[int]map[int]func() - -func init() { - - fdev = map[int]map[int]func(){ - 'r': make(map[int]func()), - 'w': make(map[int]func()), - } - - asyncClients = map[*C.redisAsyncContext]*Client{} - - startServer() + redis *conn } -// Creates a new redis client. func New() *Client { - self := &Client{} - return self -} - -//export redisEventAddWrite -func redisEventAddWrite(evptr unsafe.Pointer) { - ev := (*C.redisGoroutineEvents)(evptr) - ctx := (*C.redisContext)(&ev.context.c) - fd := int(ctx.fd) - - fdev['w'][fd] = func() { - C.redisGoroutineWriteEvent(unsafe.Pointer(ev)) - } - - pollserver.WaitWrite(fd) -} - -//export redisEventDelWrite -func redisEventDelWrite(evptr unsafe.Pointer) { - ev := (*C.redisGoroutineEvents)(evptr) - ctx := (*C.redisContext)(&ev.context.c) - fd := int(ctx.fd) - fdev['w'][fd] = nil - delete(fdev['w'], fd) - pollserver.EvictWrite(fd) - -} - -//export redisEventDelRead -func redisEventDelRead(evptr unsafe.Pointer) { - ev := (*C.redisGoroutineEvents)(evptr) - ctx := (*C.redisContext)(&ev.context.c) - fd := int(ctx.fd) - fdev['r'][fd] = nil - delete(fdev['r'], fd) - pollserver.EvictRead(fd) -} - -//export redisEventCleanup -func redisEventCleanup(evptr unsafe.Pointer) { - ev := (*C.redisGoroutineEvents)(evptr) - ctx := (*C.redisContext)(&ev.context.c) - fd := int(ctx.fd) - delete(fdev['r'], fd) - delete(fdev['w'], fd) - pollserver.EvictRead(fd) - pollserver.EvictWrite(fd) -} - -//export redisEventAddRead -func redisEventAddRead(evptr unsafe.Pointer) { - ev := (*C.redisGoroutineEvents)(evptr) - ctx := (*C.redisContext)(&ev.context.c) - fd := int(ctx.fd) - - fdev['r'][fd] = func() { - C.redisGoroutineReadEvent(unsafe.Pointer(ev)) - } - pollserver.WaitRead(fd) -} - -//export asyncReceive -func asyncReceive(a unsafe.Pointer, b unsafe.Pointer) { - if b != nil { - (*(*func(unsafe.Pointer))(unsafe.Pointer(&b)))(a) - } + c := new(Client) + return c } -//export redisAsyncConnectCallback -func redisAsyncConnectCallback(a unsafe.Pointer, b C.int) { - ac := (*C.redisAsyncContext)(a) - client := asyncClients[ac] - client.onConnect <- int(b) -} - -//export redisAsyncDisconnectCallback -func redisAsyncDisconnectCallback(a unsafe.Pointer, b C.int) { - ac := (*C.redisAsyncContext)(a) - client := asyncClients[ac] - client.onDisconnect <- int(b) -} - -// Associates context with client. -func (self *Client) setContext(ctx *C.redisContext) error { - - self.ctx = ctx - self.async = nil - - if ctx == nil { - return ErrFailedAllocation - } else if ctx.err > 0 { - err := self.redisError() - C.redisFree(ctx) - self.ctx = nil - return err +func (self *Client) Close() error { + if self.redis != nil { + self.redis.close() } - return nil } -// Creates an asynchronous connection. -func (self *Client) asyncConnect(ctx *C.redisContext) error { - var status int - - err := self.setContext(ctx) - - if err != nil { - return err - } - - self.async = C.redisAsyncInitialize(self.ctx) - - asyncClients[self.async] = self +// Connects the client to the given host and port. +func (self *Client) Connect(host string, port uint) (err error) { - if self.async == nil { - C.redisFree(self.ctx) - return ErrFailedAllocation + if self.redis != nil { + self.redis.close() } - self.onConnect = make(chan int) - self.onDisconnect = make(chan int) - - C.__redisAsyncCopyError(self.async) - - C.redisGoroutineAttach(self.async, self.ev) - - C.redisAsyncSetCallbacks(self.async) - - // Waiting for the client to connect. - status = <-self.onConnect - - if status < 0 { - return ErrServerIsDown - } else if status == C.REDIS_OK { - self.connected = true - // Waiting for disconnection. - go func() { - <-self.onDisconnect - self.connected = false - }() - } else { - return self.redisError() + if self.redis, err = dial(`tcp`, fmt.Sprintf(`%s:%d`, host, port)); err != nil { + return err } return nil } -// Returns last redis error status -func (self *Client) redisError() error { - var errno int - var errstr string - if self.ctx != nil { - errno = int(self.ctx.err) - errstr = C.GoString(&(self.ctx.errstr[0])) - } - if self.async != nil { - errno = int(self.async.err) - errstr = C.GoString(self.async.errstr) - } - switch errno { - case C.REDIS_OK: - return nil - case C.REDIS_ERR: - return ErrUnexpectedResponse - case C.REDIS_ERR_IO: - return ErrIO - case C.REDIS_ERR_EOF: - return ErrEOF - case C.REDIS_ERR_PROTOCOL: - return ErrProtocol - case C.REDIS_ERR_OOM: - return ErrOOM - } - if errstr != "" { - return errors.New(errstr) - } - return ErrOther -} - -// Connects the client to the given host and port. -func (self *Client) Connect(host string, port uint) error { - var ctx *C.redisContext - - chost := C.CString(host) - - ctx = C.redisConnect( - chost, - C.int(port), - ) - - C.free(unsafe.Pointer(chost)) - - return self.setContext(ctx) -} - -// Creates a non-blocking connection with the given host and port. -func (self *Client) ConnectNonBlock(host string, port uint) error { - - var ctx *C.redisContext - - chost := C.CString(host) - - ctx = C.redisConnectNonBlock( - chost, - C.int(port), - ) - - C.free(unsafe.Pointer(chost)) - - return self.asyncConnect(ctx) -} - // Connects to the given host and port, giving up after timeout. func (self *Client) ConnectWithTimeout(host string, port uint, timeout time.Duration) error { - - var ctx *C.redisContext - - chost := C.CString(host) - - ctx = C.redisConnectWithTimeout( - chost, - C.int(port), - cStructTimeval(timeout), - ) - - C.free(unsafe.Pointer(chost)) - - return self.setContext(ctx) + // TODO: Actually apply timeout. + return self.Connect(host, port) } // Creates a non-blocking connection between the client and the given UNIX // socket. func (self *Client) ConnectUnixNonBlock(path string) error { - var ctx *C.redisContext - - cpath := C.CString(path) - - ctx = C.redisConnectUnixNonBlock( - cpath, - ) - - C.free(unsafe.Pointer(cpath)) - - return self.asyncConnect(ctx) + return ErrNotConnected } // Connects the client to the given UNIX socket. func (self *Client) ConnectUnix(path string) error { - var ctx *C.redisContext - - cpath := C.CString(path) - - ctx = C.redisConnectUnix( - cpath, - ) - - C.free(unsafe.Pointer(cpath)) - - return self.setContext(ctx) + return ErrNotConnected } // Connects the client to the given UNIX socket, giving up after timeout. func (self *Client) ConnectUnixWithTimeout(path string, timeout time.Duration) error { - var ctx *C.redisContext - - cpath := C.CString(path) - - ctx = C.redisConnectUnixWithTimeout( - cpath, - cStructTimeval(timeout), - ) - - C.free(unsafe.Pointer(cpath)) - - return self.setContext(ctx) -} - -func setReplyValue(v reflect.Value, raw unsafe.Pointer) error { - - reply := (*C.redisReply)(raw) - - switch C.redisGetReplyType(reply) { - // Received a string. - case C.REDIS_REPLY_STRING, C.REDIS_REPLY_STATUS, C.REDIS_REPLY_ERROR: - s := C.GoStringN(reply.str, reply.len) - // Destination type. - switch v.Kind() { - case reflect.String: - // string -> string - v.Set(reflect.ValueOf(s)) - case reflect.Int: - // string -> int - v.Set(reflect.ValueOf(int(to.Int64(s)))) - case reflect.Int8: - // string -> int8 - v.Set(reflect.ValueOf(int8(to.Int64(s)))) - case reflect.Int16: - // string -> int16 - v.Set(reflect.ValueOf(int16(to.Int64(s)))) - case reflect.Int32: - // string -> int32 - v.Set(reflect.ValueOf(int32(to.Int64(s)))) - case reflect.Int64: - // string -> int64 - v.Set(reflect.ValueOf(to.Int64(s))) - case reflect.Uint: - // string -> uint - v.Set(reflect.ValueOf(uint(to.Uint64(s)))) - case reflect.Uint8: - // string -> uint8 - v.Set(reflect.ValueOf(uint8(to.Uint64(s)))) - case reflect.Uint16: - // string -> uint16 - v.Set(reflect.ValueOf(uint16(to.Uint64(s)))) - case reflect.Uint32: - // string -> uint32 - v.Set(reflect.ValueOf(uint32(to.Uint64(s)))) - case reflect.Uint64: - // string -> uint64 - v.Set(reflect.ValueOf(to.Uint64(s))) - case reflect.Bool: - // string -> bool - v.Set(reflect.ValueOf(to.Bool(s))) - case reflect.Interface: - v.Set(reflect.ValueOf(s)) - default: - return fmt.Errorf("Unsupported conversion: redis string to %v", v.Kind()) - } - // Received integer. - case C.REDIS_REPLY_INTEGER: - switch v.Kind() { - case reflect.String: - // integer -> string - v.Set(reflect.ValueOf(to.String(reply.integer))) - case reflect.Int: - // Different integer types. - v.Set(reflect.ValueOf(int(reply.integer))) - case reflect.Int8: - v.Set(reflect.ValueOf(int8(reply.integer))) - case reflect.Int16: - v.Set(reflect.ValueOf(int16(reply.integer))) - case reflect.Int32: - v.Set(reflect.ValueOf(int32(reply.integer))) - case reflect.Int64: - v.Set(reflect.ValueOf(int64(reply.integer))) - case reflect.Uint: - v.Set(reflect.ValueOf(uint(reply.integer))) - case reflect.Uint8: - v.Set(reflect.ValueOf(uint8(reply.integer))) - case reflect.Uint16: - v.Set(reflect.ValueOf(uint16(reply.integer))) - case reflect.Uint32: - v.Set(reflect.ValueOf(uint32(reply.integer))) - case reflect.Uint64: - v.Set(reflect.ValueOf(uint64(reply.integer))) - case reflect.Bool: - b := false - if reply.integer == 1 { - b = true - } - v.Set(reflect.ValueOf(b)) - case reflect.Interface: - v.Set(reflect.ValueOf(reply.integer)) - default: - return fmt.Errorf("Unsupported conversion: redis integer (%d) to %v", reply.integer, v.Kind()) - } - case C.REDIS_REPLY_ARRAY: - switch v.Kind() { - case reflect.Slice, reflect.Interface: - var err error - var elements reflect.Value - total := int(reply.elements) - if v.Kind() == reflect.Interface { - // interface{} is not an slice, but it can hold one, however you can't just - // use v.Type() here, since v.Type() is reflect.TypeOf(interface{}{}). We - // need reflect.Type([]interface{}{}) instead. - elements = reflect.MakeSlice(interfaceSliceType, total, total) - } else { - // Other types must have the right element type. - elements = reflect.MakeSlice(v.Type(), total, total) - } - for i := 0; i < total; i++ { - item := C.redisReplyGetElement(reply, C.int(i)) - err = setReplyValue(elements.Index(i), unsafe.Pointer(item)) - if err != nil { - if err != ErrNilReply { - // Allows catching situations like - // https://github.com/gosexy/redis/issues/23 - return err - } - } - // Parent's freeReplyObject already takes care. - // C.freeReplyObject(unsafe.Pointer(item)) - } - v.Set(elements) - default: - return fmt.Errorf("Unsupported conversion: redis array to %v", v.Kind()) - } - case C.REDIS_REPLY_NIL: - /* - Read here: https://github.com/gosexy/redis/issues/12 - - 'nil' was being considered and invalid response type, but that caused - trouble within perfectly valid situations, so 'nil' is now an accepted - response type. - */ - v.Set(reflect.Zero(v.Type())) - return ErrNilReply - //return nil - default: - return fmt.Errorf("Unknown redis reply type: %v", C.redisGetReplyType(reply)) - } - - return nil + return ErrNotConnected } // Sends a raw command and stores the response into a destination variable. func (self *Client) Command(dest interface{}, values ...interface{}) error { - - argc := len(values) - argv := make([][]byte, argc) - - for i := 0; i < argc; i++ { - argv[i] = to.Bytes(values[i]) - } - - return self.command(dest, argv...) + return self.redis.command(dest, values...) } func (self *Client) bcommand(c chan []string, dest interface{}, values ...[]byte) error { - - var i int - var err error - - if self.ctx == nil { - return ErrNotConnected - } - - if len(values) < 1 { - return ErrMissingCommand - } - - argc := len(values) - argv := make([](*C.char), argc) - argvlen := make([]C.size_t, argc) - - for i, _ = range values { - argv[i] = C.CString(string(values[i])) - argvlen[i] = C.size_t(len(values[i])) - } - - if self.async == nil { - - // Using panic() because we don't really want the user to try subscribing - // on a blocking conection. - panic(ErrNonBlockingRequired.Error()) - // return ErrNonBlockingRequired - - } else { - - ok := make(chan *C.redisReply) - - var fn = func(r unsafe.Pointer) { - if self.connected { - ok <- (*C.redisReply)(unsafe.Pointer(r)) - } else { - ok <- nil - } - } - - var fnptr = unsafe.Pointer(&fn) - - wait := C.redisAsyncCommandArgvWrapper(self.async, (*(*unsafe.Pointer)(fnptr)), C.int(argc), &argv[0], (*C.size_t)(&argvlen[0])) - - for i = 0; i < len(argv); i++ { - C.free(unsafe.Pointer(argv[i])) - } - - if wait != C.REDIS_OK { - return self.redisError() - } - - for self.connected { - - select { - - case res := <-ok: - - if res == nil { - return self.redisError() - } - - ptr := (unsafe.Pointer)(res) - - switch C.redisGetReplyType(res) { - case C.REDIS_REPLY_ERROR: - return ErrUnexpectedResponse - } - - if dest == nil { - return ErrMissingDestination - } - - rv := reflect.ValueOf(dest) - - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return ErrInvalidDestination - } - - err = setReplyValue(rv.Elem(), ptr) - - C.freeReplyObject(ptr) - - if err != nil { - return err - } - - c <- reflect.Indirect(rv).Interface().([]string) - - } - } - - } - return nil } func (self *Client) command(dest interface{}, values ...[]byte) error { - - var i int - var reply *C.redisReply - - if self.ctx == nil { - return ErrNotConnected - } - - if len(values) < 1 { - return ErrMissingCommand - } - - command := strings.ToUpper(string(values[0])) - - argc := len(values) - argv := make([](*C.char), argc) - argvlen := make([]C.size_t, argc) - - for i, _ = range values { - argv[i] = C.CString(string(values[i])) - argvlen[i] = C.size_t(len(values[i])) - } - - defer func() { - if reply != nil { - C.freeReplyObject(unsafe.Pointer(reply)) - } - for i = 0; i < len(argv); i++ { - C.free(unsafe.Pointer(argv[i])) - } - }() - - if self.async == nil { - - reply = (*C.redisReply)(C.redisCommandArgv(self.ctx, C.int(argc), &argv[0], (*C.size_t)(&argvlen[0]))) - - } else { - - ok := make(chan *C.redisReply) - - var fn = func(r unsafe.Pointer) { - ok <- (*C.redisReply)(unsafe.Pointer(r)) - } - - var fnptr = unsafe.Pointer(&fn) - - wait := C.redisAsyncCommandArgvWrapper(self.async, (*(*unsafe.Pointer)(fnptr)), C.int(argc), &argv[0], (*C.size_t)(&argvlen[0])) - - if wait != C.REDIS_OK { - return ErrUnexpectedResponse - } - - if command == "UNSUBSCRIBE" || command == "PUNSUBSCRIBE" { - return nil - } - - reply = <-ok - } - - if reply == nil { - return ErrNilReply - } - - if C.redisGetReplyType(reply) == C.REDIS_REPLY_ERROR { - return errors.New(C.GoString(reply.str)) - } - - if dest == nil { - return nil - } - - rv := reflect.ValueOf(dest) - - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return ErrInvalidDestination - } - - return setReplyValue(rv.Elem(), unsafe.Pointer(reply)) -} - -/* - API implementation. -*/ - -/* -If key already exists and is a string, this command appends the value at the -end of the string. If key does not exist it is created and set as an empty -string, so APPEND will be similar to SET in this special case. - -http://redis.io/commands/append -*/ -func (self *Client) Append(key string, value interface{}) (int64, error) { - var ret int64 - err := self.command( - &ret, - []byte("APPEND"), - []byte(key), - to.Bytes(value), - ) - return ret, err -} - -/* -Request for authentication in a password-protected Redis server. Redis can be -instructed to require a password before allowing clients to execute commands. -This is done using the requirepass directive in the configuration file. - -http://redis.io/commands/auth -*/ -func (self *Client) Auth(password string) (string, error) { - var ret string - err := self.command( - &ret, - []byte("AUTH"), - []byte(password), - ) - return ret, err -} - -/* -Instruct Redis to start an Append Only File rewrite process. The rewrite will -create a small optimized version of the current Append Only File. - -http://redis.io/commands/bgwriteaof -*/ -func (self *Client) BgRewriteAOF() (string, error) { - var ret string - err := self.command( - &ret, - []byte("BGREWRITEAOF"), - ) - return ret, err -} - -/* -Save the DB in background. The OK code is immediately returned. Redis forks, the -parent continues to serve the clients, the child saves the DB on disk then exits. - -A client my be able to check if the operation succeeded using the LASTSAVE -command. - -http://redis.io/commands/bgsave -*/ -func (self *Client) BgSave() (string, error) { - var ret string - err := self.command( - &ret, - []byte("BGSAVE"), - ) - return ret, err -} - -/* -Count the number of set bits (population counting) in a string. - -http://redis.io/commands/bitcount -*/ -func (self *Client) BitCount(key string, params ...int64) (int64, error) { - var ret int64 - args := make([][]byte, len(params)+2) - args[0] = []byte("BITCOUNT") - args[1] = []byte(key) - for i, _ := range params { - args[2+i] = to.Bytes(params[i]) - } - err := self.command(&ret, args...) - return ret, err -} - -/* -Perform a bitwise operation between multiple keys (containing string values) -and store the result in the destination key. - -http://redis.io/commands/bitop -*/ -func (self *Client) BitOp(op string, dest string, keys ...string) (int64, error) { - var ret int64 - args := make([][]byte, len(keys)+3) - args[0] = []byte("BITOP") - args[1] = []byte(op) - args[2] = []byte(dest) - for i, _ := range keys { - args[3+i] = to.Bytes(keys[i]) - } - err := self.command(&ret, args...) - return ret, err -} - -/* -BLPOP is a blocking list pop primitive. It is the blocking version of LPOP -because it blocks the connection when there are no elements to pop from any of -the given lists. An element is popped from the head of the first list that is -non-empty, with the given keys being checked in the order that they are given. - -http://redis.io/commands/blpop -*/ -func (self *Client) BLPop(timeout uint64, keys ...string) ([]string, error) { - var ret []string - args := make([][]byte, len(keys)+2) - args[0] = []byte("BLPOP") - - i := 0 - - for _, key := range keys { - i += 1 - args[i] = to.Bytes(key) - } - - args[1+i] = to.Bytes(timeout) - - err := self.command(&ret, args...) - return ret, err -} - -/* -BRPOP is a blocking list pop primitive. It is the blocking version of RPOP -because it blocks the connection when there are no elements to pop from any of -the given lists. An element is popped from the tail of the first list that is -non-empty, with the given keys being checked in the order that they are given. - -http://redis.io/commands/brpop -*/ -func (self *Client) BRPop(timeout uint64, keys ...string) ([]string, error) { - var ret []string - - args := make([][]byte, len(keys)+2) - args[0] = []byte("BRPOP") - - i := 0 - - for _, key := range keys { - i += 1 - args[i] = to.Bytes(key) + ivalues := make([]interface{}, len(values)) + for i := 0; i < len(values); i++ { + ivalues[i] = values[i] } - args[1+i] = to.Bytes(timeout) - - err := self.command(&ret, args...) - return ret, err -} - -/* -BRPOPLPUSH is the blocking variant of RPOPLPUSH. When source contains elements, -this command behaves exactly like RPOPLPUSH. When source is empty, Redis will -block the connection until another client pushes to it or until timeout is -reached. A timeout of zero can be used to block indefinitely. - -http://redis.io/commands/brpoplpush -*/ -func (self *Client) BRPopLPush(source string, destination string, timeout int64) (string, error) { - var ret string - err := self.command( - &ret, - []byte("BRPOPLPUSH"), - []byte(source), - []byte(destination), - to.Bytes(timeout), - ) - return ret, err -} - -/* -The CLIENT KILL command closes a given client connection identified by ip:port. - -http://redis.io/commands/client-kill -*/ -func (self *Client) ClientKill(ip string, port uint) (string, error) { - var ret string - err := self.command( - &ret, - []byte("CLIENT"), - []byte("KILL"), - to.Bytes(fmt.Sprintf("%s:%d", ip, port)), - ) - return ret, err -} - -/* -The CLIENT LIST command returns information and statistics about the client -connections server in a mostly human readable format. - -http://redis.io/commands/client-list -*/ -func (self *Client) ClientList() ([]string, error) { - var ret []string - err := self.command( - &ret, - []byte("CLIENT"), - []byte("LIST"), - ) - return ret, err -} - -/* -The CLIENT GETNAME returns the name of the current connection as set by CLIENT -SETNAME. Since every new connection starts without an associated name, if no -name was assigned a null bulk reply is returned. - -http://redis.io/commands/client-getname -*/ -func (self *Client) ClientGetName() (string, error) { - var ret string - err := self.command( - &ret, - []byte("CLIENT"), - []byte("GETNAME"), - ) - return ret, err -} - -/* -The CLIENT SETNAME command assigns a name to the current connection. - -http://redis.io/commands/client-setname -*/ -func (self *Client) ClientSetName(connectionName string) (string, error) { - var ret string - err := self.command( - &ret, - []byte("CLIENT"), - []byte("SETNAME"), - to.Bytes(connectionName), - ) - return ret, err -} - -/* -The CONFIG GET command is used to read the configuration parameters of a running -Redis server. Not all the configuration parameters are supported in Redis 2.4, -while Redis 2.6 can read the whole configuration of a server using this command. - -http://redis.io/commands/config-get -*/ -func (self *Client) ConfigGet(parameter string) (string, error) { - var ret string - err := self.command( - &ret, - []byte("CONFIG"), - []byte("GET"), - []byte(parameter), - ) - return ret, err -} - -/* -The CONFIG SET command is used in order to reconfigure the server at run time -without the need to restart Redis. You can change both trivial parameters or -switch from one to another persistence option using this command. - -http://redis.io/commands/config-set -*/ -func (self *Client) ConfigSet(parameter string, value interface{}) (string, error) { - var ret string - err := self.command( - &ret, - []byte("CONFIG"), - []byte("SET"), - []byte(parameter), - to.Bytes(value), - ) - return ret, err -} - -/* -Resets the statistics reported by Redis using the INFO command. - -http://redis.io/commands/config-resetstat -*/ -func (self *Client) ConfigResetStat() (string, error) { - var ret string - err := self.command( - &ret, - []byte("CONFIG"), - []byte("RESETSTAT"), - ) - return ret, err -} - -/* -Return the number of keys in the currently-selected database. - -http://redis.io/commands/dbsize -*/ -func (self *Client) DbSize() (uint64, error) { - var ret uint64 - err := self.command( - &ret, - []byte("DBSIZE"), - ) - return ret, err -} - -/* -DEBUG OBJECT is a debugging command that should not be used by clients. Check -the OBJECT command instead. - -http://redis.io/commands/debug-object -*/ -func (self *Client) DebugObject(key string) (string, error) { - var ret string - err := self.command( - &ret, - []byte("DEBUG"), - []byte("OBJECT"), - to.Bytes(key), - ) - return ret, err -} - -/* -DEBUG SEGFAULT performs an invalid memory access that crashes Redis. It is used -to simulate bugs during the development. - -http://redis.io/commands/debug-segfault -*/ -func (self *Client) DebugSegfault() (string, error) { - var ret string - err := self.command( - &ret, - []byte("DEBUG"), - []byte("SEGFAULT"), - ) - return ret, err -} - -/* -Decrements the number stored at key by one. If the key does not exist, it is set -to 0 before performing the operation. An error is returned if the key contains a -value of the wrong type or contains a string that can not be represented as -integer. This operation is limited to 64 bit signed integers. - -http://redis.io/commands/decr -*/ -func (self *Client) Decr(key string) (int64, error) { - var ret int64 - err := self.command( - &ret, - []byte("DECR"), - []byte(key), - ) - return ret, err -} - -/* -Decrements the number stored at key by decrement. If the key does not exist, it -is set to 0 before performing the operation. An error is returned if the key -contains a value of the wrong type or contains a string that can not be -represented as integer. This operation is limited to 64 bit signed integers. - -http://redis.io/commands/decrby -*/ -func (self *Client) DecrBy(key string, decrement int64) (int64, error) { - var ret int64 - err := self.command( - &ret, - []byte("DECRBY"), - []byte(key), - to.Bytes(decrement), - ) - return ret, err -} - -/* -Removes the specified keys. A key is ignored if it does not exist. - -http://redis.io/commands/del -*/ - -func (self *Client) Del(keys ...string) (int64, error) { - var ret int64 - args := make([][]byte, len(keys)+1) - args[0] = []byte("DEL") - for i, key := range keys { - args[1+i] = to.Bytes(key) - } - err := self.command(&ret, args...) - return ret, err -} - -/* -Flushes all previously queued commands in a transaction and restores the -connection state to normal. - -http://redis.io/commands/discard -*/ -func (self *Client) Discard() (string, error) { - var ret string - err := self.command( - &ret, - []byte("DISCARD"), - ) - return ret, err -} - -/* -Serialize the value stored at key in a Redis-specific format and return it to -the user. The returned value can be synthesized back into a Redis key using the -RESTORE command. - -http://redis.io/commands/dump -*/ -func (self *Client) Dump(key string) (string, error) { - var ret string - err := self.command( - &ret, - []byte("DUMP"), - []byte(key), - ) - return ret, err -} - -/* -Returns message. - -http://redis.io/commands/echo -*/ -func (self *Client) Echo(message interface{}) (string, error) { - var ret string - err := self.command( - &ret, - []byte("ECHO"), - to.Bytes(message), - ) - return ret, err -} - -/* -Executes all previously queued commands in a transaction and restores the -connection state to normal. - -http://redis.io/commands/exec -*/ -func (self *Client) Exec() ([]interface{}, error) { - var ret []interface{} - err := self.command( - &ret, - []byte("EXEC"), - ) - return ret, err -} - -/* -Returns if key exists. - -http://redis.io/commands/exists -*/ -func (self *Client) Exists(key string) (bool, error) { - var ret bool - err := self.command( - &ret, - []byte("EXISTS"), - to.Bytes(key), - ) - return ret, err -} - -/* -Set a timeout on key. After the timeout has expired, the key will automatically -be deleted. A key with an associated timeout is often said to be volatile in -Redis terminology. - -http://redis.io/commands/expire -*/ -func (self *Client) Expire(key string, seconds uint64) (bool, error) { - var ret bool - err := self.command( - &ret, - []byte("EXPIRE"), - []byte(key), - to.Bytes(seconds), - ) - return ret, err -} - -/* -EXPIREAT has the same effect and semantic as EXPIRE, but instead of specifying -the number of seconds representing the TTL (time to live), it takes an absolute -Unix timestamp (seconds since January 1, 1970). - -http://redis.io/commands/expireat -*/ -func (self *Client) ExpireAt(key string, unixTime uint64) (bool, error) { - var ret bool - err := self.command( - &ret, - []byte("EXPIREAT"), - []byte(key), - to.Bytes(unixTime), - ) - return ret, err -} - -/* -Delete all the keys of all the existing databases, not just the currently -selected one. This command never fails. - -http://redis.io/commands/flushall -*/ -func (self *Client) FlushAll() (string, error) { - var ret string - err := self.command( - &ret, - []byte("FLUSHALL"), - ) - return ret, err -} - -/* -Delete all the keys of the currently selected DB. This command never fails. - -http://redis.io/commands/flushdb -*/ -func (self *Client) FlushDB() (string, error) { - var ret string - err := self.command( - &ret, - []byte("FLUSHDB"), - ) - return ret, err -} - -/* -Get the value of key. If the key does not exist the special value nil is -returned. An error is returned if the value stored at key is not a string, -because GET only handles string values. - -http://redis.io/commands/get -*/ -func (self *Client) Get(key string) (string, error) { - var s string - err := self.command(&s, - []byte("GET"), - []byte(key), - ) - return s, err -} - -/* -Returns the bit value at offset in the string value stored at key. - -http://redis.io/commands/getbit -*/ -func (self *Client) GetBit(key string, offset int64) (int64, error) { - var ret int64 - err := self.command( - &ret, - []byte("GETBIT"), - []byte(key), - to.Bytes(offset), - ) - return ret, err -} - -/* -Returns the substring of the string value stored at key, determined by the -offsets start and end (both are inclusive). Negative offsets can be used in -order to provide an offset starting from the end of the string. So -1 means -the last character, -2 the penultimate and so forth. - -http://redis.io/commands/getrange -*/ -func (self *Client) GetRange(key string, start int64, end int64) (string, error) { - var ret string - err := self.command(&ret, - []byte("GETRANGE"), - []byte(key), - to.Bytes(start), - to.Bytes(end), - ) - return ret, err -} - -/* -Atomically sets key to value and returns the old value stored at key. Returns an -error when key exists but does not hold a string value. - -http://redis.io/commands/getset -*/ -func (self *Client) GetSet(key string, value interface{}) (string, error) { - var ret string - err := self.command( - &ret, - []byte(string("GETSET")), - to.Bytes(key), - to.Bytes(value), - ) - return ret, err -} - -/* -Removes the specified fields from the hash stored at key. Specified fields that -do not exist within this hash are ignored. If key does not exist, it is treated -as an empty hash and this command returns 0. - -http://redis.io/commands/hdel -*/ -func (self *Client) HDel(key string, fields ...string) (int64, error) { - var ret int64 - - args := make([][]byte, len(fields)+2) - args[0] = []byte("HDEL") - args[1] = []byte(key) - - for i, _ := range fields { - args[2+i] = to.Bytes(fields[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns if field is an existing field in the hash stored at key. - -http://redis.io/commands/hexists -*/ -func (self *Client) HExists(key string, field string) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("HEXISTS"), - []byte(key), - []byte(field), - ) - - return ret, err -} - -/* -Returns the value associated with field in the hash stored at key. - -http://redis.io/commands/hget -*/ -func (self *Client) HGet(key string, field string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("HGET"), - []byte(key), - []byte(field), - ) - - return ret, err -} - -/* -Returns all fields and values of the hash stored at key. In the returned value, -every field name is followed by its value, so the length of the reply is twice -the size of the hash. - -http://redis.io/commands/hgetall -*/ -func (self *Client) HGetAll(key string) ([]string, error) { - var ret []string - - err := self.command( - &ret, - []byte("HGETALL"), - []byte(key), - ) - - return ret, err -} - -/* -Increments the number stored at field in the hash stored at key by increment. If -key does not exist, a new key holding a hash is created. If field does not exist -the value is set to 0 before the operation is performed. - -http://redis.io/commands/hincrby -*/ -func (self *Client) HIncrBy(key string, field string, increment interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("HINCRBY"), - []byte(key), - []byte(field), - to.Bytes(increment), - ) - - return ret, err -} - -/* -Increment the specified field of an hash stored at key, and representing a -floating point number, by the specified increment. If the field does not exist, -it is set to 0 before performing the operation. - -http://redis.io/commands/hincrbyfloat -*/ -func (self *Client) HIncrByFloat(key string, field string, increment float64) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("HINCRBYFLOAT"), - []byte(key), - []byte(field), - to.Bytes(increment), - ) - - return ret, err -} - -/* -Returns all field names in the hash stored at key. - -http://redis.io/commands/hkeys -*/ -func (self *Client) HKeys(key string) ([]string, error) { - var ret []string - - err := self.command( - &ret, - []byte("HKEYS"), - []byte(key), - ) - - return ret, err - -} - -/* -Returns the number of fields contained in the hash stored at key. - -http://redis.io/commands/hlen -*/ -func (self *Client) HLen(key string) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("HLEN"), - []byte(key), - ) - - return ret, err -} - -/* -Returns the values associated with the specified fields in the hash stored at key. - -http://redis.io/commands/hmget -*/ -func (self *Client) HMGet(key string, fields ...string) ([]string, error) { - var ret []string - - args := make([][]byte, len(fields)+2) - args[0] = []byte("HMGET") - args[1] = []byte(key) - - for i, field := range fields { - args[2+i] = to.Bytes(field) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Sets the specified fields to their respective values in the hash stored at key. -This command overwrites any existing fields in the hash. If key does not exist, -a new key holding a hash is created. - -http://redis.io/commands/hmset -*/ -func (self *Client) HMSet(key string, values ...interface{}) (string, error) { - var ret string - - if len(values)%2 != 0 { - return "", ErrExpectingPairs - } - - args := make([][]byte, len(values)+2) - args[0] = []byte("HMSET") - args[1] = []byte(key) - - for i, value := range values { - args[2+i] = to.Bytes(value) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Sets field in the hash stored at key to value. If key does not exist, a new key -holding a hash is created. If field already exists in the hash, it is -overwritten. - -http://redis.io/commands/hset -*/ -func (self *Client) HSet(key string, field string, value interface{}) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("HSET"), - []byte(key), - []byte(field), - to.Bytes(value), - ) - - return ret, err -} - -/* -Sets field in the hash stored at key to value, only if field does not yet exist. -If key does not exist, a new key holding a hash is created. If field already -exists, this operation has no effect. - -http://redis.io/commands/hsetnx -*/ -func (self *Client) HSetNX(key string, field string, value interface{}) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("HSETNX"), - []byte(key), - []byte(field), - to.Bytes(value), - ) - - return ret, err -} - -/* -Returns all values in the hash stored at key. - -http://redis.io/commands/hvals -*/ -func (self *Client) HVals(key string) ([]string, error) { - var ret []string - - err := self.command( - &ret, - []byte("HVALS"), - []byte(key), - ) - - return ret, err -} - -/* -Increments the number stored at key by one. If the key does not exist, it is set -to 0 before performing the operation. An error is returned if the key contains a -value of the wrong type or contains a string that can not be represented as -integer. This operation is limited to 64 bit signed integers. - -http://redis.io/commands/incr -*/ -func (self *Client) Incr(key string) (int64, error) { - var ret int64 - err := self.command( - &ret, - []byte("INCR"), - []byte(key), - ) - return ret, err -} - -/* -Increments the number stored at key by increment. If the key does not exist, it -is set to 0 before performing the operation. An error is returned if the key -contains a value of the wrong type or contains a string that can not be -represented as integer. This operation is limited to 64 bit signed integers. - -http://redis.io/commands/incrby -*/ -func (self *Client) IncrBy(key string, increment int64) (int64, error) { - var ret int64 - err := self.command( - &ret, - []byte("INCRBY"), - []byte(key), - to.Bytes(increment), - ) - return ret, err -} - -/* -Increment the string representing a floating point number stored at key by the -specified increment. If the key does not exist, it is set to 0 before -performing the operation. - -http://redis.io/commands/incrbyfloat -*/ -func (self *Client) IncrByFloat(key string, increment float64) (string, error) { - var ret string - err := self.command( - &ret, - []byte("INCRBYFLOAT"), - []byte(key), - to.Bytes(increment), - ) - return ret, err -} - -/* -The INFO command returns information and statistics about the server in a format -that is simple to parse by computers and easy to read by humans. - -http://redis.io/commands/info -*/ -func (self *Client) Info(section string) (ret string, err error) { - err = self.command( - &ret, - []byte("INFO"), - []byte(section), - ) - return ret, err -} - -/* -Returns all keys matching pattern. - -http://redis.io/commands/keys -*/ -func (self *Client) Keys(pattern string) ([]string, error) { - var ret []string - err := self.command( - &ret, - []byte("KEYS"), - []byte(pattern), - ) - return ret, err -} - -/* -Return the UNIX TIME of the last DB save executed with success. - -http://redis.io/commands/lastsave -*/ -func (self *Client) LastSave() (int64, error) { - var ret int64 - err := self.command( - &ret, - []byte("LASTSAVE"), - ) - return ret, err -} - -/* -Returns the element at index index in the list stored at key. The index is -zero-based, so 0 means the first element, 1 the second element and so on. -Negative indices can be used to designate elements starting at the tail of the -list. Here, -1 means the last element, -2 means the penultimate and so forth. - -http://redis.io/commands/lindex -*/ -func (self *Client) LIndex(key string, index int64) (string, error) { - var ret string - err := self.command( - &ret, - []byte("LINDEX"), - []byte(key), - to.Bytes(index), - ) - return ret, err -} - -/* -Inserts value in the list stored at key either before or after the reference -value pivot. - -http://redis.io/commands/linsert -*/ -func (self *Client) LInsert(key string, where string, pivot interface{}, value interface{}) (int64, error) { - var ret int64 - - where = strings.ToUpper(where) - - if where != "AFTER" && where != "BEFORE" { - return 0, errors.New(`The "where" value must be either BEFORE or AFTER.`) - } - - err := self.command( - &ret, - []byte("LINSERT"), - []byte(key), - []byte(where), - to.Bytes(pivot), - to.Bytes(value), - ) - - return ret, err -} - -/* -Returns the length of the list stored at key. If key does not exist, it is -interpreted as an empty list and 0 is returned. An error is returned when the -value stored at key is not a list. - -http://redis.io/commands/llen -*/ -func (self *Client) LLen(key string) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("LLEN"), - []byte(key), - ) - - return ret, err -} - -/* -Removes and returns the first element of the list stored at key. - -http://redis.io/commands/lpop -*/ -func (self *Client) LPop(key string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("LPOP"), - []byte(key), - ) - - return ret, err -} - -/* -Insert all the specified values at the head of the list stored at key. If key -does not exist, it is created as empty list before performing the push -operations. When key holds a value that is not a list, an error is returned. - -http://redis.io/commands/lpush -*/ -func (self *Client) LPush(key string, values ...interface{}) (int64, error) { - var ret int64 - - args := make([][]byte, len(values)+2) - args[0] = []byte("LPUSH") - args[1] = []byte(key) - - for i, _ := range values { - args[2+i] = to.Bytes(values[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Inserts value at the head of the list stored at key, only if key already exists -and holds a list. In contrary to LPUSH, no operation will be performed when key -does not yet exist. - -http://redis.io/commands/lpushx -*/ -func (self *Client) LPushX(key string, value interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("LPUSHX"), - []byte(key), - to.Bytes(value), - ) - - return ret, err -} - -/* -Returns the specified elements of the list stored at key. The offsets start and -stop are zero-based indexes, with 0 being the first element of the list (the -head of the list), 1 being the next element and so on. - -http://redis.io/commands/lrange -*/ -func (self *Client) LRange(key string, start int64, stop int64) ([]string, error) { - var ret []string - - err := self.command( - &ret, - []byte("LRANGE"), - []byte(key), - to.Bytes(start), - to.Bytes(stop), - ) - - return ret, err -} - -/* -Removes the first count occurrences of elements equal to value from the list -stored at key. The count argument influences the operation in the following -ways: - -http://redis.io/commands/lrem -*/ -func (self *Client) LRem(key string, count int64, value interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("LREM"), - []byte(key), - to.Bytes(count), - to.Bytes(value), - ) - - return ret, err -} - -/* -Sets the list element at index to value. For more information on the index -argument, see LINDEX. - -http://redis.io/commands/lset -*/ -func (self *Client) LSet(key string, index int64, value interface{}) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("LSET"), - []byte(key), - to.Bytes(index), - to.Bytes(value), - ) - - return ret, err -} - -/* -Trim an existing list so that it will contain only the specified range of -elements specified. Both start and stop are zero-based indexes, where 0 is the -first element of the list (the head), 1 the next element and so on. - -http://redis.io/commands/ltrim -*/ -func (self *Client) LTrim(key string, start int64, stop int64) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("LTRIM"), - []byte(key), - to.Bytes(start), - to.Bytes(stop), - ) - - return ret, err -} - -/* -Returns the values of all specified keys. For every key that does not hold a -string value or does not exist, the special value nil is returned. Because of -this, the operation never fails. - -*/ -func (self *Client) MGet(keys ...string) ([]string, error) { - var ret []string - - args := make([][]byte, len(keys)+1) - args[0] = []byte("MGET") - - for i, _ := range keys { - args[1+i] = []byte(keys[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Atomically transfer a key from a source Redis instance to a destination Redis -instance. On success the key is deleted from the original instance and is -guaranteed to exist in the target instance. - -http://redis.io/commands/migrate -*/ -func (self *Client) Migrate(host string, port uint, key string, destination string, timeout int64) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("MIGRATE"), - []byte(host), - to.Bytes(port), - []byte(key), - []byte(destination), - to.Bytes(timeout), - ) - - return ret, err -} - -/* -Move key from the currently selected database (see SELECT) to the specified -destination database. When key already exists in the destination database, or -it does not exist in the source database, it does nothing. It is possible to -use MOVE as a locking primitive because of this. - -http://redis.io/commands/move -*/ -func (self *Client) Move(key string, db string) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("MOVE"), - []byte(key), - []byte(db), - ) - - return ret, err -} - -/* -Sets the given keys to their respective values. MSET replaces existing values -with new values, just as regular SET. See MSETNX if you don't want to overwrite -existing values. - -http://redis.io/commands/mset -*/ -func (self *Client) MSet(values ...interface{}) (string, error) { - var ret string - - if len(values)%2 != 0 { - return "", ErrExpectingPairs - } - - args := make([][]byte, len(values)+1) - args[0] = []byte("MSET") - - for i, _ := range values { - args[1+i] = to.Bytes(values[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Sets the given keys to their respective values. MSETNX will not perform any -operation at all even if just a single key already exists. - -http://redis.io/commands/msetnx -*/ -func (self *Client) MSetNX(values ...interface{}) (bool, error) { - var ret bool - - if len(values)%2 != 0 { - return false, ErrExpectingPairs - } - - args := make([][]byte, len(values)+1) - args[0] = []byte("MSETNX") - - for i, _ := range values { - args[1+i] = to.Bytes(values[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Marks the start of a transaction block. Subsequent commands will be queued for -atomic execution using EXEC. - -http://redis.io/commands/multi -*/ -func (self *Client) Multi() (string, error) { - var ret string - - err := self.command( - &ret, - []byte("MULTI"), - ) - - return ret, err -} - -/* -The OBJECT command allows to inspect the internals of Redis Objects associated -with keys. It is useful for debugging or to understand if your keys are using -the specially encoded data types to save space. Your application may also use -the information reported by the OBJECT command to implement application level -key eviction policies when using Redis as a Cache. - -http://redis.io/commands/object -*/ -func (self *Client) Object(subcommand string, arguments ...interface{}) (string, error) { - var ret string - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("OBJECT") - args[1] = []byte(subcommand) - - for i, arg := range arguments { - args[2+i] = to.Bytes(arg) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Remove the existing timeout on key, turning the key from volatile (a key with an -expire set) to persistent (a key that will never expire as no timeout is -associated). - -http://redis.io/commands/persist -*/ -func (self *Client) Persist(key string) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("PERSIST"), - []byte(key), - ) - - return ret, err -} - -/* -This command works exactly like EXPIRE but the time to live of the key is -specified in milliseconds instead of seconds. - -http://redis.io/commands/pexpire -*/ -func (self *Client) PExpire(key string, milliseconds int64) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("PEXPIRE"), - []byte(key), - to.Bytes(milliseconds), - ) - - return ret, err -} - -/* -PEXPIREAT has the same effect and semantic as EXPIREAT, but the Unix time at -which the key will expire is specified in milliseconds instead of seconds. - -http://redis.io/commands/pexpireat -*/ -func (self *Client) PExpireAt(key string, milliseconds int64) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("PEXPIREAT"), - []byte(key), - to.Bytes(milliseconds), - ) - - return ret, err -} - -/* -Returns PONG. This command is often used to test if a connection is still alive, -or to measure latency. - -http://redis.io/commands/ping -*/ -func (self *Client) Ping() (string, error) { - var ret string - - err := self.command( - &ret, - []byte("PING"), - ) - - return ret, err -} - -/* -PSETEX works exactly like SETEX with the sole difference that the expire time is -specified in milliseconds instead of seconds. - -http://redis.io/commands/psetex -*/ -func (self *Client) PSetEx(key string, milliseconds int64, value interface{}) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("PSETEX"), - []byte(key), - to.Bytes(milliseconds), - to.Bytes(value), - ) - - return ret, err -} - -/* -Subscribes the client to the given patterns. - -http://redis.io/commands/psubscribe -*/ -func (self *Client) PSubscribe(c chan []string, channel ...string) error { - var ret []string - - args := make([][]byte, len(channel)+1) - args[0] = []byte("PSUBSCRIBE") - - for i, _ := range channel { - args[1+i] = to.Bytes(channel[i]) - } - - err := self.bcommand(c, &ret, args...) - - return err -} - -/* -The PUBSUB command is an introspection command that allows to inspect the state -of the Pub/Sub subsystem. It is composed of subcommands that are documented -separately. - -http://redis.io/commands/pubsub -*/ -func (self *Client) PubSub(subcommand string, arguments ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("PUBSUB") - args[1] = []byte(subcommand) - - for i, _ := range arguments { - args[2+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Like TTL this command returns the remaining time to live of a key that has an -expire set, with the sole difference that TTL returns the amount of remaining -time in seconds while PTTL returns it in milliseconds. - -http://redis.io/commands/pttl -*/ -func (self *Client) PTTL(key string) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("PTTL"), - []byte(key), - ) - - return ret, err -} - -/* -Posts a message to the given channel. - -http://redis.io/commands/publish -*/ -func (self *Client) Publish(channel string, message interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("PUBLISH"), - []byte(channel), - to.Bytes(message), - ) - - return ret, err -} - -/* -Unsubscribes the client from the given patterns, or from all of them if none is -given. - -http://redis.io/commands/punsubscribe -*/ -func (self *Client) PUnsubscribe(pattern ...string) (string, error) { - var ret string - - args := make([][]byte, len(pattern)+1) - args[0] = []byte("PUNSUBSCRIBE") - - for i, _ := range pattern { - args[1+i] = to.Bytes(pattern[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Ask the server to close the connection. The connection is closed as soon as all -pending replies have been written to the client. - -http://redis.io/commands/quit -*/ -func (self *Client) Quit() (s string, err error) { - - if self.ctx != nil { - - if self.async != nil { - lastErr := self.redisError() - // Forcing async connection to clean without waiting for anything else. - if lastErr != ErrEOF { - C.redisForceAsyncFree(self.async) - } - } else { - - // Request the server to close the connection. - self.command( - nil, - []byte("QUIT"), - ) - - C.redisFree(self.ctx) - } - - self.async = nil - self.ctx = nil - } else { - err = ErrNotConnected - } - - self.connected = false - - return s, err -} - -/* -Return a random key from the currently selected database. - -http://redis.io/commands/randomkey -*/ -func (self *Client) RandomKey() (string, error) { - var ret string - - err := self.command( - &ret, - []byte("RANDOMKEY"), - ) - - return ret, err -} - -/* -Renames key to newkey. It returns an error when the source and destination names -are the same, or when key does not exist. If newkey already exists it is -overwritten. - -http://redis.io/commands/rename -*/ -func (self *Client) Rename(key string, newkey string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("RENAME"), - []byte(key), - []byte(newkey), - ) - - return ret, err -} - -/* -Renames key to newkey if newkey does not yet exist. It returns an error under -the same conditions as RENAME. - -http://redis.io/commands/renamenx -*/ -func (self *Client) RenameNX(key string, newkey string) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("RENAMENX"), - []byte(key), - []byte(newkey), - ) - - return ret, err -} - -/* -Create a key associated with a value that is obtained by deserializing the -provided serialized value (obtained via DUMP). - -http://redis.io/commands/restore -*/ -func (self *Client) Restore(key string, ttl int64, serializedValue string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("RESTORE"), - []byte(key), - to.Bytes(ttl), - []byte(serializedValue), - ) - - return ret, err -} - -/* -Removes and returns the last element of the list stored at key. - -http://redis.io/commands/restore -*/ -func (self *Client) RPop(key string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("RPOP"), - []byte(key), - ) - - return ret, err -} - -/* -Atomically returns and removes the last element (tail) of the list stored at -source, and pushes the element at the first element (head) of the list stored -at destination. - -http://redis.io/commands/rpoplpush -*/ -func (self *Client) RPopLPush(source string, destination string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("RPOPLPUSH"), - []byte(source), - []byte(destination), - ) - - return ret, err -} - -/* -Insert all the specified values at the tail of the list stored at key. If key -does not exist, it is created as empty list before performing the push -operation. When key holds a value that is not a list, an error is returned. - -http://redis.io/commands/rpush -*/ -func (self *Client) RPush(key string, values ...interface{}) (int64, error) { - var ret int64 - - args := make([][]byte, len(values)+2) - args[0] = []byte("RPUSH") - args[1] = []byte(key) - - for i, _ := range values { - args[2+i] = to.Bytes(values[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Inserts value at the tail of the list stored at key, only if key already exists -and holds a list. In contrary to RPUSH, no operation will be performed when key -does not yet exist. - -http://redis.io/commands/rpushx -*/ -func (self *Client) RPushX(key string, value interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("RPUSHX"), - []byte(key), - to.Bytes(value), - ) - - return ret, err -} - -/* -Add the specified members to the set stored at key. Specified members that are -already a member of this set are ignored. If key does not exist, a new set is -created before adding the specified members. - -http://redis.io/commands/sadd -*/ -func (self *Client) SAdd(key string, member ...interface{}) (int64, error) { - var ret int64 - - args := make([][]byte, len(member)+2) - args[0] = []byte("SADD") - args[1] = []byte(key) - - for i, _ := range member { - args[2+i] = to.Bytes(member[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -The SAVE commands performs a synchronous save of the dataset producing a point -in time snapshot of all the data inside the Redis instance, in the form of an -RDB file. - -http://redis.io/commands/save -*/ -func (self *Client) Save() (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SAVE"), - ) - - return ret, err -} - -/* -Returns the set cardinality (number of elements) of the set stored at key. - -http://redis.io/commands/scard -*/ -func (self *Client) SCard(key string) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("SCARD"), - []byte(key), - ) - - return ret, err -} - -/* -Returns information about the existence of the scripts in the script cache. - -http://redis.io/commands/script-exists -*/ -func (self *Client) ScriptExists(script ...string) ([]string, error) { - var ret []string - - args := make([][]byte, len(script)+2) - args[0] = []byte("SCRIPT") - args[1] = []byte("EXISTS") - - for i, _ := range script { - args[2+i] = []byte(script[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Flush the Lua scripts cache. - -http://redis.io/commands/script-flush -*/ -func (self *Client) ScriptFlush() (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SCRIPT"), - []byte("FLUSH"), - ) - - return ret, err - -} - -/* -Kills the currently executing Lua script, assuming no write operation was yet -performed by the script. - -http://redis.io/commands/script-kill -*/ -func (self *Client) ScriptKill() (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SCRIPT"), - []byte("KILL"), - ) - - return ret, err -} - -/* -EVAL and EVALSHA are used to evaluate scripts using the Lua interpreter built -into Redis starting from version 2.6.0. - -http://redis.io/commands/eval -*/ -func (self *Client) Eval(script string, numkeys int64, arguments ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(arguments)+3) - args[0] = []byte("EVAL") - args[1] = []byte(script) - args[2] = to.Bytes(numkeys) - - for i, _ := range arguments { - args[3+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Evaluates a script cached on the server side by its SHA1 digest. Scripts are -cached on the server side using the SCRIPT LOAD command. The command is -otherwise identical to EVAL. - -http://redis.io/commands/evalsha -*/ -func (self *Client) EvalSHA(hash string, numkeys int64, arguments ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(arguments)+3) - args[0] = []byte("EVALSHA") - args[1] = []byte(hash) - args[2] = to.Bytes(numkeys) - - for i, _ := range arguments { - args[3+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Load a script into the scripts cache, without executing it. After the specified -command is loaded into the script cache it will be callable using EVALSHA with -the correct SHA1 digest of the script, exactly like after the first successful -invocation of EVAL. - -http://redis.io/commands/script-load -*/ -func (self *Client) ScriptLoad(script string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SCRIPT"), - []byte("LOAD"), - []byte(script), - ) - - return ret, err -} - -/* -Returns the members of the set resulting from the difference between the first -set and all the successive sets. - -http://redis.io/commands/sdiff -*/ -func (self *Client) SDiff(key ...string) ([]string, error) { - var ret []string - - args := make([][]byte, len(key)+1) - args[0] = []byte("SDIFF") - - for i, _ := range key { - args[1+i] = []byte(key[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -This command is equal to SDIFF, but instead of returning the resulting set, it -is stored in destination. - -http://redis.io/commands/sdiffstore -*/ -func (self *Client) SDiffStore(destination string, key ...string) (int64, error) { - var ret int64 - - args := make([][]byte, len(key)+2) - args[0] = []byte("SDIFFSTORE") - args[1] = []byte(destination) - - for i, _ := range key { - args[2+i] = []byte(key[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Select the DB with having the specified zero-based numeric index. New -connections always use DB 0. - -http://redis.io/commands/select -*/ -func (self *Client) Select(index int64) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SELECT"), - to.Bytes(index), - ) - - return ret, err -} - -/* -Set key to hold the string value. If key already holds a value, it is -overwritten, regardless of its type. - -http://redis.io/commands/set -*/ -func (self *Client) Set(key string, value interface{}) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SET"), - []byte(key), - to.Bytes(value), - ) - - return ret, err -} - -/* -Sets or clears the bit at offset in the string value stored at key. - -http://redis.io/commands/setbit -*/ -func (self *Client) SetBit(key string, offset int64, value int64) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("SETBIT"), - []byte(key), - to.Bytes(offset), - to.Bytes(value), - ) - - return ret, err -} - -/* -Set key to hold the string value and set key to timeout after a given number of -seconds. This command is equivalent to executing the following commands: - -http://redis.io/commands/setex -*/ -func (self *Client) SetEx(key string, seconds int64, value interface{}) (int, error) { - var ret int - - err := self.command( - &ret, - []byte("SETEX"), - []byte(key), - to.Bytes(seconds), - to.Bytes(value), - ) - - return ret, err -} - -/* -Set key to hold string value if key does not exist. In that case, it is equal to -SET. When key already holds a value, no operation is performed. SETNX is short -for "SET if N ot e X ists". - -http://redis.io/commands/setnx -*/ -func (self *Client) SetNX(key string, value interface{}) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("SETNX"), - []byte(key), - to.Bytes(value), - ) - - return ret, err -} - -/* -Overwrites part of the string stored at key, starting at the specified offset, -for the entire length of value. If the offset is larger than the current length -of the string at key, the string is padded with zero-bytes to make offset fit. -Non-existing keys are considered as empty strings, so this command will make -sure it holds a string large enough to be able to set value at offset. - -http://redis.io/commands/setrange -*/ -func (self *Client) SetRange(key string, offset int64, value interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("SETRANGE"), - []byte(key), - to.Bytes(offset), - to.Bytes(value), - ) - - return ret, err -} - -/* -Returns the members of the set resulting from the intersection of all the given sets. - -http://redis.io/commands/sinter -*/ -func (self *Client) SInter(key ...string) ([]string, error) { - var ret []string - - args := make([][]byte, len(key)+1) - args[0] = []byte("SINTER") - - for i, _ := range key { - args[1+i] = []byte(key[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -This command is equal to SINTER, but instead of returning the resulting set, it -is stored in destination. - -http://redis.io/commands/sinterstore -*/ -func (self *Client) SInterStore(destination string, key ...string) (int64, error) { - var ret int64 - - args := make([][]byte, len(key)+2) - args[0] = []byte("SINTERSTORE") - args[1] = []byte(destination) - - for i, _ := range key { - args[2+i] = []byte(key[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns if member is a member of the set stored at key. - -http://redis.io/commands/sismember -*/ -func (self *Client) SIsMember(key string, member interface{}) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("SISMEMBER"), - []byte(key), - to.Bytes(member), - ) - - return ret, err -} - -/* -The SLAVEOF command can change the replication settings of a slave on the fly. -If a Redis server is already acting as slave, the command SLAVEOF NO ONE will -turn off the replication, turning the Redis server into a MASTER. In the proper -form SLAVEOF hostname port will make the server a slave of another server -listening at the specified hostname and port. - -http://redis.io/commands/slaveof -*/ -func (self *Client) SlaveOf(key string, port uint) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SLAVEOF"), - []byte(key), - to.Bytes(port), - ) - - return ret, err -} - -/* -This command is used in order to read and reset the Redis slow queries log. - -http://redis.io/commands/slowlog -*/ -func (self *Client) SlowLog(subcommand string, argument interface{}) ([]string, error) { - var ret []string - - err := self.command( - &ret, - []byte("SLOWLOG"), - []byte(subcommand), - to.Bytes(argument), - ) - - return ret, err -} - -/* -Returns all the members of the set value stored at key. - -http://redis.io/commands/smembers -*/ -func (self *Client) SMembers(key string) ([]string, error) { - var ret []string - - err := self.command( - &ret, - []byte("SMEMBERS"), - []byte(key), - ) - - return ret, err -} - -/* -Move member from the set at source to the set at destination. This operation is -atomic. In every given moment the element will appear to be a member of source -or destination for other clients. - -http://redis.io/commands/smove -*/ -func (self *Client) SMove(source string, destination string, member interface{}) (bool, error) { - var ret bool - - err := self.command( - &ret, - []byte("SMOVE"), - []byte(source), - []byte(destination), - to.Bytes(member), - ) - - return ret, err -} - -/* -Returns or stores the elements contained in the list, set or sorted set at key. -By default, sorting is numeric and elements are compared by their value -interpreted as double precision floating point number. - -http://redis.io/commands/sort -*/ -func (self *Client) Sort(key string, arguments ...string) ([]string, error) { - var ret []string - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("SORT") - args[1] = []byte(key) - - for i, _ := range arguments { - args[2+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Removes and returns a random element from the set value stored at key. - -http://redis.io/commands/spop -*/ -func (self *Client) SPop(key string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SPOP"), - []byte(key), - ) - - return ret, err -} - -/* -When called with just the key argument, return a random element from the set -value stored at key. - -http://redis.io/commands/srandmember -*/ -func (self *Client) SRandMember(key string, count int64) ([]string, error) { - var ret []string - - err := self.command( - &ret, - []byte("SRANDMEMBER"), - []byte(key), - to.Bytes(count), - ) - - return ret, err -} - -/* -Remove the specified members from the set stored at key. Specified members that -are not a member of this set are ignored. If key does not exist, it is treated -as an empty set and this command returns 0. - -http://redis.io/commands/srem -*/ -func (self *Client) SRem(key string, members ...interface{}) (int64, error) { - var ret int64 - - args := make([][]byte, len(members)+2) - args[0] = []byte("SREM") - args[1] = []byte(key) - - for i, _ := range members { - args[2+i] = to.Bytes(members[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns the length of the string value stored at key. An error is returned when -key holds a non-string value. - -http://redis.io/commands/strlen -*/ -func (self *Client) Strlen(key string) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("STRLEN"), - []byte(key), - ) - - return ret, err -} - -/* -Subscribes the client to the specified channels. - -http://redis.io/commands/subscribe -*/ -func (self *Client) Subscribe(c chan []string, channel ...string) error { - var ret []string - - args := make([][]byte, len(channel)+1) - args[0] = []byte("SUBSCRIBE") - - for i, _ := range channel { - args[1+i] = to.Bytes(channel[i]) - } - - err := self.bcommand(c, &ret, args...) - - return err -} - -/* -Returns the members of the set resulting from the union of all the given sets. - -http://redis.io/commands/sunion -*/ -func (self *Client) SUnion(key ...string) ([]string, error) { - var ret []string - - args := make([][]byte, len(key)+1) - args[0] = []byte("SUNION") - - for i, _ := range key { - args[1+i] = []byte(key[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -This command is equal to SUNION, but instead of returning the resulting set, it -is stored in destination. - -http://redis.io/commands/sunionstore -*/ -func (self *Client) SUnionStore(destination string, key ...string) (int64, error) { - var ret int64 - - args := make([][]byte, len(key)+2) - args[0] = []byte("SUNIONSTORE") - args[1] = []byte(destination) - - for i, _ := range key { - args[2+i] = []byte(key[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -http://redis.io/commands/sync -*/ -func (self *Client) Sync() (string, error) { - var ret string - - err := self.command( - &ret, - []byte("SYNC"), - ) - - return ret, err -} - -/* -The TIME command returns the current server time as a two items lists: a Unix -timestamp and the amount of microseconds already elapsed in the current second. -Basically the interface is very similar to the one of the gettimeofday system -call. - -http://redis.io/commands/time -*/ -func (self *Client) Time() ([]uint64, error) { - var ret []uint64 - - err := self.command( - &ret, - []byte("TIME"), - ) - - return ret, err -} - -/* -Returns the remaining time to live of a key that has a timeout. This -introspection capability allows a Redis client to check how many seconds a given -key will continue to be part of the dataset. - -http://redis.io/commands/ttl -*/ -func (self *Client) TTL(key string) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("TTL"), - []byte(key), - ) - - return ret, err -} - -/* -Returns the string representation of the type of the value stored at key. The -different types that can be returned are: string, list, set, zset and hash. - -http://redis.io/commands/type -*/ -func (self *Client) Type(key string) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("TYPE"), - []byte(key), - ) - - return ret, err -} - -/* -Unsubscribes the client from the given channels, or from all of them if none is -given. - -http://redis.io/commands/unsubscribe -*/ -func (self *Client) Unsubscribe(channel ...string) error { - - args := make([][]byte, len(channel)+1) - args[0] = []byte("UNSUBSCRIBE") - - for i, _ := range channel { - args[1+i] = to.Bytes(channel[i]) - } - - err := self.command(nil, args...) - - return err -} - -/* -Flushes all the previously watched keys for a transaction. - -http://redis.io/commands/unwatch -*/ -func (self *Client) Unwatch() (string, error) { - var ret string - - err := self.command( - &ret, - []byte("UNWATCH"), - ) - - return ret, err -} - -/* -Marks the given keys to be watched for conditional execution of a transaction. - -http://redis.io/commands/watch -*/ -func (self *Client) Watch(key ...string) (string, error) { - var ret string - - args := make([][]byte, len(key)+1) - args[0] = []byte("WATCH") - - for i, _ := range key { - args[1+i] = to.Bytes(key[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Adds all the specified members with the specified scores to the sorted set -stored at key. It is possible to specify multiple score/member pairs. If a -specified member is already a member of the sorted set, the score is updated and -the element reinserted at the right position to ensure the correct ordering. If -key does not exist, a new sorted set with the specified members as sole members -is created, like if the sorted set was empty. If the key exists but does not -hold a sorted set, an error is returned. - -http://redis.io/commands/zadd -*/ -func (self *Client) ZAdd(key string, arguments ...interface{}) (int64, error) { - var ret int64 - - if len(arguments)%2 != 0 { - return 0, errors.New("Failed to relate SCORE -> MEMBER using the given arguments.") - } - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("ZADD") - args[1] = []byte(key) - - for i, _ := range arguments { - args[2+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns the sorted set cardinality (number of elements) of the sorted set stored at key. - -http://redis.io/commands/zcard -*/ -func (self *Client) ZCard(key string) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("ZCARD"), - []byte(key), - ) - - return ret, err -} - -/* -Returns the number of elements in the sorted set at key with a score between min -and max. - -http://redis.io/commands/zcount -*/ -func (self *Client) ZCount(key string, min interface{}, max interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("ZCOUNT"), - []byte(key), - to.Bytes(min), - to.Bytes(max), - ) - - return ret, err -} - -/* -Increments the score of member in the sorted set stored at key by increment. If -member does not exist in the sorted set, it is added with increment as its score -(as if its previous score was 0.0). If key does not exist, a new sorted set with -the specified member as its sole member is created. - -http://redis.io/commands/zincrby -*/ -func (self *Client) ZIncrBy(key string, increment int64, member interface{}) (string, error) { - var ret string - - err := self.command( - &ret, - []byte("ZINCRBY"), - []byte(key), - to.Bytes(increment), - to.Bytes(member), - ) - - return ret, err -} - -/* -Computes the intersection of numkeys sorted sets given by the specified keys, -and stores the result in destination. It is mandatory to provide the number of -input keys (numkeys) before passing the input keys and the other (optional) -arguments. - -http://redis.io/commands/zinterstore -*/ -func (self *Client) ZInterStore(destination string, numkeys int64, arguments ...interface{}) (int64, error) { - var ret int64 - - args := make([][]byte, len(arguments)+3) - args[0] = []byte("ZINTERSTORE") - args[1] = []byte(destination) - args[2] = to.Bytes(numkeys) - - for i, _ := range arguments { - args[3+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns the specified range of elements in the sorted set stored at key. The -elements are considered to be ordered from the lowest to the highest score. -Lexicographical order is used for elements with equal score. - -http://redis.io/commands/zrange -*/ -func (self *Client) ZRange(key string, values ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(values)+2) - args[0] = []byte("ZRANGE") - args[1] = []byte(key) - - for i, v := range values { - args[2+i] = to.Bytes(v) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns all the elements in the sorted set at key with a score between min and -max (including elements with score equal to min or max). The elements are -considered to be ordered from low to high scores. - -http://redis.io/commands/zrangebyscore -*/ -func (self *Client) ZRangeByScore(key string, values ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(values)+2) - args[0] = []byte("ZRANGEBYSCORE") - args[1] = []byte(key) - - for i, _ := range values { - args[2+i] = to.Bytes(values[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns the rank of member in the sorted set stored at key, with the scores -ordered from low to high. The rank (or index) is 0-based, which means that the -member with the lowest score has rank 0. - -http://redis.io/commands/zrank -*/ -func (self *Client) ZRank(key string, member interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("ZRANK"), - []byte(key), - to.Bytes(member), - ) - - return ret, err -} - -/* -Removes the specified members from the sorted set stored at key. Non existing -members are ignored. - -http://redis.io/commands/zrem -*/ -func (self *Client) ZRem(key string, arguments ...interface{}) (int64, error) { - var ret int64 - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("ZREM") - args[1] = []byte(key) - - for i, _ := range arguments { - args[2+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Removes all elements in the sorted set stored at key with rank between start and -stop. Both start and stop are 0 -based indexes with 0 being the element with the -lowest score. These indexes can be negative numbers, where they indicate offsets -starting at the element with the highest score. For example: -1 is the element -with the highest score, -2 the element with the second highest score and so -forth. - -http://redis.io/commands/zremrangebyrank -*/ -func (self *Client) ZRemRangeByRank(key string, start interface{}, stop interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("ZREMRANGEBYRANK"), - []byte(key), - to.Bytes(start), - to.Bytes(stop), - ) - - return ret, err -} - -/* -Removes all elements in the sorted set stored at key with a score between min -and max (inclusive). - -http://redis.io/commands/zremrangebyscore -*/ -func (self *Client) ZRemRangeByScore(key string, min interface{}, max interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("ZREMRANGEBYSCORE"), - []byte(key), - to.Bytes(min), - to.Bytes(max), - ) - - return ret, err -} - -/* -Returns the specified range of elements in the sorted set stored at key. The -elements are considered to be ordered from the highest to the lowest score. -Descending lexicographical order is used for elements with equal score. - -http://redis.io/commands/zrevrange -*/ -func (self *Client) ZRevRange(key string, start int64, stop int64, params ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(params)+4) - args[0] = []byte("ZREVRANGE") - args[1] = []byte(key) - args[2] = to.Bytes(start) - args[3] = to.Bytes(stop) - - for i, v := range params { - args[4+i] = to.Bytes(v) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns all the elements in the sorted set at key with a score between max and -min (including elements with score equal to max or min). In contrary to the -default ordering of sorted sets, for this command the elements are considered to -be ordered from high to low scores. - -http://redis.io/commands/zrevrangebyscore -*/ -func (self *Client) ZRevRangeByScore(key string, start interface{}, stop interface{}, params ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(params)+4) - args[0] = []byte("ZREVRANGEBYSCORE") - args[1] = []byte(key) - args[2] = to.Bytes(start) - args[3] = to.Bytes(stop) - - for i, _ := range params { - args[4+i] = to.Bytes(params[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -Returns the rank of member in the sorted set stored at key, with the scores -ordered from high to low. The rank (or index) is 0-based, which means that the -member with the highest score has rank 0. - -http://redis.io/commands/zrevrank -*/ -func (self *Client) ZRevRank(key string, member interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("ZREVRANK"), - []byte(key), - to.Bytes(member), - ) - - return ret, err -} - -/* -Returns the score of member in the sorted set at key. - -http://redis.io/commands/zscore -*/ -func (self *Client) ZScore(key string, member interface{}) (int64, error) { - var ret int64 - - err := self.command( - &ret, - []byte("ZSCORE"), - []byte(key), - to.Bytes(member), - ) - - return ret, err -} - -/* -Computes the union of numkeys sorted sets given by the specified keys, and -stores the result in destination. It is mandatory to provide the number of input -keys (numkeys) before passing the input keys and the other (optional) arguments. - -http://redis.io/commands/zunionstore -*/ -func (self *Client) ZUnionStore(destination string, numkeys int64, key string, params ...interface{}) (int64, error) { - var ret int64 - - args := make([][]byte, len(params)+4) - args[0] = []byte("ZUNIONSTORE") - args[1] = []byte(destination) - args[2] = to.Bytes(numkeys) - args[3] = []byte(key) - - for i, _ := range params { - args[4+i] = to.Bytes(params[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -SCAN iterates the set of keys in the currently selected Redis database. - -http://redis.io/commands/scan -*/ -func (self *Client) Scan(cursor int64, arguments ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("SCAN") - args[1] = to.Bytes(cursor) - - for i, _ := range arguments { - args[2+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -SSCAN iterates elements of Sets types. - -http://redis.io/commands/scan -*/ -func (self *Client) SScan(cursor int64, arguments ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("SSCAN") - args[1] = to.Bytes(cursor) - - for i, _ := range arguments { - args[2+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -HSCAN iterates fields of Hash types and their associated values. - -http://redis.io/commands/scan -*/ -func (self *Client) HScan(cursor int64, arguments ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("HSCAN") - args[1] = to.Bytes(cursor) - - for i, _ := range arguments { - args[2+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err -} - -/* -ZSCAN iterates elements of Sorted Set types and their associated scores. - -http://redis.io/commands/zscan -*/ -func (self *Client) ZScan(cursor int64, arguments ...interface{}) ([]string, error) { - var ret []string - - args := make([][]byte, len(arguments)+2) - args[0] = []byte("ZSCAN") - args[1] = to.Bytes(cursor) - - for i, _ := range arguments { - args[2+i] = to.Bytes(arguments[i]) - } - - err := self.command(&ret, args...) - - return ret, err + return self.redis.command(dest, ivalues...) } diff --git a/main_c.c b/main_c.c deleted file mode 100644 index 367cc76..0000000 --- a/main_c.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - Copyright (c) 2013 José Carlos Nieto, https://menteslibres.net/xiam - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include -#include - -#include "net.h" -#include "net.c" - -#include "sds.h" -#include "sds.c" - -#include "async.h" -#include "async.c" - -#include "hiredis.h" -#include "hiredis.c" - -#include "_cgo_export.h" - -struct timeval redisTimeVal(long sec, long usec) { - struct timeval t = { sec, usec }; - return t; -} - -// Will call Go's asyncReceive. -void redisAsyncCatch(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - asyncReceive(reply, privdata); -} - -// Wraps redisAsyncCommandArgv that calls redisAsyncCatch(). -int redisAsyncCommandArgvWrapper(redisAsyncContext *ac, void *fn, int argc, const char **argv, const size_t *argvlen) { - return redisAsyncCommandArgv(ac, redisAsyncCatch, fn, argc, argv, argvlen); -} - -void redisAsyncDisconnectCallbackWrapper(const redisAsyncContext *ac, int status) { - redisAsyncDisconnectCallback((redisAsyncContext *)ac, status); -} - -void redisAsyncConnectCallbackWrapper(const redisAsyncContext *ac, int status) { - redisAsyncConnectCallback((redisAsyncContext *)ac, status); -} - -void redisAsyncSetCallbacks(redisAsyncContext *ac) { - redisAsyncSetConnectCallback(ac, redisAsyncConnectCallbackWrapper); - redisAsyncSetDisconnectCallback(ac, redisAsyncDisconnectCallbackWrapper); -} - -// Returns the redis reply type. -int redisGetReplyType(redisReply *r) { - if (r != NULL) { - return r->type; - } - return -1; -} - -// Returns the (i)th element of the redisReply. -redisReply *redisReplyGetElement(redisReply *el, int i) { - return el->element[i]; -} - -void redisGoroutineReadEvent(void *arg) { - redisGoroutineEvents *e = (redisGoroutineEvents*)arg; - redisAsyncContext *c = e->context; - redisAsyncHandleRead(e->context); -} - -void redisGoroutineWriteEvent(void *arg) { - redisGoroutineEvents *e = (redisGoroutineEvents*)arg; - redisAsyncHandleWrite(e->context); -} - -static void redisGoroutineAddRead(void *privdata) { - redisGoroutineEvents *e = (redisGoroutineEvents*)privdata; - redisEventAddRead(e); -} - -static void redisGoroutineDelRead(void *privdata) { - redisGoroutineEvents *e = (redisGoroutineEvents*)privdata; - redisEventDelRead(e); -} - -static void redisGoroutineAddWrite(void *privdata) { - redisGoroutineEvents *e = (redisGoroutineEvents*)privdata; - redisEventAddWrite(e); -} - -static void redisGoroutineDelWrite(void *privdata) { - redisGoroutineEvents *e = (redisGoroutineEvents*)privdata; - redisEventDelWrite(e); -} - -static void redisGoroutineCleanup(void *privdata) { - redisGoroutineEvents *e = (redisGoroutineEvents*)privdata; - redisEventCleanup(e); -} - -int redisGoroutineAttach(redisAsyncContext *ac, struct redisEvent *ev) { - redisContext *c = &(ac->c); - redisGoroutineEvents *e; - - /* Nothing should be attached when something is already attached */ - if (ac->ev.data != NULL) { - return REDIS_ERR; - } - - /* Create container for context and r/w events */ - e = (redisGoroutineEvents*)malloc(sizeof(*e)); - e->context = ac; - - /* Register functions to start/stop listening for events */ - ac->ev.addRead = redisGoroutineAddRead; - ac->ev.delRead = redisGoroutineDelRead; - ac->ev.addWrite = redisGoroutineAddWrite; - ac->ev.delWrite = redisGoroutineDelWrite; - ac->ev.cleanup = redisGoroutineCleanup; - ac->ev.data = e; - - return REDIS_OK; -} - -void redisForceAsyncFree(redisAsyncContext *ac) { - - redisContext *c = &(ac->c); - redisCallback cb; - dictIterator *it; - dictEntry *de; - - while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) { - __redisRunCallback(ac,&cb,NULL); - } - - while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) { - __redisRunCallback(ac,&cb,NULL); - } - - /* - // This chunk was hanging the freeing process. - it = dictGetIterator(ac->sub.channels); - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - dictRelease(ac->sub.channels); - */ - - it = dictGetIterator(ac->sub.patterns); - while ((de = dictNext(it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - dictReleaseIterator(it); - dictRelease(ac->sub.patterns); - - _EL_CLEANUP(ac); - - if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { - if (c->flags & REDIS_FREEING) { - ac->onDisconnect(ac,REDIS_OK); - } else { - ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); - } - } - - redisFree(c); -} diff --git a/main_test.go b/main_test.go index ac56c93..226dd84 100644 --- a/main_test.go +++ b/main_test.go @@ -18,7 +18,7 @@ var ( func init() { // Getting host and port from command line. - host := flag.String("host", "127.0.0.1", "Test hostname or address.") + host := flag.String("host", "10.1.2.201", "Test hostname or address.") port := flag.Uint("port", 6379, "Port.") flag.Parse() @@ -27,22 +27,20 @@ func init() { testPort = *port log.Printf("Running tests against host %s:%d.\n", testHost, testPort) + + client = New() } func TestConnect(t *testing.T) { var err error - client = New() - // Attempting a valid connection. - err = client.Connect(testHost, testPort) - if err != nil { - t.Fatalf("Failed to connect to test server: %v", err) + if err = client.Connect(testHost, testPort); err != nil { + t.Fatalf("Failed to connect to test server: %q", err) } // Attempting to connect to port 0, probably closed... - err = client.Connect(testHost, 0) - if err == nil { + if err = client.Connect(testHost, 0); err == nil { t.Fatalf("Expecting a connection error.") } } @@ -53,49 +51,21 @@ func TestPing(t *testing.T) { client = New() - err = client.ConnectWithTimeout(testHost, testPort, time.Second*1) - - if err != nil { - t.Fatalf(err.Error()) - } - - s, err = client.Ping() - - if err != nil { - t.Fatalf("Command failed: %v", err) - } - - if s != "PONG" { - t.Fatalf("Failed") - } - - client.Quit() -} - -func TestPingAsync(t *testing.T) { - var s string - var err error - - client = New() - - err = client.ConnectNonBlock(testHost, testPort) - - if err != nil { + if err = client.ConnectWithTimeout(testHost, testPort, time.Second*1); err != nil { t.Fatalf(err.Error()) } - s, err = client.Ping() + defer client.Close() - if err != nil { - t.Fatalf("Command failed: %v", err) + if s, err = client.Ping(); err != nil { + t.Fatalf("Command failed: %q", err) } if s != "PONG" { - t.Fatalf("Failed") + t.Fatal() } client.Quit() - } func TestSimpleSet(t *testing.T) { @@ -103,31 +73,28 @@ func TestSimpleSet(t *testing.T) { var b bool var err error - // We'll be reusing this client. - err = client.Connect(testHost, testPort) + client = New() - if err != nil { - t.Fatalf(err.Error()) + if err = client.Connect(testHost, testPort); err != nil { + t.Fatal(err) } - s, err = client.Set("foo", "hello world.") + defer client.Close() - if err != nil { - t.Fatalf("Command failed: %s", err.Error()) + if s, err = client.Set("foo", "hello world."); err != nil { + t.Fatalf("Command failed: %q", err) } if s != "OK" { - t.Fatalf("Failed") + t.Fatal() } - b, err = client.SetNX("foo", "exists!") - - if err != nil { - t.Fatalf("Command failed: %s", err.Error()) + if b, err = client.SetNX("foo", "exists!"); err != nil { + t.Fatalf("Command failed: %q", err) } if b == true { - t.Fatalf("Failed") + t.Fatal() } } @@ -135,11 +102,18 @@ func TestGet(t *testing.T) { var s string var err error + client = New() + + if err = client.Connect(testHost, testPort); err != nil { + t.Fatal(err) + } + + defer client.Close() + client.Set("foo", "hello") - s, err = client.Get("foo") - if err != nil { - t.Fatalf("Command failed: %s", err.Error()) + if s, err = client.Get("foo"); err != nil { + t.Fatalf("Command failed: %q", err) } if s != "hello" { @@ -169,7 +143,7 @@ func TestGet(t *testing.T) { vals, err = client.HMGet("test", "1232", "456") if err != nil { - t.Fatalf("Expecting no error.") + t.Fatalf("Expecting no error: %q", err) } if vals[0] != "" || vals[1] != "*" { @@ -1124,7 +1098,7 @@ func TestSubscriptions(t *testing.T) { consumer := New() - err = consumer.ConnectNonBlock(testHost, testPort) + err = consumer.Connect(testHost, testPort) consumer.Set("test", "TestSubscriptions") @@ -1155,7 +1129,7 @@ func TestPSubscriptions(t *testing.T) { consumer := New() - err = consumer.ConnectNonBlock(testHost, testPort) + err = consumer.Connect(testHost, testPort) if err != nil { t.Fatalf("Connect failed: %v", err) @@ -2030,7 +2004,7 @@ func BenchmarkConnect(b *testing.B) { client = New() err := client.ConnectWithTimeout(testHost, testPort, time.Second*1) - //err := client.ConnectNonBlock(testHost, testPort) + //err := client.Connect(testHost, testPort) if err != nil { b.Fatalf(err.Error()) @@ -2039,11 +2013,10 @@ func BenchmarkConnect(b *testing.B) { func BenchmarkPing(b *testing.B) { var err error - client.Del("hello") for i := 0; i < b.N; i++ { _, err = client.Ping() if err != nil { - b.Fatalf(err.Error()) + b.Fatal(err) break } } @@ -2051,6 +2024,7 @@ func BenchmarkPing(b *testing.B) { func BenchmarkSet(b *testing.B) { var err error + client.Del("hello") for i := 0; i < b.N; i++ { _, err = client.Set("hello", 1) if err != nil { diff --git a/newpollserver.go b/newpollserver.go deleted file mode 100644 index 8c82089..0000000 --- a/newpollserver.go +++ /dev/null @@ -1,33 +0,0 @@ -// This file is based on src/pkg/net/newpollserver.go. -// -// I removed most of the networking stuff only to leave a bare bones file -// descriptor watcher that could make use of already existing multi-OS code, -// such as fd_linux.go, fd_darwin.go, etc. -// -// -// -// Original net/newpollserver.go's notice: -// -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin freebsd linux netbsd openbsd - -package redis - -import ( - "errors" -) - -var ErrFailedPollServer = errors.New(`Failed to initialize poll server.`) - -func newPollServer() (s *pollServer, err error) { - s = new(pollServer) - if s.poll, err = newpollster(); err != nil { - return nil, ErrFailedPollServer - } - s.pending = make(map[int]*fileFD) - go s.Run() - return s, nil -}