Compare commits
94
Commits
2.2-alpha5
...
2.2.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6418b4c790 | ||
|
|
59aee5513d | ||
|
|
9fecc4057a | ||
|
|
51716cbdb8 | ||
|
|
9a7a07a466 | ||
|
|
0898aa462e | ||
|
|
4c4dec7202 | ||
|
|
401c3e213c | ||
|
|
beb1aab3fd | ||
|
|
28c07c7bf8 | ||
|
|
6674e3b910 | ||
|
|
65add0a311 | ||
|
|
b9f7e9e63c | ||
|
|
2136a880a7 | ||
|
|
f18e059e82 | ||
|
|
c392edf531 | ||
|
|
c8061392e1 | ||
|
|
53f0efe28c | ||
|
|
7d5f5712d9 | ||
|
|
ad1b4f4f59 | ||
|
|
1333f98dd2 | ||
|
|
8f8eeffec1 | ||
|
|
e983cf34be | ||
|
|
180e07b8c0 | ||
|
|
7982173fef | ||
|
|
eae9cce166 | ||
|
|
30407e1f4f | ||
|
|
6a246b1e7e | ||
|
|
cc7c4158bc | ||
|
|
8e33831b2b | ||
|
|
648e965460 | ||
|
|
603e616bf4 | ||
|
|
57997664ea | ||
|
|
2b2eca1f56 | ||
|
|
96b5d05fde | ||
|
|
f858c11d7d | ||
|
|
d8f160a848 | ||
|
|
ef11bcccca | ||
|
|
9f9e1ceaa0 | ||
|
|
8c304be359 | ||
|
|
7ecd4644e7 | ||
|
|
5ce3a24dba | ||
|
|
afd438dfff | ||
|
|
d6107fd6f1 | ||
|
|
a15742a41b | ||
|
|
b215a496a4 | ||
|
|
6f8a32d5c7 | ||
|
|
dd48de748c | ||
|
|
4b918769a1 | ||
|
|
e9bc56dbed | ||
|
|
3d24304ff9 | ||
|
|
cc20906390 | ||
|
|
586500c0ef | ||
|
|
eae33c1c81 | ||
|
|
1b508da7ca | ||
|
|
8b5db0a8dc | ||
|
|
076f88d657 | ||
|
|
e1a586ee69 | ||
|
|
3c1bf4957e | ||
|
|
a5be65f71c | ||
|
|
d51ebef509 | ||
|
|
a4ce758155 | ||
|
|
ecf9401415 | ||
|
|
8a88c368ed | ||
|
|
ac06fc011d | ||
|
|
5fa95ad763 | ||
|
|
c8a0070a61 | ||
|
|
bc8ffafeb9 | ||
|
|
bbac56c2f8 | ||
|
|
ce260f736e | ||
|
|
baa14ef913 | ||
|
|
8987bf23bf | ||
|
|
e3c51c4b1b | ||
|
|
59bd44d1c8 | ||
|
|
7c25a43adc | ||
|
|
ba3b474111 | ||
|
|
357a841714 | ||
|
|
b2a7fd0cf7 | ||
|
|
8a979f0390 | ||
|
|
c54afb6d0d | ||
|
|
b2cc45bfbc | ||
|
|
41945ba6ae | ||
|
|
ff4058183b | ||
|
|
d8d528e992 | ||
|
|
efcf948c1a | ||
|
|
5b761a3387 | ||
|
|
a2a69d5803 | ||
|
|
50d0e82d54 | ||
|
|
2612e0521f | ||
|
|
a4e48b417d | ||
|
|
bd70a5f588 | ||
|
|
9a68cf91f0 | ||
|
|
8a623a98c3 | ||
|
|
5397f2b596 |
+2
-1
@@ -1,4 +1,3 @@
|
||||
00-RELEASENOTES
|
||||
*.o
|
||||
*.rdb
|
||||
*.log
|
||||
@@ -18,3 +17,5 @@ SHORT_TERM_TODO
|
||||
redis.conf.*
|
||||
release.h
|
||||
src/transfer.sh
|
||||
src/configs
|
||||
src/redis-server.dSYM
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
Welcome to Redis 2.1.9 (2.2 Release Candidate 1)
|
||||
|
||||
This is the first Release Candidate of Redis 2.2, in our experience the
|
||||
server is very stable, but in the latest weeks we rewrote part of the internals
|
||||
in order to use a lot less memory while the saving child process is performing
|
||||
a BGREWRITEAOF or a BGSAVE, so handle with care for a couple of weeks.
|
||||
|
||||
Oh, and I've some very good news: the majority of apps can work if you simply replace 2.2 in your old 2.0 environment. I can't think of any breakage.
|
||||
|
||||
WHAT'S NEW IN REDIS 2.2.x
|
||||
|
||||
* Specially encoded data types, small lists and sets can now use up to an order of magnitude less memory.
|
||||
* VM partial rewrite for code cleaness and memory usage.
|
||||
* Change to the implementation of the top level dictionary for better memory efficienty.
|
||||
* redis-cli is hugely improved: tab completion, inline help (Thanks to TJ Holowaychuk), raw output, rewritten using the new hiredis C library.
|
||||
* Networking internals rewritten for efficiency. You can expect LRANGE and similar commands to be at least 10 times faster.
|
||||
* Most read only commands are now copy-on-write friendly, this means that Redis will use little memory when a saving child is active and the parent process is mostly stressed by read queries.
|
||||
* Non blocking replication even from the point of view of the slave, with configurable behavior about what to do when the link is disconnected. You can select if serving old data or replying with an error.
|
||||
* Check-and-set (CAS) transactions with the new WATCH command.
|
||||
* Now write operations work against keys with an EXPIRE set! Imagine the possibilities.
|
||||
* New maxmemory eviction policies. It is possible to select among LRU, farest TTL expire, and other algorithms, and if when the memory limit is reached only keys with an expire set or all the keys should be expired.
|
||||
* SETBIT / GETBIT / SETRANGE / GETRANGE / STRLEN. Now your strings are your arrays!
|
||||
* Syslog support (Thanks to Jonah H. Harris)
|
||||
* Unix domain socket support.
|
||||
* New List related functions LINSERT, LPUSHX, RPUSHX (Thanks to Robey Pointer)
|
||||
* BRPOPLPUSH (Thanks to Michel Martens and Damian Janowski)
|
||||
* Much more interesting informations in the INFO output.
|
||||
* Sorted sets are now less memory hungry.
|
||||
* Non blocking loading of .rdb / AOF file on startup, with process information in the INFO output.
|
||||
* Now Redis has a clean, powerful, supported C library: hiredis.
|
||||
* Code layout completely new, the 2.0.x huge redis.c file is now splitted in many parts.
|
||||
* Redis-benchmark rewritten to be faster and in order to use hiredis as well.
|
||||
* Endless other CPU optimizations and bugs fixed.
|
||||
|
||||
Credits: Where not specified the implementation and design are done by Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all this possible. Also many thanks to all the other contributors and the amazing community we have.
|
||||
|
||||
Cheers,
|
||||
Salvatore
|
||||
@@ -16,4 +16,7 @@ clean:
|
||||
$(TARGETS):
|
||||
cd src && $(MAKE) $@
|
||||
|
||||
src/help.h:
|
||||
@./utils/generate-command-help.rb > $@
|
||||
|
||||
dummy:
|
||||
|
||||
Vendored
+4
-4
@@ -90,13 +90,13 @@ install: ${DYLIBNAME} ${STLIBNAME}
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
make ARCH="-m32"
|
||||
$(MAKE) ARCH="-m32"
|
||||
|
||||
gprof:
|
||||
make PROF="-pg"
|
||||
$(MAKE) PROF="-pg"
|
||||
|
||||
gcov:
|
||||
make PROF="-fprofile-arcs -ftest-coverage"
|
||||
$(MAKE) PROF="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
noopt:
|
||||
make OPTIMIZATION=""
|
||||
$(MAKE) OPTIMIZATION=""
|
||||
|
||||
Vendored
+1
@@ -664,6 +664,7 @@ void __redisSetError(redisContext *c, int type, const sds errstr) {
|
||||
|
||||
static redisContext *redisContextInit() {
|
||||
redisContext *c = calloc(sizeof(redisContext),1);
|
||||
c->fd = -1; /* quick fix for a bug that should be addressed differently */
|
||||
c->err = 0;
|
||||
c->errstr = NULL;
|
||||
c->obuf = sdsempty();
|
||||
|
||||
Vendored
+9
@@ -2,9 +2,18 @@
|
||||
#include <stdlib.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
|
||||
void completion(const char *buf, linenoiseCompletions *lc) {
|
||||
if (buf[0] == 'h') {
|
||||
linenoiseAddCompletion(lc,"hello");
|
||||
linenoiseAddCompletion(lc,"hello there");
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char *line;
|
||||
|
||||
linenoiseSetCompletionCallback(completion);
|
||||
linenoiseHistoryLoad("history.txt"); /* Load the history at startup */
|
||||
while((line = linenoise("hello> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
|
||||
Vendored
+117
@@ -9,6 +9,8 @@
|
||||
* the 2010 UNIX computers around.
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -66,6 +68,17 @@
|
||||
* CUF (CUrsor Forward)
|
||||
* Sequence: ESC [ n C
|
||||
* Effect: moves cursor forward of n chars
|
||||
*
|
||||
* The following are used to clear the screen: ESC [ H ESC [ 2 J
|
||||
* This is actually composed of two sequences:
|
||||
*
|
||||
* cursorhome
|
||||
* Sequence: ESC [ H
|
||||
* Effect: moves the cursor to upper left corner
|
||||
*
|
||||
* ED2 (Clear entire screen)
|
||||
* Sequence: ESC [ 2 J
|
||||
* Effect: clear the whole screen
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -79,10 +92,12 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
|
||||
#define LINENOISE_MAX_LINE 4096
|
||||
static char *unsupported_term[] = {"dumb","cons25",NULL};
|
||||
static linenoiseCompletionCallback *completionCallback = NULL;
|
||||
|
||||
static struct termios orig_termios; /* in order to restore at exit */
|
||||
static int rawmode = 0; /* for atexit() function to check if restore is needed*/
|
||||
@@ -195,6 +210,80 @@ static void refreshLine(int fd, const char *prompt, char *buf, size_t len, size_
|
||||
if (write(fd,seq,strlen(seq)) == -1) return;
|
||||
}
|
||||
|
||||
static void beep() {
|
||||
fprintf(stderr, "\x7");
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
static void freeCompletions(linenoiseCompletions *lc) {
|
||||
size_t i;
|
||||
for (i = 0; i < lc->len; i++)
|
||||
free(lc->cvec[i]);
|
||||
if (lc->cvec != NULL)
|
||||
free(lc->cvec);
|
||||
}
|
||||
|
||||
static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, size_t *len, size_t *pos, size_t cols) {
|
||||
linenoiseCompletions lc = { 0, NULL };
|
||||
int nread, nwritten;
|
||||
char c = 0;
|
||||
|
||||
completionCallback(buf,&lc);
|
||||
if (lc.len == 0) {
|
||||
beep();
|
||||
} else {
|
||||
size_t stop = 0, i = 0;
|
||||
size_t clen;
|
||||
|
||||
while(!stop) {
|
||||
/* Show completion or original buffer */
|
||||
if (i < lc.len) {
|
||||
clen = strlen(lc.cvec[i]);
|
||||
refreshLine(fd,prompt,lc.cvec[i],clen,clen,cols);
|
||||
} else {
|
||||
refreshLine(fd,prompt,buf,*len,*pos,cols);
|
||||
}
|
||||
|
||||
nread = read(fd,&c,1);
|
||||
if (nread <= 0) {
|
||||
freeCompletions(&lc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch(c) {
|
||||
case 9: /* tab */
|
||||
i = (i+1) % (lc.len+1);
|
||||
if (i == lc.len) beep();
|
||||
break;
|
||||
case 27: /* escape */
|
||||
/* Re-show original buffer */
|
||||
if (i < lc.len) {
|
||||
refreshLine(fd,prompt,buf,*len,*pos,cols);
|
||||
}
|
||||
stop = 1;
|
||||
break;
|
||||
default:
|
||||
/* Update buffer and return */
|
||||
if (i < lc.len) {
|
||||
nwritten = snprintf(buf,buflen,"%s",lc.cvec[i]);
|
||||
*len = *pos = nwritten;
|
||||
}
|
||||
stop = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeCompletions(&lc);
|
||||
return c; /* Return last read character */
|
||||
}
|
||||
|
||||
void linenoiseClearScreen(void) {
|
||||
if (write(STDIN_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
|
||||
/* nothing to do, just to avoid warning. */
|
||||
}
|
||||
}
|
||||
|
||||
static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) {
|
||||
size_t plen = strlen(prompt);
|
||||
size_t pos = 0;
|
||||
@@ -217,6 +306,18 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt)
|
||||
|
||||
nread = read(fd,&c,1);
|
||||
if (nread <= 0) return len;
|
||||
|
||||
/* Only autocomplete when the callback is set. It returns < 0 when
|
||||
* there was an error reading from fd. Otherwise it will return the
|
||||
* character that should be handled next. */
|
||||
if (c == 9 && completionCallback != NULL) {
|
||||
c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols);
|
||||
/* Return on errors */
|
||||
if (c < 0) return len;
|
||||
/* Read next character when 0 */
|
||||
if (c == 0) continue;
|
||||
}
|
||||
|
||||
switch(c) {
|
||||
case 13: /* enter */
|
||||
case 4: /* ctrl-d */
|
||||
@@ -350,6 +451,9 @@ up_down_arrow:
|
||||
pos = len;
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
break;
|
||||
case 12: /* ctrl+l, clear screen */
|
||||
linenoiseClearScreen();
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
}
|
||||
return len;
|
||||
@@ -402,6 +506,19 @@ char *linenoise(const char *prompt) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Register a callback function to be called for tab-completion. */
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
|
||||
completionCallback = fn;
|
||||
}
|
||||
|
||||
void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) {
|
||||
size_t len = strlen(str);
|
||||
char *copy = malloc(len+1);
|
||||
memcpy(copy,str,len+1);
|
||||
lc->cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
|
||||
lc->cvec[lc->len++] = copy;
|
||||
}
|
||||
|
||||
/* Using a circular buffer is smarter, but a bit more complex to handle. */
|
||||
int linenoiseHistoryAdd(const char *line) {
|
||||
char *linecopy;
|
||||
|
||||
Vendored
+12
@@ -4,6 +4,8 @@
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -34,10 +36,20 @@
|
||||
#ifndef __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
} linenoiseCompletions;
|
||||
|
||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, char *);
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(char *filename);
|
||||
int linenoiseHistoryLoad(char *filename);
|
||||
void linenoiseClearScreen(void);
|
||||
|
||||
#endif /* __LINENOISE_H */
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>AppendOnlyFileHowto: Contents</b><br> <a href="#Append Only File HOWTO">Append Only File HOWTO</a><br> <a href="#General Information">General Information</a><br> <a href="#Log rewriting">Log rewriting</a><br> <a href="#Wait... but how does this work?">Wait... but how does this work?</a><br> <a href="#How durable is the append only file?">How durable is the append only file?</a>
|
||||
<b>AppendOnlyFileHowto: Contents</b><br> <a href="#Append Only File HOWTO">Append Only File HOWTO</a><br> <a href="#General Information">General Information</a><br> <a href="#Log rewriting">Log rewriting</a><br> <a href="#Wait... but how does this work?">Wait... but how does this work?</a><br> <a href="#How durable is the append only file?">How durable is the append only file?</a><br> <a href="#What should I do if my Append Only File gets corrupted?">What should I do if my Append Only File gets corrupted?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">AppendOnlyFileHowto</h1>
|
||||
@@ -31,8 +31,7 @@
|
||||
What is the solution? To use append only file as alternative to snapshotting. How it works?<br/><br/><ul><li> It is an 1.1 only feature.</li><li> You have to turn it on editing the configuration file. Just make sure you have "appendonly yes" somewhere.</li><li> Append only files work this way: every time Redis receive a command that changes the dataset (for instance a SET or LPUSH command) it appends this command in the append only file. When you restart Redis it will first <b>re-play</b> the append only file to rebuild the state.</li></ul>
|
||||
<h2><a name="Log rewriting">Log rewriting</a></h2>As you can guess... the append log file gets bigger and bigger, every time there is a new operation changing the dataset. Even if you set always the same key "mykey" to the values of "1", "2", "3", ... up to 10000000000 in the end you'll have just a single key in the dataset, just a few bytes! but how big will be the append log file? Very very big.<br/><br/>So Redis supports an interesting feature: it is able to rebuild the append log file, in background, without to stop processing client commands. The key is the command <a href="BGREWRITEAOF.html">BGREWRITEAOF</a>. This command basically is able to use the dataset in memory in order to rewrite the shortest sequence of commands able to rebuild the exact dataset that is currently in memory.<br/><br/>So from time to time when the log gets too big, try this command. It's safe as if it fails you will not lost your old log (but you can make a backup copy given that currently 1.1 is still in beta!).<h2><a name="Wait... but how does this work?">Wait... but how does this work?</a></h2>Basically it uses the same fork() copy-on-write trick that snapshotting already uses. This is how the algorithm works:<br/><br/><ul><li> Redis forks, so now we have a child and a parent.</li><li> The child starts writing the new append log file in a temporary file.</li><li> The parent accumulates all the new changes in an in-memory buffer (but at the same time it writes the new changes in the <b>old</b> append only file, so if the rewriting fails, we are safe).</li><li> When the child finished to rewrite the file, the parent gets a signal, and append the in-memory buffer at the end of the file generated by the child.</li><li> Profit! Now Redis atomically renames the old file into the new one, and starts appending new data into the new file.</li></ul>
|
||||
<h2><a name="How durable is the append only file?">How durable is the append only file?</a></h2>Check redis.conf, you can configure how many times Redis will fsync() data on disk. There are three options:<br/><br/><ul><li> Fsync() every time a new command is appended to the append log file. Very very slow, very safe.</li><li> Fsync() one time every second. Fast enough, and you can lose 1 second of data if there is a disaster.</li><li> Never fsync(), just put your data in the hands of the Operating System. The faster and unsafer method.</li></ul>
|
||||
Warning: by default Redis will fsync() after <b>every command</b>! This is because the Redis authors want to ship a default configuration that is the safest pick. But the best compromise for most datasets is to fsync() one time every second.
|
||||
|
||||
The suggested (and default) policy is "everysec". It is both very fast and pretty safe. The "always" policy is very slow in practice, even if it was improved in Redis 2.0.0 there is no way to make fsync() faster than it is.<h2><a name="What should I do if my Append Only File gets corrupted?">What should I do if my Append Only File gets corrupted?</a></h2>It is possible that the server crashes while writing the AOF file (this still should never lead to inconsistencies) corrupting the file in a way that is no longer loadable by Redis. When this happens you can fix this problem using the following procedure:<br/><br/><ul><li> Make a backup copy of your AOF file.</li><li> Fix the original file with: ./redis-check-aof --fix <code name="code" class="python"><filename></code></li><li> Optionally use diff -u to check what is the difference between two files.</li><li> Restart the server with the fixed file.</li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>BrpoplpushCommand: Contents</b><br> <a href="#BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis >">BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">BrpoplpushCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis >">BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Blocking version of the <a href="RpoplpushCommand.html">RPOPLPUSH</a> command. Atomically removes and returnsthe last element (tail) of the source list at <i>srckey</i>, and as a side effect pushes the returned element in the head of the list at <i>dstkey</i>.</blockquote>
|
||||
If the source list is empty, the client blocks until another client pushes against the source list. Of course in such a case the push operation against the destination list will be performed after the command unblocks detecting a push against the source list.<br/><br/>Note that the command returns an error if the target key already exists but is not a list. The error is delayed at the time the push operation is attempted, that is, immediately if the source list is not empty, or when the first push against the source list happens in the case the command would block.<br/><br/>The timeout value can be 0 or a positive integer value. When it is zero the command will block forever, until something is pushed against <i>srckey</i>. Otherwise the command will wait the specified number of seconds at max, returning an nil value when the timeout expires.<br/><br/>The source and destination of the list can be the same, having the effect of rotating the list. Please check <a href="RpoplpushCommand.html">RPOPLPUSH</a> for more information.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ExpireCommand: Contents</b><br> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#PERSIST _key_">PERSIST _key_</a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <a href="#FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a><br> <a href="#FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a>
|
||||
<b>ExpireCommand: Contents</b><br> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#PERSIST _key_ (Redis >">PERSIST _key_ (Redis ></a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <a href="#FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a><br> <a href="#FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ExpireCommand</h1>
|
||||
@@ -28,8 +28,8 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a></h1>
|
||||
<h1><a name="EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a></h1> 1.1)=
|
||||
<h1><a name="PERSIST _key_">PERSIST _key_</a></h1>
|
||||
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically delete by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
|
||||
<h1><a name="PERSIST _key_ (Redis >">PERSIST _key_ (Redis ></a></h1> 2.1.3) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically deleted by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
|
||||
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingexpires and stopping the server does not stop the flow of time as Redisstores on disk the time when the key will no longer be available as Unixtime, and not the remaining seconds.</blockquote>
|
||||
<blockquote>EXPIREAT works exctly like EXPIRE but instead to get the number of secondsrepresenting the Time To Live of the key as a second argument (that is arelative way of specifing the TTL), it takes an absolute one in the form ofa UNIX timestamp (Number of seconds elapsed since 1 Gen 1970).</blockquote>
|
||||
<blockquote>EXPIREAT was introduced in order to implement <a href="AppendOnlyFileHowto.html">the Append Only File persistence mode</a>so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
|
||||
@@ -78,7 +78,7 @@ redis> incr a
|
||||
(integer) 1
|
||||
</pre>
|
||||
I set a key to the value of 100, then set an expire of 360 seconds, and then incremented the key (before the 360 timeout expired of course). The obvious result would be: 101, instead the key is set to the value of 1. Why?
|
||||
There is a very important reason involving the Append Only File and Replication. Let's rework a bit hour example adding the notion of time to the mix:
|
||||
There is a very important reason involving the Append Only File and Replication. Let's rework a bit our example adding the notion of time to the mix:
|
||||
<pre class="codeblock python python python python" name="code">
|
||||
SET a 100
|
||||
EXPIRE a 5
|
||||
@@ -87,7 +87,6 @@ INCR a
|
||||
</pre>
|
||||
Imagine a Redis version that does not implement the "Delete keys with an expire set on write operation" semantic.
|
||||
Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)<h2><a name="FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a></h2>Since Redis 2.1.3 there are no longer restrictions in the use you can do of write commands against volatile keys, still the replication and AOF file are guaranteed to be fully consistent.<br/><br/>In order to obtain a correct behavior without sacrificing consistency now when a key expires, a DEL operation is synthesized in both the AOF file and against all the attached slaves. This way the expiration process is centralized in the master instance, and there is no longer a chance of consistency errors.<br/><br/>However while the slaves while connected to a master will not expire keys independently, they'll still take the full state of the expires existing in the dataset, so when a slave is elected to a master it will be able to expire the keys independently, fully acting as a master.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>GetbitCommand: Contents</b><br> <a href="#GETBIT _key_ _offset_ (Redis >">GETBIT _key_ _offset_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">GetbitCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="GETBIT _key_ _offset_ (Redis >">GETBIT _key_ _offset_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Returns the bit value at <i>offset</i> in the string value stored at <i>key</i>.</blockquote>
|
||||
When <i>offset</i> is beyond the string length, the string is assumed to be a contiguous space with 0 bits. When <i>key</i> does not exist it is assumed to be an empty string, so <i>offset</i> is always out of range and the value is also assumed to be a contiguous space with 0 bits.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the bit value stored at <i>offset</i>.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HDEL _key_ _field_ (Redis >">HDEL _key_ _field_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(1)</i><blockquote>Remove the specified <i>field</i> from an hash stored at <i>key</i>.</blockquote>
|
||||
<blockquote>If the <i>field</i> was present in the hash it is returned and 1 is returned, otherwise 0 is returned and no operation is performed.</blockquote>
|
||||
<blockquote>If the <i>field</i> was present in the hash it is deleted and 1 is returned, otherwise 0 is returned and no operation is performed.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HKEYS _key_ (Redis >">HKEYS _key_ (Redis ></a></h1> 1.3.10)=
|
||||
<h1><a name="HVALS _key_ (Redis >">HVALS _key_ (Redis ></a></h1> 1.3.10)=
|
||||
<h1><a name="HGETALL _key_ (Redis >">HGETALL _key_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(N), where N is the total number of entries</i><blockquote>HKEYS returns all the fields names contained into a hash, HVALS all the associated values, while HGETALL returns both the fields and values in the form of <i>field1</i>, <i>value1</i>, <i>field2</i>, <i>value2</i>, ..., <i>fieldN</i>, <i>valueN</i>.</blockquote>
|
||||
<i>Time complexity: O(N), where N is the total number of fields in the hash</i><blockquote>HKEYS returns all the fields names contained into a hash, HVALS all the associated values, while HGETALL returns both the fields and values in the form of <i>field1</i>, <i>value1</i>, <i>field2</i>, <i>value2</i>, ..., <i>fieldN</i>, <i>valueN</i>.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi Bulk Reply</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HmgetCommand: Contents</b><br> <a href="#HMGET _key_ _field1_ ... _fieldN_ (Redis >">HMGET _key_ _field1_ ... _fieldN_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HmgetCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HMGET _key_ _field1_ ... _fieldN_ (Redis >">HMGET _key_ _field1_ ... _fieldN_ (Redis ></a></h1> 1.3.10) =
|
||||
<i>Time complexity: O(N) (with N being the number of fields)</i><blockquote>Retrieve the values associated to the specified <i>fields</i>.</blockquote>
|
||||
<blockquote>If some of the specified <i>fields</i> do not exist, nil values are returned.Non existing keys are considered like empty hashes.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi Bulk Reply</a> specifically a list of all the values associated with the specified fields, in the same order of the request.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -79,7 +79,7 @@ $ ./redis-cli sismember myset 3
|
||||
(integer) 1
|
||||
$ ./redis-cli sismember myset 30
|
||||
(integer) 0
|
||||
</pre>"3" is a member of the set, while "30" is not. Sets are very good in order to express relations between objects. For instance we can easily Redis Sets in order to implement tags.<br/><br/>A simple way to model this is to have, for every object you want to tag, a Set with all the IDs of the tags associated with the object, and for every tag that exists, a Set of of all the objects tagged with this tag.<br/><br/>For instance if our news ID 1000 is tagged with tag 1,2,5 and 77, we can specify the following two Sets:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
</pre>"3" is a member of the set, while "30" is not. Sets are very good in order to express relations between objects. For instance we can easily use Redis Sets in order to implement tags.<br/><br/>A simple way to model this is to have, for every object you want to tag, a Set with all the IDs of the tags associated with the object, and for every tag that exists, a Set of of all the objects tagged with this tag.<br/><br/>For instance if our news ID 1000 is tagged with tag 1,2,5 and 77, we can specify the following two Sets:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
$ ./redis-cli sadd news:1000:tags 1
|
||||
(integer) 1
|
||||
$ ./redis-cli sadd news:1000:tags 2
|
||||
@@ -143,7 +143,6 @@ $ ./redis-cli zrangebyscore hackers -inf 1950
|
||||
$ ./redis-cli zremrangebyscore hackers 1940 1960
|
||||
(integer) 2
|
||||
</pre><a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> is not the best command name, but it can be very useful, and returns the number of removed elements.<h3><a name="Back to the reddit example">Back to the reddit example</a></h3>For the last time, back to the Reddit example. Now we have a decent plan to populate a sorted set in order to generate the home page. A sorted set can contain all the news that are not older than a few days (we remove old entries from time to time using ZREMRANGEBYSCORE). A background job gets all the elements from this sorted set, get the user votes and the time of the news, and compute the score to populate the <b>reddit.home.page</b> sorted set with the news IDs and associated scores. To show the home page we have just to perform a blazingly fast call to ZRANGE.<br/><br/>From time to time we'll remove too old news from the <b>reddit.home.page</b> sorted set as well in order for our system to work always against a limited set of news.<h3><a name="Updating the scores of a sorted set">Updating the scores of a sorted set</a></h3>Just a final note before to finish this tutorial. Sorted sets scores can be updated at any time. Just calling again ZADD against an element already included in the sorted set will update its score (and position) in O(log(N)), so sorted sets are suitable even when there are tons of updates.<br/><br/>This tutorial is in no way complete, this is just the basics to get started with Redis, read the <a href="CommandReference.html">Command Reference</a> to discover a lot more.<br/><br/>Thanks for reading. Salvatore.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== List Commands ==<br/><br/><ul><li> <a href="RpushCommand.html">RPUSH</a></li><li> <a href="RpushCommand.html">LPUSH</a></li><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li><li> <a href="LremCommand.html">LREM</a></li><li> <a href="LpopCommand.html">LPOP</a></li><li> <a href="LpopCommand.html">RPOP</a></li><li> <a href="BlpopCommand.html">BLPOP</a></li><li> <a href="BlpopCommand.html">BRPOP</a></li><li> <a href="RpoplpushCommand.html">RPOPLPUSH</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
|
||||
== List Commands ==<br/><br/><ul><li> <a href="RpushCommand.html">RPUSH</a></li><li> <a href="RpushCommand.html">LPUSH</a></li><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li><li> <a href="LremCommand.html">LREM</a></li><li> <a href="LpopCommand.html">LPOP</a></li><li> <a href="LpopCommand.html">RPOP</a></li><li> <a href="BlpopCommand.html">BLPOP</a></li><li> <a href="BlpopCommand.html">BRPOP</a></li><li> <a href="RpoplpushCommand.html">RPOPLPUSH</a></li><li> <a href="BrpoplpushCommand.html">BRPOPLPUSH</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
<h1><a name="COMMAND_1 ...">COMMAND_1 ...</a></h1>
|
||||
<h1><a name="COMMAND_2 ...">COMMAND_2 ...</a></h1>
|
||||
<h1><a name="COMMAND_N ...">COMMAND_N ...</a></h1>
|
||||
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the fundation of Redis Transactions.
|
||||
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the foundation of Redis Transactions.
|
||||
A Redis Transaction allows the execution of a group of Redis commands in a single
|
||||
step, with two important guarantees:<br/><br/><ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
|
||||
Since Redis 2.1.0, it's also possible to add a further guarantee to the above two, in the form of optimistic locking of a set of keys in a way very similar to a CAS (check and set) operation. This is documented later in this manual page.<h2><a name="Usage">Usage</a></h2>A Redis transaction is entered using the MULTI command. The command always
|
||||
replies with OK. At this point the user can issue multiple commands. Instead
|
||||
to execute this commands Redis will "queue" them. All the commands are
|
||||
of executing these commands, Redis will "queue" them. All the commands are
|
||||
executed once EXEC is called.<br/><br/>Calling DISCARD instead will flush the transaction queue and will exit
|
||||
the transaction.<br/><br/>The following is an example using the Ruby client:
|
||||
<pre class="codeblock python" name="code">
|
||||
@@ -57,7 +57,7 @@ replies, where every element is the reply of a single command in the
|
||||
transaction, in the same order the commands were queued.<br/><br/>When a Redis connection is in the context of a MULTI request, all the commands
|
||||
will reply with a simple string "QUEUED" if they are correct from the
|
||||
point of view of the syntax and arity (number of arguments) of the commaand.
|
||||
Some command is still allowed to fail during execution time.<br/><br/>This is more clear if at protocol level: in the following example one command
|
||||
Some commands are still allowed to fail during execution time.<br/><br/>This is more clear on the protocol level; In the following example one command
|
||||
will fail when executed even if the syntax is right:
|
||||
<pre class="codeblock python python" name="code">
|
||||
Trying 127.0.0.1...
|
||||
@@ -75,7 +75,7 @@ EXEC
|
||||
+OK
|
||||
-ERR Operation against a key holding the wrong kind of value
|
||||
</pre>
|
||||
MULTI returned a two elements bulk reply in witch one of this is a +OK
|
||||
MULTI returned a two elements bulk reply where one is an +OK
|
||||
code and one is a -ERR reply. It's up to the client lib to find a sensible
|
||||
way to provide the error to the user.<br/><br/><blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
|
||||
Another example, again using the write protocol with telnet, shows how
|
||||
@@ -87,9 +87,8 @@ INCR a b c
|
||||
-ERR wrong number of arguments for 'incr' command
|
||||
</pre>
|
||||
This time due to the syntax error the "bad" INCR command is not queued
|
||||
at all.<h2><a name="The DISCARD command">The DISCARD command</a></h2>DISCARD can be used in order to abort a transaction. No command will be
|
||||
executed, and the state of the client is again the normal one, outside
|
||||
<blockquote>of a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
|
||||
at all.<h2><a name="The DISCARD command">The DISCARD command</a></h2>DISCARD can be used in order to abort a transaction. No command will be executed, and the state of the client is again the normal one, outside of a transaction. Example using the Ruby client:
|
||||
<pre class="codeblock python python python python" name="code">
|
||||
?> r.set("foo",1)
|
||||
=> true
|
||||
>> r.multi
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>NonexistentCommands: Contents</b><br> <a href="#HGETSET">HGETSET</a><br> <a href="#SET with expire">SET with expire</a><br> <a href="#ZADDNX">ZADDNX</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">NonexistentCommands</h1>
|
||||
|
||||
<div class="summary">
|
||||
A list of commands that don't exist in Redis, but can be accomplished in a different way.
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
This is a list of commands that don't exist in Redis, but can be accomplished in a different way, usually by means of <a href="MultiExecCommand.html">WATCH/MULTI/EXEC</a>.<br/><br/>For better performance, you can pipeline multiple commands.<h1><a name="HGETSET">HGETSET</a></h1><a href="GetsetCommand.html">GETSET</a> for Hashes.<br/><br/><pre class="codeblock python" name="code">
|
||||
WATCH foo
|
||||
old_value = HGET foo field
|
||||
MULTI
|
||||
HSET foo field new_value
|
||||
EXEC
|
||||
</pre><h1><a name="SET with expire">SET with expire</a></h1>See <a href="SetexCommand.html">SETEX</a>.<h1><a name="ZADDNX">ZADDNX</a></h1>Add an element to a sorted set, only if the element doesn't already exist (by default, <a href="ZaddCommand.html">ZADD</a> would update the element's score if it already exists). <a href="http://groups.google.com/group/redis-db/browse_thread/thread/fc4c79d72e5bd346/6cdc07ecc36b81e7" target="_blank">See thread</a>.<br/><br/><pre class="codeblock python python" name="code">
|
||||
WATCH foo
|
||||
score = ZSCORE foo bar
|
||||
IF score != NIL
|
||||
MULTI
|
||||
ZADD foo 1 bar
|
||||
EXEC
|
||||
ENDIF
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ProtocolSpecification: Contents</b><br> <a href="#Networking layer">Networking layer</a><br> <a href="#Simple INLINE commands">Simple INLINE commands</a><br> <a href="#Bulk commands">Bulk commands</a><br> <a href="#Bulk replies">Bulk replies</a><br> <a href="#Multi-Bulk replies">Multi-Bulk replies</a><br> <a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br> <a href="#Single line reply">Single line reply</a><br> <a href="#Integer reply">Integer reply</a><br> <a href="#Multi bulk commands">Multi bulk commands</a><br> <a href="#Multiple commands and pipelining">Multiple commands and pipelining</a>
|
||||
<b>ProtocolSpecification: Contents</b><br> <a href="#Networking layer">Networking layer</a><br> <a href="#Requests">Requests</a><br> <a href="#The new unified request protocol">The new unified request protocol</a><br> <a href="#Replies">Replies</a><br> <a href="#Single line reply">Single line reply</a><br> <a href="#Error reply">Error reply</a><br> <a href="#Integer reply">Integer reply</a><br> <a href="#Bulk replies">Bulk replies</a><br> <a href="#Multi-Bulk replies">Multi-Bulk replies</a><br> <a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br> <a href="#Multiple commands and pipelining">Multiple commands and pipelining</a><br> <a href="#The old protocol for sending commands">The old protocol for sending commands</a><br> <a href="#Inline Commands">Inline Commands</a><br> <a href="#Bulk commands">Bulk commands</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ProtocolSpecification</h1>
|
||||
@@ -26,53 +26,54 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Protocol Specification =<br/><br/>The Redis protocol is a compromise between being easy to parse by a computer
|
||||
and being easy to parse by an human. Before reading this section you are
|
||||
strongly encouraged to read the "REDIS TUTORIAL" section of this README in order
|
||||
to get a first feeling of the protocol playing with it by TELNET.<h2><a name="Networking layer">Networking layer</a></h2>A client connects to a Redis server creating a TCP connection to the port 6379.
|
||||
Every redis command or data transmitted by the client and the server is
|
||||
terminated by "\r\n" (CRLF).<h2><a name="Simple INLINE commands">Simple INLINE commands</a></h2>The simplest commands are the inline commands. This is an example of a
|
||||
server/client chat (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python" name="code">
|
||||
C: PING
|
||||
S: +PONG
|
||||
</pre>An inline command is a CRLF-terminated string sent to the client. The server can reply to commands in different ways:
|
||||
<ul><li> With an error message (the first byte of the reply will be "-")</li><li> With a single line reply (the first byte of the reply will be "+)</li><li> With bulk data (the first byte of the reply will be "$")</li><li> With multi-bulk data, a list of values (the first byte of the reply will be "<code name="code" class="python">*</code>")</li><li> With an integer number (the first byte of the reply will be ":")</li></ul>
|
||||
The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python" name="code">
|
||||
C: EXISTS somekey
|
||||
S: :0
|
||||
</pre>Since 'somekey' does not exist the server returned ':0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated
|
||||
simply by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>A bulk command is exactly like an inline command, but the last argument
|
||||
of the command must be a stream of bytes in order to send data to the server.
|
||||
the "SET" command is a bulk command, see the following example:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
C: SET mykey 6
|
||||
C: foobar
|
||||
S: +OK
|
||||
</pre>The last argument of the commnad is '6'. This specify the number of DATA
|
||||
bytes that will follow (note that even this bytes are terminated by two
|
||||
additional bytes of CRLF).<br/><br/>All the bulk commands are in this exact form: instead of the last argument
|
||||
the number of bytes that will follow is specified, followed by the bytes,
|
||||
and CRLF. In order to be more clear for the programmer this is the string
|
||||
sent by the client in the above sample:<br/><br/><blockquote>"SET mykey 6\r\nfoobar\r\n"</blockquote>
|
||||
<h2><a name="Bulk replies">Bulk replies</a></h2>The server may reply to an inline or bulk command with a bulk reply. See
|
||||
the following example:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
= Protocol Specification =<br/><br/>The Redis protocol is a compromise between the following things:<br/><br/><ul><li> Simple to implement.</li><li> Fast to parse by a computer.</li><li> Easy enough to parse by a human.</li></ul>
|
||||
<h2><a name="Networking layer">Networking layer</a></h2>A client connects to a Redis server creating a TCP connection to the port 6379.
|
||||
Every Redis command or data transmitted by the client and the server is
|
||||
terminated by "\r\n" (CRLF).<h1><a name="Requests">Requests</a></h1>Redis accepts commands composed of different arguments.
|
||||
Once a command is received, it is processed and a reply is sent back to the client.<h2><a name="The new unified request protocol">The new unified request protocol</a></h2>The new unified protocol was introduced in Redis 1.2, but it became the standard way for talking with the Redis server in Redis 2.0.<br/><br/>In the unified protocol all the arguments sent to the Redis server are binary safe. This is the general form:<br/><br/><pre class="codeblock python" name="code">
|
||||
*<number of arguments> CR LF
|
||||
$<number of bytes of argument 1> CR LF
|
||||
<argument data> CR LF
|
||||
...
|
||||
$<number of bytes of argument N> CR LF
|
||||
<argument data> CR LF
|
||||
</pre>See the following example:<br/><br/><pre class="codeblock python python" name="code">
|
||||
*3
|
||||
$3
|
||||
SET
|
||||
$5
|
||||
mykey
|
||||
$7
|
||||
myvalue
|
||||
</pre>This is how the above command looks as a quoted string, so that it is possible to see the exact value of every byte in the query:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$8\r\nmyvalue\r\n"
|
||||
</pre>As you will see in a moment this format is also used in Redis replies.
|
||||
The format used for every argument "$6\r\nmydata\r\n" is called a Bulk Reply.
|
||||
While the actual unified request protocol is what Redis uses to return list of items, and is called a Multi Bulk Reply. It is just the sum of N different
|
||||
Bulk Replies prefixed by a <code name="code" class="python">*<argc>\r\n</code> string where <code name="code" class="python"><argc></code> is the number of arguments (Bulk Replies) that will follow.<h1><a name="Replies">Replies</a></h1>Redis will reply to commands with different kinds of replies. It is possible to check the kind of reply from the first byte sent by the server:<br/><br/><ul><li> With a single line reply the first byte of the reply will be "+"</li><li> With an error message the first byte of the reply will be "-"</li><li> With an integer number the first byte of the reply will be ":"</li><li> With bulk reply the first byte of the reply will be "$"</li><li> With multi-bulk reply the first byte of the reply will be "<code name="code" class="python">*</code>"</li></ul>
|
||||
<h2><a name="Single line reply">Single line reply</a></h2>A single line reply is in the form of a single line string
|
||||
starting with "+" terminated by "\r\n". For example:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
+OK
|
||||
</pre>The client library should return everything after the "+", that is, the string "OK" in the example.<br/><br/>The following commands reply with a single line reply:
|
||||
PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Error reply">Error reply</a></h2>Errors are sent exactly like Single Line Replies. The only difference is that the first byte is "-" instead of "+".<br/><br/>Error replies are only sent when something strange happened, for instance if you try to perform an operation against the wrong data type, or if the command does not exist and so forth. So an exception should be raised by the library client when an Error Reply is received.<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer, prefixed by a ":" byte. For example ":0\r\n", or ":1000\r\n" are integer replies.<br/><br/>With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.<br/><br/>The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD<h2><a name="Bulk replies">Bulk replies</a></h2>Bulk replies are used by the server in order to return a single binary safe string.<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
C: GET mykey
|
||||
S: $6
|
||||
S: foobar
|
||||
</pre>A bulk reply is very similar to the last argument of a bulk command. The
|
||||
server sends as the first line a "$" byte followed by the number of bytes
|
||||
of the actual reply followed by CRLF, then the bytes are sent followed by
|
||||
additional two bytes for the final CRLF. The exact sequence sent by the
|
||||
server is:<br/><br/><blockquote>"$6\r\nfoobar\r\n"</blockquote>
|
||||
If the requested value does not exist the bulk reply will use the special
|
||||
value -1 as data length, example:<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
</pre>The server sends as the first line a "$" byte followed by the number of bytes
|
||||
of the actual reply, followed by CRLF, then the actual data bytes are sent,
|
||||
followed by additional two bytes for the final CRLF.
|
||||
The exact sequence sent by the server is:<br/><br/><pre class="codeblock python python python python python python" name="code">
|
||||
"$6\r\nfoobar\r\n"
|
||||
</pre>If the requested value does not exist the bulk reply will use the special
|
||||
value -1 as data length, example:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
C: GET nonexistingkey
|
||||
S: $-1
|
||||
</pre>The client library API should not return an empty string, but a nil object, when the requested object does not exist.
|
||||
For example a Ruby library should return 'nil' while a C library should return
|
||||
NULL, and so forth.<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands similar to LRANGE needs to return multiple values (every element
|
||||
NULL (or set a special flag in the reply object), and so forth.<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands like LRANGE need to return multiple values (every element
|
||||
of the list is a value, and LRANGE needs to return more than a single element). This is accomplished using multiple bulk writes,
|
||||
prefixed by an initial line indicating how many bulk writes will follow.
|
||||
The first byte of a multi bulk reply is always <code name="code" class="python">*</code>. Example:<br/><br/><pre class="codeblock python python python python python python" name="code">
|
||||
The first byte of a multi bulk reply is always <code name="code" class="python">*</code>. Example:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
|
||||
C: LRANGE mylist 0 3
|
||||
S: *4
|
||||
S: $3
|
||||
@@ -83,56 +84,50 @@ S: $5
|
||||
S: Hello
|
||||
S: $5
|
||||
S: World
|
||||
</pre>The first line the server sent is "<b>4\r\n" in order to specify that four bulk
|
||||
write will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist instead of the number of elements in the
|
||||
list, the special value -1 is sent as count. Example:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
</pre>As you can see the multi bulk reply is exactly the same format used in order
|
||||
to send commands to the Redis server unsing the unified protocol.<br/><br/>The first line the server sent is "<b>4\r\n" in order to specify that four bulk
|
||||
replies will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist, instead of the number of elements in the
|
||||
list the special value -1 is sent as count. Example:<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
|
||||
C: LRANGE nokey 0 1
|
||||
S: *-1
|
||||
</pre>A client library API SHOULD return a nil object and not an empty list when this
|
||||
happens. This makes possible to distinguish between empty list and non existing ones.<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
|
||||
happens. This makes possible to distinguish between empty list and other error conditions (for instance a timeout condition in the BLPOP command).<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
|
||||
S: *3
|
||||
S: $3
|
||||
S: foo
|
||||
S: $-1
|
||||
S: $3
|
||||
S: bar
|
||||
</pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
|
||||
</pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code">
|
||||
["foo",nil,"bar"]
|
||||
</pre><h2><a name="Single line reply">Single line reply</a></h2>As already seen a single line reply is in the form of a single line string
|
||||
starting with "+" terminated by "\r\n". For example:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
|
||||
+OK
|
||||
</pre>The client library should return everything after the "+", that is, the string "OK" in the example.<br/><br/>The following commands reply with a status code reply:
|
||||
PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer, prefixed by a ":" byte. For example ":0\r\n", or ":1000\r\n" are integer replies.<br/><br/>With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.<br/><br/>The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD<h2><a name="Multi bulk commands">Multi bulk commands</a></h2>As you can see with the protocol described so far there is no way to
|
||||
send multiple binary-safe arguments to a command. With bulk commands the
|
||||
last argument is binary safe, but there are commands where multiple binary-safe
|
||||
commands are needed, like the MSET command that is able to SET multiple keys
|
||||
in a single operation.<br/><br/>In order to address this problem Redis 1.1 introduced a new way of seding
|
||||
commands to a Redis server, that uses exactly the same protocol of the
|
||||
multi bulk replies. For instance the following is a SET command using the
|
||||
normal bulk protocol:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code">
|
||||
SET mykey 8
|
||||
myvalue
|
||||
</pre>While the following uses the multi bulk command protocol:<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
|
||||
*3
|
||||
$3
|
||||
SET
|
||||
$5
|
||||
mykey
|
||||
$8
|
||||
myvalue
|
||||
</pre>Commands sent in this format are longer, so currently they are used only in
|
||||
order to transmit commands containing multiple binary-safe arguments, but
|
||||
actually this protocol can be used to send every kind of command, without to
|
||||
know if it's an inline, bulk or multi-bulk command.<br/><br/>It is possible that in the future Redis will support only this format.<br/><br/>A good client library may implement unknown commands using this
|
||||
command format in order to support new commands out of the box without
|
||||
modifications.<h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
|
||||
</pre><h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
|
||||
Pipelining is supported so multiple commands can be sent with a single
|
||||
write operation by the client, it is not needed to read the server reply
|
||||
in order to issue the next command. All the replies can be read at the end.<br/><br/>Usually Redis server and client will have a very fast link so this is not
|
||||
very important to support this feature in a client implementation, still
|
||||
if an application needs to issue a very large number of commands in short
|
||||
time to use pipelining can be much faster.
|
||||
</b>
|
||||
time to use pipelining can be much faster.<h1><a name="The old protocol for sending commands">The old protocol for sending commands</a></h1>Before of the Unified Request Protocol Redis used a different protocol to send
|
||||
commands, that is still supported since it is simpler to type by hand via telnet. In this protocol there are two kind of commands:<br/><br/><blockquote>* Inline commands: simple commands where argumnets are just space separated strings. No binary safeness is possible.* Bulk commands: bulk commands are exactly like inline commands, but the last argument is handled in a special way in order to allow for a binary-safe last argument.</blockquote>
|
||||
<h2><a name="Inline Commands">Inline Commands</a></h2>The simplest way to send Redis a command is via </b>Inline Commands.
|
||||
The following is an example of a server/client chat using an inline command (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
|
||||
C: PING
|
||||
S: +PONG
|
||||
</pre>The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python python python python python python python python python python python python" name="code">
|
||||
C: EXISTS somekey
|
||||
S: :0
|
||||
</pre>Since 'somekey' does not exist the server returned ':0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated
|
||||
by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>Some commands when sent as inline commands require a special form in order
|
||||
to support a binary safe last argument. This commands will use the last argument
|
||||
for a "byte count", then the bulk data is sent (that can be binary safe since
|
||||
the server knows how many bytes to read).<br/><br/>See for instance the following example:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python" name="code">
|
||||
C: SET mykey 6
|
||||
C: foobar
|
||||
S: +OK
|
||||
</pre>The last argument of the commnad is '6'. This specify the number of DATA
|
||||
bytes that will follow, that is, the string "foobar". Note that even this bytes are terminated by two additional bytes of CRLF.<br/><br/>All the bulk commands are in this exact form: instead of the last argument
|
||||
the number of bytes that will follow is specified, followed by the bytes
|
||||
composing the argument itself, and CRLF. In order to be more clear for the programmer this is the string sent by the client in the above sample:<br/><br/><blockquote>"SET mykey 6\r\nfoobar\r\n"</blockquote>
|
||||
Redis has an internal list of what command is inline and what command is bulk, so you have to send this commands accordingly. It is strongly suggested to use the new Unified Request Protocol instead.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<h1><a name="PSUBSCRIBE pattern_1 pattern_2 ... pattern_N">PSUBSCRIBE pattern_1 pattern_2 ... pattern_N</a></h1>
|
||||
<h1><a name="PUNSUBSCRIBE pattern_1 pattern_2 ... pattern_N">PUNSUBSCRIBE pattern_1 pattern_2 ... pattern_N</a></h1>
|
||||
<h1><a name="PUNSUBSCRIBE (unsubscribe from all patterns)">PUNSUBSCRIBE (unsubscribe from all patterns)</a></h1>
|
||||
<h1><a name="PUBLISH channel message">PUBLISH channel message</a></h1>Time complexity: subscribe is O(1), unsubscribe is O(N) where N is the number of clients already subscribed to a channel, publish is O(N+M) where N is the number of clients subscribed to the receiving channel, and M is the total number of subscribed patterns (by any client). Psubscribe is O(N) where N is the number of patterns the Psubscribing client is already subscribed to. Punsubscribe is O(N+M) where N is the number of patterns the Punsubscribing client is already subscribed and M is the number of total patterns subscribed in the system (by any client).<br/><br/><blockquote>SUBSCRIBE, UNSUBSCRIBE and PUBLISH commands implement the<a href="http://en.wikipedia.org/wiki/Publish/subscribe" target="_blank">Publish/Subscribe messaging paradigm</a> where (citing Wikipedia) senders (publishers) are not programmed to send their messages to specific receivers (subscribers). Rather, published messages are characterized into channels, without knowledge of what (if any) subscribers there may be. Subscribers express interest in one or more channels, and only receive messages that are of interest, without knowledge of what (if any) publishers there are. This decoupling of publishers and subscribers can allow for greater scalability and a more dynamic network topology.</blockquote>
|
||||
<h1><a name="PUBLISH channel message">PUBLISH channel message</a></h1>Time complexity: subscribe is O(1), unsubscribe is O(N) where N is the number of clients already subscribed to a channel, publish is O(N+M) where N is the number of clients subscribed to the receiving channel, and M is the total number of subscribed patterns (by any client). Psubscribe is O(N) where N is the number of patterns the Psubscribing client is already subscribed to. Punsubscribe is O(N+M) where N is the number of patterns the Punsubscribing client is already subscribed and M is the number of total patterns subscribed in the system (by any client).<br/><br/><b>Note</b>: this commands are available starting form Redis 2.0.0<br/><br/><blockquote>SUBSCRIBE, UNSUBSCRIBE and PUBLISH commands implement the<a href="http://en.wikipedia.org/wiki/Publish/subscribe" target="_blank">Publish/Subscribe messaging paradigm</a> where (citing Wikipedia) senders (publishers) are not programmed to send their messages to specific receivers (subscribers). Rather, published messages are characterized into channels, without knowledge of what (if any) subscribers there may be. Subscribers express interest in one or more channels, and only receive messages that are of interest, without knowledge of what (if any) publishers there are. This decoupling of publishers and subscribers can allow for greater scalability and a more dynamic network topology.</blockquote>
|
||||
<blockquote>For instance in order to subscribe to the channels foo and bar the clientwill issue the SUBSCRIBE command followed by the names of the channels.</blockquote><pre class="codeblock python" name="code">
|
||||
SUBSCRIBE foo bar
|
||||
</pre>
|
||||
@@ -106,7 +106,6 @@ The Publish command is a bulk command where the first argument is the target cla
|
||||
Pieter Noordhuis provided a great example using Event-machine and Redis to create <a href="http://chat.redis-db.com" target="_blank">a multi user high performance web chat</a>, with source code included of course!
|
||||
<h2><a name="Client library implementations hints">Client library implementations hints</a></h2>
|
||||
Because all the messages received contain the original subscription causing the message delivery (the channel in the case of "message" type, and the original pattern in the case of "pmessage" type) clinet libraries may bind the original subscription to callbacks (that can be anonymous functions, blocks, function pointers, and so forth), using an hash table.<br/><br/>When a message is received an O(1) lookup can be done in order to deliver the message to the registered callback.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisCLI: Contents</b><br> <a href="#Redis CLI">Redis CLI</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisCLI</h1>
|
||||
|
||||
<div class="summary">
|
||||
Redis Command Line Interface
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="Redis CLI">Redis CLI</a></h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisPipelining: Contents</b><br> <a href="#Request/Response protocols and RTT">Request/Response protocols and RTT</a><br> <a href="#Redis Pipelining">Redis Pipelining</a><br> <a href="#Some benchmark">Some benchmark</a><br> <a href="#Pipelining VS other multi-commands">Pipelining VS other multi-commands</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisPipelining</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Request/Response protocols and RTT">Request/Response protocols and RTT</a></h1>
|
||||
Redis is a TCP server using the client-server model and what is called a <b>Request/Response</b> protocol.<br/><br/>This means that usually a request is accomplished with the following steps:
|
||||
<ul><li> The client sends a query to the server, and reads from the socket, usually in a blocking way, for the server response.</li><li> The server processes the command and sends the response back to the server.</li></ul>So for instance a four commands sequence is something like this:
|
||||
<ul><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 1</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 2</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 3</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 4</li></ul>Clients and Servers are connected via a networking link. Such a link can be very fast (a loopback interface) or very slow (a connection established over the internet with many hops between the two hosts). Whatever the network latency is, there is a time for the packets to travel from the client to the server, and back from the server to the client to carry the reply.<br/><br/>This time is called RTT (Round Trip Time). It is very easy to see how this can affect the performances when a client needs to perform many requests in a row (for instance adding many elements to the same list, or populating a database with many keys). For instance if the RTT time is 250 milliseconds (in the case of a very slow link over the internet), even if the server is able to process 100k requests per second, we'll be able to process at max four requests per second.<br/><br/>If the interface used is a loopback interface, the RTT is much shorter (for instance my host reports 0,044 milliseconds pinging 127.0.0.1), but it is still a lot if you need to perform many writes in a row.<br/><br/>Fortunately there is a way to improve this use cases.
|
||||
<h1><a name="Redis Pipelining">Redis Pipelining</a></h1>
|
||||
A Request/Response server can be implemented so that it is able to process new requests even if the client didn't already read the old responses. This way it is possible to send <b>multiple commands</b> to the server without waiting for the replies at all, and finally read the replies in a single step.<br/><br/>This is called pipelining, and is a technique widely in use since many decades. For instance many POP3 protocol implementations already supported this feature, dramatically speeding up the process of downloading new emails from the server.<br/><br/>Redis supports pipelining since the very early days, so whatever version you are running, you can use pipelining with Redis. This is an example using the raw netcat utility:
|
||||
<pre class="codeblock python" name="code">
|
||||
$ (echo -en "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
|
||||
+PONG
|
||||
+PONG
|
||||
+PONG
|
||||
</pre>
|
||||
This time we are not paying the cost of RTT for every call, but just one time for the three commands.<br/><br/>To be very explicit, with pipelining the order of operations of our very first example will be the following:
|
||||
<ul><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 1</li><li> <b>Server:</b> 2</li><li> <b>Server:</b> 3</li><li> <b>Server:</b> 4</li></ul><b>IMPORTANT NOTE</b>: while the client sends commands using pipelining, the server will be forced to queue the replies, using memory. So if you need to send many many commands with pipelining it's better to send this commands up to a given reasonable number, for instance 10k commands, read the replies, and send again other 10k commands and so forth. The speed will be nearly the same, but the additional memory used will be at max the amount needed to queue the replies for this 10k commands.
|
||||
<h1><a name="Some benchmark">Some benchmark</a></h1>
|
||||
In the following benchmark we'll use the Redis Ruby client, supporting pipelining, to test the speed improvement due to pipelining:
|
||||
<pre class="codeblock python python" name="code">
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
|
||||
def bench(descr)
|
||||
start = Time.now
|
||||
yield
|
||||
puts "#{descr} #{Time.now-start} seconds"
|
||||
end
|
||||
|
||||
def without_pipelining
|
||||
r = Redis.new
|
||||
10000.times {
|
||||
r.ping
|
||||
}
|
||||
end
|
||||
|
||||
def with_pipelining
|
||||
r = Redis.new
|
||||
r.pipelined {
|
||||
10000.times {
|
||||
r.ping
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
bench("without pipelining") {
|
||||
without_pipelining
|
||||
}
|
||||
bench("with pipelining") {
|
||||
with_pipelining
|
||||
}
|
||||
</pre>
|
||||
Running the above simple script will provide this figures in my Mac OS X system, running over the loopback interface, where pipelining will provide the smallest improvement as the RTT is already pretty low:
|
||||
<pre class="codeblock python python python" name="code">
|
||||
without pipelining 1.185238 seconds
|
||||
with pipelining 0.250783 seconds
|
||||
</pre>
|
||||
As you can see using pipelining we improved the transfer by a factor of five.
|
||||
<h1><a name="Pipelining VS other multi-commands">Pipelining VS other multi-commands</a></h1>
|
||||
Often we get requests about adding new commands performing multiple operations in a single pass.
|
||||
For instance there is no command to add multiple elements in a set. You need calling many times SADD.<br/><br/>With pipelining you can have performances near to an MSADD command, but at the same time we'll avoid bloating the Redis command set with too many commands. An additional advantage is that the version written using just SADD will be ready for a distributed environment (for instance Redis Cluster, that is in the process of being developed) just dropping the pipelining code.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisStatus: Contents</b><br> <a href="#Redis Status Page">Redis Status Page</a><br> <a href="#How stable are the alpha previews?">How stable are the alpha previews?</a><br> <a href="#How to obtain a 2.2-alpha preview">How to obtain a 2.2-alpha preview</a><br> <a href="#ETA for Redis 2.2?">ETA for Redis 2.2?</a><br> <a href="#When will we be able to see a working version of Redis Cluster?">When will we be able to see a working version of Redis Cluster?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisStatus</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Redis Status Page">Redis Status Page</a></h1>Hello! Redis uses versions composed of three numbers separated by a dot: <b>major</b>.<b>minor</b>.<b>patchlevel</b>.<br/><br/>When the <b>minor</b> is an odd number, it is used for an unstable release, so stable releases are for instance 1.2, 2.0, and so forth.<br/><br/>This is the status of the different Redis versions currently available:<br/><br/><ul><li> 1.2 is the <b>legacy redis stable release</b>, now it is completely obsoleted by Redis 2.0. Redis 2.0 is almost completely back compatible with 1.2 so upgrading is usually not a problem. Still 1.2 is believed to be a very stable release that works well, so if you are using it in production with code that probably will not modified to use more advanced Redis features available in 2.0, it makes sense to take 1.2 running. For everything new, it's better to start with 2.0.</li></ul>
|
||||
<ul><li> 2.0 is the current <b>stable release</b>. It is better than 1.2 in more or less everything: more features, more mature code, better replication, better persistence, and so forth. It is currently what most users should use, unless they really need features that are only available into an <b>unstable</b> release.</li></ul>
|
||||
<ul><li> 2.1 is the current <b>unstable release</b>, and there are no tar.gz for this release, you need to download it from git. <b>Warning:</b> the master branch in git may work most of the time but is NOT what you should use. What's better instead is to use the 2.2-alpha tags: every time Redis 2.1.x is stable enough and the new features merged passed all the tests for a couple of weeks, and we didn't received severe bug reports from users, we tag master as 2.2-alpha <i>number</i>, where <i>number</i> is simply a progressive number. Just pick this number.</li></ul>
|
||||
<h1><a name="How stable are the alpha previews?">How stable are the alpha previews?</a></h1>
|
||||
Well it is surely ok for development, but it is not recommended for production. Still there are many users that trust Redis development process so much to use alpha releases in production, but this is up to you, we don't give any guarantee ;)<h1><a name="How to obtain a 2.2-alpha preview">How to obtain a 2.2-alpha preview</a></h1>Simply using git:
|
||||
<pre class="codeblock python" name="code">
|
||||
$ git clone git://github.com/antirez/redis.git
|
||||
Initialized empty Git repository in /tmp/redis/.git/
|
||||
...
|
||||
</pre>
|
||||
Then you can list all the branches matching 2.1-alpha with:
|
||||
<pre class="codeblock python python" name="code">
|
||||
cd redis
|
||||
$ git tag | grep 2.2-alpha
|
||||
2.2-alpha0
|
||||
2.2-alpha1
|
||||
2.2-alpha2
|
||||
</pre>
|
||||
At this point you can just use <b>git checkout <i>tagname</i></b>, substituting <i>tagname</i> with 2.2-alphaX where X is the greater progressive number you see in the listing.<h1><a name="ETA for Redis 2.2?">ETA for Redis 2.2?</a></h1>
|
||||
Redis 2.2 is planned to enter the release candidate stage before the end of the 2010.<h1><a name="When will we be able to see a working version of Redis Cluster?">When will we be able to see a working version of Redis Cluster?</a></h1>
|
||||
I'm already working at it, I mean not just designing, but writing code. In three months we should have some kind of experimental version, while in six months we should have the first release candidate.<br/><br/>Probably the first <b>stable</b> release of Redis with working cluster will be called 3.0, but I'll try to merge it into 2.2 as an experimental support if we'll be sure there is no impact in the stability of the system when clustering is not used.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>Redis_2_0_0_Changelog: Contents</b><br> <a href="#Redis 2.0: What's new?">Redis 2.0: What's new?</a><br> <a href="#MULTI/EXEC">MULTI/EXEC</a><br> <a href="#Blocking pop">Blocking pop</a><br> <a href="#Publish/subscribe">Publish/subscribe</a><br> <a href="#Hashes">Hashes</a><br> <a href="#Virtual Memory">Virtual Memory</a><br> <a href="#Contributors">Contributors</a><br> <a href="#Special Thanks">Special Thanks</a><br> <a href="#DOWNLOAD">DOWNLOAD</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">Redis_2_0_0_Changelog</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Redis 2.0: What's new?">Redis 2.0: What's new?</a></h1>The release of Redis 2.0 marks a major milestone in Redis development. Apart from an endless list of new features, there are some major ones that deserve to be highlighted.<br/><br/>It's worth to mention that while Redis 2.0.0 just reached its first stable release, Redis 2.2.0 is near to reach feature freeze, so ... be prepared for new exiting things in very short time!<h3><a name="MULTI/EXEC">MULTI/EXEC</a></h3>
|
||||
The MULTI/EXEC family of commands were added to fulfill the need to execute multiple commands as a single atomic block.
|
||||
Because all commands inside a MULTI/EXEC block are serialized and executed sequentially, it is not possible that another
|
||||
client request is served in the middle of executing this block. All commands are executed one after the other when
|
||||
EXEC is called, which makes sure either <b>all</b> or <b>no</b> commands are executed, independent of the state of the client connection.<br/><br/>More on MULTI/EXEC:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/MultiExecCommand" target="_blank">http://code.google.com/p/redis/wiki/MultiExecCommand</a></li></ul>
|
||||
Note that WATCH, a CAS (check and set) variant of MULTI/EXEC will be available on 2.2.0 and is not part of 2.0.0.<h3><a name="Blocking pop">Blocking pop</a></h3>
|
||||
The commands BLPOP and BRPOP were added to support popping from a list in a blocking fashion. This means the client
|
||||
connection will be blocked for a certain amount of time until another client pushes an item on a list. These commands
|
||||
are frequently used in producer/consumer scenarios.<br/><br/>More on blocking pop:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/BlpopCommand" target="_blank">http://code.google.com/p/redis/wiki/BlpopCommand</a></li></ul>
|
||||
<h3><a name="Publish/subscribe">Publish/subscribe</a></h3>
|
||||
The family of publish/subscribe commands let clients publish messages onto channels and subscribe to receive all messages
|
||||
that are published on channels. Also included are commands to receive all messages for which the channel matches a given pattern.<br/><br/>More on publish/subscribe:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/PublishSubscribe" target="_blank">http://code.google.com/p/redis/wiki/PublishSubscribe</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-3-publish-submit.html" target="_blank">http://antirez.com/post/redis-weekly-update-3-publish-submit.html</a></li><li> <a href="http://rediscookbook.org/pubsub_for_asynchronous_communication.html" target="_blank">http://rediscookbook.org/pubsub_for_asynchronous_communication.html</a></li></ul>
|
||||
<h3><a name="Hashes">Hashes</a></h3>
|
||||
This new datatype allows to store multiple key/value pairs on a single key. Together with the list of regular commands you
|
||||
would expect for such a datatype (HSET, HGET, HDEL, HLEN, HKEYS, ...), it is also possible to use the values <i>inside</i> a hash
|
||||
for any SORT operation.<br/><br/>More on hashes:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/HsetCommand" target="_blank">http://code.google.com/p/redis/wiki/HsetCommand</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-1.html" target="_blank">http://antirez.com/post/redis-weekly-update-1.html</a></li></ul>
|
||||
<h3><a name="Virtual Memory">Virtual Memory</a></h3>
|
||||
Redis Virtual Memory allows users to grow their dataset beyond the limits of their RAM.<br/><br/>More on virtual memory:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide" target="_blank">http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide</a></li><li> <a href="http://antirez.com/post/redis-virtual-memory-story.html" target="_blank">http://antirez.com/post/redis-virtual-memory-story.html</a></li></ul>
|
||||
<h3><a name="Contributors">Contributors</a></h3><ul><li> Salvatore Sanfilippo</li><li> Pieter Noordhuis</li><li> Antonio Ognio</li><li> Alex McHale</li><li> Michel Martens</li><li> Damian Janowski</li><li> Bruno Deferrari</li><li> Ashley Martens</li><li> Derek Collison</li><li> Damian Janowski</li><li> Jeremy Zawodny</li><li> Konstantin Merenkov</li><li> Michel Martens</li><li> Sam Hendley</li></ul>
|
||||
<h3><a name="Special Thanks">Special Thanks</a></h3>
|
||||
Thanks to VMware sponsoring the work of Salvatore and Pieter, and the Redis community of users and client library developers. Redis 2.0.0 was possible only thanks to your support.<h3><a name="DOWNLOAD">DOWNLOAD</a></h3>
|
||||
You can grab Redis 2.0.0 from <a href="http://code.google.com/p/redis/downloads/list" target="_blank">Google Code</a>.<br/><br/>It is also tagged on Git.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>Redis_2_0_Whats_new: Contents</b><br> <a href="#Redis 2.0: What's new?">Redis 2.0: What's new?</a><br> <a href="#MULTI/EXEC">MULTI/EXEC</a><br> <a href="#Blocking pop">Blocking pop</a><br> <a href="#Publish/subscribe">Publish/subscribe</a><br> <a href="#Hashes">Hashes</a><br> <a href="#Virtual Memory">Virtual Memory</a><br> <a href="#Contributors">Contributors</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">Redis_2_0_Whats_new</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Redis 2.0: What's new?">Redis 2.0: What's new?</a></h1>The release of Redis 2.0 marks a major milestone in Redis development. Apart from an endless list of new features, there are some major ones that deserve to be highlighted.<h3><a name="MULTI/EXEC">MULTI/EXEC</a></h3>
|
||||
The MULTI/EXEC family of commands were added to fulfill the need to execute multiple commands as a single atomic block.
|
||||
Because all commands inside a MULTI/EXEC block are serialized and executed sequentially, it is not possible that another
|
||||
client request is served in the middle of executing this block. All commands are executed one after the other when
|
||||
EXEC is called, which makes sure either <b>all</b> or <b>no</b> commands are executed, independent of the state of the client connection.<br/><br/>More on MULTI/EXEC:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/MultiExecCommand" target="_blank">http://code.google.com/p/redis/wiki/MultiExecCommand</a></li></ul>
|
||||
<h3><a name="Blocking pop">Blocking pop</a></h3>
|
||||
The commands BLPOP and BRPOP were added to support popping from a list in a blocking fashion. This means the client
|
||||
connection will be blocked for a certain amount of time until another client pushes an item on a list. These commands
|
||||
are frequently used in producer/consumer scenarios.<br/><br/>More on blocking pop:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/BlpopCommand" target="_blank">http://code.google.com/p/redis/wiki/BlpopCommand</a></li></ul>
|
||||
<h3><a name="Publish/subscribe">Publish/subscribe</a></h3>
|
||||
The family of publish/subscribe commands let clients publish messages onto channels and subscribe to receive all messages
|
||||
that are published on channels. Also included are commands to receive all messages for which the channel matches a given pattern.<br/><br/>More on publish/subscribe:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/PublishSubscribe" target="_blank">http://code.google.com/p/redis/wiki/PublishSubscribe</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-3-publish-submit.html" target="_blank">http://antirez.com/post/redis-weekly-update-3-publish-submit.html</a></li><li> <a href="http://rediscookbook.org/pubsub_for_asynchronous_communication.html" target="_blank">http://rediscookbook.org/pubsub_for_asynchronous_communication.html</a></li></ul>
|
||||
<h3><a name="Hashes">Hashes</a></h3>
|
||||
This new datatype allows to store multiple key/value pairs on a single key. Together with the list of regular commands you
|
||||
would expect for such a datatype (HSET, HGET, HDEL, HLEN, HKEYS, ...), it is also possible to use the values <i>inside</i> a hash
|
||||
for any SORT operation.<br/><br/>More on hashes:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/HsetCommand" target="_blank">http://code.google.com/p/redis/wiki/HsetCommand</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-1.html" target="_blank">http://antirez.com/post/redis-weekly-update-1.html</a></li></ul>
|
||||
<h3><a name="Virtual Memory">Virtual Memory</a></h3>
|
||||
Redis Virtual Memory allows users to grow their dataset beyond the limits of their RAM.<br/><br/>More on virtual memory:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide" target="_blank">http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide</a></li><li> <a href="http://antirez.com/post/redis-virtual-memory-story.html" target="_blank">http://antirez.com/post/redis-virtual-memory-story.html</a></li></ul>
|
||||
<h2><a name="Contributors">Contributors</a></h2><ul><li> Salvatore Sanfilippo</li><li> Pieter Noordhuis</li><li> Antonio Ognio</li><li> Alex McHale</li><li> Michel Martens</li><li> Damian Janowski</li><li> Bruno Deferrari</li><li> Ashley Martens</li><li> Derek Collison</li><li> Damian Janowski</li><li> Jeremy Zawodny</li><li> Konstantin Merenkov</li><li> Michel Martens</li><li> Sam Hendley</li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>SetbitCommand: Contents</b><br> <a href="#SETBIT _key_ _offset_ _value_ (Redis >">SETBIT _key_ _offset_ _value_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">SetbitCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="SETBIT _key_ _offset_ _value_ (Redis >">SETBIT _key_ _offset_ _value_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Sets or clears the bit at <i>offset</i> in the string value stored at <i>key</i>.</blockquote>
|
||||
The bit is either set or cleared depending on <i>value</i>, which can be either 0 or 1. When <i>key</i> does not exist, a new string value is created. The string is grown to make sure it can hold a bit at <i>offset</i>. The <i>offset</i> argument is required to be greater than or equal to 0, and is limited to 2<sup>32-1 (which limits bitmaps to 512MB).
|
||||
When the string at <i>key</i> is grown, added bits are set to 0.<br/><br/><b>Warning</b>: When setting the last possible bit (<i>offset</i> equal to 2</sup>32-1) and the string value stored at <i>key</i> does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time.
|
||||
On a 2010 Macbook Pro, setting bit number 2<sup>32-1 (512MB allocation) takes ~300ms,
|
||||
setting bit number 2</sup>30-1 (128MB allocation) takes ~80ms,
|
||||
setting bit number 2<sup>28-1 (32MB allocation) takes ~30ms and
|
||||
setting bit number 2</sup>26-1 (8MB allocation) takes ~8ms.
|
||||
Note that once this first allocation is done, subsequent calls to SETBIT for the same <i>key</i> will not have the allocation overhead.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the original bit value stored at <i>offset</i>.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>SetrangeCommand: Contents</b><br> <a href="#SETRANGE _key_ _offset_ _value_ (Redis >">SETRANGE _key_ _offset_ _value_ (Redis ></a><br> <a href="#Examples">Examples</a><br> <a href="#Patterns">Patterns</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">SetrangeCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="SETRANGE _key_ _offset_ _value_ (Redis >">SETRANGE _key_ _offset_ _value_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1) not counting the time taken to copy the new string in place, as usually this string is small so the amoritzed time is O(1). Otheriwse O(M) with M being the length of the value argument</i><blockquote>Overwrites part of a string at <i>key</i> starting at the specified offset,for all the length of <i>value</i>.If the offset is over the old length of the string, the string is paddedwith zero bytes until needed. Non existing keys are considered likealready containing an empty string.</blockquote>
|
||||
<h2><a name="Examples">Examples</a></h2>First example, basic usage setting a range.<br/><br/><pre class="codeblock python" name="code">
|
||||
redis> set foo "Hello World"
|
||||
OK
|
||||
redis> setrange foo 6 "Redis"
|
||||
(integer) 11
|
||||
redis> get foo
|
||||
"Hello Redis"
|
||||
</pre>Example of the zero padding behavior.<br/><br/><pre class="codeblock python python" name="code">
|
||||
redis> del foo
|
||||
(integer) 1
|
||||
redis> setrange foo 10 bar
|
||||
(integer) 13
|
||||
redis> get foo
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bar"
|
||||
</pre>Note that the maximum offset that you can set is 536870911 as Redis Strings are limited to 512 megabytes. You can still create longer arrays of values using multiple keys.<br/><br/><b>Warning</b>: When setting the last possible byte and the string value stored at <i>key</i> does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time.
|
||||
On a 2010 Macbook Pro, setting byte number 536870911 (512MB allocation) takes ~300ms,
|
||||
setting byte number 134217728 (128MB allocation) takes ~80ms,
|
||||
setting bit number 33554432 (32MB allocation) takes ~30ms and
|
||||
setting bit number 8388608 (8MB allocation) takes ~8ms.
|
||||
Note that once this first allocation is done, subsequent calls to SETRANGE for the same <i>key</i> will not have the allocation overhead.<h2><a name="Patterns">Patterns</a></h2>Thanks to SETRANGE and the analogous GETRANGE command you can use Redis strings as a linear array of memory with O(1) random access. This is a very fast and efficient storage in many real world use cases.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the length of the string after it was modified by the command.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== String Commands ==<br/><br/><ul><li> <a href="SetCommand.html">SET</a></li><li> <a href="GetCommand.html">GET</a></li><li> <a href="GetsetCommand.html">GETSET</a></li><li> <a href="MgetCommand.html">MGET</a></li><li> <a href="SetnxCommand.html">SETNX</a></li><li> <a href="SetexCommand.html">SETEX</a></li><li> <a href="MsetCommand.html">MSET</a></li><li> <a href="MsetCommand.html">MSETNX</a></li><li> <a href="IncrCommand.html">INCR</a></li><li> <a href="IncrCommand.html">INCRBY</a></li><li> <a href="IncrCommand.html">DECR</a></li><li> <a href="IncrCommand.html">DECRBY</a></li><li> <a href="AppendCommand.html">APPEND</a></li><li> <a href="SubstrCommand.html">SUBSTR</a></li></ul>
|
||||
== String Commands ==<br/><br/><ul><li> <a href="SetCommand.html">SET</a></li><li> <a href="GetCommand.html">GET</a></li><li> <a href="StrlenCommand.html">STRLEN</a></li><li> <a href="GetsetCommand.html">GETSET</a></li><li> <a href="SetnxCommand.html">SETNX</a></li><li> <a href="SetexCommand.html">SETEX</a></li><li> <a href="SetbitCommand.html">SETBIT</a></li><li> <a href="GetbitCommand.html">GETBIT</a></li><li> <a href="MsetCommand.html">MSET</a></li><li> <a href="MsetCommand.html">MSETNX</a></li><li> <a href="MgetCommand.html">MGET</a></li><li> <a href="IncrCommand.html">INCR</a></li><li> <a href="IncrCommand.html">INCRBY</a></li><li> <a href="IncrCommand.html">DECR</a></li><li> <a href="IncrCommand.html">DECRBY</a></li><li> <a href="AppendCommand.html">APPEND</a></li><li> <a href="SetrangeCommand.html">SETRANGE</a></li><li> <a href="SubstrCommand.html">GETRANGE (or SUBSTR)</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="Redis String Type">Redis String Type</a></h1>Strings are the most basic Redis kind of values. Redis Strings are binary safe, this means a Redis string can contain any kind of data, for instance a JPEG image or a serialized Ruby object, and so forth.<br/><br/>A String value can be at max 1 Gigabyte in length.<br/><br/>Strings are treated as integer values by the <a href="IncrCommand.html">INCR</a> commands family, in this respect the value of an intger is limited to a singed 64 bit value.<br/><br/>Note that the single elements contained in Redis <a href="Lists.html">Lists</a>, <a href="Sets.html">Sets</a> and <a href="SortedSets.html">Sorted Sets</a>, are Redis Strings.<h1><a name="Implementation details">Implementation details</a></h1>Strings are implemented using a dynamic strings library called <code name="code" class="python">sds.c</code> (simple dynamic strings). This library caches the current length of the string, so to obtain the length of a Redis string is an O(1) operation (but currently there is no such STRLEN command. It will likely be added later).<br/><br/>Redis strings are incapsualted into Redis Objects. Redis Objects use a reference counting memory management system, so a single Redis String can be shared in different places of the dataset. This means that if you happen to use the same strings many times (especially if you have <i>object sharing</i> turned on in the configuration file) Redis will try to use the same string object instead to allocate one new every time.<br/><br/>Starting from version 1.1 Redis is also able to encode in a special way strings that are actually just numbers. Instead to save the string as an array of characters Redis will save the integer value in order to use less memory. With many datasets this can reduce the memory usage of about 30% compared to Redis 1.0.
|
||||
#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="Redis String Type">Redis String Type</a></h1>Strings are the most basic Redis kind of values. Redis Strings are binary safe, this means a Redis string can contain any kind of data, for instance a JPEG image or a serialized Ruby object, and so forth.<br/><br/>A String value can be at max 512 Megabytes in length.<br/><br/>Strings are treated as integer values by the <a href="IncrCommand.html">INCR</a> commands family, in this respect the value of an intger is limited to a singed 64 bit value.<br/><br/>Note that the single elements contained in Redis <a href="Lists.html">Lists</a>, <a href="Sets.html">Sets</a> and <a href="SortedSets.html">Sorted Sets</a>, are Redis Strings.<h1><a name="Implementation details">Implementation details</a></h1>Strings are implemented using a dynamic strings library called <code name="code" class="python">sds.c</code> (simple dynamic strings). This library caches the current length of the string, so to obtain the length of a Redis string is an O(1) operation (but currently there is no such STRLEN command. It will likely be added later).<br/><br/>Redis strings are incapsualted into Redis Objects. Redis Objects use a reference counting memory management system, so a single Redis String can be shared in different places of the dataset. This means that if you happen to use the same strings many times (especially if you have <i>object sharing</i> turned on in the configuration file) Redis will try to use the same string object instead to allocate one new every time.<br/><br/>Starting from version 1.1 Redis is also able to encode in a special way strings that are actually just numbers. Instead to save the string as an array of characters Redis will save the integer value in order to use less memory. With many datasets this can reduce the memory usage of about 30% compared to Redis 1.0.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>StrlenCommand: Contents</b><br> <a href="#STRLEN _key_ (Redis >">STRLEN _key_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">StrlenCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="STRLEN _key_ (Redis >">STRLEN _key_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Returns the length of the string stored at the specified <i>key</i>.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the length of the string.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>SubstrCommand: Contents</b><br> <a href="#SUBSTR _key_ _start_ _end_">SUBSTR _key_ _start_ _end_</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
|
||||
<b>SubstrCommand: Contents</b><br> <a href="#SUBSTR _key_ _start_ _end_">SUBSTR _key_ _start_ _end_</a><br> <a href="#GETRANGE _key_ _start_ _end_">GETRANGE _key_ _start_ _end_</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">SubstrCommand</h1>
|
||||
@@ -27,7 +27,8 @@
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="SUBSTR _key_ _start_ _end_">SUBSTR _key_ _start_ _end_</a></h1>
|
||||
<i>Time complexity: O(start+n) (with start being the start index and n the total length of the requested range). Note that the lookup part of this command is O(1) so for small strings this is actually an O(1) command.</i><blockquote>Return a subset of the string from offset <i>start</i> to offset <i>end</i>(both offsets are inclusive).Negative offsets can be used in order to provide an offset starting fromthe end of the string. So -1 means the last char, -2 the penultimate andso forth.</blockquote>
|
||||
<h1><a name="GETRANGE _key_ _start_ _end_">GETRANGE _key_ _start_ _end_</a></h1>
|
||||
<i>Time complexity: O(start+n) (with start being the start index and n the total length of the requested range). Note that the lookup part of this command is O(1) so for small strings this is actually an O(1) command.</i><b>Warning:</b> this command was renamed into GETRANGE. SUBSTR will be taken as an alias until the next major release of Redis.<br/><br/><blockquote>Return a subset of the string from offset <i>start</i> to offset <i>end</i>(both offsets are inclusive).Negative offsets can be used in order to provide an offset starting fromthe end of the string. So -1 means the last char, -2 the penultimate andso forth.</blockquote>
|
||||
<blockquote>The function handles out of range requests without raising an error, butjust limiting the resulting range to the actual length of the string.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a><h2><a name="Examples">Examples</a></h2><pre class="codeblock python" name="code">
|
||||
redis> set s "This is a string"
|
||||
|
||||
@@ -39,7 +39,7 @@ vm-max-threads 4
|
||||
</pre>This is the maximum number of threads used in order to perform I/O from/to the swap file. A good value is just to match the number of cores in your system.<br/><br/>However the special value of "0" will enable blocking VM. When VM is configured to be blocking it performs the I/O in a synchronous blocking way. This is what you can expect from blocking VM:
|
||||
<ul><li> Clients accessing swapped out keys will block other clients while reading from disk, so the latency experimented by clients can be larger, especially if the disk is slow or busy and/or if there are big values swapped on disk.</li><li> The blocking VM performances are <b>overall</b> better, as there is no time lost in synchronization, spawning of threads, resuming blocked clients waiting for values.</li></ul>So if you are willing to accept an higher latency from time to time, blocking VM can be a good pick, especially if swapping happens rarely as most of your often accessed data happens to fit in your memory.<br/><br/>If instead you have a lot of swap in and swap out operations and you have many cores that you want to exploit, and in general when you don't want that clients dealing with swapped values will block other clients for a few milliseconds (or more if the swapped value is very big), then it's better to use threaded VM.<br/><br/>To experiment with your dataset and different configurations is warmly encouraged...<h1><a name="Random things to know">Random things to know</a></h1>
|
||||
<h2><a name="A good place for the swap file">A good place for the swap file</a></h2>In many configurations the swap file can be fairly large, even 40GB or more.
|
||||
Not all the kind of file systems are able to deal with large files in a good way, especially Mac OS X file system tends to be really lame about it.<br/><br/>The suggestion is to use Linux ext3 file system, or any other file system with good support for <b>sparse files</b>. What are sparse files?<br/><br/>Sparse files are files where a lot of the content happen to be empty. Advanced file systems like ext2, ext3, ext4, RaiserFS, Raiser4, and many others, are able to encode this files in a more efficient way and will allocate more space for the file when needed, that is, when more actual blocks of the file will be used.<br/><br/>The swap file is obviously pretty sparse, especially if the server is running since little time or it is much bigger compared to the amount of data swapped out. A file system not supporting sparse files can at some point block the Redis process while creating a very big file at once.<br/><br/>For a list of file systems supporting spare files <a href="http://en.wikipedia.org/wiki/Comparison_of_file_systems" target="_blank">check this Wikipedia page comparing different files systems</a>.<h2><a name="Monitoring the VM">Monitoring the VM</a></h2>Once you have a Redis system with VM enabled up and running, you may be very interested in knowing how it's working: how many objects are swapped in total, the number of objects swapped and loaded every second, and so forth.<br/><br/>There is an utility that is very handy in checking how the VM is working, that is part of <a href="http://code.google.com/p/redis-tools" target="_blank">Redis Tools</a>. This tool is called redis-stat, and using it is pretty straightforward:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
Not all the kind of file systems are able to deal with large files in a good way, especially Mac OS X file system tends to be really lame about it.<br/><br/>The suggestion is to use Linux ext3 file system, or any other file system with good support for <b>sparse files</b>. What are sparse files?<br/><br/>Sparse files are files where a lot of the content happen to be empty. Advanced file systems like ext2, ext3, ext4, RaiserFS, Raiser4, and many others, are able to encode this files in a more efficient way and will allocate more space for the file when needed, that is, when more actual blocks of the file will be used.<br/><br/>The swap file is obviously pretty sparse, especially if the server is running since little time or it is much bigger compared to the amount of data swapped out. A file system not supporting sparse files can at some point block the Redis process while creating a very big file at once.<br/><br/>For a list of file systems supporting spare files <a href="http://en.wikipedia.org/wiki/Comparison_of_file_systems" target="_blank">check this Wikipedia page comparing different files systems</a>.<h2><a name="Monitoring the VM">Monitoring the VM</a></h2>Once you have a Redis system with VM enabled up and running, you may be very interested in knowing how it's working: how many objects are swapped in total, the number of objects swapped and loaded every second, and so forth.<br/><br/>There is an utility that is very handy in checking how the VM is working, that is part of <a href="http://github.com/antirez/redis-tools" target="_blank">Redis Tools</a>. This tool is called redis-stat, and using it is pretty straightforward:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
$ ./redis-stat vmstat
|
||||
--------------- objects --------------- ------ pages ------ ----- memory -----
|
||||
load-in swap-out swapped delta used delta used delta
|
||||
@@ -57,7 +57,6 @@ $ ./redis-stat vmstat
|
||||
10087 18784 886771 -1574 894577 -1828 200.36M +91.60K
|
||||
9330 19350 887411 +640 894817 +240 200.17M -189.72K
|
||||
</pre>The above output is about a redis-server with VM enable, around 1 million of keys inside, and a lot of simulated load using the redis-load utility.<br/><br/>As you can see from the output a number of load-in and swap-out operations are happening every second. Note that the first line reports the actual values since the server was started, while the next lines are differences compared to the previous reading.<br/><br/>If you assigned enough memory to hold your working set of data, probably you should see a lot less dramatic swapping happening, so redis-stat can be a really valuable tool in order to understand if you need to shop for RAM ;)<h2><a name="Redis with VM enabled: better .rdb files or Append Only File?">Redis with VM enabled: better .rdb files or Append Only File?</a></h2>When VM is enabled, saving and loading the database are <b>much slower</b> operations. A DB that usually loads in 2 seconds takes 13 seconds with VM enabled if the server is configured to use the smallest memory possible (that is, vm-max-memory set to 0).<br/><br/>So you probably want to switch to a configuration using the Append Only File for persistence, so that you can perform the BGREWRITEAOF from time to time.<br/><br/>It is important to note that while a BGSAVE or BGREWRITEAOF is in progress Redis does <b>not</b> swap new values on disk. The VM will be read-only while there is another child accessing it. So if you have a lot of writes while there is a child working, the memory usage may grow.<h2><a name="Using as little memory as possible">Using as little memory as possible</a></h2>An interesting setup to turn Redis into an on-disk DB with just keys in memory is setting vm-max-memory to 0. If you don't mind some latency more and poorer performances but want to use very little memory for very big values, this is a good setup.<br/><br/>In this setup you should first try setting the VM as blocking (vm-max-threads 0) as with this configuration and high traffic the number of swap in and swap out operations will be huge, and threading will consume a lot of resources compared to a simple blocking implementation.<h2><a name="VM Stability">VM Stability</a></h2>VM is still experimental code, but in the latest weeks it was tested in many ways in development environments, and even in some production environment. No bugs were noticed during this testing period. Still the more obscure bugs may happen in non controlled environments where there are setups that we are not able to reproduce for some reason.<br/><br/>In this stage you are encouraged to try VM in your development environment, and even in production if your DB is not mission critical, but for instance just a big persistent cache of data that may go away without too much problems.<br/><br/>Please report any problem you will notice to the Redis Google Group or by IRC joining the #redis IRC channel on freenode.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
<h1><a name="ZRANK _key_ _member_ (Redis >">ZRANK _key_ _member_ (Redis ></a></h1> 1.3.4) =
|
||||
<h1><a name="ZREVRANK _key_ _member_ (Redis >">ZREVRANK _key_ _member_ (Redis ></a></h1> 1.3.4) =
|
||||
<i>Time complexity: O(log(N))</i><blockquote>ZRANK returns the rank of the member in the sorted set, with scores ordered from low to high. ZREVRANK returns the rank with scores ordered from high to low. When the given member does not exist in the sorted set, the special value 'nil' is returned. The returned rank (or index) of the member is 0-based for both commands.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
|
||||
the rank (an integer number) represented as an string.
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a> or a nil <a href="ReplyTypes.html">bulk reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
|
||||
the rank of the element as an integer reply if the element exists.
|
||||
A nil bulk reply if there is no such element.
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
+8
-6
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>index: Contents</b><br> <a href="#HOWTOs about selected features">HOWTOs about selected features</a><br> <a href="#Hacking">Hacking</a><br> <a href="#Videos">Videos</a>
|
||||
<b>index: Contents</b><br> <a href="#Getting started">Getting started</a><br> <a href="#Full programming examples">Full programming examples</a><br> <a href="#FAQs and benchmarks">FAQs and benchmarks</a><br> <a href="#HOWTOs about selected features">HOWTOs about selected features</a><br> <a href="#Hacking">Hacking</a><br> <a href="#Videos">Videos</a><br> <a href="#Recipes and books">Recipes and books</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">index</h1>
|
||||
@@ -26,12 +26,14 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tutorial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
|
||||
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python"><-></code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virtual Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
|
||||
= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<h1><a name="Getting started">Getting started</a></h1><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is a good starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="http://blog.mjrusso.com/2010/10/17/redis-from-the-ground-up.html" target="_blank">Redis from the ground up</a> is an impressive article to know more about Redis if you are a newcomer.</li><li> <a href="http://playnice.ly/blog/2010/10/19/getting-started-redis-python/" target="_blank">Getting started with Redis and Python</a></li></ul>
|
||||
<h1><a name="Full programming examples">Full programming examples</a></h1><ul><li> <a href="TwitterAlikeExample.html">This is a tutorial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li></ul>
|
||||
<h1><a name="FAQs and benchmarks">FAQs and benchmarks</a></h1><ul><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis. Not very up-to-date.</li></ul>
|
||||
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python"><-></code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virtual Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li><li> <a href="RedisPipelining.html">Redis Pipelining Guide</a>.</li><li> Memory optimization: Full of keys, a <a href="http://antirez.com/post/redis-weekly-update-7.html" target="_blank">blog post at antirez.com</a> showing how to use Redis 2.0 and hashes to create a setup where you can store 5 times more data in your Redis instance.</li><li> <a href="http://antirez.com/post/autocomplete-with-redis.html" target="_blank">Implementing auto complete with Redis</a>.</li></ul>
|
||||
<h1><a name="Hacking">Hacking</a></h1>
|
||||
<ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li></ul>
|
||||
<ul><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul>
|
||||
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li><li> <a href="http://www.ustream.tv/recorded/7855635" target="_blank">Salvatore Sanfilippo and Pieter Noordhuis at the SF Redis Meetup</a></li></ul>
|
||||
<ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul>
|
||||
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://confreaks.net/videos/62-mwrc2009-redis-key-value-nirvana" target="_blank">Ezra Zygmuntowicz at the Mountain West Ruby Conference 2009</a> to know the most important Redis ideas in few minutes.</li><li> <a href="http://www.ustream.tv/recorded/7855635" target="_blank">Salvatore Sanfilippo and Pieter Noordhuis at the SF Redis Meetup</a></li></ul>
|
||||
<h1><a name="Recipes and books">Recipes and books</a></h1><ul><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe.</li><li> There is an ongoing effort to write a Redis book for O'Reilly</li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 2.7 KiB |
+23
@@ -50,6 +50,16 @@ loglevel verbose
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile stdout
|
||||
|
||||
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
|
||||
# and optionally update the other syslog parameters to suit your needs.
|
||||
# syslog-enabled no
|
||||
|
||||
# Specify the syslog identity.
|
||||
# syslog-ident redis
|
||||
|
||||
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
|
||||
# syslog-facility local0
|
||||
|
||||
# Set the number of databases. The default database is DB 0, you can select
|
||||
# a different one on a per-connection basis using SELECT <dbid> where
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
@@ -367,6 +377,19 @@ glueoutputbuf yes
|
||||
hash-max-zipmap-entries 64
|
||||
hash-max-zipmap-value 512
|
||||
|
||||
# Similarly to hashes, small lists are also encoded in a special way in order
|
||||
# to save a lot of space. The special representation is only used when
|
||||
# you are under the following limits:
|
||||
list-max-ziplist-entries 512
|
||||
list-max-ziplist-value 64
|
||||
|
||||
# Sets have a special encoding in just one case: when a set is composed
|
||||
# of just strings that happens to be integers in radix 10 in the range
|
||||
# of 64 bit signed integers.
|
||||
# The following configuration setting sets the limit in the size of the
|
||||
# set in order to use this special memory saving encoding.
|
||||
set-max-intset-entries 512
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation redis uses (see dict.c)
|
||||
|
||||
+16
-16
@@ -37,6 +37,9 @@ CHECKDUMPPRGNAME = redis-check-dump
|
||||
CHECKAOFPRGNAME = redis-check-aof
|
||||
|
||||
all: redis-benchmark redis-cli redis-check-dump redis-check-aof redis-server
|
||||
@echo ""
|
||||
@echo "Hint: To run 'make test' is a good idea ;)"
|
||||
@echo ""
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
adlist.o: adlist.c adlist.h zmalloc.h
|
||||
@@ -102,22 +105,21 @@ ziplist.o: ziplist.c zmalloc.h ziplist.h
|
||||
zipmap.o: zipmap.c zmalloc.h
|
||||
zmalloc.o: zmalloc.c config.h
|
||||
|
||||
dependencies:
|
||||
cd ../deps/hiredis && $(MAKE) static ARCH="$(ARCH)"
|
||||
cd ../deps/linenoise && $(MAKE) ARCH="$(ARCH)"
|
||||
|
||||
redis-server: $(OBJ)
|
||||
$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
|
||||
@echo ""
|
||||
@echo "Hint: To run 'make test' is a good idea ;)"
|
||||
@echo ""
|
||||
|
||||
redis-benchmark: $(BENCHOBJ)
|
||||
cd ../deps/hiredis && make static
|
||||
redis-benchmark: dependencies $(BENCHOBJ)
|
||||
cd ../deps/hiredis && $(MAKE) static
|
||||
$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ) ../deps/hiredis/libhiredis.a
|
||||
|
||||
redis-benchmark.o:
|
||||
$(CC) -c $(CFLAGS) -I../deps/hiredis $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
redis-cli: $(CLIOBJ)
|
||||
cd ../deps/hiredis && make static ARCH="$(ARCH)"
|
||||
cd ../deps/linenoise && make ARCH="$(ARCH)"
|
||||
redis-cli: dependencies $(CLIOBJ)
|
||||
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ) ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o
|
||||
|
||||
redis-cli.o:
|
||||
@@ -134,13 +136,11 @@ redis-check-aof: $(CHECKAOFOBJ)
|
||||
|
||||
clean:
|
||||
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) $(CHECKAOFPRGNAME) *.o *.gcda *.gcno *.gcov
|
||||
cd ../deps/hiredis && make clean
|
||||
cd ../deps/linenoise && make clean
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
|
||||
test:
|
||||
test: redis-server
|
||||
(cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}")
|
||||
|
||||
bench:
|
||||
@@ -153,19 +153,19 @@ log:
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
make ARCH="-m32"
|
||||
$(MAKE) ARCH="-m32"
|
||||
|
||||
gprof:
|
||||
make PROF="-pg"
|
||||
$(MAKE) PROF="-pg"
|
||||
|
||||
gcov:
|
||||
make PROF="-fprofile-arcs -ftest-coverage"
|
||||
$(MAKE) PROF="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
noopt:
|
||||
make OPTIMIZATION=""
|
||||
$(MAKE) OPTIMIZATION=""
|
||||
|
||||
32bitgprof:
|
||||
make PROF="-pg" ARCH="-arch i386"
|
||||
$(MAKE) PROF="-pg" ARCH="-arch i386"
|
||||
|
||||
install: all
|
||||
mkdir -p $(INSTALL_BIN)
|
||||
|
||||
@@ -114,6 +114,42 @@ void loadServerConfig(char *filename) {
|
||||
}
|
||||
fclose(logfp);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
|
||||
if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"syslog-ident") && argc == 2) {
|
||||
if (server.syslog_ident) zfree(server.syslog_ident);
|
||||
server.syslog_ident = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) {
|
||||
struct {
|
||||
const char *name;
|
||||
const int value;
|
||||
} validSyslogFacilities[] = {
|
||||
{"user", LOG_USER},
|
||||
{"local0", LOG_LOCAL0},
|
||||
{"local1", LOG_LOCAL1},
|
||||
{"local2", LOG_LOCAL2},
|
||||
{"local3", LOG_LOCAL3},
|
||||
{"local4", LOG_LOCAL4},
|
||||
{"local5", LOG_LOCAL5},
|
||||
{"local6", LOG_LOCAL6},
|
||||
{"local7", LOG_LOCAL7},
|
||||
{NULL, 0}
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; validSyslogFacilities[i].name; i++) {
|
||||
if (!strcasecmp(validSyslogFacilities[i].name, argv[1])) {
|
||||
server.syslog_facility = validSyslogFacilities[i].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!validSyslogFacilities[i].name) {
|
||||
err = "Invalid log facility. Must be one of USER or between LOCAL0-LOCAL7";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"databases") && argc == 2) {
|
||||
server.dbnum = atoi(argv[1]);
|
||||
if (server.dbnum < 1) {
|
||||
|
||||
+4
-4
@@ -121,7 +121,7 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
} else if (o->type == REDIS_SET) {
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
robj *ele;
|
||||
while((ele = setTypeNext(si)) != NULL) {
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
xorObjectDigest(digest,ele);
|
||||
decrRefCount(ele);
|
||||
}
|
||||
@@ -152,10 +152,10 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
unsigned char eledigest[20];
|
||||
|
||||
memset(eledigest,0,20);
|
||||
obj = hashTypeCurrent(hi,REDIS_HASH_KEY);
|
||||
obj = hashTypeCurrentObject(hi,REDIS_HASH_KEY);
|
||||
mixObjectDigest(eledigest,obj);
|
||||
decrRefCount(obj);
|
||||
obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
|
||||
obj = hashTypeCurrentObject(hi,REDIS_HASH_VALUE);
|
||||
mixObjectDigest(eledigest,obj);
|
||||
decrRefCount(obj);
|
||||
xorDigest(digest,eledigest,20);
|
||||
@@ -216,7 +216,7 @@ void debugCommand(redisClient *c) {
|
||||
"encoding:%s serializedlength:%lld "
|
||||
"lru:%d lru_seconds_idle:%lu",
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val,NULL),
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
} else {
|
||||
vmpointer *vp = (vmpointer*) val;
|
||||
|
||||
+638
@@ -0,0 +1,638 @@
|
||||
/* Automatically generated by generate-command-help.rb, do not edit. */
|
||||
|
||||
#ifndef __REDIS_HELP_H
|
||||
#define __REDIS_HELP_H
|
||||
|
||||
static char *commandGroups[] = {
|
||||
"generic",
|
||||
"string",
|
||||
"list",
|
||||
"set",
|
||||
"sorted_set",
|
||||
"hash",
|
||||
"pubsub",
|
||||
"transactions",
|
||||
"connection",
|
||||
"server"
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
char *name;
|
||||
char *params;
|
||||
char *summary;
|
||||
int group;
|
||||
char *since;
|
||||
} commandHelp[] = {
|
||||
{ "APPEND",
|
||||
"key value",
|
||||
"Append a value to a key",
|
||||
1,
|
||||
"1.3.3" },
|
||||
{ "AUTH",
|
||||
"password",
|
||||
"Authenticate to the server",
|
||||
8,
|
||||
"0.08" },
|
||||
{ "BGREWRITEAOF",
|
||||
"-",
|
||||
"Asynchronously rewrite the append-only file",
|
||||
9,
|
||||
"1.07" },
|
||||
{ "BGSAVE",
|
||||
"-",
|
||||
"Asynchronously save the dataset to disk",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "BLPOP",
|
||||
"key [key ...] timeout",
|
||||
"Remove and get the first element in a list, or block until one is available",
|
||||
2,
|
||||
"1.3.1" },
|
||||
{ "BRPOP",
|
||||
"key [key ...] timeout",
|
||||
"Remove and get the last element in a list, or block until one is available",
|
||||
2,
|
||||
"1.3.1" },
|
||||
{ "BRPOPLPUSH",
|
||||
"source destination timeout",
|
||||
"Pop a value from a list, push it to another list and return it; or block until one is available",
|
||||
2,
|
||||
"2.1.7" },
|
||||
{ "CONFIG GET",
|
||||
"parameter",
|
||||
"Get the value of a configuration parameter",
|
||||
9,
|
||||
"2.0" },
|
||||
{ "CONFIG RESETSTAT",
|
||||
"-",
|
||||
"Reset the stats returned by INFO",
|
||||
9,
|
||||
"2.0" },
|
||||
{ "CONFIG SET",
|
||||
"parameter value",
|
||||
"Set a configuration parameter to the given value",
|
||||
9,
|
||||
"2.0" },
|
||||
{ "DBSIZE",
|
||||
"-",
|
||||
"Return the number of keys in the selected database",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "DEBUG OBJECT",
|
||||
"key",
|
||||
"Get debugging information about a key",
|
||||
9,
|
||||
"0.101" },
|
||||
{ "DEBUG SEGFAULT",
|
||||
"-",
|
||||
"Make the server crash",
|
||||
9,
|
||||
"0.101" },
|
||||
{ "DECR",
|
||||
"key",
|
||||
"Decrement the integer value of a key by one",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "DECRBY",
|
||||
"key decrement",
|
||||
"Decrement the integer value of a key by the given number",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "DEL",
|
||||
"key [key ...]",
|
||||
"Delete a key",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "DISCARD",
|
||||
"-",
|
||||
"Discard all commands issued after MULTI",
|
||||
7,
|
||||
"1.3.3" },
|
||||
{ "ECHO",
|
||||
"message",
|
||||
"Echo the given string",
|
||||
8,
|
||||
"0.07" },
|
||||
{ "EXEC",
|
||||
"-",
|
||||
"Execute all commands issued after MULTI",
|
||||
7,
|
||||
"1.1.95" },
|
||||
{ "EXISTS",
|
||||
"key",
|
||||
"Determine if a key exists",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "EXPIRE",
|
||||
"key seconds",
|
||||
"Set a key's time to live in seconds",
|
||||
0,
|
||||
"0.09" },
|
||||
{ "EXPIREAT",
|
||||
"key timestamp",
|
||||
"Set the expiration for a key as a UNIX timestamp",
|
||||
0,
|
||||
"1.1" },
|
||||
{ "FLUSHALL",
|
||||
"-",
|
||||
"Remove all keys from all databases",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "FLUSHDB",
|
||||
"-",
|
||||
"Remove all keys from the current database",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "GET",
|
||||
"key",
|
||||
"Get the value of a key",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "GETBIT",
|
||||
"key offset",
|
||||
"Returns the bit value at offset in the string value stored at key",
|
||||
1,
|
||||
"2.1.8" },
|
||||
{ "GETSET",
|
||||
"key value",
|
||||
"Set the string value of a key and return its old value",
|
||||
1,
|
||||
"0.091" },
|
||||
{ "HDEL",
|
||||
"key field",
|
||||
"Delete a hash field",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HEXISTS",
|
||||
"key field",
|
||||
"Determine if a hash field exists",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HGET",
|
||||
"key field",
|
||||
"Get the value of a hash field",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HGETALL",
|
||||
"key",
|
||||
"Get all the fields and values in a hash",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HINCRBY",
|
||||
"key field increment",
|
||||
"Increment the integer value of a hash field by the given number",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HKEYS",
|
||||
"key",
|
||||
"Get all the fields in a hash",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HLEN",
|
||||
"key",
|
||||
"Get the number of fields in a hash",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HMGET",
|
||||
"key field [field ...]",
|
||||
"Get the values of all the given hash fields",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HMSET",
|
||||
"key field value [field value ...]",
|
||||
"Set multiple hash fields to multiple values",
|
||||
5,
|
||||
"1.3.8" },
|
||||
{ "HSET",
|
||||
"key field value",
|
||||
"Set the string value of a hash field",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "HSETNX",
|
||||
"key field value",
|
||||
"Set the value of a hash field, only if the field does not exist",
|
||||
5,
|
||||
"1.3.8" },
|
||||
{ "HVALS",
|
||||
"key",
|
||||
"Get all the values in a hash",
|
||||
5,
|
||||
"1.3.10" },
|
||||
{ "INCR",
|
||||
"key",
|
||||
"Increment the integer value of a key by one",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "INCRBY",
|
||||
"key increment",
|
||||
"Increment the integer value of a key by the given number",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "INFO",
|
||||
"-",
|
||||
"Get information and statistics about the server",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "KEYS",
|
||||
"pattern",
|
||||
"Find all keys matching the given pattern",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "LASTSAVE",
|
||||
"-",
|
||||
"Get the UNIX time stamp of the last successful save to disk",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "LINDEX",
|
||||
"key index",
|
||||
"Get an element from a list by its index",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LINSERT",
|
||||
"key BEFORE|AFTER pivot value",
|
||||
"Insert an element before or after another element in a list",
|
||||
2,
|
||||
"2.1.1" },
|
||||
{ "LLEN",
|
||||
"key",
|
||||
"Get the length of a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LPOP",
|
||||
"key",
|
||||
"Remove and get the first element in a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LPUSH",
|
||||
"key value",
|
||||
"Prepend a value to a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LPUSHX",
|
||||
"key value",
|
||||
"Prepend a value to a list, only if the list exists",
|
||||
2,
|
||||
"2.1.1" },
|
||||
{ "LRANGE",
|
||||
"key start stop",
|
||||
"Get a range of elements from a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LREM",
|
||||
"key count value",
|
||||
"Remove elements from a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LSET",
|
||||
"key index value",
|
||||
"Set the value of an element in a list by its index",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "LTRIM",
|
||||
"key start stop",
|
||||
"Trim a list to the specified range",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "MGET",
|
||||
"key [key ...]",
|
||||
"Get the values of all the given keys",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "MONITOR",
|
||||
"-",
|
||||
"Listen for all requests received by the server in real time",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "MOVE",
|
||||
"key db",
|
||||
"Move a key to another database",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "MSET",
|
||||
"key value [key value ...]",
|
||||
"Set multiple keys to multiple values",
|
||||
1,
|
||||
"1.001" },
|
||||
{ "MSETNX",
|
||||
"key value [key value ...]",
|
||||
"Set multiple keys to multiple values, only if none of the keys exist",
|
||||
1,
|
||||
"1.001" },
|
||||
{ "MULTI",
|
||||
"-",
|
||||
"Mark the start of a transaction block",
|
||||
7,
|
||||
"1.1.95" },
|
||||
{ "PERSIST",
|
||||
"key",
|
||||
"Remove the expiration from a key",
|
||||
0,
|
||||
"2.1.2" },
|
||||
{ "PING",
|
||||
"-",
|
||||
"Ping the server",
|
||||
8,
|
||||
"0.07" },
|
||||
{ "PSUBSCRIBE",
|
||||
"pattern",
|
||||
"Listen for messages published to channels matching the given patterns",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "PUBLISH",
|
||||
"channel message",
|
||||
"Post a message to a channel",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "PUNSUBSCRIBE",
|
||||
"[pattern [pattern ...]]",
|
||||
"Stop listening for messages posted to channels matching the given patterns",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "QUIT",
|
||||
"-",
|
||||
"Close the connection",
|
||||
8,
|
||||
"0.07" },
|
||||
{ "RANDOMKEY",
|
||||
"-",
|
||||
"Return a random key from the keyspace",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "RENAME",
|
||||
"key newkey",
|
||||
"Rename a key",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "RENAMENX",
|
||||
"key newkey",
|
||||
"Rename a key, only if the new key does not exist",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "RPOP",
|
||||
"key",
|
||||
"Remove and get the last element in a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "RPOPLPUSH",
|
||||
"source destination",
|
||||
"Remove the last element in a list, append it to another list and return it",
|
||||
2,
|
||||
"1.1" },
|
||||
{ "RPUSH",
|
||||
"key value",
|
||||
"Append a value to a list",
|
||||
2,
|
||||
"0.07" },
|
||||
{ "RPUSHX",
|
||||
"key value",
|
||||
"Append a value to a list, only if the list exists",
|
||||
2,
|
||||
"2.1.1" },
|
||||
{ "SADD",
|
||||
"key member",
|
||||
"Add a member to a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SAVE",
|
||||
"-",
|
||||
"Synchronously save the dataset to disk",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "SCARD",
|
||||
"key",
|
||||
"Get the number of members in a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SDIFF",
|
||||
"key [key ...]",
|
||||
"Subtract multiple sets",
|
||||
3,
|
||||
"0.100" },
|
||||
{ "SDIFFSTORE",
|
||||
"destination key [key ...]",
|
||||
"Subtract multiple sets and store the resulting set in a key",
|
||||
3,
|
||||
"0.100" },
|
||||
{ "SELECT",
|
||||
"index",
|
||||
"Change the selected database for the current connection",
|
||||
8,
|
||||
"0.07" },
|
||||
{ "SET",
|
||||
"key value",
|
||||
"Set the string value of a key",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "SETBIT",
|
||||
"key offset value",
|
||||
"Sets or clears the bit at offset in the string value stored at key",
|
||||
1,
|
||||
"2.1.8" },
|
||||
{ "SETEX",
|
||||
"key seconds value",
|
||||
"Set the value and expiration of a key",
|
||||
1,
|
||||
"1.3.10" },
|
||||
{ "SETNX",
|
||||
"key value",
|
||||
"Set the value of a key, only if the key does not exist",
|
||||
1,
|
||||
"0.07" },
|
||||
{ "SETRANGE",
|
||||
"key offset value",
|
||||
"Overwrite part of a string at key starting at the specified offset",
|
||||
1,
|
||||
"2.1.8" },
|
||||
{ "SHUTDOWN",
|
||||
"-",
|
||||
"Synchronously save the dataset to disk and then shut down the server",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "SINTER",
|
||||
"key [key ...]",
|
||||
"Intersect multiple sets",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SINTERSTORE",
|
||||
"destination key [key ...]",
|
||||
"Intersect multiple sets and store the resulting set in a key",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SISMEMBER",
|
||||
"key member",
|
||||
"Determine if a given value is a member of a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SLAVEOF",
|
||||
"host port",
|
||||
"Make the server a slave of another instance, or promote it as master",
|
||||
9,
|
||||
"0.100" },
|
||||
{ "SMEMBERS",
|
||||
"key",
|
||||
"Get all the members in a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "SMOVE",
|
||||
"source destination member",
|
||||
"Move a member from one set to another",
|
||||
3,
|
||||
"0.091" },
|
||||
{ "SORT",
|
||||
"key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]",
|
||||
"Sort the elements in a list, set or sorted set",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "SPOP",
|
||||
"key",
|
||||
"Remove and return a random member from a set",
|
||||
3,
|
||||
"0.101" },
|
||||
{ "SRANDMEMBER",
|
||||
"key",
|
||||
"Get a random member from a set",
|
||||
3,
|
||||
"1.001" },
|
||||
{ "SREM",
|
||||
"key member",
|
||||
"Remove a member from a set",
|
||||
3,
|
||||
"0.07" },
|
||||
{ "STRLEN",
|
||||
"key",
|
||||
"Get the length of the value stored in a key",
|
||||
1,
|
||||
"2.1.2" },
|
||||
{ "SUBSCRIBE",
|
||||
"channel",
|
||||
"Listen for messages published to the given channels",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "SUBSTR",
|
||||
"key start end",
|
||||
"Get a substring of the string stored at a key",
|
||||
1,
|
||||
"1.3.4" },
|
||||
{ "SUNION",
|
||||
"key [key ...]",
|
||||
"Add multiple sets",
|
||||
3,
|
||||
"0.091" },
|
||||
{ "SUNIONSTORE",
|
||||
"destination key [key ...]",
|
||||
"Add multiple sets and store the resulting set in a key",
|
||||
3,
|
||||
"0.091" },
|
||||
{ "SYNC",
|
||||
"-",
|
||||
"Internal command used for replication",
|
||||
9,
|
||||
"0.07" },
|
||||
{ "TTL",
|
||||
"key",
|
||||
"Get the time to live for a key",
|
||||
0,
|
||||
"0.100" },
|
||||
{ "TYPE",
|
||||
"key",
|
||||
"Determine the type stored at key",
|
||||
0,
|
||||
"0.07" },
|
||||
{ "UNSUBSCRIBE",
|
||||
"[channel [channel ...]]",
|
||||
"Stop listening for messages posted to the given channels",
|
||||
6,
|
||||
"1.3.8" },
|
||||
{ "UNWATCH",
|
||||
"-",
|
||||
"Forget about all watched keys",
|
||||
7,
|
||||
"2.1.0" },
|
||||
{ "WATCH",
|
||||
"key [key ...]",
|
||||
"Watch the given keys to determine execution of the MULTI/EXEC block",
|
||||
7,
|
||||
"2.1.0" },
|
||||
{ "ZADD",
|
||||
"key score member",
|
||||
"Add a member to a sorted set, or update its score if it already exists",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZCARD",
|
||||
"key",
|
||||
"Get the number of members in a sorted set",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZCOUNT",
|
||||
"key min max",
|
||||
"Count the members in a sorted set with scores within the given values",
|
||||
4,
|
||||
"1.3.3" },
|
||||
{ "ZINCRBY",
|
||||
"key increment member",
|
||||
"Increment the score of a member in a sorted set",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZINTERSTORE",
|
||||
"destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
|
||||
"Intersect multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"1.3.10" },
|
||||
{ "ZRANGE",
|
||||
"key start stop [WITHSCORES]",
|
||||
"Return a range of members in a sorted set, by index",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZRANGEBYSCORE",
|
||||
"key min max [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score",
|
||||
4,
|
||||
"1.050" },
|
||||
{ "ZRANK",
|
||||
"key member",
|
||||
"Determine the index of a member in a sorted set",
|
||||
4,
|
||||
"1.3.4" },
|
||||
{ "ZREM",
|
||||
"key member",
|
||||
"Remove a member from a sorted set",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZREMRANGEBYRANK",
|
||||
"key start stop",
|
||||
"Remove all members in a sorted set within the given indexes",
|
||||
4,
|
||||
"1.3.4" },
|
||||
{ "ZREMRANGEBYSCORE",
|
||||
"key min max",
|
||||
"Remove all members in a sorted set within the given scores",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZREVRANGE",
|
||||
"key start stop [WITHSCORES]",
|
||||
"Return a range of members in a sorted set, by index, with scores ordered from high to low",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZREVRANGEBYSCORE",
|
||||
"key max min [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score, with scores ordered from high to low",
|
||||
4,
|
||||
"2.1.6" },
|
||||
{ "ZREVRANK",
|
||||
"key member",
|
||||
"Determine the index of a member in a sorted set, with scores ordered from high to low",
|
||||
4,
|
||||
"1.3.4" },
|
||||
{ "ZSCORE",
|
||||
"key member",
|
||||
"Get the score associated with the given member in a sorted set",
|
||||
4,
|
||||
"1.1" },
|
||||
{ "ZUNIONSTORE",
|
||||
"destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
|
||||
"Add multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"1.3.10" }
|
||||
};
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -179,7 +179,7 @@ intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
|
||||
}
|
||||
|
||||
/* Delete integer from intset */
|
||||
intset *intsetRemove(intset *is, int64_t value, uint8_t *success) {
|
||||
intset *intsetRemove(intset *is, int64_t value, int *success) {
|
||||
uint8_t valenc = _intsetValueEncoding(value);
|
||||
uint32_t pos;
|
||||
if (success) *success = 0;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ typedef struct intset {
|
||||
|
||||
intset *intsetNew(void);
|
||||
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
|
||||
intset *intsetRemove(intset *is, int64_t value, uint8_t *success);
|
||||
intset *intsetRemove(intset *is, int64_t value, int *success);
|
||||
uint8_t intsetFind(intset *is, int64_t value);
|
||||
int64_t intsetRandom(intset *is);
|
||||
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
|
||||
|
||||
+32
-8
@@ -41,8 +41,10 @@ redisClient *createClient(int fd) {
|
||||
c->reply = listCreate();
|
||||
listSetFreeMethod(c->reply,decrRefCount);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
c->blocking_keys = NULL;
|
||||
c->blocking_keys_num = 0;
|
||||
c->bpop.keys = NULL;
|
||||
c->bpop.count = 0;
|
||||
c->bpop.timeout = 0;
|
||||
c->bpop.target = NULL;
|
||||
c->io_keys = listCreate();
|
||||
c->watched_keys = listCreate();
|
||||
listSetFreeMethod(c->io_keys,decrRefCount);
|
||||
@@ -178,6 +180,9 @@ void addReply(redisClient *c, robj *obj) {
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
} else {
|
||||
/* FIXME: convert the long into string and use _addReplyToBuffer()
|
||||
* instead of calling getDecodedObject. As this place in the
|
||||
* code is too performance critical. */
|
||||
obj = getDecodedObject(obj);
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
@@ -275,6 +280,7 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a duble as a bulk reply */
|
||||
void addReplyDouble(redisClient *c, double d) {
|
||||
char dbuf[128], sbuf[128];
|
||||
int dlen, slen;
|
||||
@@ -283,6 +289,8 @@ void addReplyDouble(redisClient *c, double d) {
|
||||
addReplyString(c,sbuf,slen);
|
||||
}
|
||||
|
||||
/* Add a long long as integer reply or bulk len / multi bulk count.
|
||||
* Basically this is used to output <prefix><long long><crlf>. */
|
||||
void _addReplyLongLong(redisClient *c, long long ll, char prefix) {
|
||||
char buf[128];
|
||||
int len;
|
||||
@@ -301,6 +309,7 @@ void addReplyMultiBulkLen(redisClient *c, long length) {
|
||||
_addReplyLongLong(c,length,'*');
|
||||
}
|
||||
|
||||
/* Create the length prefix of a bulk reply, example: $2234 */
|
||||
void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
size_t len;
|
||||
|
||||
@@ -322,23 +331,38 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
_addReplyLongLong(c,len,'$');
|
||||
}
|
||||
|
||||
/* Add a Redis Object as a bulk reply */
|
||||
void addReplyBulk(redisClient *c, robj *obj) {
|
||||
addReplyBulkLen(c,obj);
|
||||
addReply(c,obj);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
|
||||
/* In the CONFIG command we need to add vanilla C string as bulk replies */
|
||||
/* Add a C buffer as bulk reply */
|
||||
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) {
|
||||
_addReplyLongLong(c,len,'$');
|
||||
addReplyString(c,p,len);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
|
||||
/* Add a C nul term string as bulk reply */
|
||||
void addReplyBulkCString(redisClient *c, char *s) {
|
||||
if (s == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
robj *o = createStringObject(s,strlen(s));
|
||||
addReplyBulk(c,o);
|
||||
decrRefCount(o);
|
||||
addReplyBulkCBuffer(c,s,strlen(s));
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a long long as a bulk reply */
|
||||
void addReplyBulkLongLong(redisClient *c, long long ll) {
|
||||
char buf[64];
|
||||
int len;
|
||||
|
||||
len = ll2string(buf,64,ll);
|
||||
addReplyBulkCBuffer(c,buf,len);
|
||||
}
|
||||
|
||||
static void acceptCommonHandler(int fd) {
|
||||
redisClient *c;
|
||||
if ((c = createClient(fd)) == NULL) {
|
||||
@@ -677,7 +701,7 @@ void closeTimedoutClients(void) {
|
||||
redisLog(REDIS_VERBOSE,"Closing idle client");
|
||||
freeClient(c);
|
||||
} else if (c->flags & REDIS_BLOCKED) {
|
||||
if (c->blockingto != 0 && c->blockingto < now) {
|
||||
if (c->bpop.timeout != 0 && c->bpop.timeout < now) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
unblockClientWaitingData(c);
|
||||
}
|
||||
@@ -780,7 +804,7 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
bulklen = strtol(c->querybuf+pos+1,&eptr,10);
|
||||
tolerr = (eptr[0] != '\r');
|
||||
if (tolerr || bulklen == LONG_MIN || bulklen == LONG_MAX ||
|
||||
bulklen < 0 || bulklen > 1024*1024*1024)
|
||||
bulklen < 0 || bulklen > 512*1024*1024)
|
||||
{
|
||||
addReplyError(c,"Protocol error: invalid bulk length");
|
||||
setProtocolError(c,pos);
|
||||
|
||||
@@ -9,38 +9,49 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* Convenience wrapper around fwrite, that returns the number of bytes written
|
||||
* to the file instead of the number of objects (see fwrite(3)) and -1 in the
|
||||
* case of an error. It also supports a NULL *fp to skip writing altogether
|
||||
* instead of writing to /dev/null. */
|
||||
static int rdbWriteRaw(FILE *fp, void *p, size_t len) {
|
||||
if (fp != NULL && fwrite(p,len,1,fp) == 0) return -1;
|
||||
return len;
|
||||
}
|
||||
|
||||
int rdbSaveType(FILE *fp, unsigned char type) {
|
||||
if (fwrite(&type,1,1,fp) == 0) return -1;
|
||||
return 0;
|
||||
return rdbWriteRaw(fp,&type,1);
|
||||
}
|
||||
|
||||
int rdbSaveTime(FILE *fp, time_t t) {
|
||||
int32_t t32 = (int32_t) t;
|
||||
if (fwrite(&t32,4,1,fp) == 0) return -1;
|
||||
return 0;
|
||||
return rdbWriteRaw(fp,&t32,4);
|
||||
}
|
||||
|
||||
/* check rdbLoadLen() comments for more info */
|
||||
int rdbSaveLen(FILE *fp, uint32_t len) {
|
||||
unsigned char buf[2];
|
||||
int nwritten;
|
||||
|
||||
if (len < (1<<6)) {
|
||||
/* Save a 6 bit len */
|
||||
buf[0] = (len&0xFF)|(REDIS_RDB_6BITLEN<<6);
|
||||
if (fwrite(buf,1,1,fp) == 0) return -1;
|
||||
if (rdbWriteRaw(fp,buf,1) == -1) return -1;
|
||||
nwritten = 1;
|
||||
} else if (len < (1<<14)) {
|
||||
/* Save a 14 bit len */
|
||||
buf[0] = ((len>>8)&0xFF)|(REDIS_RDB_14BITLEN<<6);
|
||||
buf[1] = len&0xFF;
|
||||
if (fwrite(buf,2,1,fp) == 0) return -1;
|
||||
if (rdbWriteRaw(fp,buf,2) == -1) return -1;
|
||||
nwritten = 2;
|
||||
} else {
|
||||
/* Save a 32 bit len */
|
||||
buf[0] = (REDIS_RDB_32BITLEN<<6);
|
||||
if (fwrite(buf,1,1,fp) == 0) return -1;
|
||||
if (rdbWriteRaw(fp,buf,1) == -1) return -1;
|
||||
len = htonl(len);
|
||||
if (fwrite(&len,4,1,fp) == 0) return -1;
|
||||
if (rdbWriteRaw(fp,&len,4) == -1) return -1;
|
||||
nwritten = 1+4;
|
||||
}
|
||||
return 0;
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
/* Encode 'value' as an integer if possible (if integer will fit the
|
||||
@@ -93,6 +104,7 @@ int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {
|
||||
int rdbSaveLzfStringObject(FILE *fp, unsigned char *s, size_t len) {
|
||||
size_t comprlen, outlen;
|
||||
unsigned char byte;
|
||||
int n, nwritten = 0;
|
||||
void *out;
|
||||
|
||||
/* We require at least four bytes compression for this to be worth it */
|
||||
@@ -106,12 +118,20 @@ int rdbSaveLzfStringObject(FILE *fp, unsigned char *s, size_t len) {
|
||||
}
|
||||
/* Data compressed! Let's save it on disk */
|
||||
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
|
||||
if (fwrite(&byte,1,1,fp) == 0) goto writeerr;
|
||||
if (rdbSaveLen(fp,comprlen) == -1) goto writeerr;
|
||||
if (rdbSaveLen(fp,len) == -1) goto writeerr;
|
||||
if (fwrite(out,comprlen,1,fp) == 0) goto writeerr;
|
||||
if ((n = rdbWriteRaw(fp,&byte,1)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(fp,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(fp,len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbWriteRaw(fp,out,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
zfree(out);
|
||||
return comprlen;
|
||||
return nwritten;
|
||||
|
||||
writeerr:
|
||||
zfree(out);
|
||||
@@ -122,47 +142,53 @@ writeerr:
|
||||
* representation of an integer value we try to safe it in a special form */
|
||||
int rdbSaveRawString(FILE *fp, unsigned char *s, size_t len) {
|
||||
int enclen;
|
||||
int n, nwritten = 0;
|
||||
|
||||
/* Try integer encoding */
|
||||
if (len <= 11) {
|
||||
unsigned char buf[5];
|
||||
if ((enclen = rdbTryIntegerEncoding((char*)s,len,buf)) > 0) {
|
||||
if (fwrite(buf,enclen,1,fp) == 0) return -1;
|
||||
return 0;
|
||||
if (rdbWriteRaw(fp,buf,enclen) == -1) return -1;
|
||||
return enclen;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try LZF compression - under 20 bytes it's unable to compress even
|
||||
* aaaaaaaaaaaaaaaaaa so skip it */
|
||||
if (server.rdbcompression && len > 20) {
|
||||
int retval;
|
||||
|
||||
retval = rdbSaveLzfStringObject(fp,s,len);
|
||||
if (retval == -1) return -1;
|
||||
if (retval > 0) return 0;
|
||||
/* retval == 0 means data can't be compressed, save the old way */
|
||||
n = rdbSaveLzfStringObject(fp,s,len);
|
||||
if (n == -1) return -1;
|
||||
if (n > 0) return n;
|
||||
/* Return value of 0 means data can't be compressed, save the old way */
|
||||
}
|
||||
|
||||
/* Store verbatim */
|
||||
if (rdbSaveLen(fp,len) == -1) return -1;
|
||||
if (len && fwrite(s,len,1,fp) == 0) return -1;
|
||||
return 0;
|
||||
if ((n = rdbSaveLen(fp,len)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if (len > 0) {
|
||||
if (rdbWriteRaw(fp,s,len) == -1) return -1;
|
||||
nwritten += len;
|
||||
}
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
/* Save a long long value as either an encoded string or a string. */
|
||||
int rdbSaveLongLongAsStringObject(FILE *fp, long long value) {
|
||||
unsigned char buf[32];
|
||||
int n, nwritten = 0;
|
||||
int enclen = rdbEncodeInteger(value,buf);
|
||||
if (enclen > 0) {
|
||||
if (fwrite(buf,enclen,1,fp) == 0) return -1;
|
||||
return rdbWriteRaw(fp,buf,enclen);
|
||||
} else {
|
||||
/* Encode as string */
|
||||
enclen = ll2string((char*)buf,32,value);
|
||||
redisAssert(enclen < 32);
|
||||
if (rdbSaveLen(fp,enclen) == -1) return -1;
|
||||
if (fwrite(buf,enclen,1,fp) == 0) return -1;
|
||||
if ((n = rdbSaveLen(fp,enclen)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if ((n = rdbWriteRaw(fp,buf,enclen)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
return 0;
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
/* Like rdbSaveStringObjectRaw() but handle encoded objects */
|
||||
@@ -216,15 +242,17 @@ int rdbSaveDoubleValue(FILE *fp, double val) {
|
||||
buf[0] = strlen((char*)buf+1);
|
||||
len = buf[0]+1;
|
||||
}
|
||||
if (fwrite(buf,len,1,fp) == 0) return -1;
|
||||
return 0;
|
||||
return rdbWriteRaw(fp,buf,len);
|
||||
}
|
||||
|
||||
/* Save a Redis object. */
|
||||
int rdbSaveObject(FILE *fp, robj *o) {
|
||||
int n, nwritten = 0;
|
||||
|
||||
if (o->type == REDIS_STRING) {
|
||||
/* Save a string value */
|
||||
if (rdbSaveStringObject(fp,o) == -1) return -1;
|
||||
if ((n = rdbSaveStringObject(fp,o)) == -1) return -1;
|
||||
nwritten += n;
|
||||
} else if (o->type == REDIS_LIST) {
|
||||
/* Save a list value */
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
@@ -233,15 +261,19 @@ int rdbSaveObject(FILE *fp, robj *o) {
|
||||
unsigned int vlen;
|
||||
long long vlong;
|
||||
|
||||
if (rdbSaveLen(fp,ziplistLen(o->ptr)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(fp,ziplistLen(o->ptr))) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
p = ziplistIndex(o->ptr,0);
|
||||
while(ziplistGet(p,&vstr,&vlen,&vlong)) {
|
||||
if (vstr) {
|
||||
if (rdbSaveRawString(fp,vstr,vlen) == -1)
|
||||
if ((n = rdbSaveRawString(fp,vstr,vlen)) == -1)
|
||||
return -1;
|
||||
nwritten += n;
|
||||
} else {
|
||||
if (rdbSaveLongLongAsStringObject(fp,vlong) == -1)
|
||||
if ((n = rdbSaveLongLongAsStringObject(fp,vlong)) == -1)
|
||||
return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
p = ziplistNext(o->ptr,p);
|
||||
}
|
||||
@@ -250,11 +282,14 @@ int rdbSaveObject(FILE *fp, robj *o) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
if (rdbSaveLen(fp,listLength(list)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(fp,listLength(list))) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
listRewind(list,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
robj *eleobj = listNodeValue(ln);
|
||||
if (rdbSaveStringObject(fp,eleobj) == -1) return -1;
|
||||
if ((n = rdbSaveStringObject(fp,eleobj)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
@@ -266,10 +301,13 @@ int rdbSaveObject(FILE *fp, robj *o) {
|
||||
dictIterator *di = dictGetIterator(set);
|
||||
dictEntry *de;
|
||||
|
||||
if (rdbSaveLen(fp,dictSize(set)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(fp,dictSize(set))) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *eleobj = dictGetEntryKey(de);
|
||||
if (rdbSaveStringObject(fp,eleobj) == -1) return -1;
|
||||
if ((n = rdbSaveStringObject(fp,eleobj)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else if (o->encoding == REDIS_ENCODING_INTSET) {
|
||||
@@ -277,9 +315,12 @@ int rdbSaveObject(FILE *fp, robj *o) {
|
||||
int64_t llval;
|
||||
int i = 0;
|
||||
|
||||
if (rdbSaveLen(fp,intsetLen(is)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(fp,intsetLen(is))) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
while(intsetGet(is,i++,&llval)) {
|
||||
if (rdbSaveLongLongAsStringObject(fp,llval) == -1) return -1;
|
||||
if ((n = rdbSaveLongLongAsStringObject(fp,llval)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown set encoding");
|
||||
@@ -290,13 +331,17 @@ int rdbSaveObject(FILE *fp, robj *o) {
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
|
||||
if (rdbSaveLen(fp,dictSize(zs->dict)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(fp,dictSize(zs->dict))) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *eleobj = dictGetEntryKey(de);
|
||||
double *score = dictGetEntryVal(de);
|
||||
|
||||
if (rdbSaveStringObject(fp,eleobj) == -1) return -1;
|
||||
if (rdbSaveDoubleValue(fp,*score) == -1) return -1;
|
||||
if ((n = rdbSaveStringObject(fp,eleobj)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveDoubleValue(fp,*score)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else if (o->type == REDIS_HASH) {
|
||||
@@ -307,46 +352,52 @@ int rdbSaveObject(FILE *fp, robj *o) {
|
||||
unsigned char *key, *val;
|
||||
unsigned int klen, vlen;
|
||||
|
||||
if (rdbSaveLen(fp,count) == -1) return -1;
|
||||
if ((n = rdbSaveLen(fp,count)) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
while((p = zipmapNext(p,&key,&klen,&val,&vlen)) != NULL) {
|
||||
if (rdbSaveRawString(fp,key,klen) == -1) return -1;
|
||||
if (rdbSaveRawString(fp,val,vlen) == -1) return -1;
|
||||
if ((n = rdbSaveRawString(fp,key,klen)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveRawString(fp,val,vlen)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
} else {
|
||||
dictIterator *di = dictGetIterator(o->ptr);
|
||||
dictEntry *de;
|
||||
|
||||
if (rdbSaveLen(fp,dictSize((dict*)o->ptr)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(fp,dictSize((dict*)o->ptr))) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *key = dictGetEntryKey(de);
|
||||
robj *val = dictGetEntryVal(de);
|
||||
|
||||
if (rdbSaveStringObject(fp,key) == -1) return -1;
|
||||
if (rdbSaveStringObject(fp,val) == -1) return -1;
|
||||
if ((n = rdbSaveStringObject(fp,key)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveStringObject(fp,val)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown object type");
|
||||
}
|
||||
return 0;
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
/* Return the length the object will have on disk if saved with
|
||||
* the rdbSaveObject() function. Currently we use a trick to get
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
off_t rdbSavedObjectLen(robj *o, FILE *fp) {
|
||||
if (fp == NULL) fp = server.devnull;
|
||||
rewind(fp);
|
||||
redisAssert(rdbSaveObject(fp,o) != 1);
|
||||
return ftello(fp);
|
||||
off_t rdbSavedObjectLen(robj *o) {
|
||||
int len = rdbSaveObject(NULL,o);
|
||||
redisAssert(len != -1);
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Return the number of pages required to save this object in the swap file */
|
||||
off_t rdbSavedObjectPages(robj *o, FILE *fp) {
|
||||
off_t bytes = rdbSavedObjectLen(o,fp);
|
||||
|
||||
off_t rdbSavedObjectPages(robj *o) {
|
||||
off_t bytes = rdbSavedObjectLen(o);
|
||||
return (bytes+(server.vm_page_size-1))/server.vm_page_size;
|
||||
}
|
||||
|
||||
|
||||
+285
-73
@@ -39,11 +39,13 @@
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "sds.h"
|
||||
#include "zmalloc.h"
|
||||
#include "linenoise.h"
|
||||
#include "help.h"
|
||||
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
|
||||
@@ -58,16 +60,16 @@ static struct config {
|
||||
int shutdown;
|
||||
int monitor_mode;
|
||||
int pubsub_mode;
|
||||
int raw_output; /* output mode per command */
|
||||
int tty; /* flag for default output format */
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char mb_sep;
|
||||
char *auth;
|
||||
char *historyfile;
|
||||
int raw_output; /* output mode per command */
|
||||
sds mb_delim;
|
||||
} config;
|
||||
|
||||
static void usage();
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Utility functions
|
||||
@@ -83,6 +85,165 @@ static long long mstime(void) {
|
||||
return mst;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Help functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define CLI_HELP_COMMAND 1
|
||||
#define CLI_HELP_GROUP 2
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
int argc;
|
||||
sds *argv;
|
||||
sds full;
|
||||
|
||||
/* Only used for help on commands */
|
||||
struct commandHelp *org;
|
||||
} helpEntry;
|
||||
|
||||
static helpEntry *helpEntries;
|
||||
static int helpEntriesLen;
|
||||
|
||||
static sds cliVersion() {
|
||||
sds version;
|
||||
version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
|
||||
|
||||
/* Add git commit and working tree status when available */
|
||||
if (strtoll(redisGitSHA1(),NULL,16)) {
|
||||
version = sdscatprintf(version, " (git:%s", redisGitSHA1());
|
||||
if (strtoll(redisGitDirty(),NULL,10))
|
||||
version = sdscatprintf(version, "-dirty");
|
||||
version = sdscat(version, ")");
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
static void cliInitHelp() {
|
||||
int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
|
||||
int groupslen = sizeof(commandGroups)/sizeof(char*);
|
||||
int i, len, pos = 0;
|
||||
helpEntry tmp;
|
||||
|
||||
helpEntriesLen = len = commandslen+groupslen;
|
||||
helpEntries = malloc(sizeof(helpEntry)*len);
|
||||
|
||||
for (i = 0; i < groupslen; i++) {
|
||||
tmp.argc = 1;
|
||||
tmp.argv = malloc(sizeof(sds));
|
||||
tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
|
||||
tmp.full = tmp.argv[0];
|
||||
tmp.type = CLI_HELP_GROUP;
|
||||
tmp.org = NULL;
|
||||
helpEntries[pos++] = tmp;
|
||||
}
|
||||
|
||||
for (i = 0; i < commandslen; i++) {
|
||||
tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
|
||||
tmp.full = sdsnew(commandHelp[i].name);
|
||||
tmp.type = CLI_HELP_COMMAND;
|
||||
tmp.org = &commandHelp[i];
|
||||
helpEntries[pos++] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Output command help to stdout. */
|
||||
static void cliOutputCommandHelp(struct commandHelp *help, int group) {
|
||||
printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
|
||||
printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
|
||||
printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since);
|
||||
if (group) {
|
||||
printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print generic help. */
|
||||
static void cliOutputGenericHelp() {
|
||||
sds version = cliVersion();
|
||||
printf(
|
||||
"redis-cli %s\r\n"
|
||||
"Type: \"help @<group>\" to get a list of commands in <group>\r\n"
|
||||
" \"help <command>\" for help on <command>\r\n"
|
||||
" \"help <tab>\" to get a list of possible help topics\r\n"
|
||||
" \"quit\" to exit\r\n",
|
||||
version
|
||||
);
|
||||
sdsfree(version);
|
||||
}
|
||||
|
||||
/* Output all command help, filtering by group or command name. */
|
||||
static void cliOutputHelp(int argc, char **argv) {
|
||||
int i, j, len;
|
||||
int group = -1;
|
||||
helpEntry *entry;
|
||||
struct commandHelp *help;
|
||||
|
||||
if (argc == 0) {
|
||||
cliOutputGenericHelp();
|
||||
return;
|
||||
} else if (argc > 0 && argv[0][0] == '@') {
|
||||
len = sizeof(commandGroups)/sizeof(char*);
|
||||
for (i = 0; i < len; i++) {
|
||||
if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
|
||||
group = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(argc > 0);
|
||||
for (i = 0; i < helpEntriesLen; i++) {
|
||||
entry = &helpEntries[i];
|
||||
if (entry->type != CLI_HELP_COMMAND) continue;
|
||||
|
||||
help = entry->org;
|
||||
if (group == -1) {
|
||||
/* Compare all arguments */
|
||||
if (argc == entry->argc) {
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
|
||||
}
|
||||
if (j == argc) {
|
||||
cliOutputCommandHelp(help,1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (group == help->group) {
|
||||
cliOutputCommandHelp(help,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
static void completionCallback(const char *buf, linenoiseCompletions *lc) {
|
||||
size_t startpos = 0;
|
||||
int mask;
|
||||
int i;
|
||||
size_t matchlen;
|
||||
sds tmp;
|
||||
|
||||
if (strncasecmp(buf,"help ",5) == 0) {
|
||||
startpos = 5;
|
||||
while (isspace(buf[startpos])) startpos++;
|
||||
mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
|
||||
} else {
|
||||
mask = CLI_HELP_COMMAND;
|
||||
}
|
||||
|
||||
for (i = 0; i < helpEntriesLen; i++) {
|
||||
if (!(helpEntries[i].type & mask)) continue;
|
||||
|
||||
matchlen = strlen(buf+startpos);
|
||||
if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
|
||||
tmp = sdsnewlen(buf,startpos);
|
||||
tmp = sdscat(tmp,helpEntries[i].full);
|
||||
linenoiseAddCompletion(lc,tmp);
|
||||
sdsfree(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Networking / parsing
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -154,30 +315,24 @@ static void cliPrintContextErrorAndExit() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static sds cliFormatReply(redisReply *r, char *prefix) {
|
||||
static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
sds out = sdsempty();
|
||||
switch (r->type) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
if (config.tty) out = sdscat(out,"(error) ");
|
||||
out = sdscatprintf(out,"%s\n", r->str);
|
||||
out = sdscatprintf(out,"(error) %s\n", r->str);
|
||||
break;
|
||||
case REDIS_REPLY_STATUS:
|
||||
out = sdscat(out,r->str);
|
||||
out = sdscat(out,"\n");
|
||||
break;
|
||||
case REDIS_REPLY_INTEGER:
|
||||
if (config.tty) out = sdscat(out,"(integer) ");
|
||||
out = sdscatprintf(out,"%lld\n",r->integer);
|
||||
out = sdscatprintf(out,"(integer) %lld\n",r->integer);
|
||||
break;
|
||||
case REDIS_REPLY_STRING:
|
||||
if (config.raw_output || !config.tty) {
|
||||
out = sdscatlen(out,r->str,r->len);
|
||||
} else {
|
||||
/* If you are producing output for the standard output we want
|
||||
* a more interesting output with quoted characters and so forth */
|
||||
out = sdscatrepr(out,r->str,r->len);
|
||||
out = sdscat(out,"\n");
|
||||
}
|
||||
/* If you are producing output for the standard output we want
|
||||
* a more interesting output with quoted characters and so forth */
|
||||
out = sdscatrepr(out,r->str,r->len);
|
||||
out = sdscat(out,"\n");
|
||||
break;
|
||||
case REDIS_REPLY_NIL:
|
||||
out = sdscat(out,"(nil)\n");
|
||||
@@ -213,7 +368,7 @@ static sds cliFormatReply(redisReply *r, char *prefix) {
|
||||
out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
|
||||
|
||||
/* Format the multi bulk entry */
|
||||
tmp = cliFormatReply(r->element[i],_prefix);
|
||||
tmp = cliFormatReplyTTY(r->element[i],_prefix);
|
||||
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||
sdsfree(tmp);
|
||||
}
|
||||
@@ -227,7 +382,38 @@ static sds cliFormatReply(redisReply *r, char *prefix) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static int cliReadReply() {
|
||||
static sds cliFormatReplyRaw(redisReply *r) {
|
||||
sds out = sdsempty(), tmp;
|
||||
size_t i;
|
||||
|
||||
switch (r->type) {
|
||||
case REDIS_REPLY_NIL:
|
||||
/* Nothing... */
|
||||
break;
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_STRING:
|
||||
out = sdscatlen(out,r->str,r->len);
|
||||
break;
|
||||
case REDIS_REPLY_INTEGER:
|
||||
out = sdscatprintf(out,"%lld",r->integer);
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
if (i > 0) out = sdscat(out,config.mb_delim);
|
||||
tmp = cliFormatReplyRaw(r->element[i]);
|
||||
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||
sdsfree(tmp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr,"Unknown reply type: %d\n", r->type);
|
||||
exit(1);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static int cliReadReply(int output_raw_strings) {
|
||||
redisReply *reply;
|
||||
sds out;
|
||||
|
||||
@@ -245,37 +431,35 @@ static int cliReadReply() {
|
||||
return REDIS_ERR; /* avoid compiler warning */
|
||||
}
|
||||
|
||||
out = cliFormatReply(reply,"");
|
||||
freeReplyObject(reply);
|
||||
if (output_raw_strings) {
|
||||
out = cliFormatReplyRaw(reply);
|
||||
} else {
|
||||
if (config.raw_output) {
|
||||
out = cliFormatReplyRaw(reply);
|
||||
out = sdscat(out,"\n");
|
||||
} else {
|
||||
out = cliFormatReplyTTY(reply,"");
|
||||
}
|
||||
}
|
||||
fwrite(out,sdslen(out),1,stdout);
|
||||
sdsfree(out);
|
||||
freeReplyObject(reply);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static void showInteractiveHelp(void) {
|
||||
printf(
|
||||
"\n"
|
||||
"Welcome to redis-cli " REDIS_VERSION "!\n"
|
||||
"Just type any valid Redis command to see a pretty printed output.\n"
|
||||
"\n"
|
||||
"It is possible to quote strings, like in:\n"
|
||||
" set \"my key\" \"some string \\xff\\n\"\n"
|
||||
"\n"
|
||||
"You can find a list of valid Redis commands at\n"
|
||||
" http://code.google.com/p/redis/wiki/CommandReference\n"
|
||||
"\n"
|
||||
"Note: redis-cli supports line editing, use up/down arrows for history."
|
||||
"\n\n");
|
||||
}
|
||||
|
||||
static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
char *command = argv[0];
|
||||
size_t *argvlen;
|
||||
int j;
|
||||
int j, output_raw;
|
||||
|
||||
config.raw_output = !strcasecmp(command,"info");
|
||||
if (!strcasecmp(command,"help")) {
|
||||
showInteractiveHelp();
|
||||
if (context == NULL) {
|
||||
printf("Not connected, please use: connect <host> <port>\n");
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
output_raw = !strcasecmp(command,"info");
|
||||
if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
|
||||
cliOutputHelp(--argc, ++argv);
|
||||
return REDIS_OK;
|
||||
}
|
||||
if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
|
||||
@@ -291,18 +475,19 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
while(repeat--) {
|
||||
redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
|
||||
while (config.monitor_mode) {
|
||||
if (cliReadReply() != REDIS_OK) exit(1);
|
||||
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (config.pubsub_mode) {
|
||||
printf("Reading messages... (press Ctrl-C to quit)\n");
|
||||
if (!config.raw_output)
|
||||
printf("Reading messages... (press Ctrl-C to quit)\n");
|
||||
while (1) {
|
||||
if (cliReadReply() != REDIS_OK) exit(1);
|
||||
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (cliReadReply() != REDIS_OK)
|
||||
if (cliReadReply(output_raw) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
@@ -319,10 +504,13 @@ static int parseOptions(int argc, char **argv) {
|
||||
int lastarg = i==argc-1;
|
||||
|
||||
if (!strcmp(argv[i],"-h") && !lastarg) {
|
||||
config.hostip = argv[i+1];
|
||||
sdsfree(config.hostip);
|
||||
config.hostip = sdsnew(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-h") && lastarg) {
|
||||
usage();
|
||||
} else if (!strcmp(argv[i],"--help")) {
|
||||
usage();
|
||||
} else if (!strcmp(argv[i],"-x")) {
|
||||
config.stdinarg = 1;
|
||||
} else if (!strcmp(argv[i],"-p") && !lastarg) {
|
||||
@@ -340,19 +528,16 @@ static int parseOptions(int argc, char **argv) {
|
||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
||||
config.auth = argv[i+1];
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-i")) {
|
||||
fprintf(stderr,
|
||||
"Starting interactive mode using -i is deprecated. Interactive mode is started\n"
|
||||
"by default when redis-cli is executed without a command to execute.\n"
|
||||
);
|
||||
} else if (!strcmp(argv[i],"-c")) {
|
||||
fprintf(stderr,
|
||||
"Reading last argument from standard input using -c is deprecated.\n"
|
||||
"When standard input is connected to a pipe or regular file, it is\n"
|
||||
"automatically used as last argument.\n"
|
||||
);
|
||||
} else if (!strcmp(argv[i],"-v")) {
|
||||
printf("redis-cli shipped with Redis version %s (%s)\n", REDIS_VERSION, redisGitSHA1());
|
||||
} else if (!strcmp(argv[i],"--raw")) {
|
||||
config.raw_output = 1;
|
||||
} else if (!strcmp(argv[i],"-d") && !lastarg) {
|
||||
sdsfree(config.mb_delim);
|
||||
config.mb_delim = sdsnew(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
|
||||
sds version = cliVersion();
|
||||
printf("redis-cli %s\n", version);
|
||||
sdsfree(version);
|
||||
exit(0);
|
||||
} else {
|
||||
break;
|
||||
@@ -379,12 +564,33 @@ static sds readArgFromStdin(void) {
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-s /path/to/socket] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
|
||||
fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
|
||||
fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
|
||||
fprintf(stderr, "example: redis-cli get my_passwd\n");
|
||||
fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
|
||||
fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
|
||||
sds version = cliVersion();
|
||||
fprintf(stderr,
|
||||
"redis-cli %s\n"
|
||||
"\n"
|
||||
"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
|
||||
" -h <hostname> Server hostname (default: 127.0.0.1)\n"
|
||||
" -p <port> Server port (default: 6379)\n"
|
||||
" -s <socket> Server socket (overrides hostname and port)\n"
|
||||
" -a <password> Password to use when connecting to the server\n"
|
||||
" -r <repeat> Execute specified command N times\n"
|
||||
" -n <db> Database number\n"
|
||||
" -x Read last argument from STDIN\n"
|
||||
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n"
|
||||
" --raw Use raw formatting for replies (default when STDOUT is not a tty)\n"
|
||||
" --help Output this help and exit\n"
|
||||
" --version Output version and exit\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" cat /etc/passwd | redis-cli -x set mypasswd\n"
|
||||
" redis-cli get mypasswd\n"
|
||||
" redis-cli -r 100 lpush mylist x\n"
|
||||
"\n"
|
||||
"When no command is given, redis-cli starts in interactive mode.\n"
|
||||
"Type \"help\" in interactive mode for information on available commands.\n"
|
||||
"\n",
|
||||
version);
|
||||
sdsfree(version);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -406,7 +612,9 @@ static void repl() {
|
||||
sds *argv;
|
||||
|
||||
config.interactive = 1;
|
||||
while((line = linenoise("redis> ")) != NULL) {
|
||||
linenoiseSetCompletionCallback(completionCallback);
|
||||
|
||||
while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
argv = sdssplitargs(line,&argc);
|
||||
linenoiseHistoryAdd(line);
|
||||
@@ -419,14 +627,18 @@ static void repl() {
|
||||
strcasecmp(argv[0],"exit") == 0)
|
||||
{
|
||||
exit(0);
|
||||
} else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
|
||||
sdsfree(config.hostip);
|
||||
config.hostip = sdsnew(argv[1]);
|
||||
config.hostport = atoi(argv[2]);
|
||||
cliConnect(1);
|
||||
} else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
|
||||
linenoiseClearScreen();
|
||||
} else {
|
||||
long long start_time = mstime(), elapsed;
|
||||
|
||||
if (cliSendCommand(argc,argv,1) != REDIS_OK) {
|
||||
printf("Reconnecting... ");
|
||||
fflush(stdout);
|
||||
if (cliConnect(1) != REDIS_OK) exit(1);
|
||||
printf("OK\n");
|
||||
cliConnect(1);
|
||||
|
||||
/* If we still cannot send the command,
|
||||
* print error and abort. */
|
||||
@@ -466,7 +678,7 @@ static int noninteractive(int argc, char **argv) {
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
|
||||
config.hostip = "127.0.0.1";
|
||||
config.hostip = sdsnew("127.0.0.1");
|
||||
config.hostport = 6379;
|
||||
config.hostsocket = NULL;
|
||||
config.repeat = 1;
|
||||
@@ -475,12 +687,12 @@ int main(int argc, char **argv) {
|
||||
config.shutdown = 0;
|
||||
config.monitor_mode = 0;
|
||||
config.pubsub_mode = 0;
|
||||
config.raw_output = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.historyfile = NULL;
|
||||
config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
|
||||
config.mb_sep = '\n';
|
||||
config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL);
|
||||
config.mb_delim = sdsnew("\n");
|
||||
cliInitHelp();
|
||||
|
||||
if (getenv("HOME") != NULL) {
|
||||
config.historyfile = malloc(256);
|
||||
|
||||
+46
-20
@@ -74,10 +74,14 @@ struct redisCommand readonlyCommandTable[] = {
|
||||
{"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0},
|
||||
{"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0},
|
||||
{"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"substr",substrCommand,4,0,NULL,1,1,1},
|
||||
{"strlen",strlenCommand,2,0,NULL,1,1,1},
|
||||
{"del",delCommand,-2,0,NULL,0,0,0},
|
||||
{"exists",existsCommand,2,0,NULL,1,1,1},
|
||||
{"setbit",setbitCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"getbit",getbitCommand,3,0,NULL,1,1,1},
|
||||
{"setrange",setrangeCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"getrange",getrangeCommand,4,0,NULL,1,1,1},
|
||||
{"substr",getrangeCommand,4,0,NULL,1,1,1},
|
||||
{"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"mget",mgetCommand,-2,0,NULL,1,-1,1},
|
||||
@@ -89,6 +93,7 @@ struct redisCommand readonlyCommandTable[] = {
|
||||
{"rpop",rpopCommand,2,0,NULL,1,1,1},
|
||||
{"lpop",lpopCommand,2,0,NULL,1,1,1},
|
||||
{"brpop",brpopCommand,-3,0,NULL,1,1,1},
|
||||
{"brpoplpush",brpoplpushCommand,4,REDIS_CMD_DENYOOM,NULL,1,2,1},
|
||||
{"blpop",blpopCommand,-3,0,NULL,1,1,1},
|
||||
{"llen",llenCommand,2,0,NULL,1,1,1},
|
||||
{"lindex",lindexCommand,3,0,NULL,1,1,1},
|
||||
@@ -96,7 +101,7 @@ struct redisCommand readonlyCommandTable[] = {
|
||||
{"lrange",lrangeCommand,4,0,NULL,1,1,1},
|
||||
{"ltrim",ltrimCommand,4,0,NULL,1,1,1},
|
||||
{"lrem",lremCommand,4,0,NULL,1,1,1},
|
||||
{"rpoplpush",rpoplpushcommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
|
||||
{"rpoplpush",rpoplpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
|
||||
{"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"srem",sremCommand,3,0,NULL,1,1,1},
|
||||
{"smove",smoveCommand,4,0,NULL,1,2,1},
|
||||
@@ -188,11 +193,13 @@ struct redisCommand readonlyCommandTable[] = {
|
||||
/*============================ Utility functions ============================ */
|
||||
|
||||
void redisLog(int level, const char *fmt, ...) {
|
||||
const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING };
|
||||
const char *c = ".-*#";
|
||||
time_t now = time(NULL);
|
||||
va_list ap;
|
||||
FILE *fp;
|
||||
char *c = ".-*#";
|
||||
char buf[64];
|
||||
time_t now;
|
||||
char msg[REDIS_MAX_LOGMSG_LEN];
|
||||
|
||||
if (level < server.verbosity) return;
|
||||
|
||||
@@ -200,15 +207,16 @@ void redisLog(int level, const char *fmt, ...) {
|
||||
if (!fp) return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
now = time(NULL);
|
||||
strftime(buf,64,"%d %b %H:%M:%S",localtime(&now));
|
||||
fprintf(fp,"[%d] %s %c ",(int)getpid(),buf,c[level]);
|
||||
vfprintf(fp, fmt, ap);
|
||||
fprintf(fp,"\n");
|
||||
fflush(fp);
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
strftime(buf,sizeof(buf),"%d %b %H:%M:%S",localtime(&now));
|
||||
fprintf(fp,"[%d] %s %c %s\n",(int)getpid(),buf,c[level],msg);
|
||||
fflush(fp);
|
||||
|
||||
if (server.logfile) fclose(fp);
|
||||
|
||||
if (server.syslog_enabled) syslog(syslogLevelMap[level], "%s", msg);
|
||||
}
|
||||
|
||||
/* Redis generally does not try to recover from out of memory conditions
|
||||
@@ -572,7 +580,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
|
||||
/* Close connections of timedout clients */
|
||||
if ((server.maxidletime && !(loops % 100)) || server.blpop_blocked_clients)
|
||||
if ((server.maxidletime && !(loops % 100)) || server.bpop_blocked_clients)
|
||||
closeTimedoutClients();
|
||||
|
||||
/* Check if a background saving or AOF rewrite in progress terminated */
|
||||
@@ -645,15 +653,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
* for ready file descriptors. */
|
||||
void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
REDIS_NOTUSED(eventLoop);
|
||||
listNode *ln;
|
||||
redisClient *c;
|
||||
|
||||
/* Awake clients that got all the swapped keys they requested */
|
||||
if (server.vm_enabled && listLength(server.io_ready_clients)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.io_ready_clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *c = ln->value;
|
||||
c = ln->value;
|
||||
struct redisCommand *cmd;
|
||||
|
||||
/* Resume the client. */
|
||||
@@ -671,6 +680,19 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
processInputBuffer(c);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to process pending commands for clients that were just unblocked. */
|
||||
while (listLength(server.unblocked_clients)) {
|
||||
ln = listFirst(server.unblocked_clients);
|
||||
redisAssert(ln != NULL);
|
||||
c = ln->value;
|
||||
listDelNode(server.unblocked_clients,ln);
|
||||
|
||||
/* Process remaining data in the input buffer. */
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0)
|
||||
processInputBuffer(c);
|
||||
}
|
||||
|
||||
/* Write the AOF buffer on disk */
|
||||
flushAppendOnlyFile();
|
||||
}
|
||||
@@ -743,6 +765,9 @@ void initServerConfig() {
|
||||
server.saveparams = NULL;
|
||||
server.loading = 0;
|
||||
server.logfile = NULL; /* NULL = log on standard output */
|
||||
server.syslog_enabled = 0;
|
||||
server.syslog_ident = zstrdup("redis");
|
||||
server.syslog_facility = LOG_LOCAL0;
|
||||
server.glueoutputbuf = 1;
|
||||
server.daemonize = 0;
|
||||
server.appendonly = 0;
|
||||
@@ -758,7 +783,7 @@ void initServerConfig() {
|
||||
server.rdbcompression = 1;
|
||||
server.activerehashing = 1;
|
||||
server.maxclients = 0;
|
||||
server.blpop_blocked_clients = 0;
|
||||
server.bpop_blocked_clients = 0;
|
||||
server.maxmemory = 0;
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
|
||||
server.maxmemory_samples = 3;
|
||||
@@ -813,15 +838,16 @@ void initServer() {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
setupSigSegvAction();
|
||||
|
||||
server.mainthread = pthread_self();
|
||||
server.devnull = fopen("/dev/null","w");
|
||||
if (server.devnull == NULL) {
|
||||
redisLog(REDIS_WARNING, "Can't open /dev/null: %s", server.neterr);
|
||||
exit(1);
|
||||
if (server.syslog_enabled) {
|
||||
openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
|
||||
server.syslog_facility);
|
||||
}
|
||||
|
||||
server.mainthread = pthread_self();
|
||||
server.clients = listCreate();
|
||||
server.slaves = listCreate();
|
||||
server.monitors = listCreate();
|
||||
server.unblocked_clients = listCreate();
|
||||
createSharedObjects();
|
||||
server.el = aeCreateEventLoop();
|
||||
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
|
||||
@@ -1174,7 +1200,7 @@ sds genRedisInfoString(void) {
|
||||
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
listLength(server.slaves),
|
||||
server.blpop_blocked_clients,
|
||||
server.bpop_blocked_clients,
|
||||
zmalloc_used_memory(),
|
||||
hmem,
|
||||
zmalloc_get_rss(),
|
||||
|
||||
+38
-18
@@ -17,6 +17,7 @@
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <pthread.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#include "ae.h" /* Event driven programming library */
|
||||
#include "sds.h" /* Dynamic safe strings */
|
||||
@@ -47,6 +48,7 @@
|
||||
#define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */
|
||||
#define REDIS_SHARED_INTEGERS 10000
|
||||
#define REDIS_REPLY_CHUNK_BYTES (5*1500) /* 5 TCP packets with default MTU */
|
||||
#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */
|
||||
|
||||
/* If more then REDIS_WRITEV_THRESHOLD write packets are pending use writev */
|
||||
#define REDIS_WRITEV_THRESHOLD 3
|
||||
@@ -194,9 +196,9 @@
|
||||
/* Zip structure related defaults */
|
||||
#define REDIS_HASH_MAX_ZIPMAP_ENTRIES 64
|
||||
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512
|
||||
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024
|
||||
#define REDIS_LIST_MAX_ZIPLIST_VALUE 32
|
||||
#define REDIS_SET_MAX_INTSET_ENTRIES 4096
|
||||
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
|
||||
#define REDIS_LIST_MAX_ZIPLIST_VALUE 64
|
||||
#define REDIS_SET_MAX_INTSET_ENTRIES 512
|
||||
|
||||
/* Sets operations codes */
|
||||
#define REDIS_OP_UNION 0
|
||||
@@ -293,6 +295,16 @@ typedef struct multiState {
|
||||
int count; /* Total number of MULTI commands */
|
||||
} multiState;
|
||||
|
||||
typedef struct blockingState {
|
||||
robj **keys; /* The key we are waiting to terminate a blocking
|
||||
* operation such as BLPOP. Otherwise NULL. */
|
||||
int count; /* Number of blocking keys */
|
||||
time_t timeout; /* Blocking operation timeout. If UNIX current time
|
||||
* is >= timeout then the operation timed out. */
|
||||
robj *target; /* The key that should receive the element,
|
||||
* for BRPOPLPUSH. */
|
||||
} blockingState;
|
||||
|
||||
/* With multiplexing we need to take per-clinet state.
|
||||
* Clients are taken in a liked list. */
|
||||
typedef struct redisClient {
|
||||
@@ -316,11 +328,7 @@ typedef struct redisClient {
|
||||
long repldboff; /* replication DB file offset */
|
||||
off_t repldbsize; /* replication DB file size */
|
||||
multiState mstate; /* MULTI/EXEC state */
|
||||
robj **blocking_keys; /* The key we are waiting to terminate a blocking
|
||||
* operation such as BLPOP. Otherwise NULL. */
|
||||
int blocking_keys_num; /* Number of blocking keys */
|
||||
time_t blockingto; /* Blocking operation timeout. If UNIX current time
|
||||
* is >= blockingto then the operation timed out. */
|
||||
blockingState bpop; /* blocking state */
|
||||
list *io_keys; /* Keys this client is waiting to be loaded from the
|
||||
* swap file in order to continue. */
|
||||
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
|
||||
@@ -402,6 +410,9 @@ struct redisServer {
|
||||
struct saveparam *saveparams;
|
||||
int saveparamslen;
|
||||
char *logfile;
|
||||
int syslog_enabled;
|
||||
char *syslog_ident;
|
||||
int syslog_facility;
|
||||
char *dbfilename;
|
||||
char *appendfilename;
|
||||
char *requirepass;
|
||||
@@ -427,8 +438,9 @@ struct redisServer {
|
||||
int maxmemory_policy;
|
||||
int maxmemory_samples;
|
||||
/* Blocked clients */
|
||||
unsigned int blpop_blocked_clients;
|
||||
unsigned int bpop_blocked_clients;
|
||||
unsigned int vm_blocked_clients;
|
||||
list *unblocked_clients;
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
@@ -481,7 +493,6 @@ struct redisServer {
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
/* Misc */
|
||||
FILE *devnull;
|
||||
unsigned lruclock:22; /* clock incrementing every minute, for LRU */
|
||||
unsigned lruclock_padding:10;
|
||||
};
|
||||
@@ -640,6 +651,8 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReplyBulk(redisClient *c, robj *obj);
|
||||
void addReplyBulkCString(redisClient *c, char *s);
|
||||
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len);
|
||||
void addReplyBulkLongLong(redisClient *c, long long ll);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReply(redisClient *c, robj *obj);
|
||||
void addReplySds(redisClient *c, sds s);
|
||||
@@ -743,8 +756,8 @@ int rdbSaveBackground(char *filename);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename);
|
||||
int rdbSaveObject(FILE *fp, robj *o);
|
||||
off_t rdbSavedObjectPages(robj *o, FILE *fp);
|
||||
off_t rdbSavedObjectLen(robj *o, FILE *fp);
|
||||
off_t rdbSavedObjectLen(robj *o);
|
||||
off_t rdbSavedObjectPages(robj *o);
|
||||
robj *rdbLoadObject(int type, FILE *fp);
|
||||
void backgroundSaveDoneHandler(int statloc);
|
||||
|
||||
@@ -812,8 +825,9 @@ int setTypeRemove(robj *subject, robj *value);
|
||||
int setTypeIsMember(robj *subject, robj *value);
|
||||
setTypeIterator *setTypeInitIterator(robj *subject);
|
||||
void setTypeReleaseIterator(setTypeIterator *si);
|
||||
robj *setTypeNext(setTypeIterator *si);
|
||||
robj *setTypeRandomElement(robj *subject);
|
||||
int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele);
|
||||
robj *setTypeNextObject(setTypeIterator *si);
|
||||
int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele);
|
||||
unsigned long setTypeSize(robj *subject);
|
||||
void setTypeConvert(robj *subject, int enc);
|
||||
|
||||
@@ -821,7 +835,8 @@ void setTypeConvert(robj *subject, int enc);
|
||||
void convertToRealHash(robj *o);
|
||||
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
|
||||
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2);
|
||||
robj *hashTypeGet(robj *o, robj *key);
|
||||
int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v, unsigned int *vlen);
|
||||
robj *hashTypeGetObject(robj *o, robj *key);
|
||||
int hashTypeExists(robj *o, robj *key);
|
||||
int hashTypeSet(robj *o, robj *key, robj *value);
|
||||
int hashTypeDelete(robj *o, robj *key);
|
||||
@@ -829,7 +844,8 @@ unsigned long hashTypeLength(robj *o);
|
||||
hashTypeIterator *hashTypeInitIterator(robj *subject);
|
||||
void hashTypeReleaseIterator(hashTypeIterator *hi);
|
||||
int hashTypeNext(hashTypeIterator *hi);
|
||||
robj *hashTypeCurrent(hashTypeIterator *hi, int what);
|
||||
int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char **v, unsigned int *vlen);
|
||||
robj *hashTypeCurrentObject(hashTypeIterator *hi, int what);
|
||||
robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key);
|
||||
|
||||
/* Pub / Sub */
|
||||
@@ -886,6 +902,10 @@ void setexCommand(redisClient *c);
|
||||
void getCommand(redisClient *c);
|
||||
void delCommand(redisClient *c);
|
||||
void existsCommand(redisClient *c);
|
||||
void setbitCommand(redisClient *c);
|
||||
void getbitCommand(redisClient *c);
|
||||
void setrangeCommand(redisClient *c);
|
||||
void getrangeCommand(redisClient *c);
|
||||
void incrCommand(redisClient *c);
|
||||
void decrCommand(redisClient *c);
|
||||
void incrbyCommand(redisClient *c);
|
||||
@@ -933,7 +953,7 @@ void flushdbCommand(redisClient *c);
|
||||
void flushallCommand(redisClient *c);
|
||||
void sortCommand(redisClient *c);
|
||||
void lremCommand(redisClient *c);
|
||||
void rpoplpushcommand(redisClient *c);
|
||||
void rpoplpushCommand(redisClient *c);
|
||||
void infoCommand(redisClient *c);
|
||||
void mgetCommand(redisClient *c);
|
||||
void monitorCommand(redisClient *c);
|
||||
@@ -962,8 +982,8 @@ void execCommand(redisClient *c);
|
||||
void discardCommand(redisClient *c);
|
||||
void blpopCommand(redisClient *c);
|
||||
void brpopCommand(redisClient *c);
|
||||
void brpoplpushCommand(redisClient *c);
|
||||
void appendCommand(redisClient *c);
|
||||
void substrCommand(redisClient *c);
|
||||
void strlenCommand(redisClient *c);
|
||||
void zrankCommand(redisClient *c);
|
||||
void zrevrankCommand(redisClient *c);
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ void replicationFeedMonitors(list *monitors, int dictid, robj **argv, int argc)
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv,NULL);
|
||||
cmdrepr = sdscatprintf(cmdrepr,"%ld.%ld ",(long)tv.tv_sec,(long)tv.tv_usec);
|
||||
cmdrepr = sdscatprintf(cmdrepr,"%ld.%06ld ",(long)tv.tv_sec,(long)tv.tv_usec);
|
||||
if (dictid != 0) cmdrepr = sdscatprintf(cmdrepr,"(db %d) ", dictid);
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
|
||||
@@ -116,6 +116,25 @@ static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
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, void *t, size_t len) {
|
||||
struct sdshdr *sh;
|
||||
size_t curlen = sdslen(s);
|
||||
|
||||
@@ -49,6 +49,7 @@ 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, void *t, size_t len);
|
||||
sds sdscat(sds s, char *t);
|
||||
sds sdscpylen(sds s, char *t, size_t len);
|
||||
|
||||
+2
-2
@@ -76,7 +76,7 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
|
||||
/* Retrieve value from hash by the field name. This operation
|
||||
* already increases the refcount of the returned object. */
|
||||
initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(struct sdshdr)));
|
||||
o = hashTypeGet(o, &fieldobj);
|
||||
o = hashTypeGetObject(o, &fieldobj);
|
||||
} else {
|
||||
if (o->type != REDIS_STRING) return NULL;
|
||||
|
||||
@@ -222,7 +222,7 @@ void sortCommand(redisClient *c) {
|
||||
} else if (sortval->type == REDIS_SET) {
|
||||
setTypeIterator *si = setTypeInitIterator(sortval);
|
||||
robj *ele;
|
||||
while((ele = setTypeNext(si)) != NULL) {
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
vector[j].obj = ele;
|
||||
vector[j].u.score = 0;
|
||||
vector[j].u.cmpobj = NULL;
|
||||
|
||||
+118
-42
@@ -31,27 +31,56 @@ void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the value from a hash identified by key. Returns either a string
|
||||
* object or NULL if the value cannot be found. The refcount of the object
|
||||
* is always increased by 1 when the value was found. */
|
||||
robj *hashTypeGet(robj *o, robj *key) {
|
||||
robj *value = NULL;
|
||||
/* Get the value from a hash identified by key.
|
||||
*
|
||||
* If the string is found either REDIS_ENCODING_HT or REDIS_ENCODING_ZIPMAP
|
||||
* is returned, and either **objval or **v and *vlen are set accordingly,
|
||||
* so that objects in hash tables are returend as objects and pointers
|
||||
* inside a zipmap are returned as such.
|
||||
*
|
||||
* If the object was not found -1 is returned.
|
||||
*
|
||||
* This function is copy on write friendly as there is no incr/decr
|
||||
* of refcount needed if objects are accessed just for reading operations. */
|
||||
int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v,
|
||||
unsigned int *vlen)
|
||||
{
|
||||
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
|
||||
unsigned char *v;
|
||||
unsigned int vlen;
|
||||
int found;
|
||||
|
||||
key = getDecodedObject(key);
|
||||
if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) {
|
||||
value = createStringObject((char*)v,vlen);
|
||||
}
|
||||
found = zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),v,vlen);
|
||||
decrRefCount(key);
|
||||
if (!found) return -1;
|
||||
} else {
|
||||
dictEntry *de = dictFind(o->ptr,key);
|
||||
if (de != NULL) {
|
||||
value = dictGetEntryVal(de);
|
||||
incrRefCount(value);
|
||||
}
|
||||
if (de == NULL) return -1;
|
||||
*objval = dictGetEntryVal(de);
|
||||
}
|
||||
return o->encoding;
|
||||
}
|
||||
|
||||
/* Higher level function of hashTypeGet() that always returns a Redis
|
||||
* object (either new or with refcount incremented), so that the caller
|
||||
* can retain a reference or call decrRefCount after the usage.
|
||||
*
|
||||
* The lower level function can prevent copy on write so it is
|
||||
* the preferred way of doing read operations. */
|
||||
robj *hashTypeGetObject(robj *o, robj *key) {
|
||||
robj *objval;
|
||||
unsigned char *v;
|
||||
unsigned int vlen;
|
||||
|
||||
int encoding = hashTypeGet(o,key,&objval,&v,&vlen);
|
||||
switch(encoding) {
|
||||
case REDIS_ENCODING_HT:
|
||||
incrRefCount(objval);
|
||||
return objval;
|
||||
case REDIS_ENCODING_ZIPMAP:
|
||||
objval = createStringObject((char*)v,vlen);
|
||||
return objval;
|
||||
default: return NULL;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Test if the key exists in the given hash. Returns 1 if the key
|
||||
@@ -156,24 +185,50 @@ int hashTypeNext(hashTypeIterator *hi) {
|
||||
}
|
||||
|
||||
/* Get key or value object at current iteration position.
|
||||
* This increases the refcount of the field object by 1. */
|
||||
robj *hashTypeCurrent(hashTypeIterator *hi, int what) {
|
||||
robj *o;
|
||||
* The returned item differs with the hash object encoding:
|
||||
* - When encoding is REDIS_ENCODING_HT, the objval pointer is populated
|
||||
* with the original object.
|
||||
* - When encoding is REDIS_ENCODING_ZIPMAP, a pointer to the string and
|
||||
* its length is retunred populating the v and vlen pointers.
|
||||
* This function is copy on write friendly as accessing objects in read only
|
||||
* does not require writing to any memory page.
|
||||
*
|
||||
* The function returns the encoding of the object, so that the caller
|
||||
* can underestand if the key or value was returned as object or C string. */
|
||||
int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char **v, unsigned int *vlen) {
|
||||
if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
|
||||
if (what & REDIS_HASH_KEY) {
|
||||
o = createStringObject((char*)hi->zk,hi->zklen);
|
||||
*v = hi->zk;
|
||||
*vlen = hi->zklen;
|
||||
} else {
|
||||
o = createStringObject((char*)hi->zv,hi->zvlen);
|
||||
*v = hi->zv;
|
||||
*vlen = hi->zvlen;
|
||||
}
|
||||
} else {
|
||||
if (what & REDIS_HASH_KEY) {
|
||||
o = dictGetEntryKey(hi->de);
|
||||
} else {
|
||||
o = dictGetEntryVal(hi->de);
|
||||
}
|
||||
incrRefCount(o);
|
||||
if (what & REDIS_HASH_KEY)
|
||||
*objval = dictGetEntryKey(hi->de);
|
||||
else
|
||||
*objval = dictGetEntryVal(hi->de);
|
||||
}
|
||||
return hi->encoding;
|
||||
}
|
||||
|
||||
/* A non copy-on-write friendly but higher level version of hashTypeCurrent()
|
||||
* that always returns an object with refcount incremented by one (or a new
|
||||
* object), so it's up to the caller to decrRefCount() the object if no
|
||||
* reference is retained. */
|
||||
robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) {
|
||||
robj *obj;
|
||||
unsigned char *v = NULL;
|
||||
unsigned int vlen = 0;
|
||||
int encoding = hashTypeCurrent(hi,what,&obj,&v,&vlen);
|
||||
|
||||
if (encoding == REDIS_ENCODING_HT) {
|
||||
incrRefCount(obj);
|
||||
return obj;
|
||||
} else {
|
||||
return createStringObject((char*)v,vlen);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
|
||||
@@ -270,7 +325,7 @@ void hincrbyCommand(redisClient *c) {
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
if ((current = hashTypeGet(o,c->argv[2])) != NULL) {
|
||||
if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
|
||||
if (getLongLongFromObjectOrReply(c,current,&value,
|
||||
"hash value is not an integer") != REDIS_OK) {
|
||||
decrRefCount(current);
|
||||
@@ -293,20 +348,29 @@ void hincrbyCommand(redisClient *c) {
|
||||
|
||||
void hgetCommand(redisClient *c) {
|
||||
robj *o, *value;
|
||||
unsigned char *v;
|
||||
unsigned int vlen;
|
||||
int encoding;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,o,REDIS_HASH)) return;
|
||||
|
||||
if ((value = hashTypeGet(o,c->argv[2])) != NULL) {
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
if ((encoding = hashTypeGet(o,c->argv[2],&value,&v,&vlen)) != -1) {
|
||||
if (encoding == REDIS_ENCODING_HT)
|
||||
addReplyBulk(c,value);
|
||||
else
|
||||
addReplyBulkCBuffer(c,v,vlen);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
}
|
||||
}
|
||||
|
||||
void hmgetCommand(redisClient *c) {
|
||||
int i;
|
||||
int i, encoding;
|
||||
robj *o, *value;
|
||||
unsigned char *v;
|
||||
unsigned int vlen;
|
||||
|
||||
o = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (o != NULL && o->type != REDIS_HASH) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
@@ -318,9 +382,12 @@ void hmgetCommand(redisClient *c) {
|
||||
* an empty hash. The reply should then be a series of NULLs. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (i = 2; i < c->argc; i++) {
|
||||
if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) {
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
if (o != NULL &&
|
||||
(encoding = hashTypeGet(o,c->argv[i],&value,&v,&vlen)) != -1) {
|
||||
if (encoding == REDIS_ENCODING_HT)
|
||||
addReplyBulk(c,value);
|
||||
else
|
||||
addReplyBulkCBuffer(c,v,vlen);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
}
|
||||
@@ -351,7 +418,7 @@ void hlenCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
void genericHgetallCommand(redisClient *c, int flags) {
|
||||
robj *o, *obj;
|
||||
robj *o;
|
||||
unsigned long count = 0;
|
||||
hashTypeIterator *hi;
|
||||
void *replylen = NULL;
|
||||
@@ -362,16 +429,25 @@ void genericHgetallCommand(redisClient *c, int flags) {
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != REDIS_ERR) {
|
||||
robj *obj;
|
||||
unsigned char *v = NULL;
|
||||
unsigned int vlen = 0;
|
||||
int encoding;
|
||||
|
||||
if (flags & REDIS_HASH_KEY) {
|
||||
obj = hashTypeCurrent(hi,REDIS_HASH_KEY);
|
||||
addReplyBulk(c,obj);
|
||||
decrRefCount(obj);
|
||||
encoding = hashTypeCurrent(hi,REDIS_HASH_KEY,&obj,&v,&vlen);
|
||||
if (encoding == REDIS_ENCODING_HT)
|
||||
addReplyBulk(c,obj);
|
||||
else
|
||||
addReplyBulkCBuffer(c,v,vlen);
|
||||
count++;
|
||||
}
|
||||
if (flags & REDIS_HASH_VALUE) {
|
||||
obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
|
||||
addReplyBulk(c,obj);
|
||||
decrRefCount(obj);
|
||||
encoding = hashTypeCurrent(hi,REDIS_HASH_VALUE,&obj,&v,&vlen);
|
||||
if (encoding == REDIS_ENCODING_HT)
|
||||
addReplyBulk(c,obj);
|
||||
else
|
||||
addReplyBulkCBuffer(c,v,vlen);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
+171
-74
@@ -472,12 +472,11 @@ void rpopCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
void lrangeCommand(redisClient *c) {
|
||||
robj *o, *value;
|
||||
robj *o;
|
||||
int start = atoi(c->argv[2]->ptr);
|
||||
int end = atoi(c->argv[3]->ptr);
|
||||
int llen;
|
||||
int rangelen, j;
|
||||
listTypeEntry entry;
|
||||
int rangelen;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|
||||
|| checkType(c,o,REDIS_LIST)) return;
|
||||
@@ -499,14 +498,31 @@ void lrangeCommand(redisClient *c) {
|
||||
|
||||
/* Return the result in form of a multi-bulk reply */
|
||||
addReplyMultiBulkLen(c,rangelen);
|
||||
listTypeIterator *li = listTypeInitIterator(o,start,REDIS_TAIL);
|
||||
for (j = 0; j < rangelen; j++) {
|
||||
redisAssert(listTypeNext(li,&entry));
|
||||
value = listTypeGet(&entry);
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *p = ziplistIndex(o->ptr,start);
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vlong;
|
||||
|
||||
while(rangelen--) {
|
||||
ziplistGet(p,&vstr,&vlen,&vlong);
|
||||
if (vstr) {
|
||||
addReplyBulkCBuffer(c,vstr,vlen);
|
||||
} else {
|
||||
addReplyBulkLongLong(c,vlong);
|
||||
}
|
||||
p = ziplistNext(o->ptr,p);
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
listNode *ln = listIndex(o->ptr,start);
|
||||
|
||||
while(rangelen--) {
|
||||
addReplyBulk(c,ln->value);
|
||||
ln = ln->next;
|
||||
}
|
||||
} else {
|
||||
redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
}
|
||||
|
||||
void ltrimCommand(redisClient *c) {
|
||||
@@ -605,20 +621,38 @@ void lremCommand(redisClient *c) {
|
||||
|
||||
/* This is the semantic of this command:
|
||||
* RPOPLPUSH srclist dstlist:
|
||||
* IF LLEN(srclist) > 0
|
||||
* element = RPOP srclist
|
||||
* LPUSH dstlist element
|
||||
* RETURN element
|
||||
* ELSE
|
||||
* RETURN nil
|
||||
* END
|
||||
* IF LLEN(srclist) > 0
|
||||
* element = RPOP srclist
|
||||
* LPUSH dstlist element
|
||||
* RETURN element
|
||||
* ELSE
|
||||
* RETURN nil
|
||||
* END
|
||||
* END
|
||||
*
|
||||
* The idea is to be able to get an element from a list in a reliable way
|
||||
* since the element is not just returned but pushed against another list
|
||||
* as well. This command was originally proposed by Ezra Zygmuntowicz.
|
||||
*/
|
||||
void rpoplpushcommand(redisClient *c) {
|
||||
|
||||
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
|
||||
if (!handleClientsWaitingListPush(c,dstkey,value)) {
|
||||
/* Create the list if the key does not exist */
|
||||
if (!dstobj) {
|
||||
dstobj = createZiplistObject();
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
} else {
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
listTypePush(dstobj,value,REDIS_HEAD);
|
||||
}
|
||||
|
||||
/* Always send the pushed value to the client. */
|
||||
addReplyBulk(c,value);
|
||||
}
|
||||
|
||||
void rpoplpushCommand(redisClient *c) {
|
||||
robj *sobj, *value;
|
||||
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,sobj,REDIS_LIST)) return;
|
||||
@@ -629,20 +663,7 @@ void rpoplpushcommand(redisClient *c) {
|
||||
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
|
||||
if (dobj && checkType(c,dobj,REDIS_LIST)) return;
|
||||
value = listTypePop(sobj,REDIS_TAIL);
|
||||
|
||||
/* Add the element to the target list (unless it's directly
|
||||
* passed to some BLPOP-ing client */
|
||||
if (!handleClientsWaitingListPush(c,c->argv[2],value)) {
|
||||
/* Create the list if the key does not exist */
|
||||
if (!dobj) {
|
||||
dobj = createZiplistObject();
|
||||
dbAdd(c->db,c->argv[2],dobj);
|
||||
}
|
||||
listTypePush(dobj,value,REDIS_HEAD);
|
||||
}
|
||||
|
||||
/* Send the element to the client as reply as well */
|
||||
addReplyBulk(c,value);
|
||||
rpoplpushHandlePush(c,c->argv[2],dobj,value);
|
||||
|
||||
/* listTypePop returns an object with its refcount incremented */
|
||||
decrRefCount(value);
|
||||
@@ -689,17 +710,23 @@ void rpoplpushcommand(redisClient *c) {
|
||||
|
||||
/* Set a client in blocking mode for the specified key, with the specified
|
||||
* timeout */
|
||||
void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout) {
|
||||
void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj *target) {
|
||||
dictEntry *de;
|
||||
list *l;
|
||||
int j;
|
||||
|
||||
c->blocking_keys = zmalloc(sizeof(robj*)*numkeys);
|
||||
c->blocking_keys_num = numkeys;
|
||||
c->blockingto = timeout;
|
||||
c->bpop.keys = zmalloc(sizeof(robj*)*numkeys);
|
||||
c->bpop.count = numkeys;
|
||||
c->bpop.timeout = timeout;
|
||||
c->bpop.target = target;
|
||||
|
||||
if (target != NULL) {
|
||||
incrRefCount(target);
|
||||
}
|
||||
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
/* Add the key in the client structure, to map clients -> keys */
|
||||
c->blocking_keys[j] = keys[j];
|
||||
c->bpop.keys[j] = keys[j];
|
||||
incrRefCount(keys[j]);
|
||||
|
||||
/* And in the other "side", to map keys -> clients */
|
||||
@@ -719,7 +746,7 @@ void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout) {
|
||||
}
|
||||
/* Mark the client as a blocked client */
|
||||
c->flags |= REDIS_BLOCKED;
|
||||
server.blpop_blocked_clients++;
|
||||
server.bpop_blocked_clients++;
|
||||
}
|
||||
|
||||
/* Unblock a client that's waiting in a blocking operation such as BLPOP */
|
||||
@@ -728,30 +755,27 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
list *l;
|
||||
int j;
|
||||
|
||||
redisAssert(c->blocking_keys != NULL);
|
||||
redisAssert(c->bpop.keys != NULL);
|
||||
/* The client may wait for multiple keys, so unblock it for every key. */
|
||||
for (j = 0; j < c->blocking_keys_num; j++) {
|
||||
for (j = 0; j < c->bpop.count; j++) {
|
||||
/* Remove this client from the list of clients waiting for this key. */
|
||||
de = dictFind(c->db->blocking_keys,c->blocking_keys[j]);
|
||||
de = dictFind(c->db->blocking_keys,c->bpop.keys[j]);
|
||||
redisAssert(de != NULL);
|
||||
l = dictGetEntryVal(de);
|
||||
listDelNode(l,listSearchKey(l,c));
|
||||
/* If the list is empty we need to remove it to avoid wasting memory */
|
||||
if (listLength(l) == 0)
|
||||
dictDelete(c->db->blocking_keys,c->blocking_keys[j]);
|
||||
decrRefCount(c->blocking_keys[j]);
|
||||
dictDelete(c->db->blocking_keys,c->bpop.keys[j]);
|
||||
decrRefCount(c->bpop.keys[j]);
|
||||
}
|
||||
|
||||
/* Cleanup the client structure */
|
||||
zfree(c->blocking_keys);
|
||||
c->blocking_keys = NULL;
|
||||
zfree(c->bpop.keys);
|
||||
c->bpop.keys = NULL;
|
||||
c->bpop.target = NULL;
|
||||
c->flags &= (~REDIS_BLOCKED);
|
||||
server.blpop_blocked_clients--;
|
||||
/* We want to process data if there is some command waiting
|
||||
* in the input buffer. Note that this is safe even if
|
||||
* unblockClientWaitingData() gets called from freeClient() because
|
||||
* freeClient() will be smart enough to call this function
|
||||
* *after* c->querybuf was set to NULL. */
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0) processInputBuffer(c);
|
||||
server.bpop_blocked_clients--;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
|
||||
/* This should be called from any function PUSHing into lists.
|
||||
@@ -767,39 +791,81 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
|
||||
struct dictEntry *de;
|
||||
redisClient *receiver;
|
||||
list *l;
|
||||
int numclients;
|
||||
list *clients;
|
||||
listNode *ln;
|
||||
robj *dstkey, *dstobj;
|
||||
|
||||
de = dictFind(c->db->blocking_keys,key);
|
||||
if (de == NULL) return 0;
|
||||
l = dictGetEntryVal(de);
|
||||
ln = listFirst(l);
|
||||
redisAssert(ln != NULL);
|
||||
receiver = ln->value;
|
||||
clients = dictGetEntryVal(de);
|
||||
numclients = listLength(clients);
|
||||
|
||||
addReplyMultiBulkLen(receiver,2);
|
||||
addReplyBulk(receiver,key);
|
||||
addReplyBulk(receiver,ele);
|
||||
unblockClientWaitingData(receiver);
|
||||
return 1;
|
||||
/* Try to handle the push as long as there are clients waiting for a push.
|
||||
* Note that "numclients" is used because the list of clients waiting for a
|
||||
* push on "key" is deleted by unblockClient() when empty.
|
||||
*
|
||||
* This loop will have more than 1 iteration when there is a BRPOPLPUSH
|
||||
* that cannot push the target list because it does not contain a list. If
|
||||
* this happens, it simply tries the next client waiting for a push. */
|
||||
while (numclients--) {
|
||||
ln = listFirst(clients);
|
||||
redisAssert(ln != NULL);
|
||||
receiver = ln->value;
|
||||
dstkey = receiver->bpop.target;
|
||||
|
||||
/* This should remove the first element of the "clients" list. */
|
||||
unblockClientWaitingData(receiver);
|
||||
redisAssert(ln != listFirst(clients));
|
||||
|
||||
if (dstkey == NULL) {
|
||||
/* BRPOP/BLPOP */
|
||||
addReplyMultiBulkLen(receiver,2);
|
||||
addReplyBulk(receiver,key);
|
||||
addReplyBulk(receiver,ele);
|
||||
return 1;
|
||||
} else {
|
||||
/* BRPOPLPUSH */
|
||||
dstobj = lookupKeyWrite(receiver->db,dstkey);
|
||||
if (dstobj && checkType(receiver,dstobj,REDIS_LIST)) {
|
||||
decrRefCount(dstkey);
|
||||
} else {
|
||||
rpoplpushHandlePush(receiver,dstkey,dstobj,ele);
|
||||
decrRefCount(dstkey);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) {
|
||||
long tval;
|
||||
|
||||
if (getLongFromObjectOrReply(c,object,&tval,
|
||||
"timeout is not an integer or out of range") != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (tval < 0) {
|
||||
addReplyError(c,"timeout is negative");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (tval > 0) tval += time(NULL);
|
||||
*timeout = tval;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Blocking RPOP/LPOP */
|
||||
void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
robj *o;
|
||||
long long lltimeout;
|
||||
time_t timeout;
|
||||
int j;
|
||||
|
||||
/* Make sure timeout is an integer value */
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[c->argc-1],&lltimeout,
|
||||
"timeout is not an integer") != REDIS_OK) return;
|
||||
|
||||
/* Make sure the timeout is not negative */
|
||||
if (lltimeout < 0) {
|
||||
addReplyError(c,"timeout is negative");
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout) != REDIS_OK)
|
||||
return;
|
||||
}
|
||||
|
||||
for (j = 1; j < c->argc-1; j++) {
|
||||
o = lookupKeyWrite(c->db,c->argv[j]);
|
||||
@@ -829,11 +895,13 @@ void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
* because it is... */
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyBulk(c,argv[1]);
|
||||
|
||||
popGenericCommand(c,where);
|
||||
|
||||
/* Fix the client structure with the original stuff */
|
||||
c->argv = orig_argv;
|
||||
c->argc = orig_argc;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -848,9 +916,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
}
|
||||
|
||||
/* If the list is empty or the key does not exists we must block */
|
||||
timeout = lltimeout;
|
||||
if (timeout > 0) timeout += time(NULL);
|
||||
blockForKeys(c,c->argv+1,c->argc-2,timeout);
|
||||
blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL);
|
||||
}
|
||||
|
||||
void blpopCommand(redisClient *c) {
|
||||
@@ -860,3 +926,34 @@ void blpopCommand(redisClient *c) {
|
||||
void brpopCommand(redisClient *c) {
|
||||
blockingPopGenericCommand(c,REDIS_TAIL);
|
||||
}
|
||||
|
||||
void brpoplpushCommand(redisClient *c) {
|
||||
time_t timeout;
|
||||
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout) != REDIS_OK)
|
||||
return;
|
||||
|
||||
robj *key = lookupKeyWrite(c->db, c->argv[1]);
|
||||
|
||||
if (key == NULL) {
|
||||
if (c->flags & REDIS_MULTI) {
|
||||
|
||||
/* Blocking against an empty list in a multi state
|
||||
* returns immediately. */
|
||||
addReply(c, shared.nullmultibulk);
|
||||
} else {
|
||||
/* The list is empty and the client blocks. */
|
||||
blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]);
|
||||
}
|
||||
} else {
|
||||
if (key->type != REDIS_LIST) {
|
||||
addReply(c, shared.wrongtypeerr);
|
||||
} else {
|
||||
|
||||
/* The list exists and has elements, so
|
||||
* the regular rpoplpushCommand is executed. */
|
||||
redisAssert(listTypeLength(key) > 0);
|
||||
rpoplpushCommand(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+150
-63
@@ -47,17 +47,17 @@ int setTypeAdd(robj *subject, robj *value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setTypeRemove(robj *subject, robj *value) {
|
||||
int setTypeRemove(robj *setobj, robj *value) {
|
||||
long long llval;
|
||||
if (subject->encoding == REDIS_ENCODING_HT) {
|
||||
if (dictDelete(subject->ptr,value) == DICT_OK) {
|
||||
if (htNeedsResize(subject->ptr)) dictResize(subject->ptr);
|
||||
if (setobj->encoding == REDIS_ENCODING_HT) {
|
||||
if (dictDelete(setobj->ptr,value) == DICT_OK) {
|
||||
if (htNeedsResize(setobj->ptr)) dictResize(setobj->ptr);
|
||||
return 1;
|
||||
}
|
||||
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
|
||||
} else if (setobj->encoding == REDIS_ENCODING_INTSET) {
|
||||
if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
|
||||
uint8_t success;
|
||||
subject->ptr = intsetRemove(subject->ptr,llval,&success);
|
||||
int success;
|
||||
setobj->ptr = intsetRemove(setobj->ptr,llval,&success);
|
||||
if (success) return 1;
|
||||
}
|
||||
} else {
|
||||
@@ -101,40 +101,77 @@ void setTypeReleaseIterator(setTypeIterator *si) {
|
||||
}
|
||||
|
||||
/* Move to the next entry in the set. Returns the object at the current
|
||||
* position, or NULL when the end is reached. This object will have its
|
||||
* refcount incremented, so the caller needs to take care of this. */
|
||||
robj *setTypeNext(setTypeIterator *si) {
|
||||
robj *ret = NULL;
|
||||
* position.
|
||||
*
|
||||
* Since set elements can be internally be stored as redis objects or
|
||||
* simple arrays of integers, setTypeNext returns the encoding of the
|
||||
* set object you are iterating, and will populate the appropriate pointer
|
||||
* (eobj) or (llobj) accordingly.
|
||||
*
|
||||
* When there are no longer elements -1 is returned.
|
||||
* Returned objects ref count is not incremented, so this function is
|
||||
* copy on write friendly. */
|
||||
int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) {
|
||||
if (si->encoding == REDIS_ENCODING_HT) {
|
||||
dictEntry *de = dictNext(si->di);
|
||||
if (de != NULL) {
|
||||
ret = dictGetEntryKey(de);
|
||||
incrRefCount(ret);
|
||||
}
|
||||
if (de == NULL) return -1;
|
||||
*objele = dictGetEntryKey(de);
|
||||
} else if (si->encoding == REDIS_ENCODING_INTSET) {
|
||||
int64_t llval;
|
||||
if (intsetGet(si->subject->ptr,si->ii++,&llval))
|
||||
ret = createStringObjectFromLongLong(llval);
|
||||
if (!intsetGet(si->subject->ptr,si->ii++,llele))
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
return si->encoding;
|
||||
}
|
||||
|
||||
/* The not copy on write friendly version but easy to use version
|
||||
* of setTypeNext() is setTypeNextObject(), returning new objects
|
||||
* or incrementing the ref count of returned objects. So if you don't
|
||||
* retain a pointer to this object you should call decrRefCount() against it.
|
||||
*
|
||||
* This function is the way to go for write operations where COW is not
|
||||
* an issue as the result will be anyway of incrementing the ref count. */
|
||||
robj *setTypeNextObject(setTypeIterator *si) {
|
||||
int64_t intele;
|
||||
robj *objele;
|
||||
int encoding;
|
||||
|
||||
/* Return random element from set. The returned object will always have
|
||||
* an incremented refcount. */
|
||||
robj *setTypeRandomElement(robj *subject) {
|
||||
robj *ret = NULL;
|
||||
if (subject->encoding == REDIS_ENCODING_HT) {
|
||||
dictEntry *de = dictGetRandomKey(subject->ptr);
|
||||
ret = dictGetEntryKey(de);
|
||||
incrRefCount(ret);
|
||||
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
|
||||
long long llval = intsetRandom(subject->ptr);
|
||||
ret = createStringObjectFromLongLong(llval);
|
||||
encoding = setTypeNext(si,&objele,&intele);
|
||||
switch(encoding) {
|
||||
case -1: return NULL;
|
||||
case REDIS_ENCODING_INTSET:
|
||||
return createStringObjectFromLongLong(intele);
|
||||
case REDIS_ENCODING_HT:
|
||||
incrRefCount(objele);
|
||||
return objele;
|
||||
default:
|
||||
redisPanic("Unsupported encoding");
|
||||
}
|
||||
return NULL; /* just to suppress warnings */
|
||||
}
|
||||
|
||||
/* Return random element from a non empty set.
|
||||
* The returned element can be a int64_t value if the set is encoded
|
||||
* as an "intset" blob of integers, or a redis object if the set
|
||||
* is a regular set.
|
||||
*
|
||||
* The caller provides both pointers to be populated with the right
|
||||
* object. The return value of the function is the object->encoding
|
||||
* field of the object and is used by the caller to check if the
|
||||
* int64_t pointer or the redis object pointere was populated.
|
||||
*
|
||||
* When an object is returned (the set was a real set) the ref count
|
||||
* of the object is not incremented so this function can be considered
|
||||
* copy on write friendly. */
|
||||
int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
|
||||
if (setobj->encoding == REDIS_ENCODING_HT) {
|
||||
dictEntry *de = dictGetRandomKey(setobj->ptr);
|
||||
*objele = dictGetEntryKey(de);
|
||||
} else if (setobj->encoding == REDIS_ENCODING_INTSET) {
|
||||
*llele = intsetRandom(setobj->ptr);
|
||||
} else {
|
||||
redisPanic("Unknown set encoding");
|
||||
}
|
||||
return ret;
|
||||
return setobj->encoding;
|
||||
}
|
||||
|
||||
unsigned long setTypeSize(robj *subject) {
|
||||
@@ -150,25 +187,30 @@ unsigned long setTypeSize(robj *subject) {
|
||||
/* Convert the set to specified encoding. The resulting dict (when converting
|
||||
* to a hashtable) is presized to hold the number of elements in the original
|
||||
* set. */
|
||||
void setTypeConvert(robj *subject, int enc) {
|
||||
void setTypeConvert(robj *setobj, int enc) {
|
||||
setTypeIterator *si;
|
||||
robj *element;
|
||||
redisAssert(subject->type == REDIS_SET);
|
||||
redisAssert(setobj->type == REDIS_SET &&
|
||||
setobj->encoding == REDIS_ENCODING_INTSET);
|
||||
|
||||
if (enc == REDIS_ENCODING_HT) {
|
||||
int64_t intele;
|
||||
dict *d = dictCreate(&setDictType,NULL);
|
||||
/* Presize the dict to avoid rehashing */
|
||||
dictExpand(d,intsetLen(subject->ptr));
|
||||
robj *element;
|
||||
|
||||
/* setTypeGet returns a robj with incremented refcount */
|
||||
si = setTypeInitIterator(subject);
|
||||
while ((element = setTypeNext(si)) != NULL)
|
||||
/* Presize the dict to avoid rehashing */
|
||||
dictExpand(d,intsetLen(setobj->ptr));
|
||||
|
||||
/* To add the elements we extract integers and create redis objects */
|
||||
si = setTypeInitIterator(setobj);
|
||||
while (setTypeNext(si,NULL,&intele) != -1) {
|
||||
element = createStringObjectFromLongLong(intele);
|
||||
redisAssert(dictAdd(d,element,NULL) == DICT_OK);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
|
||||
subject->encoding = REDIS_ENCODING_HT;
|
||||
zfree(subject->ptr);
|
||||
subject->ptr = d;
|
||||
setobj->encoding = REDIS_ENCODING_HT;
|
||||
zfree(setobj->ptr);
|
||||
setobj->ptr = d;
|
||||
} else {
|
||||
redisPanic("Unsupported set conversion");
|
||||
}
|
||||
@@ -284,35 +326,38 @@ void scardCommand(redisClient *c) {
|
||||
|
||||
void spopCommand(redisClient *c) {
|
||||
robj *set, *ele;
|
||||
int64_t llele;
|
||||
int encoding;
|
||||
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,set,REDIS_SET)) return;
|
||||
|
||||
ele = setTypeRandomElement(set);
|
||||
if (ele == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
encoding = setTypeRandomElement(set,&ele,&llele);
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
addReplyBulkLongLong(c,llele);
|
||||
set->ptr = intsetRemove(set->ptr,llele,NULL);
|
||||
} else {
|
||||
setTypeRemove(set,ele);
|
||||
addReplyBulk(c,ele);
|
||||
decrRefCount(ele);
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
setTypeRemove(set,ele);
|
||||
}
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
void srandmemberCommand(redisClient *c) {
|
||||
robj *set, *ele;
|
||||
int64_t llele;
|
||||
int encoding;
|
||||
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,set,REDIS_SET)) return;
|
||||
|
||||
ele = setTypeRandomElement(set);
|
||||
if (ele == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
encoding = setTypeRandomElement(set,&ele,&llele);
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
addReplyBulkLongLong(c,llele);
|
||||
} else {
|
||||
addReplyBulk(c,ele);
|
||||
decrRefCount(ele);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,9 +368,11 @@ int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
|
||||
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
|
||||
robj **sets = zmalloc(sizeof(robj*)*setnum);
|
||||
setTypeIterator *si;
|
||||
robj *ele, *dstset = NULL;
|
||||
robj *eleobj, *dstset = NULL;
|
||||
int64_t intobj;
|
||||
void *replylen = NULL;
|
||||
unsigned long j, cardinality = 0;
|
||||
int encoding;
|
||||
|
||||
for (j = 0; j < setnum; j++) {
|
||||
robj *setobj = dstkey ?
|
||||
@@ -371,20 +418,60 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
|
||||
* the element against all the other sets, if at least one set does
|
||||
* not include the element it is discarded */
|
||||
si = setTypeInitIterator(sets[0]);
|
||||
while((ele = setTypeNext(si)) != NULL) {
|
||||
for (j = 1; j < setnum; j++)
|
||||
if (!setTypeIsMember(sets[j],ele)) break;
|
||||
while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
|
||||
for (j = 1; j < setnum; j++) {
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
/* intset with intset is simple... and fast */
|
||||
if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
|
||||
!intsetFind((intset*)sets[j]->ptr,intobj))
|
||||
{
|
||||
break;
|
||||
/* in order to compare an integer with an object we
|
||||
* have to use the generic function, creating an object
|
||||
* for this */
|
||||
} else if (sets[j]->encoding == REDIS_ENCODING_HT) {
|
||||
eleobj = createStringObjectFromLongLong(intobj);
|
||||
if (!setTypeIsMember(sets[j],eleobj)) {
|
||||
decrRefCount(eleobj);
|
||||
break;
|
||||
}
|
||||
decrRefCount(eleobj);
|
||||
}
|
||||
} else if (encoding == REDIS_ENCODING_HT) {
|
||||
/* Optimization... if the source object is integer
|
||||
* encoded AND the target set is an intset, we can get
|
||||
* a much faster path. */
|
||||
if (eleobj->encoding == REDIS_ENCODING_INT &&
|
||||
sets[j]->encoding == REDIS_ENCODING_INTSET &&
|
||||
!intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr))
|
||||
{
|
||||
break;
|
||||
/* else... object to object check is easy as we use the
|
||||
* type agnostic API here. */
|
||||
} else if (!setTypeIsMember(sets[j],eleobj)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Only take action when all sets contain the member */
|
||||
if (j == setnum) {
|
||||
if (!dstkey) {
|
||||
addReplyBulk(c,ele);
|
||||
if (encoding == REDIS_ENCODING_HT)
|
||||
addReplyBulk(c,eleobj);
|
||||
else
|
||||
addReplyBulkLongLong(c,intobj);
|
||||
cardinality++;
|
||||
} else {
|
||||
setTypeAdd(dstset,ele);
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
eleobj = createStringObjectFromLongLong(intobj);
|
||||
setTypeAdd(dstset,eleobj);
|
||||
decrRefCount(eleobj);
|
||||
} else {
|
||||
setTypeAdd(dstset,eleobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
decrRefCount(ele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
|
||||
@@ -452,7 +539,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
if (!sets[j]) continue; /* non existing keys are like empty sets */
|
||||
|
||||
si = setTypeInitIterator(sets[j]);
|
||||
while((ele = setTypeNext(si)) != NULL) {
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
if (op == REDIS_OP_UNION || j == 0) {
|
||||
if (setTypeAdd(dstset,ele)) {
|
||||
cardinality++;
|
||||
@@ -474,7 +561,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
if (!dstkey) {
|
||||
addReplyMultiBulkLen(c,cardinality);
|
||||
si = setTypeInitIterator(dstset);
|
||||
while((ele = setTypeNext(si)) != NULL) {
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
addReplyBulk(c,ele);
|
||||
decrRefCount(ele);
|
||||
}
|
||||
|
||||
+225
-60
@@ -1,9 +1,18 @@
|
||||
#include <limits.h>
|
||||
#include "redis.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* String Commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
static int checkStringLength(redisClient *c, long long size) {
|
||||
if (size > 512*1024*1024) {
|
||||
addReplyError(c,"string exceeds maximum allowed size (512MB)");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
|
||||
int retval;
|
||||
long seconds = 0; /* initialized to avoid an harmness warning */
|
||||
@@ -80,6 +89,204 @@ void getsetCommand(redisClient *c) {
|
||||
removeExpire(c->db,c->argv[1]);
|
||||
}
|
||||
|
||||
static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
|
||||
long long loffset;
|
||||
char *err = "bit offset is not an integer or out of range";
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,o,&loffset,err) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Limit offset to 512MB in bytes */
|
||||
if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
|
||||
{
|
||||
addReplyError(c,err);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
*offset = (size_t)loffset;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void setbitCommand(redisClient *c) {
|
||||
robj *o;
|
||||
char *err = "bit is not an integer or out of range";
|
||||
size_t bitoffset;
|
||||
int byte, bit;
|
||||
int byteval, bitval;
|
||||
long on;
|
||||
|
||||
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
|
||||
return;
|
||||
|
||||
if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != REDIS_OK)
|
||||
return;
|
||||
|
||||
/* Bits can only be set or cleared... */
|
||||
if (on & ~1) {
|
||||
addReplyError(c,err);
|
||||
return;
|
||||
}
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (o == NULL) {
|
||||
o = createObject(REDIS_STRING,sdsempty());
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
} else {
|
||||
if (checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
/* Create a copy when the object is shared or encoded. */
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow sds value to the right length if necessary */
|
||||
byte = bitoffset >> 3;
|
||||
o->ptr = sdsgrowzero(o->ptr,byte+1);
|
||||
|
||||
/* Get current values */
|
||||
byteval = ((char*)o->ptr)[byte];
|
||||
bit = 7 - (bitoffset & 0x7);
|
||||
bitval = byteval & (1 << bit);
|
||||
|
||||
/* Update byte with new bit value and return original value */
|
||||
byteval &= ~(1 << bit);
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
((char*)o->ptr)[byte] = byteval;
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
}
|
||||
|
||||
void getbitCommand(redisClient *c) {
|
||||
robj *o;
|
||||
char llbuf[32];
|
||||
size_t bitoffset;
|
||||
size_t byte, bit;
|
||||
size_t bitval = 0;
|
||||
|
||||
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
|
||||
return;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
byte = bitoffset >> 3;
|
||||
bit = 7 - (bitoffset & 0x7);
|
||||
if (o->encoding != REDIS_ENCODING_RAW) {
|
||||
if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
|
||||
bitval = llbuf[byte] & (1 << bit);
|
||||
} else {
|
||||
if (byte < sdslen(o->ptr))
|
||||
bitval = ((char*)o->ptr)[byte] & (1 << bit);
|
||||
}
|
||||
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
}
|
||||
|
||||
void setrangeCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long offset;
|
||||
sds value = c->argv[3]->ptr;
|
||||
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
|
||||
return;
|
||||
|
||||
if (offset < 0) {
|
||||
addReplyError(c,"offset is out of range");
|
||||
return;
|
||||
}
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (o == NULL) {
|
||||
/* Return 0 when setting nothing on a non-existing string */
|
||||
if (sdslen(value) == 0) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return when the resulting string exceeds allowed size */
|
||||
if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
|
||||
return;
|
||||
|
||||
o = createObject(REDIS_STRING,sdsempty());
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
} else {
|
||||
size_t olen;
|
||||
|
||||
/* Key exists, check type */
|
||||
if (checkType(c,o,REDIS_STRING))
|
||||
return;
|
||||
|
||||
/* Return existing string length when setting nothing */
|
||||
olen = stringObjectLen(o);
|
||||
if (sdslen(value) == 0) {
|
||||
addReplyLongLong(c,olen);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return when the resulting string exceeds allowed size */
|
||||
if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
|
||||
return;
|
||||
|
||||
/* Create a copy when the object is shared or encoded. */
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
}
|
||||
}
|
||||
|
||||
if (sdslen(value) > 0) {
|
||||
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
|
||||
memcpy((char*)o->ptr+offset,value,sdslen(value));
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
addReplyLongLong(c,sdslen(o->ptr));
|
||||
}
|
||||
|
||||
void getrangeCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long start, end;
|
||||
char *str, llbuf[32];
|
||||
size_t strlen;
|
||||
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
|
||||
return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
str = llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
} else {
|
||||
str = o->ptr;
|
||||
strlen = sdslen(str);
|
||||
}
|
||||
|
||||
/* Convert negative indexes */
|
||||
if (start < 0) start = strlen+start;
|
||||
if (end < 0) end = strlen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
if ((unsigned)end >= strlen) end = strlen-1;
|
||||
|
||||
/* Precondition: end >= 0 && end < strlen, so the only condition where
|
||||
* nothing can be returned is: start > end. */
|
||||
if (start > end) {
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
|
||||
}
|
||||
}
|
||||
|
||||
void mgetCommand(redisClient *c) {
|
||||
int j;
|
||||
|
||||
@@ -179,39 +386,37 @@ void decrbyCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
void appendCommand(redisClient *c) {
|
||||
int retval;
|
||||
size_t totlen;
|
||||
robj *o;
|
||||
robj *o, *append;
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
if (o == NULL) {
|
||||
/* Create the key */
|
||||
retval = dbAdd(c->db,c->argv[1],c->argv[2]);
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
dbAdd(c->db,c->argv[1],c->argv[2]);
|
||||
incrRefCount(c->argv[2]);
|
||||
totlen = stringObjectLen(c->argv[2]);
|
||||
} else {
|
||||
if (o->type != REDIS_STRING) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
/* Key exists, check type */
|
||||
if (checkType(c,o,REDIS_STRING))
|
||||
return;
|
||||
}
|
||||
/* If the object is specially encoded or shared we have to make
|
||||
* a copy */
|
||||
|
||||
/* "append" is an argument, so always an sds */
|
||||
append = c->argv[2];
|
||||
totlen = stringObjectLen(o)+sdslen(append->ptr);
|
||||
if (checkStringLength(c,totlen) != REDIS_OK)
|
||||
return;
|
||||
|
||||
/* If the object is shared or encoded, we have to make a copy */
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
}
|
||||
/* APPEND! */
|
||||
if (c->argv[2]->encoding == REDIS_ENCODING_RAW) {
|
||||
o->ptr = sdscatlen(o->ptr,
|
||||
c->argv[2]->ptr, sdslen(c->argv[2]->ptr));
|
||||
} else {
|
||||
o->ptr = sdscatprintf(o->ptr, "%ld",
|
||||
(unsigned long) c->argv[2]->ptr);
|
||||
}
|
||||
|
||||
/* Append the value */
|
||||
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
|
||||
totlen = sdslen(o->ptr);
|
||||
}
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
@@ -219,50 +424,10 @@ void appendCommand(redisClient *c) {
|
||||
addReplyLongLong(c,totlen);
|
||||
}
|
||||
|
||||
void substrCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long start = atoi(c->argv[2]->ptr);
|
||||
long end = atoi(c->argv[3]->ptr);
|
||||
size_t rangelen, strlen;
|
||||
sds range;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
o = getDecodedObject(o);
|
||||
strlen = sdslen(o->ptr);
|
||||
|
||||
/* convert negative indexes */
|
||||
if (start < 0) start = strlen+start;
|
||||
if (end < 0) end = strlen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
|
||||
/* indexes sanity checks */
|
||||
if (start > end || (size_t)start >= strlen) {
|
||||
/* Out of range start or start > end result in null reply */
|
||||
addReply(c,shared.nullbulk);
|
||||
decrRefCount(o);
|
||||
return;
|
||||
}
|
||||
if ((size_t)end >= strlen) end = strlen-1;
|
||||
rangelen = (end-start)+1;
|
||||
|
||||
/* Return the result */
|
||||
addReplySds(c,sdscatprintf(sdsempty(),"$%zu\r\n",rangelen));
|
||||
range = sdsnewlen((char*)o->ptr+start,rangelen);
|
||||
addReplySds(c,range);
|
||||
addReply(c,shared.crlf);
|
||||
decrRefCount(o);
|
||||
}
|
||||
|
||||
void strlenCommand(redisClient *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
o = getDecodedObject(o);
|
||||
addReplyLongLong(c,sdslen(o->ptr));
|
||||
decrRefCount(o);
|
||||
addReplyLongLong(c,stringObjectLen(o));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
<lzf_unitialized_hash_table>
|
||||
Memcheck:Cond
|
||||
fun:lzf_compress
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.1.7"
|
||||
#define REDIS_VERSION "2.1.8"
|
||||
|
||||
@@ -263,7 +263,7 @@ int vmWriteObjectOnSwap(robj *o, off_t page) {
|
||||
* If we can't find enough contiguous empty pages to swap the object on disk
|
||||
* NULL is returned. */
|
||||
vmpointer *vmSwapObjectBlocking(robj *val) {
|
||||
off_t pages = rdbSavedObjectPages(val,NULL);
|
||||
off_t pages = rdbSavedObjectPages(val);
|
||||
off_t page;
|
||||
vmpointer *vp;
|
||||
|
||||
@@ -821,9 +821,7 @@ void *IOThreadEntryPoint(void *arg) {
|
||||
vmpointer *vp = (vmpointer*)j->id;
|
||||
j->val = vmReadObjectFromSwap(j->page,vp->vtype);
|
||||
} else if (j->type == REDIS_IOJOB_PREPARE_SWAP) {
|
||||
FILE *fp = fopen("/dev/null","w+");
|
||||
j->pages = rdbSavedObjectPages(j->val,fp);
|
||||
fclose(fp);
|
||||
j->pages = rdbSavedObjectPages(j->val);
|
||||
} else if (j->type == REDIS_IOJOB_DO_SWAP) {
|
||||
if (vmWriteObjectOnSwap(j->val,j->page) == REDIS_ERR)
|
||||
j->canceled = 1;
|
||||
|
||||
+6
-1
@@ -595,7 +595,12 @@ unsigned char *ziplistIndex(unsigned char *zl, int index) {
|
||||
return (p[0] == ZIP_END || index > 0) ? NULL : p;
|
||||
}
|
||||
|
||||
/* Return pointer to next entry in ziplist. */
|
||||
/* Return pointer to next entry in ziplist.
|
||||
*
|
||||
* zl is the pointer to the ziplist
|
||||
* p is the pointer to the current element
|
||||
*
|
||||
* The element after 'p' is returned, otherwise NULL if we are at the end. */
|
||||
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) {
|
||||
((void) zl);
|
||||
|
||||
|
||||
+36
-33
@@ -83,9 +83,13 @@ proc ping_server {host port} {
|
||||
}
|
||||
close $fd
|
||||
} e]} {
|
||||
puts -nonewline "."
|
||||
if {$::verbose} {
|
||||
puts -nonewline "."
|
||||
}
|
||||
} else {
|
||||
puts -nonewline "ok"
|
||||
if {$::verbose} {
|
||||
puts -nonewline "ok"
|
||||
}
|
||||
}
|
||||
return $retval
|
||||
}
|
||||
@@ -171,7 +175,7 @@ proc start_server {options {code undefined}} {
|
||||
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
|
||||
|
||||
if {$::valgrind} {
|
||||
exec valgrind src/redis-server $config_file > $stdout 2> $stderr &
|
||||
exec valgrind --suppressions=src/valgrind.sup src/redis-server $config_file > $stdout 2> $stderr &
|
||||
} else {
|
||||
exec src/redis-server $config_file > $stdout 2> $stderr &
|
||||
}
|
||||
@@ -181,7 +185,10 @@ proc start_server {options {code undefined}} {
|
||||
set retrynum 20
|
||||
set serverisup 0
|
||||
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
if {$::verbose} {
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
}
|
||||
|
||||
after 10
|
||||
if {$code ne "undefined"} {
|
||||
while {[incr retrynum -1]} {
|
||||
@@ -196,7 +203,10 @@ proc start_server {options {code undefined}} {
|
||||
} else {
|
||||
set serverisup 1
|
||||
}
|
||||
puts {}
|
||||
|
||||
if {$::verbose} {
|
||||
puts ""
|
||||
}
|
||||
|
||||
if {!$serverisup} {
|
||||
error_and_quit $config_file [exec cat $stderr]
|
||||
@@ -246,41 +256,34 @@ proc start_server {options {code undefined}} {
|
||||
reconnect
|
||||
|
||||
# execute provided block
|
||||
set curnum $::testnum
|
||||
if {![catch { uplevel 1 $code } err]} {
|
||||
# zero exit status is good
|
||||
unset err
|
||||
set num_tests $::num_tests
|
||||
if {[catch { uplevel 1 $code } error]} {
|
||||
set backtrace $::errorInfo
|
||||
|
||||
# Kill the server without checking for leaks
|
||||
dict set srv "skipleaks" 1
|
||||
kill_server $srv
|
||||
|
||||
# Print warnings from log
|
||||
puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]]
|
||||
set warnings [warnings_from_file [dict get $srv "stdout"]]
|
||||
if {[string length $warnings] > 0} {
|
||||
puts "$warnings"
|
||||
} else {
|
||||
puts "(none)"
|
||||
}
|
||||
puts ""
|
||||
|
||||
error $error $backtrace
|
||||
}
|
||||
|
||||
if {$curnum == $::testnum} {
|
||||
# don't check for leaks when no tests were executed
|
||||
# Don't do the leak check when no tests were run
|
||||
if {$num_tests == $::num_tests} {
|
||||
dict set srv "skipleaks" 1
|
||||
}
|
||||
|
||||
# pop the server object
|
||||
set ::servers [lrange $::servers 0 end-1]
|
||||
|
||||
# allow an exception to bubble up the call chain but still kill this
|
||||
# server, because we want to reuse the ports when the tests are re-run
|
||||
if {[info exists err]} {
|
||||
if {$err eq "exception"} {
|
||||
puts [format "Logged warnings (pid %d):" [dict get $srv "pid"]]
|
||||
set warnings [warnings_from_file [dict get $srv "stdout"]]
|
||||
if {[string length $warnings] > 0} {
|
||||
puts "$warnings"
|
||||
} else {
|
||||
puts "(none)"
|
||||
}
|
||||
# kill this server without checking for leaks
|
||||
dict set srv "skipleaks" 1
|
||||
kill_server $srv
|
||||
error "exception"
|
||||
} elseif {[string length $err] > 0} {
|
||||
puts "Error executing the suite, aborting..."
|
||||
puts $err
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
set ::tags [lrange $::tags 0 end-[llength $tags]]
|
||||
kill_server $srv
|
||||
|
||||
+111
-26
@@ -1,25 +1,23 @@
|
||||
set ::passed 0
|
||||
set ::failed 0
|
||||
set ::testnum 0
|
||||
set ::num_tests 0
|
||||
set ::num_passed 0
|
||||
set ::num_failed 0
|
||||
set ::tests_failed {}
|
||||
|
||||
proc assert {condition} {
|
||||
if {![uplevel 1 expr $condition]} {
|
||||
puts "!! ERROR\nExpected '$value' to evaluate to true"
|
||||
error "assertion"
|
||||
error "assertion:Expected '$value' to be true"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_match {pattern value} {
|
||||
if {![string match $pattern $value]} {
|
||||
puts "!! ERROR\nExpected '$value' to match '$pattern'"
|
||||
error "assertion"
|
||||
error "assertion:Expected '$value' to match '$pattern'"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_equal {expected value} {
|
||||
if {$expected ne $value} {
|
||||
puts "!! ERROR\nExpected '$value' to be equal to '$expected'"
|
||||
error "assertion"
|
||||
error "assertion:Expected '$value' to be equal to '$expected'"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +25,7 @@ proc assert_error {pattern code} {
|
||||
if {[catch {uplevel 1 $code} error]} {
|
||||
assert_match $pattern $error
|
||||
} else {
|
||||
puts "!! ERROR\nExpected an error but nothing was catched"
|
||||
error "assertion"
|
||||
error "assertion:Expected an error but nothing was catched"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +44,61 @@ proc assert_type {type key} {
|
||||
assert_equal $type [r type $key]
|
||||
}
|
||||
|
||||
proc test {name code {okpattern notspecified}} {
|
||||
# This is called before starting the test
|
||||
proc announce_test {s} {
|
||||
if {[info exists ::env(TERM)] && [string match $::env(TERM) xterm]} {
|
||||
puts -nonewline "$s\033\[0K"
|
||||
flush stdout
|
||||
set ::backward_count [string length $s]
|
||||
}
|
||||
}
|
||||
|
||||
# This is called after the test finished
|
||||
proc colored_dot {tags passed} {
|
||||
if {[info exists ::env(TERM)] && [string match $::env(TERM) xterm]} {
|
||||
# Go backward and delete what announc_test function printed.
|
||||
puts -nonewline "\033\[${::backward_count}D\033\[0K\033\[J"
|
||||
|
||||
# Print a coloured char, accordingly to test outcome and tags.
|
||||
if {[lsearch $tags list] != -1} {
|
||||
set colorcode {31}
|
||||
set ch L
|
||||
} elseif {[lsearch $tags hash] != -1} {
|
||||
set colorcode {32}
|
||||
set ch H
|
||||
} elseif {[lsearch $tags set] != -1} {
|
||||
set colorcode {33}
|
||||
set ch S
|
||||
} elseif {[lsearch $tags zset] != -1} {
|
||||
set colorcode {34}
|
||||
set ch Z
|
||||
} elseif {[lsearch $tags basic] != -1} {
|
||||
set colorcode {35}
|
||||
set ch B
|
||||
} else {
|
||||
set colorcode {37}
|
||||
set ch .
|
||||
}
|
||||
if {$colorcode ne {}} {
|
||||
if {$passed} {
|
||||
puts -nonewline "\033\[0;${colorcode};40m"
|
||||
} else {
|
||||
puts -nonewline "\033\[7;${colorcode};40m"
|
||||
}
|
||||
puts -nonewline $ch
|
||||
puts -nonewline "\033\[0m"
|
||||
flush stdout
|
||||
}
|
||||
} else {
|
||||
if {$passed} {
|
||||
puts -nonewline .
|
||||
} else {
|
||||
puts -nonewline F
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc test {name code {okpattern undefined}} {
|
||||
# abort if tagged with a tag to deny
|
||||
foreach tag $::denytags {
|
||||
if {[lsearch $::tags $tag] >= 0} {
|
||||
@@ -69,30 +120,64 @@ proc test {name code {okpattern notspecified}} {
|
||||
}
|
||||
}
|
||||
|
||||
incr ::testnum
|
||||
puts -nonewline [format "#%03d %-68s " $::testnum $name]
|
||||
flush stdout
|
||||
incr ::num_tests
|
||||
set details {}
|
||||
lappend details $::curfile
|
||||
lappend details $::tags
|
||||
lappend details $name
|
||||
|
||||
if {$::verbose} {
|
||||
puts -nonewline [format "#%03d %-68s " $::num_tests $name]
|
||||
flush stdout
|
||||
} else {
|
||||
announce_test $name
|
||||
}
|
||||
|
||||
if {[catch {set retval [uplevel 1 $code]} error]} {
|
||||
if {$error eq "assertion"} {
|
||||
incr ::failed
|
||||
if {[string match "assertion:*" $error]} {
|
||||
set msg [string range $error 10 end]
|
||||
lappend details $msg
|
||||
lappend ::tests_failed $details
|
||||
|
||||
incr ::num_failed
|
||||
if {$::verbose} {
|
||||
puts "FAILED"
|
||||
puts "$msg\n"
|
||||
} else {
|
||||
colored_dot $::tags 0
|
||||
}
|
||||
} else {
|
||||
puts "EXCEPTION"
|
||||
puts "\nCaught error: $error"
|
||||
error "exception"
|
||||
# Re-raise, let handler up the stack take care of this.
|
||||
error $error $::errorInfo
|
||||
}
|
||||
} else {
|
||||
if {$okpattern eq "notspecified" || $okpattern eq $retval || [string match $okpattern $retval]} {
|
||||
puts "PASSED"
|
||||
incr ::passed
|
||||
if {$okpattern eq "undefined" || $okpattern eq $retval || [string match $okpattern $retval]} {
|
||||
incr ::num_passed
|
||||
if {$::verbose} {
|
||||
puts "PASSED"
|
||||
} else {
|
||||
colored_dot $::tags 1
|
||||
}
|
||||
} else {
|
||||
puts "!! ERROR expected\n'$okpattern'\nbut got\n'$retval'"
|
||||
incr ::failed
|
||||
set msg "Expected '$okpattern' to equal or match '$retval'"
|
||||
lappend details $msg
|
||||
lappend ::tests_failed $details
|
||||
|
||||
incr ::num_failed
|
||||
if {$::verbose} {
|
||||
puts "FAILED"
|
||||
puts "$msg\n"
|
||||
} else {
|
||||
colored_dot $::tags 0
|
||||
}
|
||||
}
|
||||
}
|
||||
flush stdout
|
||||
|
||||
if {$::traceleaks} {
|
||||
set output [exec leaks redis-server]
|
||||
if {![string match {*0 leaks*} $output]} {
|
||||
puts "--------- Test $::testnum LEAKED! --------"
|
||||
puts "--- Test \"$name\" leaked! ---"
|
||||
puts $output
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -52,8 +52,10 @@ proc status {r property} {
|
||||
proc waitForBgsave r {
|
||||
while 1 {
|
||||
if {[status r bgsave_in_progress] eq 1} {
|
||||
puts -nonewline "\nWaiting for background save to finish... "
|
||||
flush stdout
|
||||
if {$::verbose} {
|
||||
puts -nonewline "\nWaiting for background save to finish... "
|
||||
flush stdout
|
||||
}
|
||||
after 1000
|
||||
} else {
|
||||
break
|
||||
@@ -64,8 +66,10 @@ proc waitForBgsave r {
|
||||
proc waitForBgrewriteaof r {
|
||||
while 1 {
|
||||
if {[status r bgrewriteaof_in_progress] eq 1} {
|
||||
puts -nonewline "\nWaiting for background AOF rewrite to finish... "
|
||||
flush stdout
|
||||
if {$::verbose} {
|
||||
puts -nonewline "\nWaiting for background AOF rewrite to finish... "
|
||||
flush stdout
|
||||
}
|
||||
after 1000
|
||||
} else {
|
||||
break
|
||||
|
||||
+31
-5
@@ -13,13 +13,17 @@ set ::host 127.0.0.1
|
||||
set ::port 16379
|
||||
set ::traceleaks 0
|
||||
set ::valgrind 0
|
||||
set ::verbose 0
|
||||
set ::denytags {}
|
||||
set ::allowtags {}
|
||||
set ::external 0; # If "1" this means, we are running against external instance
|
||||
set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
set ::curfile ""; # Hold the filename of the current suite
|
||||
|
||||
proc execute_tests name {
|
||||
source "tests/$name.tcl"
|
||||
set path "tests/$name.tcl"
|
||||
set ::curfile $path
|
||||
source $path
|
||||
}
|
||||
|
||||
# Setup a list to hold a stack of server configs. When calls to start_server
|
||||
@@ -147,9 +151,27 @@ proc main {} {
|
||||
}
|
||||
|
||||
cleanup
|
||||
puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed"
|
||||
if {$::failed > 0} {
|
||||
puts "\n*** WARNING!!! $::failed FAILED TESTS ***\n"
|
||||
puts "\n[expr $::num_tests] tests, $::num_passed passed, $::num_failed failed\n"
|
||||
if {$::num_failed > 0} {
|
||||
set curheader ""
|
||||
puts "Failures:"
|
||||
foreach {test} $::tests_failed {
|
||||
set header [lindex $test 0]
|
||||
append header " ("
|
||||
append header [join [lindex $test 1] ","]
|
||||
append header ")"
|
||||
|
||||
if {$curheader ne $header} {
|
||||
set curheader $header
|
||||
puts "\n$curheader:"
|
||||
}
|
||||
|
||||
set name [lindex $test 2]
|
||||
set msg [lindex $test 3]
|
||||
puts "- $name: $msg"
|
||||
}
|
||||
|
||||
puts ""
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
@@ -167,6 +189,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
}
|
||||
}
|
||||
incr j
|
||||
} elseif {$opt eq {--valgrind}} {
|
||||
set ::valgrind 1
|
||||
} elseif {$opt eq {--file}} {
|
||||
set ::file $arg
|
||||
incr j
|
||||
@@ -177,6 +201,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--port}} {
|
||||
set ::port $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--verbose}} {
|
||||
set ::verbose 1
|
||||
} else {
|
||||
puts "Wrong argument: $opt"
|
||||
exit 1
|
||||
@@ -187,7 +213,7 @@ if {[catch { main } err]} {
|
||||
if {[string length $err] > 0} {
|
||||
# only display error when not generated by the test suite
|
||||
if {$err ne "exception"} {
|
||||
puts $err
|
||||
puts $::errorInfo
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
+220
-8
@@ -361,17 +361,229 @@ start_server {tags {"basic"}} {
|
||||
list [r msetnx x1 xxx y2 yyy] [r get x1] [r get y2]
|
||||
} {1 xxx yyy}
|
||||
|
||||
test {STRLEN against non existing key} {
|
||||
r strlen notakey
|
||||
} {0}
|
||||
test "STRLEN against non-existing key" {
|
||||
assert_equal 0 [r strlen notakey]
|
||||
}
|
||||
|
||||
test {STRLEN against integer} {
|
||||
test "STRLEN against integer-encoded value" {
|
||||
r set myinteger -555
|
||||
r strlen myinteger
|
||||
} {4}
|
||||
assert_equal 4 [r strlen myinteger]
|
||||
}
|
||||
|
||||
test {STRLEN against plain string} {
|
||||
test "STRLEN against plain string" {
|
||||
r set mystring "foozzz0123456789 baz"
|
||||
r strlen mystring
|
||||
assert_equal 20 [r strlen mystring]
|
||||
}
|
||||
|
||||
test "SETBIT against non-existing key" {
|
||||
r del mykey
|
||||
assert_equal 0 [r setbit mykey 1 1]
|
||||
assert_equal [binary format B* 01000000] [r get mykey]
|
||||
}
|
||||
|
||||
test "SETBIT against string-encoded key" {
|
||||
# Ascii "@" is integer 64 = 01 00 00 00
|
||||
r set mykey "@"
|
||||
|
||||
assert_equal 0 [r setbit mykey 2 1]
|
||||
assert_equal [binary format B* 01100000] [r get mykey]
|
||||
assert_equal 1 [r setbit mykey 1 0]
|
||||
assert_equal [binary format B* 00100000] [r get mykey]
|
||||
}
|
||||
|
||||
test "SETBIT against integer-encoded key" {
|
||||
# Ascii "1" is integer 49 = 00 11 00 01
|
||||
r set mykey 1
|
||||
assert_encoding int mykey
|
||||
|
||||
assert_equal 0 [r setbit mykey 6 1]
|
||||
assert_equal [binary format B* 00110011] [r get mykey]
|
||||
assert_equal 1 [r setbit mykey 2 0]
|
||||
assert_equal [binary format B* 00010011] [r get mykey]
|
||||
}
|
||||
|
||||
test "SETBIT against key with wrong type" {
|
||||
r del mykey
|
||||
r lpush mykey "foo"
|
||||
assert_error "*wrong kind*" {r setbit mykey 0 1}
|
||||
}
|
||||
|
||||
test "SETBIT with out of range bit offset" {
|
||||
r del mykey
|
||||
assert_error "*out of range*" {r setbit mykey [expr 4*1024*1024*1024] 1}
|
||||
assert_error "*out of range*" {r setbit mykey -1 1}
|
||||
}
|
||||
|
||||
test "SETBIT with non-bit argument" {
|
||||
r del mykey
|
||||
assert_error "*out of range*" {r setbit mykey 0 -1}
|
||||
assert_error "*out of range*" {r setbit mykey 0 2}
|
||||
assert_error "*out of range*" {r setbit mykey 0 10}
|
||||
assert_error "*out of range*" {r setbit mykey 0 20}
|
||||
}
|
||||
|
||||
test "SETBIT fuzzing" {
|
||||
set str ""
|
||||
set len [expr 256*8]
|
||||
r del mykey
|
||||
|
||||
for {set i 0} {$i < 2000} {incr i} {
|
||||
set bitnum [randomInt $len]
|
||||
set bitval [randomInt 2]
|
||||
set fmt [format "%%-%ds%%d%%-s" $bitnum]
|
||||
set head [string range $str 0 $bitnum-1]
|
||||
set tail [string range $str $bitnum+1 end]
|
||||
set str [string map {" " 0} [format $fmt $head $bitval $tail]]
|
||||
|
||||
r setbit mykey $bitnum $bitval
|
||||
assert_equal [binary format B* $str] [r get mykey]
|
||||
}
|
||||
}
|
||||
|
||||
test "GETBIT against non-existing key" {
|
||||
r del mykey
|
||||
assert_equal 0 [r getbit mykey 0]
|
||||
}
|
||||
|
||||
test "GETBIT against string-encoded key" {
|
||||
# Single byte with 2nd and 3rd bit set
|
||||
r set mykey "`"
|
||||
|
||||
# In-range
|
||||
assert_equal 0 [r getbit mykey 0]
|
||||
assert_equal 1 [r getbit mykey 1]
|
||||
assert_equal 1 [r getbit mykey 2]
|
||||
assert_equal 0 [r getbit mykey 3]
|
||||
|
||||
# Out-range
|
||||
assert_equal 0 [r getbit mykey 8]
|
||||
assert_equal 0 [r getbit mykey 100]
|
||||
assert_equal 0 [r getbit mykey 10000]
|
||||
}
|
||||
|
||||
test "GETBIT against integer-encoded key" {
|
||||
r set mykey 1
|
||||
assert_encoding int mykey
|
||||
|
||||
# Ascii "1" is integer 49 = 00 11 00 01
|
||||
assert_equal 0 [r getbit mykey 0]
|
||||
assert_equal 0 [r getbit mykey 1]
|
||||
assert_equal 1 [r getbit mykey 2]
|
||||
assert_equal 1 [r getbit mykey 3]
|
||||
|
||||
# Out-range
|
||||
assert_equal 0 [r getbit mykey 8]
|
||||
assert_equal 0 [r getbit mykey 100]
|
||||
assert_equal 0 [r getbit mykey 10000]
|
||||
}
|
||||
|
||||
test "SETRANGE against non-existing key" {
|
||||
r del mykey
|
||||
assert_equal 3 [r setrange mykey 0 foo]
|
||||
assert_equal "foo" [r get mykey]
|
||||
|
||||
r del mykey
|
||||
assert_equal 0 [r setrange mykey 0 ""]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r del mykey
|
||||
assert_equal 4 [r setrange mykey 1 foo]
|
||||
assert_equal "\000foo" [r get mykey]
|
||||
}
|
||||
|
||||
test "SETRANGE against string-encoded key" {
|
||||
r set mykey "foo"
|
||||
assert_equal 3 [r setrange mykey 0 b]
|
||||
assert_equal "boo" [r get mykey]
|
||||
|
||||
r set mykey "foo"
|
||||
assert_equal 3 [r setrange mykey 0 ""]
|
||||
assert_equal "foo" [r get mykey]
|
||||
|
||||
r set mykey "foo"
|
||||
assert_equal 3 [r setrange mykey 1 b]
|
||||
assert_equal "fbo" [r get mykey]
|
||||
|
||||
r set mykey "foo"
|
||||
assert_equal 7 [r setrange mykey 4 bar]
|
||||
assert_equal "foo\000bar" [r get mykey]
|
||||
}
|
||||
|
||||
test "SETRANGE against integer-encoded key" {
|
||||
r set mykey 1234
|
||||
assert_encoding int mykey
|
||||
assert_equal 4 [r setrange mykey 0 2]
|
||||
assert_encoding raw mykey
|
||||
assert_equal 2234 [r get mykey]
|
||||
|
||||
# Shouldn't change encoding when nothing is set
|
||||
r set mykey 1234
|
||||
assert_encoding int mykey
|
||||
assert_equal 4 [r setrange mykey 0 ""]
|
||||
assert_encoding int mykey
|
||||
assert_equal 1234 [r get mykey]
|
||||
|
||||
r set mykey 1234
|
||||
assert_encoding int mykey
|
||||
assert_equal 4 [r setrange mykey 1 3]
|
||||
assert_encoding raw mykey
|
||||
assert_equal 1334 [r get mykey]
|
||||
|
||||
r set mykey 1234
|
||||
assert_encoding int mykey
|
||||
assert_equal 6 [r setrange mykey 5 2]
|
||||
assert_encoding raw mykey
|
||||
assert_equal "1234\0002" [r get mykey]
|
||||
}
|
||||
|
||||
test "SETRANGE against key with wrong type" {
|
||||
r del mykey
|
||||
r lpush mykey "foo"
|
||||
assert_error "*wrong kind*" {r setrange mykey 0 bar}
|
||||
}
|
||||
|
||||
test "SETRANGE with out of range offset" {
|
||||
r del mykey
|
||||
assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world}
|
||||
|
||||
r set mykey "hello"
|
||||
assert_error "*out of range*" {r setrange mykey -1 world}
|
||||
assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world}
|
||||
}
|
||||
|
||||
test "GETRANGE against non-existing key" {
|
||||
r del mykey
|
||||
assert_equal "" [r getrange mykey 0 -1]
|
||||
}
|
||||
|
||||
test "GETRANGE against string value" {
|
||||
r set mykey "Hello World"
|
||||
assert_equal "Hell" [r getrange mykey 0 3]
|
||||
assert_equal "Hello World" [r getrange mykey 0 -1]
|
||||
assert_equal "orld" [r getrange mykey -4 -1]
|
||||
assert_equal "" [r getrange mykey 5 3]
|
||||
assert_equal " World" [r getrange mykey 5 5000]
|
||||
assert_equal "Hello World" [r getrange mykey -5000 10000]
|
||||
}
|
||||
|
||||
test "GETRANGE against integer-encoded value" {
|
||||
r set mykey 1234
|
||||
assert_equal "123" [r getrange mykey 0 2]
|
||||
assert_equal "1234" [r getrange mykey 0 -1]
|
||||
assert_equal "234" [r getrange mykey -3 -1]
|
||||
assert_equal "" [r getrange mykey 5 3]
|
||||
assert_equal "4" [r getrange mykey 3 5000]
|
||||
assert_equal "1234" [r getrange mykey -5000 10000]
|
||||
}
|
||||
|
||||
test "GETRANGE fuzzing" {
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
r set bin [set bin [randstring 0 1024 binary]]
|
||||
set _start [set start [randomInt 1500]]
|
||||
set _end [set end [randomInt 1500]]
|
||||
if {$_start < 0} {set _start "end-[abs($_start)-1]"}
|
||||
if {$_end < 0} {set _end "end-[abs($_end)-1]"}
|
||||
assert_equal [string range $bin $_start $_end] [r getrange bin $start $end]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,42 +216,6 @@ start_server {tags {"other"}} {
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
test {SUBSTR basics} {
|
||||
set res {}
|
||||
r set foo "Hello World"
|
||||
lappend res [r substr foo 0 3]
|
||||
lappend res [r substr foo 0 -1]
|
||||
lappend res [r substr foo -4 -1]
|
||||
lappend res [r substr foo 5 3]
|
||||
lappend res [r substr foo 5 5000]
|
||||
lappend res [r substr foo -5000 10000]
|
||||
set _ $res
|
||||
} {Hell {Hello World} orld {} { World} {Hello World}}
|
||||
|
||||
test {SUBSTR against integer encoded values} {
|
||||
r set foo 123
|
||||
r substr foo 0 -2
|
||||
} {12}
|
||||
|
||||
test {SUBSTR fuzzing} {
|
||||
set err {}
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
set bin [randstring 0 1024 binary]
|
||||
set _start [set start [randomInt 1500]]
|
||||
set _end [set end [randomInt 1500]]
|
||||
if {$_start < 0} {set _start "end-[abs($_start)-1]"}
|
||||
if {$_end < 0} {set _end "end-[abs($_end)-1]"}
|
||||
set s1 [string range $bin $_start $_end]
|
||||
r set bin $bin
|
||||
set s2 [r substr bin $start $end]
|
||||
if {$s1 != $s2} {
|
||||
set err "String mismatch"
|
||||
break
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
# Leave the user with a clean DB before to exit
|
||||
test {FLUSHDB} {
|
||||
set aux {}
|
||||
|
||||
+16
-8
@@ -144,8 +144,10 @@ start_server {
|
||||
set sorted [r sort tosort BY weight_* LIMIT 0 10]
|
||||
}
|
||||
set elapsed [expr [clock clicks -milliseconds]-$start]
|
||||
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
|
||||
flush stdout
|
||||
if {$::verbose} {
|
||||
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
|
||||
flush stdout
|
||||
}
|
||||
}
|
||||
|
||||
test "SORT speed, $num element list BY hash field, 100 times" {
|
||||
@@ -154,8 +156,10 @@ start_server {
|
||||
set sorted [r sort tosort BY wobj_*->weight LIMIT 0 10]
|
||||
}
|
||||
set elapsed [expr [clock clicks -milliseconds]-$start]
|
||||
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
|
||||
flush stdout
|
||||
if {$::verbose} {
|
||||
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
|
||||
flush stdout
|
||||
}
|
||||
}
|
||||
|
||||
test "SORT speed, $num element list directly, 100 times" {
|
||||
@@ -164,8 +168,10 @@ start_server {
|
||||
set sorted [r sort tosort LIMIT 0 10]
|
||||
}
|
||||
set elapsed [expr [clock clicks -milliseconds]-$start]
|
||||
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
|
||||
flush stdout
|
||||
if {$::verbose} {
|
||||
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
|
||||
flush stdout
|
||||
}
|
||||
}
|
||||
|
||||
test "SORT speed, $num element list BY <const>, 100 times" {
|
||||
@@ -174,8 +180,10 @@ start_server {
|
||||
set sorted [r sort tosort BY nokey LIMIT 0 10]
|
||||
}
|
||||
set elapsed [expr [clock clicks -milliseconds]-$start]
|
||||
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
|
||||
flush stdout
|
||||
if {$::verbose} {
|
||||
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
|
||||
flush stdout
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +127,141 @@ start_server {
|
||||
assert_equal 0 [r llen blist1]
|
||||
assert_equal 1 [r llen blist2]
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH - $type" {
|
||||
r del target
|
||||
|
||||
set rd [redis_deferring_client]
|
||||
create_$type blist "a b $large c d"
|
||||
|
||||
$rd brpoplpush blist target 1
|
||||
assert_equal d [$rd read]
|
||||
|
||||
assert_equal d [r rpop target]
|
||||
assert_equal "a b $large c" [r lrange blist 0 -1]
|
||||
}
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH with zero timeout should block indefinitely" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist target
|
||||
$rd brpoplpush blist target 0
|
||||
after 1000
|
||||
r rpush blist foo
|
||||
assert_equal foo [$rd read]
|
||||
assert_equal {foo} [r lrange target 0 -1]
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH with a client BLPOPing the target list" {
|
||||
set rd [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
r del blist target
|
||||
$rd2 blpop target 0
|
||||
$rd brpoplpush blist target 0
|
||||
after 1000
|
||||
r rpush blist foo
|
||||
assert_equal foo [$rd read]
|
||||
assert_equal {target foo} [$rd2 read]
|
||||
assert_equal 0 [r exists target]
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH with wrong source type" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist target
|
||||
r set blist nolist
|
||||
$rd brpoplpush blist target 1
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH with wrong destination type" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist target
|
||||
r set target nolist
|
||||
r lpush blist foo
|
||||
$rd brpoplpush blist target 1
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
|
||||
set rd [redis_deferring_client]
|
||||
r del blist target
|
||||
r set target nolist
|
||||
$rd brpoplpush blist target 0
|
||||
after 1000
|
||||
r rpush blist foo
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
assert_equal {foo} [r lrange blist 0 -1]
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH with multiple blocked clients" {
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
r del blist target1 target2
|
||||
r set target1 nolist
|
||||
$rd1 brpoplpush blist target1 0
|
||||
$rd2 brpoplpush blist target2 0
|
||||
r lpush blist foo
|
||||
|
||||
assert_error "ERR*wrong kind*" {$rd1 read}
|
||||
assert_equal {foo} [$rd2 read]
|
||||
assert_equal {foo} [r lrange target2 0 -1]
|
||||
}
|
||||
|
||||
test "Linked BRPOPLPUSH" {
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
|
||||
r del list1 list2 list3
|
||||
|
||||
$rd1 brpoplpush list1 list2 0
|
||||
$rd2 brpoplpush list2 list3 0
|
||||
|
||||
r rpush list1 foo
|
||||
|
||||
assert_equal {} [r lrange list1 0 -1]
|
||||
assert_equal {} [r lrange list2 0 -1]
|
||||
assert_equal {foo} [r lrange list3 0 -1]
|
||||
}
|
||||
|
||||
test "Circular BRPOPLPUSH" {
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
|
||||
r del list1 list2
|
||||
|
||||
$rd1 brpoplpush list1 list2 0
|
||||
$rd2 brpoplpush list2 list1 0
|
||||
|
||||
r rpush list1 foo
|
||||
|
||||
assert_equal {foo} [r lrange list1 0 -1]
|
||||
assert_equal {} [r lrange list2 0 -1]
|
||||
}
|
||||
|
||||
test "Self-referential BRPOPLPUSH" {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
r del blist
|
||||
|
||||
$rd brpoplpush blist blist 0
|
||||
|
||||
r rpush blist foo
|
||||
|
||||
assert_equal {foo} [r lrange blist 0 -1]
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH inside a transaction" {
|
||||
r del xlist target
|
||||
r lpush xlist foo
|
||||
r lpush xlist bar
|
||||
|
||||
r multi
|
||||
r brpoplpush xlist target 0
|
||||
r brpoplpush xlist target 0
|
||||
r brpoplpush xlist target 0
|
||||
r lrange xlist 0 -1
|
||||
r lrange target 0 -1
|
||||
r exec
|
||||
} {foo bar {} {} {bar foo}}
|
||||
|
||||
foreach {pop} {BLPOP BRPOP} {
|
||||
test "$pop: with single empty list argument" {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
GROUPS = [
|
||||
"generic",
|
||||
"string",
|
||||
"list",
|
||||
"set",
|
||||
"sorted_set",
|
||||
"hash",
|
||||
"pubsub",
|
||||
"transactions",
|
||||
"connection",
|
||||
"server"
|
||||
].freeze
|
||||
|
||||
GROUPS_BY_NAME = Hash[*
|
||||
GROUPS.each_with_index.map do |n,i|
|
||||
[n,i]
|
||||
end.flatten
|
||||
].freeze
|
||||
|
||||
def argument arg
|
||||
name = arg["name"].is_a?(Array) ? arg["name"].join(" ") : arg["name"]
|
||||
name = arg["enum"].join "|" if "enum" == arg["type"]
|
||||
name = arg["command"] + " " + name if arg["command"]
|
||||
if arg["multiple"]
|
||||
name = "#{name} [#{name} ...]"
|
||||
end
|
||||
if arg["optional"]
|
||||
name = "[#{name}]"
|
||||
end
|
||||
name
|
||||
end
|
||||
|
||||
def arguments command
|
||||
return "-" unless command["arguments"]
|
||||
command["arguments"].map do |arg|
|
||||
argument arg
|
||||
end.join " "
|
||||
end
|
||||
|
||||
def commands
|
||||
return @commands if @commands
|
||||
|
||||
require "rubygems"
|
||||
require "net/http"
|
||||
require "net/https"
|
||||
require "json"
|
||||
require "uri"
|
||||
|
||||
url = URI.parse "https://github.com/antirez/redis-doc/raw/master/commands.json"
|
||||
client = Net::HTTP.new url.host, url.port
|
||||
client.use_ssl = true
|
||||
response = client.get url.path
|
||||
if response.is_a?(Net::HTTPSuccess)
|
||||
@commands = JSON.parse(response.body)
|
||||
else
|
||||
response.error!
|
||||
end
|
||||
end
|
||||
|
||||
def generate_groups
|
||||
GROUPS.map do |n|
|
||||
"\"#{n}\""
|
||||
end.join(",\n ");
|
||||
end
|
||||
|
||||
def generate_commands
|
||||
commands.to_a.sort do |x,y|
|
||||
x[0] <=> y[0]
|
||||
end.map do |key, command|
|
||||
group = GROUPS_BY_NAME[command["group"]]
|
||||
if group.nil?
|
||||
STDERR.puts "Please update groups array in #{__FILE__}"
|
||||
raise "Unknown group #{command["group"]}"
|
||||
end
|
||||
|
||||
ret = <<-SPEC
|
||||
{ "#{key}",
|
||||
"#{arguments(command)}",
|
||||
"#{command["summary"]}",
|
||||
#{group},
|
||||
"#{command["since"]}" }
|
||||
SPEC
|
||||
ret.strip
|
||||
end.join(",\n ")
|
||||
end
|
||||
|
||||
# Write to stdout
|
||||
puts <<-HELP_H
|
||||
/* Automatically generated by #{__FILE__}, do not edit. */
|
||||
|
||||
#ifndef __REDIS_HELP_H
|
||||
#define __REDIS_HELP_H
|
||||
|
||||
static char *commandGroups[] = {
|
||||
#{generate_groups}
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
char *name;
|
||||
char *params;
|
||||
char *summary;
|
||||
int group;
|
||||
char *since;
|
||||
} commandHelp[] = {
|
||||
#{generate_commands}
|
||||
};
|
||||
|
||||
#endif
|
||||
HELP_H
|
||||
|
||||
Reference in New Issue
Block a user