Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c14507366 | ||
|
|
19c8d5bf22 | ||
|
|
fac62d47e7 | ||
|
|
f5936fa1c2 | ||
|
|
ff29ca1c46 | ||
|
|
b766149d5f | ||
|
|
4ac0c844ed | ||
|
|
87c96a6ed8 | ||
|
|
a58426fb15 | ||
|
|
2385ef4dfe | ||
|
|
d7554adade | ||
|
|
70a08f00d7 | ||
|
|
6ebd11cfa8 | ||
|
|
42daeb4b48 | ||
|
|
3306aefc87 | ||
|
|
c8baa20507 | ||
|
|
068eb3bf44 | ||
|
|
9fb471adbe | ||
|
|
40b8264eb9 | ||
|
|
bf8d4cf547 | ||
|
|
a7b547db9b | ||
|
|
b2e16cb0c8 | ||
|
|
55f6287677 | ||
|
|
521cdafafe | ||
|
|
00a90feb0b | ||
|
|
aaed0894cc | ||
|
|
4f4e0f1cdd | ||
|
|
47ae4b6a17 | ||
|
|
db7633e888 | ||
|
|
4212dd5402 | ||
|
|
a3267b8084 | ||
|
|
29a8c8d20b | ||
|
|
7caf0591a7 | ||
|
|
eaaf62f6a4 | ||
|
|
eff09a7bd8 | ||
|
|
efc8a6beee | ||
|
|
b059a563d9 | ||
|
|
a5eddcdff1 | ||
|
|
f8fa3e2833 | ||
|
|
32241a0c4e | ||
|
|
dda93651f7 | ||
|
|
3a1ab86a35 | ||
|
|
c6375e6a60 | ||
|
|
655f79a933 | ||
|
|
ac10524377 | ||
|
|
30a7f9b387 | ||
|
|
39926d0411 | ||
|
|
7905331ede | ||
|
|
a29b8f58cc | ||
|
|
fe2173ae53 | ||
|
|
de4aebe9fd | ||
|
|
c9c7ecad96 | ||
|
|
dc44326f7a | ||
|
|
d8f25ce3ac | ||
|
|
b25b61eb97 | ||
|
|
797c9ffb6b | ||
|
|
75d675d954 | ||
|
|
243b914bd6 | ||
|
|
e4a3d7fffc | ||
|
|
de8c659026 | ||
|
|
f9231c2c9a | ||
|
|
c7543ab6f1 | ||
|
|
e8de5c7a3f | ||
|
|
61e4ff2f9c | ||
|
|
a0ed0067cc | ||
|
|
bd29be7db5 | ||
|
|
15f763eef6 | ||
|
|
55ebd6a213 | ||
|
|
1f19628bee | ||
|
|
f3f35ced13 | ||
|
|
0319bd1e9f | ||
|
|
19b49c31f3 | ||
|
|
3c5b5e15d4 | ||
|
|
c80ba69173 | ||
|
|
f7b8ff227a | ||
|
|
8a58008b1b | ||
|
|
903b7484af | ||
|
|
b03dce9537 | ||
|
|
1bc183731d | ||
|
|
2691b01bee | ||
|
|
3294ef172a | ||
|
|
d1481e023a |
@@ -1,4 +1,3 @@
|
||||
00-RELEASENOTES
|
||||
*.o
|
||||
*.rdb
|
||||
*.log
|
||||
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
Welcome to Redis 2.0.4
|
||||
|
||||
This release fixes two non critical bugs:
|
||||
|
||||
- HMGET used to crash when called against a key that was not holding
|
||||
an hash. Fixed by Pieter Noordhuis.
|
||||
|
||||
- Redis will now not try to save the DB if no save points for RDB are
|
||||
configured, when used as a non persistent cache. Thanks to Robey
|
||||
Pointer from Twitter for providing this patch.
|
||||
|
||||
Waiting 2.2 ...
|
||||
Salvatore
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Welcome to Redis 2.0.3
|
||||
|
||||
This release fixes two important bugs:
|
||||
|
||||
- The maxmemory directive was broken in Redis <= 2.0.2, since from time to
|
||||
time it replayed with an error about memory limit reached even when
|
||||
it was possible to expire some volatile key to make room for new data.
|
||||
|
||||
The new behaviour is the correct one of always allowing write operations
|
||||
to succeed as long as there are other volatile keys to remove.
|
||||
|
||||
- An AOF bug related to blocking POP could crash Redis on AOF reload.
|
||||
This is now fixed thanks to Pieter Noordhuis and a kind user that
|
||||
helped us on IRC.
|
||||
|
||||
Enjoy!
|
||||
Salvatore
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Welcome to Redis 2.0.2
|
||||
|
||||
This is a bugfix release, with the followign changes:
|
||||
|
||||
- Fixed a bug that may slow down significantly (from a few milliseconds
|
||||
to many seconds) server side intersections when a background write is
|
||||
in progress. This was due to the hash table resize policy, prevented when
|
||||
there was a saving child. Now it's prevented only up to 5 times
|
||||
overbooking, so we try hard to prevent a lot of copy on write, but
|
||||
avoiding to trigger pathological hash table performances of O(N) instead
|
||||
of O(1).
|
||||
- Fixed expired keys counter in INFO output. It was not counting keys
|
||||
force-expired due to max-memory limit reached.
|
||||
|
||||
Ciao,
|
||||
Salvatore
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Welcome to Redis 2.0.1
|
||||
|
||||
This is a bugfix release, with the following changes:
|
||||
|
||||
- Fixed a not critical but important VM bug: from time to time a race
|
||||
condition may happen and a client may never get the reply from a given
|
||||
request.
|
||||
- Now we have a make install target as stated in the README
|
||||
- redis-cli no longer tries to understand if it's part of a pipe
|
||||
so there is to use the -x option to read the last arg from stdin.
|
||||
This prevents it from creating problem when running inside cron scripts.
|
||||
- Fixed the init script provided in the tar.gz. It was totally broken.
|
||||
- Fixed a bug related to connecting more than 10k clients to Redis.
|
||||
|
||||
Good hacking,
|
||||
Salvatore
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Welcome to Redis 2.0.0
|
||||
|
||||
Finally we reached 2.0.0 stable!
|
||||
|
||||
This is just RC4 with (many) bug fixes applied.
|
||||
Also this release features an updated redis-cli, backported from master.
|
||||
|
||||
Many thanks to VMware, Pieter Noordhuis and the Redis community for making
|
||||
this possible.
|
||||
|
||||
For a list of new / improved things in 2.0.0 please check this web page:
|
||||
|
||||
http://code.google.com/p/redis/wiki/Redis_2_0_0_Changelog
|
||||
|
||||
Enjoy!
|
||||
Salvatore
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Welcome to Redis 1.3.17 (2.0.0 RC4)
|
||||
|
||||
Compared to RC3 this one contains only an important replication related bugfix
|
||||
about calls to ZINTERSTORE. Replication users are strongly encouraged to
|
||||
upgrade ASAP!
|
||||
|
||||
If no new bugs will be found in a few weeks this is likely to become
|
||||
2.0.0 stable in short time (one or two weeks).
|
||||
|
||||
Regards,
|
||||
Salvatore
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Welcome to Redis 1.3.16 (2.0.0 RC3)
|
||||
|
||||
This is the third Release Candidate of Redis 2.0.0, fixing a few important
|
||||
Virtual Memory bugs. If you use VM it is a very good idea to upgrade ASAP.
|
||||
|
||||
List of chages:
|
||||
|
||||
2010-07-22 don't open/close log file if log level is not matched (antirez)
|
||||
2010-07-22 recent VM fixes backported to 2.0.0 branch (antirez)
|
||||
2010-07-22 fix rare condition where 'key' would already be destroyed while is was needed later on (Pieter Noordhuis)
|
||||
2010-07-21 vm_blocked_clients count fixed in INFO, thanks to Pietern Noordhuis (antirez)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Welcome to Redis 1.3.15 (2.0.0 RC2)
|
||||
|
||||
This is the second Release Candidate of Redis 2.0.0.
|
||||
|
||||
List of changes:
|
||||
|
||||
* Faster DB and AOF loading
|
||||
* Fixed swappability score computation
|
||||
* New version of the test suite
|
||||
* Fixed ZINCR Nan bugs leading to server crash and added tests
|
||||
* Require the flag "-c" to be used for redis-cli to read the last argument
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Welcome to Redis 1.3.14 (2.0.0 RC1)
|
||||
|
||||
This is the first Release Candidate of Redis 2.0.0
|
||||
2.0.0 is a big release with many interesting news, the following is the
|
||||
list of the major improvements.
|
||||
|
||||
WHAT'S NEW IN REDIS 2.0.x
|
||||
|
||||
* Redis transactions: MULTI/EXEC (antirez)
|
||||
* Blocking POP: BLPOP and BRPOP (antirez)
|
||||
* Virtual Memory (antirez with contributions from Pieter Noordhuis)
|
||||
* APPEND command (antirez)
|
||||
* WITHSCORES in ZRANGEBYSCORE thanks to Sam Hendley (antirez)
|
||||
* Much faster .rdb loading/saving times (antirez)
|
||||
* Add DISCARD command to discard queued MULTI commands. (djanowski, antirez)
|
||||
* Interactive mode to redis-cli. (Michel Martens & Damian Janowski)
|
||||
* Countless improvements to skip list implementation (Pieter Noordhuis)
|
||||
* ZINTERSTORE, ZUNIONSTORE commands (Pieter Noordhuis)
|
||||
* ZRANK command (Pieter Noordhuis)
|
||||
* SUBSTR command (antirez)
|
||||
* Now list push commands return the length of the new list (Gustavo Picon, antirez)
|
||||
* ZREMBYRANK command (Pieter Noordhuis)
|
||||
* Hash data type with all the associated commands (antirez & Pieter Noordhuis)
|
||||
* Added ZREVRANK (Pieter Noordhuis)
|
||||
* Fixed hard to track replication bug (Jeremy Zawodny)
|
||||
* Tool to check rdb files for unprocessable opcodes (Pieter Noordhuis)
|
||||
* Random internals refactoring / cleanups (antirez & Pieter Noordhuis)
|
||||
* Fixed issues 193, 194, 191 (Antonio Ognio)
|
||||
* Support for include directive in config parser (Jeremy Zawodny)
|
||||
* Fix authentication for redis-cli on non-interactive mode. (Michel Martens)
|
||||
* Added linenoise.c for zero deps line editing support. (antirez)
|
||||
* Use linenoise for line editing on redis-cli. (Michel Martens)
|
||||
* More incremental expiration of keys. (antirez)
|
||||
* CONFIG command (antirez)
|
||||
* Publis/Subscribe and associated commands (antirez)
|
||||
* New form of shared object, sharing all the small integers (antirez)
|
||||
* Validate numeric inputs. (Alex McHale)
|
||||
* Non blocking hash table resize, thanks to Derek Collison and Pieter Noordhuis for design ideas and help. (antirez)
|
||||
* Enable hash dereference in SORT on BY and GET (Pieter Noordhuis)
|
||||
* Now all commands support binary safe keys (antirez)
|
||||
* SETEX (Set + EXPIRE) implemented (antirez & Pieter Noordhuis)
|
||||
* redis-cli now accepts and outputs quoted strings in interactive mode (antirez)
|
||||
* New MONITOR output format with timestamp, 1 command per line. (antirez)
|
||||
* Tool to check if AOF is valid, and fix it if needed (Pieter Noordhuis)
|
||||
* Major speedup to AOF with fsync always, thanks to Derek Collison for the algorithm (antirez)
|
||||
* Append only filename configurable (Pieter Noordhuis)
|
||||
* Add SIGTERM shutdown handling. (Ashley Martens)
|
||||
* Many improvements in Redis test suite (Pieter Noordhuis)
|
||||
* Add git commit information to INFO listing (Pieter Noordhuis & antirez)
|
||||
* Many more minor things mainly from me and Pieter Noordhuis, but this changelog is already too big ;)
|
||||
|
||||
@@ -1,9 +1,43 @@
|
||||
2010-07-22 don't open/close log file if log level is not matched (antirez)
|
||||
2010-07-22 fixed a typo preventing compilation (antirez)
|
||||
2010-07-22 version is now 1.3.16 (antirez)
|
||||
2010-07-22 recent VM fixes backported to 2.0.0 branch (antirez)
|
||||
2010-07-22 fix rare condition where 'key' would already be destroyed while is was needed later on (Pieter Noordhuis)
|
||||
2010-07-21 vm_blocked_clients count fixed in INFO, thanks to Pietern Noordhuis (antirez)
|
||||
2010-07-09 old redis test deleted (antirez)
|
||||
2010-07-01 HTML doc updated (antirez)
|
||||
2010-07-01 Version is now 1.3.15 (antirez)
|
||||
2010-07-01 Fixed MONITOR output for consistency: now integer encoded values are also formatted like this: "3932" (antirez)
|
||||
2010-07-01 fixed error code checking for *write operations and return value in AOF rewriting function (antirez)
|
||||
2010-06-07 Fix for VM swapping out at DB loading time when key is shared (antirez)
|
||||
2010-06-03 cas test doesn't belong in 2.0.0 (Pieter Noordhuis)
|
||||
2010-06-03 tag memory leak check on kill server as "leaks" (Pieter Noordhuis)
|
||||
2010-06-03 tag test with sleep() as slow (Pieter Noordhuis)
|
||||
2010-06-03 make sure the config it returned when called without code (Pieter Noordhuis)
|
||||
2010-06-03 tag more slow tests (Pieter Noordhuis)
|
||||
2010-06-03 change how arguments are passed from the AOF tests (Pieter Noordhuis)
|
||||
2010-06-03 scope res variable outside test (Pieter Noordhuis)
|
||||
2010-06-02 tags for existing tests (Pieter Noordhuis)
|
||||
2010-06-02 pass tags to filter and match via arguments (Pieter Noordhuis)
|
||||
2010-06-02 basic support to tag tests (Pieter Noordhuis)
|
||||
2010-06-02 changed how server.tcl accepts options to support more directives without requiring more arguments to the proc (Pieter Noordhuis)
|
||||
2010-06-02 removed obsolete code (Pieter Noordhuis)
|
||||
2010-06-02 catch exceptions in the server proc, to be able to kill the entire chain of running servers (Pieter Noordhuis)
|
||||
2010-06-02 fixed VM object swappability computation (antirez)
|
||||
2010-06-02 smarter swapout policy on AOF too (antirez)
|
||||
2010-06-02 better swapout policy while loading RDB file (antirez)
|
||||
2010-05-26 build command outside while loop (antirez)
|
||||
2010-05-26 require the flag "-c" to be used for redis-cli to read the last argument from stdin (antirez)
|
||||
2010-05-28 Fixed ZINCR Nan bugs leading to server crash and added tests (antirez)
|
||||
2010-05-21 Merge branch 'solaris' of git://github.com/pietern/redis (antirez)
|
||||
2010-05-21 Changelog updated (antirez)
|
||||
2010-05-21 redis version is now 1.3.14 (aka 2.0.0 RC1) (antirez)
|
||||
2010-05-21 html doc updated (antirez)
|
||||
2010-05-21 by default test with valgrind does not show full leak info (antirez)
|
||||
2010-05-21 minor fix for the skiplist code, resulting in a false positive with valgrind, and in general into a useless small allocation (antirez)
|
||||
2010-05-21 Merge branch 'master' of git@github.com:antirez/redis (antirez)
|
||||
2010-05-21 tests suite initial support for valgrind, fixed the old test suite until the new one is able to target a specific host/port (antirez)
|
||||
2010-05-21 include solaris fixes in sha1.c (Pieter Noordhuis)
|
||||
2010-05-20 Don't exit with error in tests temp file cleanup if there are no files to clean (antirez)
|
||||
2010-05-20 fix memory leak on 32-bit builds (Pieter Noordhuis)
|
||||
2010-05-20 Merge branch 'master' of github.com:antirez/redis (antirez)
|
||||
|
||||
@@ -15,6 +15,10 @@ endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
|
||||
DEBUG?= -g -rdynamic -ggdb
|
||||
|
||||
INSTALL_TOP= /usr/local
|
||||
INSTALL_BIN= $(INSTALL_TOP)/bin
|
||||
INSTALL= cp -p
|
||||
|
||||
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o
|
||||
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
|
||||
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
|
||||
@@ -88,7 +92,7 @@ staticsymbols:
|
||||
tclsh utils/build-static-symbols.tcl > staticsymbols.h
|
||||
|
||||
test:
|
||||
tclsh8.5 tests/test_helper.tcl
|
||||
tclsh8.5 tests/test_helper.tcl --tags "${TAGS}"
|
||||
|
||||
bench:
|
||||
./redis-benchmark
|
||||
@@ -113,3 +117,10 @@ noopt:
|
||||
|
||||
32bitgprof:
|
||||
make PROF="-pg" ARCH="-arch i386"
|
||||
|
||||
install: all
|
||||
$(INSTALL) $(PRGNAME) $(INSTALL_BIN)
|
||||
$(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN)
|
||||
$(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN)
|
||||
$(INSTALL) $(CHECKDUMPPRGNAME) $(INSTALL_BIN)
|
||||
$(INSTALL) $(CHECKAOFPRGNAME) $(INSTALL_BIN)
|
||||
|
||||
@@ -1 +1,65 @@
|
||||
Check the 'doc' directory. doc/README.html is a good starting point :)
|
||||
Where to find complete Redis documentation?
|
||||
-------------------------------------------
|
||||
|
||||
This README is just a fast "quick start" document. You can find more detailed
|
||||
documentation here:
|
||||
|
||||
1) http://code.google.com/p/redis
|
||||
2) Check the 'doc' directory. doc/README.html is a good starting point :)
|
||||
|
||||
Building Redis
|
||||
--------------
|
||||
|
||||
It is as simple as:
|
||||
|
||||
% make
|
||||
|
||||
Redis is just a single binary, but if you want to install it you can use
|
||||
the "make install" target that will copy the binary in /usr/local/bin
|
||||
for default.
|
||||
|
||||
You can run a 32 bit Redis binary using:
|
||||
|
||||
% make 32bit
|
||||
|
||||
After you build Redis is a good idea to test it, using:
|
||||
|
||||
% make test
|
||||
|
||||
Running Redis
|
||||
-------------
|
||||
|
||||
To run Redis with the default configuration just type:
|
||||
|
||||
% ./redis-server
|
||||
|
||||
If you want to provide your redis.conf, you have to run it using an additional
|
||||
parameter (the path of the configuration file):
|
||||
|
||||
% ./redis-server /path/to/redis.conf
|
||||
|
||||
Playing with Redis
|
||||
------------------
|
||||
|
||||
You can use redis-cli to play with Redis. Start a redis-server instance,
|
||||
then in another terminal try the following:
|
||||
|
||||
% ./redis-cli
|
||||
redis> ping
|
||||
PONG
|
||||
redis> set foo bar
|
||||
OK
|
||||
redis> get foo
|
||||
"bar"
|
||||
redis> incr mycounter
|
||||
(integer) 1
|
||||
redis> incr mycounter
|
||||
(integer) 2
|
||||
redis>
|
||||
|
||||
You can find the list of all the available commands here:
|
||||
|
||||
http://code.google.com/p/redis/wiki/CommandReference
|
||||
|
||||
Enjoy!
|
||||
|
||||
|
||||
@@ -49,8 +49,13 @@
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to
|
||||
* enable/disable resizing of the hash table as needed. This is very important
|
||||
* for Redis, as we use copy-on-write and don't want to move too much memory
|
||||
* around when there is a child performing saving operations. */
|
||||
* around when there is a child performing saving operations.
|
||||
*
|
||||
* Note that even when dict_can_resize is set to 0, not all resizes are
|
||||
* prevented: an hash table is still allowed to grow if the ratio between
|
||||
* the number of elements and the buckets > dict_force_resize_ratio. */
|
||||
static int dict_can_resize = 1;
|
||||
static unsigned int dict_force_resize_ratio = 5;
|
||||
|
||||
/* ---------------------------- Utility funcitons --------------------------- */
|
||||
|
||||
@@ -522,14 +527,23 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
/* Expand the hash table if needed */
|
||||
static int _dictExpandIfNeeded(dict *d)
|
||||
{
|
||||
/* If the hash table is empty expand it to the intial size,
|
||||
* if the table is "full" dobule its size. */
|
||||
/* Incremental rehashing already in progress. Return. */
|
||||
if (dictIsRehashing(d)) return DICT_OK;
|
||||
if (d->ht[0].size == 0)
|
||||
return dictExpand(d, DICT_HT_INITIAL_SIZE);
|
||||
if (d->ht[0].used >= d->ht[0].size && dict_can_resize)
|
||||
|
||||
/* If the hash table is empty expand it to the intial size. */
|
||||
if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
|
||||
|
||||
/* If we reached the 1:1 ratio, and we are allowed to resize the hash
|
||||
* table (global setting) or we should avoid it but the ratio between
|
||||
* elements/buckets is over the "safe" threshold, we resize doubling
|
||||
* the number of buckets. */
|
||||
if (d->ht[0].used >= d->ht[0].size &&
|
||||
(dict_can_resize ||
|
||||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
|
||||
{
|
||||
return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?
|
||||
d->ht[0].size : d->ht[0].used)*2);
|
||||
}
|
||||
return DICT_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
<h2><a name="Commands operating on lists">Commands operating on lists</a></h2><ul><li> <a href="RpushCommand.html">RPUSH</a> <i>key</i> <i>value</i> <code name="code" class="python">Append an element to the tail of the List value at key</code></li><li> <a href="RpushCommand.html">LPUSH</a> <i>key</i> <i>value</i> <code name="code" class="python">Append an element to the head of the List value at key</code></li><li> <a href="LlenCommand.html">LLEN</a> <i>key</i> <code name="code" class="python">Return the length of the List value at key</code></li><li> <a href="LrangeCommand.html">LRANGE</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Return a range of elements from the List at key</code></li><li> <a href="LtrimCommand.html">LTRIM</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Trim the list at key to the specified range of elements</code></li><li> <a href="LindexCommand.html">LINDEX</a> <i>key</i> <i>index</i> <code name="code" class="python">Return the element at index position from the List at key</code></li><li> <a href="LsetCommand.html">LSET</a> <i>key</i> <i>index</i> <i>value</i> <code name="code" class="python">Set a new value as the element at index position of the List at key</code></li><li> <a href="LremCommand.html">LREM</a> <i>key</i> <i>count</i> <i>value</i> <code name="code" class="python">Remove the first-N, last-N, or all the elements matching value from the List at key</code></li><li> <a href="LpopCommand.html">LPOP</a> <i>key</i> <code name="code" class="python">Return and remove (atomically) the first element of the List at key</code></li><li> <a href="LpopCommand.html">RPOP</a> <i>key</i> <code name="code" class="python">Return and remove (atomically) the last element of the List at key</code></li><li> <a href="BlpopCommand.html">BLPOP</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <i>timeout</i> <code name="code" class="python">Blocking LPOP</code></li><li> <a href="BlpopCommand.html">BRPOP</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <i>timeout</i> <code name="code" class="python">Blocking RPOP</code></li><li> <a href="RpoplpushCommand.html">RPOPLPUSH</a> <i>srckey</i> <i>dstkey</i> <code name="code" class="python">Return and remove (atomically) the last element of the source List stored at _srckey_ and push the same element to the destination List stored at _dstkey_</code></li></ul>
|
||||
<h2><a name="Commands operating on sets">Commands operating on sets</a></h2><ul><li> <a href="SaddCommand.html">SADD</a> <i>key</i> <i>member</i> <code name="code" class="python">Add the specified member to the Set value at key</code></li><li> <a href="SremCommand.html">SREM</a> <i>key</i> <i>member</i> <code name="code" class="python">Remove the specified member from the Set value at key</code></li><li> <a href="SpopCommand.html">SPOP</a> <i>key</i> <code name="code" class="python">Remove and return (pop) a random element from the Set value at key</code></li><li> <a href="SmoveCommand.html">SMOVE</a> <i>srckey</i> <i>dstkey</i> <i>member</i> <code name="code" class="python">Move the specified member from one Set to another atomically</code></li><li> <a href="ScardCommand.html">SCARD</a> <i>key</i> <code name="code" class="python">Return the number of elements (the cardinality) of the Set at key</code></li><li> <a href="SismemberCommand.html">SISMEMBER</a> <i>key</i> <i>member</i> <code name="code" class="python">Test if the specified value is a member of the Set at key</code></li><li> <a href="SinterCommand.html">SINTER</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Return the intersection between the Sets stored at key1, key2, ..., keyN</code></li><li> <a href="SinterstoreCommand.html">SINTERSTORE</a> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Compute the intersection between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey</code></li><li> <a href="SunionCommand.html">SUNION</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Return the union between the Sets stored at key1, key2, ..., keyN</code></li><li> <a href="SunionstoreCommand.html">SUNIONSTORE</a> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Compute the union between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey</code></li><li> <a href="SdiffCommand.html">SDIFF</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN</code></li><li> <a href="SdiffstoreCommand.html">SDIFFSTORE</a> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Compute the difference between the Set key1 and all the Sets key2, ..., keyN, and store the resulting Set at dstkey</code></li><li> <a href="SmembersCommand.html">SMEMBERS</a> <i>key</i> <code name="code" class="python">Return all the members of the Set value at key</code></li><li> <a href="SrandmemberCommand.html">SRANDMEMBER</a> <i>key</i> <code name="code" class="python">Return a random member of the Set value at key</code></li></ul>
|
||||
<h2><a name="Commands operating on sorted sets (zsets, Redis version >">Commands operating on sorted sets (zsets, Redis version ></a></h2> 1.1) ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a> <i>key</i> <i>score</i> <i>member</i> <code name="code" class="python">Add the specified member to the Sorted Set value at key or update the score if it already exist</code></li><li> <a href="ZremCommand.html">ZREM</a> <i>key</i> <i>member</i> <code name="code" class="python">Remove the specified member from the Sorted Set value at key</code></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a> <i>key</i> <i>increment</i> <i>member</i> <code name="code" class="python">If the member already exists increment its score by _increment_, otherwise add the member setting _increment_ as score</code></li><li> <a href="ZrankCommand.html">ZRANK</a> <i>key</i> <i>member</i> <code name="code" class="python">Return the rank (or index) or _member_ in the sorted set at _key_, with scores being ordered from low to high</code></li><li> <a href="ZrankCommand.html">ZREVRANK</a> <i>key</i> <i>member</i> <code name="code" class="python">Return the rank (or index) or _member_ in the sorted set at _key_, with scores being ordered from high to low</code></li><li> <a href="ZrangeCommand.html">ZRANGE</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Return a range of elements from the sorted set at key</code></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Return a range of elements from the sorted set at key, exactly like ZRANGE, but the sorted set is ordered in traversed in reverse order, from the greatest to the smallest score</code></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a> <i>key</i> <i>min</i> <i>max</i> <code name="code" class="python">Return all the elements with score >= min and score <= max (a range query) from the sorted set</code></li><li> <a href="ZcardCommand.html">ZCARD</a> <i>key</i> <code name="code" class="python">Return the cardinality (number of elements) of the sorted set at key</code></li><li> <a href="ZscoreCommand.html">ZSCORE</a> <i>key</i> <i>element</i> <code name="code" class="python">Return the score associated with the specified element of the sorted set at key</code></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a> <i>key</i> <i>min</i> <i>max</i> <code name="code" class="python">Remove all the elements with rank >= min and rank <= max from the sorted set</code></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> <i>key</i> <i>min</i> <i>max</i> <code name="code" class="python">Remove all the elements with score >= min and score <= max from the sorted set</code></li><li> <a href="ZunionstoreCommand.html">ZUNIONSTORE / ZINTERSTORE</a> <i>dstkey</i> <i>N</i> <i>key1</i> ... <i>keyN</i> WEIGHTS <i>w1</i> ... <i>wN</i> AGGREGATE SUM|MIN|MAX <code name="code" class="python">Perform a union or intersection over a number of sorted sets with optional weight and aggregate</code></li></ul>
|
||||
<h2><a name="Commands operating on hashes">Commands operating on hashes</a></h2><ul><li> <a href="HsetCommand.html">HSET</a> <i>key</i> <i>field</i> <i>value</i> <code name="code" class="python">Set the hash field to the specified value. Creates the hash if needed.</code></li><li> <a href="HgetCommand.html">HGET</a> <i>key</i> <i>field</i> <code name="code" class="python">Retrieve the value of the specified hash field.</code></li><li> <a href="HmsetCommand.html">HMSET</a> <i>key</i> <i>field1</i> <i>value1</i> ... <i>fieldN</i> <i>valueN</i> <code name="code" class="python">Set the hash fields to their respective values.</code></li><li> <a href="HincrbyCommand.html">HINCRBY</a> <i>key</i> <i>field</i> <i>integer</i> <code name="code" class="python">Increment the integer value of the hash at _key_ on _field_ with _integer_.</code></li><li> <a href="HexistsCommand.html">HEXISTS</a> <i>key</i> <i>field</i> <code name="code" class="python">Test for existence of a specified field in a hash</code></li><li> <a href="HdelCommand.html">HDEL</a> <i>key</i> <i>field</i> <code name="code" class="python">Remove the specified field from a hash</code></li><li> <a href="HlenCommand.html">HLEN</a> <i>key</i> <code name="code" class="python">Return the number of items in a hash.</code></li><li> <a href="HgetallCommand.html">HKEYS</a> <i>key</i> <code name="code" class="python">Return all the fields in a hash.</code></li><li> <a href="HgetallCommand.html">HVALS</a> <i>key</i> <code name="code" class="python">Return all the values in a hash.</code></li><li> <a href="HgetallCommand.html">HGETALL</a> <i>key</i> <code name="code" class="python">Return all the fields and associated values in a hash.</code></li></ul>
|
||||
<h2><a name="Commands operating on hashes">Commands operating on hashes</a></h2><ul><li> <a href="HsetCommand.html">HSET</a> <i>key</i> <i>field</i> <i>value</i> <code name="code" class="python">Set the hash field to the specified value. Creates the hash if needed.</code></li><li> <a href="HgetCommand.html">HGET</a> <i>key</i> <i>field</i> <code name="code" class="python">Retrieve the value of the specified hash field.</code></li><li> <a href="HmgetCommand.html">HMGET</a> <i>key</i> <i>field1</i> ... <i>fieldN</i> <code name="code" class="python">Get the hash values assoicated to the specified fields.</code></li><li> <a href="HmsetCommand.html">HMSET</a> <i>key</i> <i>field1</i> <i>value1</i> ... <i>fieldN</i> <i>valueN</i> <code name="code" class="python">Set the hash fields to their respective values.</code></li><li> <a href="HincrbyCommand.html">HINCRBY</a> <i>key</i> <i>field</i> <i>integer</i> <code name="code" class="python">Increment the integer value of the hash at _key_ on _field_ with _integer_.</code></li><li> <a href="HexistsCommand.html">HEXISTS</a> <i>key</i> <i>field</i> <code name="code" class="python">Test for existence of a specified field in a hash</code></li><li> <a href="HdelCommand.html">HDEL</a> <i>key</i> <i>field</i> <code name="code" class="python">Remove the specified field from a hash</code></li><li> <a href="HlenCommand.html">HLEN</a> <i>key</i> <code name="code" class="python">Return the number of items in a hash.</code></li><li> <a href="HgetallCommand.html">HKEYS</a> <i>key</i> <code name="code" class="python">Return all the fields in a hash.</code></li><li> <a href="HgetallCommand.html">HVALS</a> <i>key</i> <code name="code" class="python">Return all the values in a hash.</code></li><li> <a href="HgetallCommand.html">HGETALL</a> <i>key</i> <code name="code" class="python">Return all the fields and associated values in a hash.</code></li></ul>
|
||||
<h2><a name="Sorting">Sorting</a></h2><ul><li> <a href="SortCommand.html">SORT</a> <i>key</i> BY <i>pattern</i> LIMIT <i>start</i> <i>end</i> GET <i>pattern</i> ASC|DESC ALPHA <code name="code" class="python">Sort a Set or a List accordingly to the specified parameters</code></li></ul>
|
||||
<h2><a name="Transactions">Transactions</a></h2><ul><li> <a href="MultiExecCommand.html">MULTI/EXEC/DISCARD</a> <code name="code" class="python">Redis atomic transactions</code></li></ul>
|
||||
<h2><a name="Transactions">Transactions</a></h2><ul><li> <a href="MultiExecCommand.html">MULTI/EXEC/DISCARD/WATCH/UNWATCH</a> <code name="code" class="python">Redis atomic transactions</code></li></ul>
|
||||
<h2><a name="Publish/Subscribe">Publish/Subscribe</a></h2><ul><li> <a href="PublishSubscribe.html">SUBSCRIBE/UNSUBSCRIBE/PUBLISH</a> <code name="code" class="python">Redis Public/Subscribe messaging paradigm implementation</code></li></ul>
|
||||
<h2><a name="Persistence control commands">Persistence control commands</a></h2><ul><li> <a href="SaveCommand.html">SAVE</a> <code name="code" class="python">Synchronously save the DB on disk</code></li><li> <a href="BgsaveCommand.html">BGSAVE</a> <code name="code" class="python">Asynchronously save the DB on disk</code></li><li> <a href="LastsaveCommand.html">LASTSAVE</a> <code name="code" class="python">Return the UNIX time stamp of the last successfully saving of the dataset on disk</code></li><li> <a href="ShutdownCommand.html">SHUTDOWN</a> <code name="code" class="python">Synchronously save the DB on disk, then shutdown the server</code></li><li> <a href="BgrewriteaofCommand.html">BGREWRITEAOF</a> <code name="code" class="python">Rewrite the append only file in background when it gets too big</code></li></ul>
|
||||
<h2><a name="Remote server control commands">Remote server control commands</a></h2><ul><li> <a href="InfoCommand.html">INFO</a> <code name="code" class="python">Provide information and statistics about the server</code></li><li> <a href="MonitorCommand.html">MONITOR</a> <code name="code" class="python">Dump all the received requests in real time</code></li><li> <a href="SlaveofCommand.html">SLAVEOF</a> <code name="code" class="python">Change the replication settings</code></li><li> <a href="ConfigCommand.html">CONFIG</a> <code name="code" class="python">Configure a Redis server at runtime</code></li></ul>
|
||||
|
||||
+1
-2
@@ -27,12 +27,11 @@
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="DEL _key1_ _key2_ ... _keyN_">DEL _key1_ _key2_ ... _keyN_</a></h1>
|
||||
<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The commnad returns the number ofkeys removed.</blockquote>
|
||||
<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The command returns the number ofkeys removed.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
|
||||
an integer greater than 0 if one or more keys were removed
|
||||
0 if none of the specified key existed
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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="#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="#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 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?</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="#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 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ExpireCommand</h1>
|
||||
@@ -45,8 +45,13 @@ OK
|
||||
OK
|
||||
% ./redis-cli lrange mylist 0 -1 /Users/antirez/hack/redis
|
||||
1. newelement
|
||||
</pre><blockquote>What happened here is that lpush against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timeof the commands to. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
|
||||
<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
|
||||
</pre><blockquote>What happened here is that LPUSH against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timethe commands were issued. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
|
||||
<h2><a name="Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a></h2>Even when the volatile key is not modified as part of a write operation, if it is
|
||||
read in a composite write operation (such as SINTERSTORE) it will be cleared at the
|
||||
start of the operation. This is done to avoid concurrency issues in replication.
|
||||
Imagine a key that is about to expire and the composite operation is run against it.
|
||||
On a slave node, this key might already be expired, which leaves you with a
|
||||
desync in your dataset.<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
|
||||
<h2><a name="Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a></h2><blockquote>Redis does not constantly monitor keys that are going to be expired.Keys are expired simply when some client tries to access a key, andthe key is found to be timed out.</blockquote>
|
||||
<blockquote>Of course this is not enough as there are expired keys that will neverbe accessed again. This keys should be expired anyway, so once everysecond Redis test a few keys at random among keys with an expire set.All the keys that are already expired are deleted from the keyspace. </blockquote>
|
||||
<h3><a name="Version 1.0">Version 1.0</a></h3><blockquote>Each time a fixed number of keys where tested (100 by default). So ifyou had a client setting keys with a very short expire faster than 100for second the memory continued to grow. When you stopped to insertnew keys the memory started to be freed, 100 keys every second in thebest conditions. Under a peak Redis continues to use more and more RAMeven if most keys are expired in each sweep.</blockquote>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li></ul>
|
||||
== Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li><li> <a href="MultiExecCommand.html">Redis Transactions</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
|
||||
== Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HmgetCommand.html">HMGET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<blockquote>the slow commands that may ruin the DB performance if not usedwith care*.</blockquote>
|
||||
<blockquote>In other words this command is intended only for debugging and *special* operations like creating a script to change the DB schema. Don't use it in your normal code. Use Redis <a href="Sets.html">Sets</a> in order to group together a subset of objects.</blockquote>
|
||||
Glob style patterns examples:
|
||||
<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically a string in the form of space separated list of keys. Note that most client libraries will return an Array of keys and not a single string with space separated keys (that is, split by " " is performed in the client library usually).</b></blockquote>
|
||||
<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2>
|
||||
<a href="ReplyTypes.html">Multi bulk reply</a></b></blockquote>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -28,10 +28,9 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h1><a name="LINDEX _key_ _index_">LINDEX _key_ _index_</a></h1>
|
||||
<i>Time complexity: O(n) (with n being the length of the list)</i><blockquote>Return the specified element of the list stored at the specifiedkey. 0 is the first element, 1 the second and so on. Negative indexesare supported, for example -1 is the last element, -2 the penultimateand so on.</blockquote>
|
||||
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range an empty string is returned.</blockquote>
|
||||
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range a 'nil' reply is returned.</blockquote>
|
||||
<blockquote>Note that even if the average time complexity is O(n) asking forthe first or the last element of the list is O(1).</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically the requested element.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+88
-17
@@ -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>MultiExecCommand: Contents</b><br> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Return value">Return value</a>
|
||||
<b>MultiExecCommand: Contents</b><br> <a href="#WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a><br> <a href="#UNWATCH">UNWATCH</a><br> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a><br> <a href="#WATCH explained">WATCH explained</a><br> <a href="#WATCH used to implement ZPOP">WATCH used to implement ZPOP</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">MultiExecCommand</h1>
|
||||
@@ -26,15 +26,21 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="MULTI">MULTI</a></h1>
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a></h1> 2.1.0)=
|
||||
<h1><a name="UNWATCH">UNWATCH</a></h1>
|
||||
<h1><a name="MULTI">MULTI</a></h1>
|
||||
<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><blockquote>MULTI, EXEC and DISCARD commands are the fundation of Redis Transactions.A Redis Transaction allows to execute a group of Redis commands in a singlestep, with two important guarantees:</blockquote>
|
||||
<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>
|
||||
<h2><a name="Usage">Usage</a></h2><blockquote>A Redis transaction is entered using the MULTI command. The command alwaysreplies with OK. At this point the user can issue multiple commands. Insteadto execute this commands Redis will "queue" them. All the commands areexecuted once EXEC is called.</blockquote>
|
||||
<blockquote>Calling DISCARD instead will flush the transaction queue and will exitthe transaction.</blockquote>
|
||||
<blockquote>The following is an example using the Ruby client:</blockquote><pre class="codeblock python" name="code">
|
||||
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the fundation 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
|
||||
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">
|
||||
?> r.multi
|
||||
=> "OK"
|
||||
>> r.incr "foo"
|
||||
@@ -46,9 +52,14 @@
|
||||
>> r.exec
|
||||
=> [1, 1, 2]
|
||||
</pre>
|
||||
<blockquote>As it is possible to see from the session above, MULTI returns an "array" ofreplies, where every element is the reply of a single command in thetransaction, in the same order the commands were queued.</blockquote>
|
||||
<blockquote>When a Redis connection is in the context of a MULTI request, all the commandswill reply with a simple string "QUEUED" if they are correct from thepoint of view of the syntax and arity (number of arguments) of the commaand.Some command is still allowed to fail during execution time.</blockquote>
|
||||
<blockquote>This is more clear if at protocol level: in the following example one commandwill fail when executed even if the syntax is right:</blockquote><pre class="codeblock python python" name="code">
|
||||
As it is possible to see from the session above, MULTI returns an "array" of
|
||||
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
|
||||
will fail when executed even if the syntax is right:
|
||||
<pre class="codeblock python python" name="code">
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
Escape character is '^]'.
|
||||
@@ -64,16 +75,21 @@ EXEC
|
||||
+OK
|
||||
-ERR Operation against a key holding the wrong kind of value
|
||||
</pre>
|
||||
<blockquote>MULTI returned a two elements bulk reply in witch one of this is a +OKcode and one is a -ERR reply. It's up to the client lib to find a sensibleway to provide the error to the user.</blockquote>
|
||||
<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>
|
||||
<blockquote>Another example, again using the write protocol with telnet, shows howsyntax errors are reported ASAP instead:</blockquote><pre class="codeblock python python python" name="code">
|
||||
MULTI returned a two elements bulk reply in witch one of this is a +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
|
||||
syntax errors are reported ASAP instead:
|
||||
<pre class="codeblock python python python" name="code">
|
||||
MULTI
|
||||
+OK
|
||||
INCR a b c
|
||||
-ERR wrong number of arguments for 'incr' command
|
||||
</pre>
|
||||
<blockquote>This time due to the syntax error the "bad" INCR command is not queuedat all.</blockquote>
|
||||
<h2><a name="The DISCARD command">The DISCARD command</a></h2><blockquote>DISCARD can be used in order to abort a transaction. No command will beexecuted, and the state of the client is again the normal one, outsideof a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
|
||||
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">
|
||||
?> r.set("foo",1)
|
||||
=> true
|
||||
>> r.multi
|
||||
@@ -84,9 +100,64 @@ INCR a b c
|
||||
=> "OK"
|
||||
>> r.get("foo")
|
||||
=> "1"
|
||||
</pre><h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
|
||||
</pre><h2><a name="Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a></h2>WATCH is used in order to provide a CAS (Check and Set) behavior to
|
||||
Redis Transactions.<br/><br/>WATCHed keys are monitored in order to detect changes against this keys.
|
||||
If at least a watched key will be modified before the EXEC call, the
|
||||
whole transaction will abort, and EXEC will return a nil object
|
||||
(A Null Multi Bulk reply) to notify that the transaction failed.<br/><br/>For example imagine we have the need to atomically increment the value
|
||||
of a key by 1 (I know we have INCR, let's suppose we don't have it).<br/><br/>The first try may be the following:
|
||||
<pre class="codeblock python python python python python" name="code">
|
||||
val = GET mykey
|
||||
val = val + 1
|
||||
SET mykey $val
|
||||
</pre>
|
||||
This will work reliably only if we have a single client performing the operation in a given time.
|
||||
If multiple clients will try to increment the key about at the same time
|
||||
there will be a race condition. For instance client A and B will read the
|
||||
old value, for instance, 10. The value will be incremented to 11 by both
|
||||
the clients, and finally SET as the value of the key. So the final value
|
||||
will be "11" instead of "12".<br/><br/>Thanks to WATCH we area able to model the problem very well:
|
||||
<pre class="codeblock python python python python python python" name="code">
|
||||
WATCH mykey
|
||||
val = GET mykey
|
||||
val = val + 1
|
||||
MULTI
|
||||
SET mykey $val
|
||||
EXEC
|
||||
</pre>
|
||||
Using the above code, if there are race conditions and another client
|
||||
modified the result of <i>val</i> in the time between our call to WATCH and
|
||||
our call to EXEC, the transaction will fail.<br/><br/>We'll have just to re-iterate the operation hoping this time we'll not get
|
||||
a new race. This form of locking is called <b>optimistic locking</b> and is
|
||||
a very powerful form of locking as in many problems there are multiple
|
||||
clients accessing a much bigger number of keys, so it's very unlikely that
|
||||
there are collisions: usually operations don't need to be performed
|
||||
multiple times.<h2><a name="WATCH explained">WATCH explained</a></h2>So what is WATCH really about? It is a command that will make the EXEC
|
||||
conditional: we are asking Redis to perform the transaction only if all
|
||||
the values WATCHed are still the same. Otherwise the transaction is not
|
||||
entered at all.<br/><br/>WATCH can be called multiple times. Simply all the WATCH calls will
|
||||
have the effects to watch for changes starting from the call, up to the
|
||||
moment EXEC is called.<br/><br/>When EXEC is called, either if it will fail or succeed, all keys are
|
||||
UNWATCHed. Also when a client connection is closed, everything gets
|
||||
UNWATCHed.<br/><br/>It is also possible to use the UNWATCH command (without arguments) in order
|
||||
to flush all the watched keys. Sometimes this is useful as we
|
||||
optimistically lock a few keys, since possibly we need to perform a transaction
|
||||
to alter those keys, but after reading the current content of the keys
|
||||
we don't want to proceed. When this happens we just call UNWATCH so that
|
||||
the connection can already be used freely for new transactions.<h2><a name="WATCH used to implement ZPOP">WATCH used to implement ZPOP</a></h2>A good example to illustrate how WATCH can be used to create new atomic
|
||||
operations otherwise not supported by Redis is to implement ZPOP, that is
|
||||
a command that pops the element with the lower score from a sorted set
|
||||
in an atomic way. This is the simplest implementation:
|
||||
<pre class="codeblock python python python python python python python" name="code">
|
||||
WATCH zset
|
||||
ele = ZRANGE zset 0 0
|
||||
MULTI
|
||||
ZREM zset ele
|
||||
EXEC
|
||||
</pre>
|
||||
If EXEC fails (returns a nil value) we just re-iterate the operation.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
|
||||
The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
|
||||
</pre>If a MULTI/EXEC transaction is aborted because of WATCH detected modified keys, a <a href="ReplyTypes.html">Null Multi Bulk reply</a> is returned.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== Sorted Set Commands ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a></li><li> <a href="ZremCommand.html">ZREM</a></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a></li><li> <a href="ZrankCommand.html">ZRANK</a></li><li> <a href="ZrankCommand.html">ZREVRANK</a></li><li> <a href="ZrangeCommand.html">ZRANGE</a></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </li><li> <a href="ZcardCommand.html">ZCARD</a></li><li> <a href="ZscoreCommand.html">ZSCORE</a></li><li> <a href="ZunionstoreCommand.html">ZUNION / ZINTER</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
|
||||
== Sorted Set Commands ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a></li><li> <a href="ZremCommand.html">ZREM</a></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a></li><li> <a href="ZrankCommand.html">ZRANK</a></li><li> <a href="ZrankCommand.html">ZREVRANK</a></li><li> <a href="ZrangeCommand.html">ZRANGE</a></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </li><li> <a href="ZcardCommand.html">ZCARD</a></li><li> <a href="ZscoreCommand.html">ZSCORE</a></li><li> <a href="ZunionstoreCommand.html">ZUNIONSTORE</a></li><li> <a href="ZunionstoreCommand.html">ZINTERSTORE</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
<i>Time complexity O(log(N)) with N being the number of elements in the sorted set</i><blockquote>If <i>member</i> already exists in the sorted set adds the <i>increment</i> to its scoreand updates the position of the element in the sorted set accordingly.If <i>member</i> does not already exist in the sorted set it is added with_increment_ as score (that is, like if the previous score was virtually zero).If <i>key</i> does not exist a new sorted set with the specified_member_ as sole member is crated. If the key exists but does not hold asorted set value an error is returned.</blockquote>
|
||||
<blockquote>The score value can be the string representation of a double precision floatingpoint number. It's possible to provide a negative value to perform a decrement.</blockquote>
|
||||
<blockquote>For an introduction to sorted sets check the <a href="IntroductionToRedisDataTypes.html">Introduction to Redis data types</a> page.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
|
||||
The score of the member after the increment is performed.
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a><pre class="codeblock python" name="code">
|
||||
The new score (a double precision floating point number) represented as string.
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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>ZunionCommand: Contents</b><br> <a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
<b>ZunionCommand: Contents</b><br> <a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ZunionCommand</h1>
|
||||
@@ -27,8 +27,9 @@
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.5) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
|
||||
<blockquote>As the terms imply, the ZINTER command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNION command inserts all elements across all inputs.</blockquote>
|
||||
<h1><a name="ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
|
||||
<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
|
||||
<blockquote>As the terms imply, the ZINTERSTORE command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNIONSTORE command inserts all elements across all inputs.</blockquote>
|
||||
<blockquote>Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means that the score of each element in the sorted set is first multiplied by this weight before being passed to the aggregation. When this option is not given, all weights default to 1.</blockquote>
|
||||
<blockquote>With the AGGREGATE option, it's possible to specify how the results of the union or intersection are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to be either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the number of elements in the sorted set at <i>dstkey</i>.
|
||||
|
||||
@@ -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>ZunionstoreCommand: Contents</b><br> <a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
<b>ZunionstoreCommand: Contents</b><br> <a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ZunionstoreCommand</h1>
|
||||
@@ -27,8 +27,10 @@
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.5) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
|
||||
<blockquote>As the terms imply, the ZINTER command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNION command inserts all elements across all inputs.</blockquote>
|
||||
<h1><a name="ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
|
||||
<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
|
||||
<i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
|
||||
<blockquote>As the terms imply, the ZINTERSTORE command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNIONSTORE command inserts all elements across all inputs.</blockquote>
|
||||
<blockquote>Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means that the score of each element in the sorted set is first multiplied by this weight before being passed to the aggregation. When this option is not given, all weights default to 1.</blockquote>
|
||||
<blockquote>With the AGGREGATE option, it's possible to specify how the results of the union or intersection are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to be either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the number of elements in the sorted set at <i>dstkey</i>.
|
||||
|
||||
+43
-5
@@ -70,6 +70,7 @@
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
@@ -81,13 +82,14 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
|
||||
#define LINENOISE_MAX_LINE 4096
|
||||
static char *unsupported_term[] = {"dumb","cons25",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*/
|
||||
static int atexit_registered = 0; /* register atexit just 1 time */
|
||||
static int history_max_len = 100;
|
||||
static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
|
||||
static int history_len = 0;
|
||||
char **history = NULL;
|
||||
|
||||
@@ -219,11 +221,10 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt)
|
||||
if (nread <= 0) return len;
|
||||
switch(c) {
|
||||
case 13: /* enter */
|
||||
history_len--;
|
||||
return len;
|
||||
case 4: /* ctrl-d */
|
||||
history_len--;
|
||||
return (len == 0) ? -1 : (int)len;
|
||||
free(history[history_len]);
|
||||
return (len == 0 && c == 4) ? -1 : (int)len;
|
||||
case 3: /* ctrl-c */
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
@@ -396,7 +397,7 @@ int linenoiseHistoryAdd(const char *line) {
|
||||
char *linecopy;
|
||||
|
||||
if (history_max_len == 0) return 0;
|
||||
if (history == 0) {
|
||||
if (history == NULL) {
|
||||
history = malloc(sizeof(char*)*history_max_len);
|
||||
if (history == NULL) return 0;
|
||||
memset(history,0,(sizeof(char*)*history_max_len));
|
||||
@@ -404,6 +405,7 @@ int linenoiseHistoryAdd(const char *line) {
|
||||
linecopy = strdup(line);
|
||||
if (!linecopy) return 0;
|
||||
if (history_len == history_max_len) {
|
||||
free(history[0]);
|
||||
memmove(history,history+1,sizeof(char*)*(history_max_len-1));
|
||||
history_len--;
|
||||
}
|
||||
@@ -431,3 +433,39 @@ int linenoiseHistorySetMaxLen(int len) {
|
||||
history_len = history_max_len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Save the history in the specified file. On success 0 is returned
|
||||
* otherwise -1 is returned. */
|
||||
int linenoiseHistorySave(char *filename) {
|
||||
FILE *fp = fopen(filename,"w");
|
||||
int j;
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
for (j = 0; j < history_len; j++)
|
||||
fprintf(fp,"%s\n",history[j]);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Load the history from the specified file. If the file does not exist
|
||||
* zero is returned and no operation is performed.
|
||||
*
|
||||
* If the file exists and the operation succeeded 0 is returned, otherwise
|
||||
* on error -1 is returned. */
|
||||
int linenoiseHistoryLoad(char *filename) {
|
||||
FILE *fp = fopen(filename,"r");
|
||||
char buf[LINENOISE_MAX_LINE];
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
|
||||
while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
|
||||
char *p;
|
||||
|
||||
p = strchr(buf,'\r');
|
||||
if (!p) p = strchr(buf,'\n');
|
||||
if (p) *p = '\0';
|
||||
linenoiseHistoryAdd(buf);
|
||||
}
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+3
-1
@@ -35,7 +35,9 @@
|
||||
#define __LINENOISE_H
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
int linenoiseHistoryAdd(char *line);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(char *filename);
|
||||
int linenoiseHistoryLoad(char *filename);
|
||||
|
||||
#endif /* __LINENOISE_H */
|
||||
|
||||
+23
-14
@@ -65,8 +65,8 @@
|
||||
/* data type to hold offset in file and size */
|
||||
typedef struct {
|
||||
void *data;
|
||||
unsigned long size;
|
||||
unsigned long offset;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
} pos;
|
||||
|
||||
static unsigned char level = 0;
|
||||
@@ -77,8 +77,8 @@ static pos positions[16];
|
||||
/* Hold a stack of errors */
|
||||
typedef struct {
|
||||
char error[16][1024];
|
||||
unsigned long offset[16];
|
||||
unsigned int level;
|
||||
size_t offset[16];
|
||||
size_t level;
|
||||
} errors_t;
|
||||
static errors_t errors;
|
||||
|
||||
@@ -112,7 +112,7 @@ int readBytes(void *target, long num) {
|
||||
if (p.offset + num > p.size) {
|
||||
return 0;
|
||||
} else {
|
||||
memcpy(target, (void*)((unsigned long)p.data + p.offset), num);
|
||||
memcpy(target, (void*)((size_t)p.data + p.offset), num);
|
||||
if (!peek) positions[level].offset += num;
|
||||
}
|
||||
return 1;
|
||||
@@ -494,15 +494,17 @@ void printCentered(int indent, int width, char* body) {
|
||||
printf("%s %s %s\n", head, body, tail);
|
||||
}
|
||||
|
||||
void printValid(int ops, int bytes) {
|
||||
void printValid(uint64_t ops, uint64_t bytes) {
|
||||
char body[80];
|
||||
sprintf(body, "Processed %d valid opcodes (in %d bytes)", ops, bytes);
|
||||
sprintf(body, "Processed %llu valid opcodes (in %llu bytes)",
|
||||
(unsigned long long) ops, (unsigned long long) bytes);
|
||||
printCentered(4, 80, body);
|
||||
}
|
||||
|
||||
void printSkipped(int bytes, int offset) {
|
||||
void printSkipped(uint64_t bytes, uint64_t offset) {
|
||||
char body[80];
|
||||
sprintf(body, "Skipped %d bytes (resuming at 0x%08x)", bytes, offset);
|
||||
sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)",
|
||||
(unsigned long long) bytes, (unsigned long long) offset);
|
||||
printCentered(4, 80, body);
|
||||
}
|
||||
|
||||
@@ -541,7 +543,7 @@ void printErrorStack(entry *e) {
|
||||
}
|
||||
|
||||
void process() {
|
||||
int i, num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
|
||||
uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
|
||||
entry entry;
|
||||
processHeader();
|
||||
|
||||
@@ -558,7 +560,9 @@ void process() {
|
||||
num_valid_bytes = 0;
|
||||
|
||||
/* search for next valid entry */
|
||||
unsigned long offset = positions[0].offset + 1;
|
||||
uint64_t offset = positions[0].offset + 1;
|
||||
int i = 0;
|
||||
|
||||
while (!entry.success && offset < positions[0].size) {
|
||||
positions[1].offset = offset;
|
||||
|
||||
@@ -606,9 +610,10 @@ void process() {
|
||||
}
|
||||
|
||||
/* print summary on errors */
|
||||
if (num_errors > 0) {
|
||||
if (num_errors) {
|
||||
printf("\n");
|
||||
printf("Total unprocessable opcodes: %d\n", num_errors);
|
||||
printf("Total unprocessable opcodes: %llu\n",
|
||||
(unsigned long long) num_errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +625,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
int fd;
|
||||
unsigned long size;
|
||||
off_t size;
|
||||
struct stat stat;
|
||||
void *data;
|
||||
|
||||
@@ -634,6 +639,10 @@ int main(int argc, char **argv) {
|
||||
size = stat.st_size;
|
||||
}
|
||||
|
||||
if (sizeof(size_t) == sizeof(int32_t) && size >= INT_MAX) {
|
||||
ERROR("Cannot check dump files >2GB on a 32-bit platform\n");
|
||||
}
|
||||
|
||||
data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
ERROR("Cannot mmap: %s\n", argv[1]);
|
||||
|
||||
+152
-269
@@ -35,6 +35,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "anet.h"
|
||||
#include "sds.h"
|
||||
@@ -57,144 +59,25 @@ static struct config {
|
||||
int shutdown;
|
||||
int monitor_mode;
|
||||
int pubsub_mode;
|
||||
int raw_output;
|
||||
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;
|
||||
} config;
|
||||
|
||||
struct redisCommand {
|
||||
char *name;
|
||||
int arity;
|
||||
int flags;
|
||||
};
|
||||
|
||||
#define CMDFLAG_NONE 0
|
||||
#define CMDFLAG_RAWOUTPUT 1
|
||||
|
||||
static struct redisCommand cmdTable[] = {
|
||||
{"auth",2,CMDFLAG_NONE},
|
||||
{"get",2,CMDFLAG_NONE},
|
||||
{"set",3,CMDFLAG_NONE},
|
||||
{"setnx",3,CMDFLAG_NONE},
|
||||
{"setex",4,CMDFLAG_NONE},
|
||||
{"append",3,CMDFLAG_NONE},
|
||||
{"substr",4,CMDFLAG_NONE},
|
||||
{"del",-2,CMDFLAG_NONE},
|
||||
{"exists",2,CMDFLAG_NONE},
|
||||
{"incr",2,CMDFLAG_NONE},
|
||||
{"decr",2,CMDFLAG_NONE},
|
||||
{"rpush",3,CMDFLAG_NONE},
|
||||
{"lpush",3,CMDFLAG_NONE},
|
||||
{"rpop",2,CMDFLAG_NONE},
|
||||
{"lpop",2,CMDFLAG_NONE},
|
||||
{"brpop",-3,CMDFLAG_NONE},
|
||||
{"blpop",-3,CMDFLAG_NONE},
|
||||
{"llen",2,CMDFLAG_NONE},
|
||||
{"lindex",3,CMDFLAG_NONE},
|
||||
{"lset",4,CMDFLAG_NONE},
|
||||
{"lrange",4,CMDFLAG_NONE},
|
||||
{"ltrim",4,CMDFLAG_NONE},
|
||||
{"lrem",4,CMDFLAG_NONE},
|
||||
{"rpoplpush",3,CMDFLAG_NONE},
|
||||
{"sadd",3,CMDFLAG_NONE},
|
||||
{"srem",3,CMDFLAG_NONE},
|
||||
{"smove",4,CMDFLAG_NONE},
|
||||
{"sismember",3,CMDFLAG_NONE},
|
||||
{"scard",2,CMDFLAG_NONE},
|
||||
{"spop",2,CMDFLAG_NONE},
|
||||
{"srandmember",2,CMDFLAG_NONE},
|
||||
{"sinter",-2,CMDFLAG_NONE},
|
||||
{"sinterstore",-3,CMDFLAG_NONE},
|
||||
{"sunion",-2,CMDFLAG_NONE},
|
||||
{"sunionstore",-3,CMDFLAG_NONE},
|
||||
{"sdiff",-2,CMDFLAG_NONE},
|
||||
{"sdiffstore",-3,CMDFLAG_NONE},
|
||||
{"smembers",2,CMDFLAG_NONE},
|
||||
{"zadd",4,CMDFLAG_NONE},
|
||||
{"zincrby",4,CMDFLAG_NONE},
|
||||
{"zrem",3,CMDFLAG_NONE},
|
||||
{"zremrangebyscore",4,CMDFLAG_NONE},
|
||||
{"zunion",-4,CMDFLAG_NONE},
|
||||
{"zinter",-4,CMDFLAG_NONE},
|
||||
{"zrange",-4,CMDFLAG_NONE},
|
||||
{"zrank",3,CMDFLAG_NONE},
|
||||
{"zrevrank",3,CMDFLAG_NONE},
|
||||
{"zrangebyscore",-4,CMDFLAG_NONE},
|
||||
{"zcount",4,CMDFLAG_NONE},
|
||||
{"zrevrange",-4,CMDFLAG_NONE},
|
||||
{"zcard",2,CMDFLAG_NONE},
|
||||
{"zscore",3,CMDFLAG_NONE},
|
||||
{"incrby",3,CMDFLAG_NONE},
|
||||
{"decrby",3,CMDFLAG_NONE},
|
||||
{"getset",3,CMDFLAG_NONE},
|
||||
{"randomkey",1,CMDFLAG_NONE},
|
||||
{"select",2,CMDFLAG_NONE},
|
||||
{"move",3,CMDFLAG_NONE},
|
||||
{"rename",3,CMDFLAG_NONE},
|
||||
{"renamenx",3,CMDFLAG_NONE},
|
||||
{"keys",2,CMDFLAG_NONE},
|
||||
{"dbsize",1,CMDFLAG_NONE},
|
||||
{"ping",1,CMDFLAG_NONE},
|
||||
{"echo",2,CMDFLAG_NONE},
|
||||
{"save",1,CMDFLAG_NONE},
|
||||
{"bgsave",1,CMDFLAG_NONE},
|
||||
{"rewriteaof",1,CMDFLAG_NONE},
|
||||
{"bgrewriteaof",1,CMDFLAG_NONE},
|
||||
{"shutdown",1,CMDFLAG_NONE},
|
||||
{"lastsave",1,CMDFLAG_NONE},
|
||||
{"type",2,CMDFLAG_NONE},
|
||||
{"flushdb",1,CMDFLAG_NONE},
|
||||
{"flushall",1,CMDFLAG_NONE},
|
||||
{"sort",-2,CMDFLAG_NONE},
|
||||
{"info",1,CMDFLAG_RAWOUTPUT},
|
||||
{"mget",-2,CMDFLAG_NONE},
|
||||
{"expire",3,CMDFLAG_NONE},
|
||||
{"expireat",3,CMDFLAG_NONE},
|
||||
{"ttl",2,CMDFLAG_NONE},
|
||||
{"slaveof",3,CMDFLAG_NONE},
|
||||
{"debug",-2,CMDFLAG_NONE},
|
||||
{"mset",-3,CMDFLAG_NONE},
|
||||
{"msetnx",-3,CMDFLAG_NONE},
|
||||
{"monitor",1,CMDFLAG_NONE},
|
||||
{"multi",1,CMDFLAG_NONE},
|
||||
{"exec",1,CMDFLAG_NONE},
|
||||
{"discard",1,CMDFLAG_NONE},
|
||||
{"hset",4,CMDFLAG_NONE},
|
||||
{"hget",3,CMDFLAG_NONE},
|
||||
{"hmset",-4,CMDFLAG_NONE},
|
||||
{"hmget",-3,CMDFLAG_NONE},
|
||||
{"hincrby",4,CMDFLAG_NONE},
|
||||
{"hdel",3,CMDFLAG_NONE},
|
||||
{"hlen",2,CMDFLAG_NONE},
|
||||
{"hkeys",2,CMDFLAG_NONE},
|
||||
{"hvals",2,CMDFLAG_NONE},
|
||||
{"hgetall",2,CMDFLAG_NONE},
|
||||
{"hexists",3,CMDFLAG_NONE},
|
||||
{"config",-2,CMDFLAG_NONE},
|
||||
{"subscribe",-2,CMDFLAG_NONE},
|
||||
{"unsubscribe",-1,CMDFLAG_NONE},
|
||||
{"psubscribe",-2,CMDFLAG_NONE},
|
||||
{"punsubscribe",-1,CMDFLAG_NONE},
|
||||
{"publish",3,CMDFLAG_NONE},
|
||||
{NULL,0,CMDFLAG_NONE}
|
||||
};
|
||||
|
||||
static int cliReadReply(int fd);
|
||||
static void usage();
|
||||
|
||||
static struct redisCommand *lookupCommand(char *name) {
|
||||
int j = 0;
|
||||
while(cmdTable[j].name != NULL) {
|
||||
if (!strcasecmp(name,cmdTable[j].name)) return &cmdTable[j];
|
||||
j++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int cliConnect(void) {
|
||||
/* Connect to the client. If force is not zero the connection is performed
|
||||
* even if there is already a connected socket. */
|
||||
static int cliConnect(int force) {
|
||||
char err[ANET_ERR_LEN];
|
||||
static int fd = ANET_ERR;
|
||||
|
||||
if (fd == ANET_ERR) {
|
||||
if (fd == ANET_ERR || force) {
|
||||
if (force) close(fd);
|
||||
fd = anetTcpConnect(err,config.hostip,config.hostport);
|
||||
if (fd == ANET_ERR) {
|
||||
fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err);
|
||||
@@ -213,7 +96,7 @@ static sds cliReadLine(int fd) {
|
||||
ssize_t ret;
|
||||
|
||||
ret = read(fd,&c,1);
|
||||
if (ret == -1) {
|
||||
if (ret <= 0) {
|
||||
sdsfree(line);
|
||||
return NULL;
|
||||
} else if ((ret == 0) || (c == '\n')) {
|
||||
@@ -230,7 +113,7 @@ static int cliReadSingleLineReply(int fd, int quiet) {
|
||||
|
||||
if (reply == NULL) return 1;
|
||||
if (!quiet)
|
||||
printf("%s\n", reply);
|
||||
printf("%s", reply);
|
||||
sdsfree(reply);
|
||||
return 0;
|
||||
}
|
||||
@@ -257,7 +140,7 @@ static void printStringRepr(char *s, int len) {
|
||||
}
|
||||
s++;
|
||||
}
|
||||
printf("\"\n");
|
||||
printf("\"");
|
||||
}
|
||||
|
||||
static int cliReadBulkReply(int fd) {
|
||||
@@ -275,7 +158,7 @@ static int cliReadBulkReply(int fd) {
|
||||
reply = zmalloc(bulklen);
|
||||
anetRead(fd,reply,bulklen);
|
||||
anetRead(fd,crlf,2);
|
||||
if (config.raw_output || !isatty(fileno(stdout))) {
|
||||
if (config.raw_output || !config.tty) {
|
||||
if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
|
||||
zfree(reply);
|
||||
return 1;
|
||||
@@ -292,6 +175,7 @@ static int cliReadBulkReply(int fd) {
|
||||
static int cliReadMultiBulkReply(int fd) {
|
||||
sds replylen = cliReadLine(fd);
|
||||
int elements, c = 1;
|
||||
int retval = 0;
|
||||
|
||||
if (replylen == NULL) return 1;
|
||||
elements = atoi(replylen);
|
||||
@@ -304,36 +188,45 @@ static int cliReadMultiBulkReply(int fd) {
|
||||
printf("(empty list or set)\n");
|
||||
}
|
||||
while(elements--) {
|
||||
printf("%d. ", c);
|
||||
if (cliReadReply(fd)) return 1;
|
||||
if (config.tty) printf("%d. ", c);
|
||||
if (cliReadReply(fd)) retval = 1;
|
||||
if (elements) printf("%c",config.mb_sep);
|
||||
c++;
|
||||
}
|
||||
return 0;
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int cliReadReply(int fd) {
|
||||
char type;
|
||||
int nread;
|
||||
|
||||
if (anetRead(fd,&type,1) <= 0) {
|
||||
if ((nread = anetRead(fd,&type,1)) <= 0) {
|
||||
if (config.shutdown) return 0;
|
||||
exit(1);
|
||||
if (config.interactive &&
|
||||
(nread == 0 || (nread == -1 && errno == ECONNRESET)))
|
||||
{
|
||||
return ECONNRESET;
|
||||
} else {
|
||||
printf("I/O error while reading from socket: %s",strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
switch(type) {
|
||||
case '-':
|
||||
printf("(error) ");
|
||||
if (config.tty) printf("(error) ");
|
||||
cliReadSingleLineReply(fd,0);
|
||||
return 1;
|
||||
case '+':
|
||||
return cliReadSingleLineReply(fd,0);
|
||||
case ':':
|
||||
printf("(integer) ");
|
||||
if (config.tty) printf("(integer) ");
|
||||
return cliReadSingleLineReply(fd,0);
|
||||
case '$':
|
||||
return cliReadBulkReply(fd);
|
||||
case '*':
|
||||
return cliReadMultiBulkReply(fd);
|
||||
default:
|
||||
printf("protocol error, got '%c' as reply type byte\n", type);
|
||||
printf("protocol error, got '%c' as reply type byte", type);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -358,28 +251,37 @@ static int selectDb(int fd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void showInteractiveHelp(void) {
|
||||
printf(
|
||||
"\n"
|
||||
"Welcome to redis-cli 2.0.0!\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) {
|
||||
struct redisCommand *rc = lookupCommand(argv[0]);
|
||||
char *command = argv[0];
|
||||
int fd, j, retval = 0;
|
||||
sds cmd;
|
||||
|
||||
if (!rc) {
|
||||
fprintf(stderr,"Unknown command '%s'\n",argv[0]);
|
||||
return 1;
|
||||
config.raw_output = !strcasecmp(command,"info");
|
||||
if (!strcasecmp(command,"help")) {
|
||||
showInteractiveHelp();
|
||||
return 0;
|
||||
}
|
||||
config.raw_output = (rc->flags & CMDFLAG_RAWOUTPUT);
|
||||
|
||||
if ((rc->arity > 0 && argc != rc->arity) ||
|
||||
(rc->arity < 0 && argc < -rc->arity)) {
|
||||
fprintf(stderr,"Wrong number of arguments for '%s'\n",rc->name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcasecmp(rc->name,"shutdown")) config.shutdown = 1;
|
||||
if (!strcasecmp(rc->name,"monitor")) config.monitor_mode = 1;
|
||||
if (!strcasecmp(rc->name,"subscribe") ||
|
||||
!strcasecmp(rc->name,"psubscribe")) config.pubsub_mode = 1;
|
||||
if ((fd = cliConnect()) == -1) return 1;
|
||||
if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
|
||||
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
|
||||
if (!strcasecmp(command,"subscribe") ||
|
||||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
|
||||
if ((fd = cliConnect(0)) == -1) return 1;
|
||||
|
||||
/* Select db number */
|
||||
retval = selectDb(fd);
|
||||
@@ -388,35 +290,33 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
while(repeat--) {
|
||||
/* Build the command to send */
|
||||
cmd = sdscatprintf(sdsempty(),"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
cmd = sdscatprintf(cmd,"$%lu\r\n",
|
||||
(unsigned long)sdslen(argv[j]));
|
||||
cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
}
|
||||
anetWrite(fd,cmd,sdslen(cmd));
|
||||
sdsfree(cmd);
|
||||
/* Build the command to send */
|
||||
cmd = sdscatprintf(sdsempty(),"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
cmd = sdscatprintf(cmd,"$%lu\r\n",
|
||||
(unsigned long)sdslen(argv[j]));
|
||||
cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
}
|
||||
|
||||
while(repeat--) {
|
||||
anetWrite(fd,cmd,sdslen(cmd));
|
||||
while (config.monitor_mode) {
|
||||
cliReadSingleLineReply(fd,0);
|
||||
if (cliReadSingleLineReply(fd,0)) exit(1);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (config.pubsub_mode) {
|
||||
printf("Reading messages... (press Ctrl-c to quit)\n");
|
||||
while (1) {
|
||||
cliReadReply(fd);
|
||||
printf("\n");
|
||||
printf("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
retval = cliReadReply(fd);
|
||||
|
||||
if (retval) {
|
||||
return retval;
|
||||
}
|
||||
if (!config.raw_output && config.tty) printf("\n");
|
||||
if (retval) return retval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -437,6 +337,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-h") && lastarg) {
|
||||
usage();
|
||||
} else if (!strcmp(argv[i],"-x")) {
|
||||
config.stdinarg = 1;
|
||||
} else if (!strcmp(argv[i],"-p") && !lastarg) {
|
||||
config.hostport = atoi(argv[i+1]);
|
||||
i++;
|
||||
@@ -450,7 +352,19 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.auth = argv[i+1];
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-i")) {
|
||||
config.interactive = 1;
|
||||
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 verison %s\n", "2.0.0");
|
||||
exit(0);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -476,10 +390,9 @@ static sds readArgFromStdin(void) {
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
fprintf(stderr, "usage: redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] [-i] cmd arg1 arg2 arg3 ... argN\n");
|
||||
fprintf(stderr, "usage: echo \"argN\" | redis-cli [-h host] [-a authpw] [-p port] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
|
||||
fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
|
||||
fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
|
||||
fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-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");
|
||||
@@ -489,7 +402,7 @@ static void usage() {
|
||||
/* Turn the plain C strings into Sds strings */
|
||||
static char **convertToSds(int count, char** args) {
|
||||
int j;
|
||||
char **sds = zmalloc(sizeof(char*)*count+1);
|
||||
char **sds = zmalloc(sizeof(char*)*count);
|
||||
|
||||
for(j = 0; j < count; j++)
|
||||
sds[j] = sdsnew(args[j]);
|
||||
@@ -497,86 +410,39 @@ static char **convertToSds(int count, char** args) {
|
||||
return sds;
|
||||
}
|
||||
|
||||
static char **splitArguments(char *line, int *argc) {
|
||||
char *p = line;
|
||||
char *current = NULL;
|
||||
char **vector = NULL;
|
||||
|
||||
*argc = 0;
|
||||
while(1) {
|
||||
/* skip blanks */
|
||||
while(*p && isspace(*p)) p++;
|
||||
if (*p) {
|
||||
/* get a token */
|
||||
int inq=0; /* set to 1 if we are in "quotes" */
|
||||
int done = 0;
|
||||
|
||||
if (current == NULL) current = sdsempty();
|
||||
while(!done) {
|
||||
if (inq) {
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
char c;
|
||||
|
||||
p++;
|
||||
switch(*p) {
|
||||
case 'n': c = '\n'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'b': c = '\b'; break;
|
||||
case 'a': c = '\a'; break;
|
||||
default: c = *p; break;
|
||||
}
|
||||
current = sdscatlen(current,&c,1);
|
||||
} else if (*p == '"') {
|
||||
done = 1;
|
||||
} else {
|
||||
current = sdscatlen(current,p,1);
|
||||
}
|
||||
} else {
|
||||
switch(*p) {
|
||||
case ' ':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\0':
|
||||
done=1;
|
||||
break;
|
||||
case '"':
|
||||
inq=1;
|
||||
break;
|
||||
default:
|
||||
current = sdscatlen(current,p,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define LINE_BUFLEN 4096
|
||||
static void repl() {
|
||||
int argc, j;
|
||||
char *line, **argv;
|
||||
char *line;
|
||||
sds *argv;
|
||||
|
||||
config.interactive = 1;
|
||||
while((line = linenoise("redis> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
argv = splitArguments(line,&argc);
|
||||
argv = sdssplitargs(line,&argc);
|
||||
linenoiseHistoryAdd(line);
|
||||
if (argc > 0) {
|
||||
if (config.historyfile) linenoiseHistorySave(config.historyfile);
|
||||
if (argv == NULL) {
|
||||
printf("Invalid argument(s)\n");
|
||||
continue;
|
||||
} else if (argc > 0) {
|
||||
if (strcasecmp(argv[0],"quit") == 0 ||
|
||||
strcasecmp(argv[0],"exit") == 0)
|
||||
exit(0);
|
||||
else
|
||||
cliSendCommand(argc, argv, 1);
|
||||
{
|
||||
exit(0);
|
||||
} else {
|
||||
int err;
|
||||
|
||||
if ((err = cliSendCommand(argc, argv, 1)) != 0) {
|
||||
if (err == ECONNRESET) {
|
||||
printf("Reconnecting... ");
|
||||
fflush(stdout);
|
||||
if (cliConnect(1) == -1) exit(1);
|
||||
printf("OK\n");
|
||||
cliSendCommand(argc,argv,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Free the argument vector */
|
||||
for (j = 0; j < argc; j++)
|
||||
@@ -589,21 +455,42 @@ static void repl() {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int noninteractive(int argc, char **argv) {
|
||||
int retval = 0;
|
||||
if (config.stdinarg) {
|
||||
argv = zrealloc(argv, (argc+1)*sizeof(char*));
|
||||
argv[argc] = readArgFromStdin();
|
||||
retval = cliSendCommand(argc+1, argv, config.repeat);
|
||||
} else {
|
||||
/* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
|
||||
retval = cliSendCommand(argc, argv, config.repeat);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
char **argvcopy;
|
||||
struct redisCommand *rc;
|
||||
|
||||
config.hostip = "127.0.0.1";
|
||||
config.hostport = 6379;
|
||||
config.repeat = 1;
|
||||
config.dbnum = 0;
|
||||
config.shutdown = 0;
|
||||
config.interactive = 0;
|
||||
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';
|
||||
|
||||
if (getenv("HOME") != NULL) {
|
||||
config.historyfile = malloc(256);
|
||||
snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
|
||||
linenoiseHistoryLoad(config.historyfile);
|
||||
}
|
||||
|
||||
firstarg = parseOptions(argc,argv);
|
||||
argc -= firstarg;
|
||||
@@ -611,24 +498,20 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (config.auth != NULL) {
|
||||
char *authargv[2];
|
||||
int dbnum = config.dbnum;
|
||||
|
||||
/* We need to save the real configured database number and set it to
|
||||
* zero here, otherwise cliSendCommand() will try to perform the
|
||||
* SELECT command before the authentication, and it will fail. */
|
||||
config.dbnum = 0;
|
||||
authargv[0] = "AUTH";
|
||||
authargv[1] = config.auth;
|
||||
cliSendCommand(2, convertToSds(2, authargv), 1);
|
||||
config.dbnum = dbnum; /* restore the right DB number */
|
||||
}
|
||||
|
||||
if (argc == 0 || config.interactive == 1) repl();
|
||||
|
||||
argvcopy = convertToSds(argc, argv);
|
||||
|
||||
/* Read the last argument from stdandard input if needed */
|
||||
if ((rc = lookupCommand(argv[0])) != NULL) {
|
||||
if (rc->arity > 0 && argc == rc->arity-1) {
|
||||
sds lastarg = readArgFromStdin();
|
||||
argvcopy[argc] = lastarg;
|
||||
argc++;
|
||||
}
|
||||
}
|
||||
|
||||
return cliSendCommand(argc, argvcopy, config.repeat);
|
||||
/* Start interactive mode when no command is provided */
|
||||
if (argc == 0) repl();
|
||||
/* Otherwise, we have some arguments to execute */
|
||||
return noninteractive(argc,convertToSds(argc,argv));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define REDIS_VERSION "1.3.14"
|
||||
#define REDIS_VERSION "2.0.4"
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "config.h"
|
||||
@@ -342,10 +342,12 @@ struct saveparam {
|
||||
|
||||
/* Global server state structure */
|
||||
struct redisServer {
|
||||
pthread_t mainthread;
|
||||
int port;
|
||||
int fd;
|
||||
redisDb *db;
|
||||
long long dirty; /* changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* used to restore dirty on failed BGSAVE */
|
||||
list *clients;
|
||||
list *slaves, *monitors;
|
||||
char neterr[ANET_ERR_LEN];
|
||||
@@ -1057,23 +1059,22 @@ static int ll2string(char *s, size_t len, long long value) {
|
||||
static void redisLog(int level, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
FILE *fp;
|
||||
char *c = ".-*#";
|
||||
char buf[64];
|
||||
time_t now;
|
||||
|
||||
if (level < server.verbosity) return;
|
||||
|
||||
fp = (server.logfile == NULL) ? stdout : fopen(server.logfile,"a");
|
||||
if (!fp) return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
if (level >= server.verbosity) {
|
||||
char *c = ".-*#";
|
||||
char buf[64];
|
||||
time_t now;
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
va_end(ap);
|
||||
|
||||
if (server.logfile) fclose(fp);
|
||||
@@ -1320,7 +1321,7 @@ void backgroundSaveDoneHandler(int statloc) {
|
||||
if (!bysignal && exitcode == 0) {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Background saving terminated with success");
|
||||
server.dirty = 0;
|
||||
server.dirty = server.dirty - server.dirty_before_bgsave;
|
||||
server.lastsave = time(NULL);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
redisLog(REDIS_WARNING, "Background saving error");
|
||||
@@ -1514,10 +1515,21 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD
|
||||
num = REDIS_EXPIRELOOKUPS_PER_CRON;
|
||||
while (num--) {
|
||||
dictEntry *de;
|
||||
robj *key;
|
||||
time_t t;
|
||||
|
||||
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
|
||||
t = (time_t) dictGetEntryVal(de);
|
||||
key = dictGetEntryKey(de);
|
||||
/* Don't expire keys that are in the contest of I/O jobs.
|
||||
* Otherwise decrRefCount will kill the I/O thread and
|
||||
* clients waiting for this keys will wait forever.
|
||||
*
|
||||
* In general this change will not have any impact on the
|
||||
* performance of the expiring algorithm but it's much safer. */
|
||||
if (server.vm_enabled &&
|
||||
(key->storage == REDIS_VM_SWAPPING ||
|
||||
key->storage == REDIS_VM_LOADING)) continue;
|
||||
if (now > t) {
|
||||
deleteKey(db,dictGetEntryKey(de));
|
||||
expired++;
|
||||
@@ -1725,6 +1737,7 @@ static 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);
|
||||
@@ -2026,17 +2039,24 @@ static void freeClient(redisClient *c) {
|
||||
ln = listSearchKey(server.clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.clients,ln);
|
||||
/* Remove from the list of clients waiting for swapped keys */
|
||||
if (c->flags & REDIS_IO_WAIT && listLength(c->io_keys) == 0) {
|
||||
ln = listSearchKey(server.io_ready_clients,c);
|
||||
if (ln) {
|
||||
/* Remove from the list of clients waiting for swapped keys, or ready
|
||||
* to be restarted, but not yet woken up again. */
|
||||
if (c->flags & REDIS_IO_WAIT) {
|
||||
redisAssert(server.vm_enabled);
|
||||
if (listLength(c->io_keys) == 0) {
|
||||
ln = listSearchKey(server.io_ready_clients,c);
|
||||
|
||||
/* When this client is waiting to be woken up (REDIS_IO_WAIT),
|
||||
* it should be present in the list io_ready_clients */
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.io_ready_clients,ln);
|
||||
server.vm_blocked_clients--;
|
||||
} else {
|
||||
while (listLength(c->io_keys)) {
|
||||
ln = listFirst(c->io_keys);
|
||||
dontWaitForSwappedKey(c,ln->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (server.vm_enabled && listLength(c->io_keys)) {
|
||||
ln = listFirst(c->io_keys);
|
||||
dontWaitForSwappedKey(c,ln->value);
|
||||
server.vm_blocked_clients--;
|
||||
}
|
||||
listRelease(c->io_keys);
|
||||
/* Master/slave cleanup */
|
||||
@@ -2276,9 +2296,6 @@ static void call(redisClient *c, struct redisCommand *cmd) {
|
||||
static int processCommand(redisClient *c) {
|
||||
struct redisCommand *cmd;
|
||||
|
||||
/* Free some memory if needed (maxmemory setting) */
|
||||
if (server.maxmemory) freeMemoryIfNeeded();
|
||||
|
||||
/* Handle the multi bulk command type. This is an alternative protocol
|
||||
* supported by Redis in order to receive commands that are composed of
|
||||
* multiple binary-safe "bulk" arguments. The latency of processing is
|
||||
@@ -2410,7 +2427,12 @@ static int processCommand(redisClient *c) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Handle the maxmemory directive */
|
||||
/* Handle the maxmemory directive.
|
||||
*
|
||||
* First we try to free some memory if possible (if there are volatile
|
||||
* keys in the dataset). If there are not the only thing we can do
|
||||
* is returning an error. */
|
||||
if (server.maxmemory) freeMemoryIfNeeded();
|
||||
if (server.maxmemory && (cmd->flags & REDIS_CMD_DENYOOM) &&
|
||||
zmalloc_used_memory() > server.maxmemory)
|
||||
{
|
||||
@@ -2555,7 +2577,7 @@ static void replicationFeedMonitors(list *monitors, int dictid, robj **argv, int
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (argv[j]->encoding == REDIS_ENCODING_INT) {
|
||||
cmdrepr = sdscatprintf(cmdrepr, "%ld", (long)argv[j]->ptr);
|
||||
cmdrepr = sdscatprintf(cmdrepr, "\"%ld\"", (long)argv[j]->ptr);
|
||||
} else {
|
||||
cmdrepr = sdscatrepr(cmdrepr,(char*)argv[j]->ptr,
|
||||
sdslen(argv[j]->ptr));
|
||||
@@ -2708,6 +2730,14 @@ static redisClient *createClient(int fd) {
|
||||
anetNonBlock(NULL,fd);
|
||||
anetTcpNoDelay(NULL,fd);
|
||||
if (!c) return NULL;
|
||||
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
|
||||
readQueryFromClient, c) == AE_ERR)
|
||||
{
|
||||
close(fd);
|
||||
zfree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
selectDb(c,0);
|
||||
c->fd = fd;
|
||||
c->querybuf = sdsempty();
|
||||
@@ -2733,11 +2763,6 @@ static redisClient *createClient(int fd) {
|
||||
c->pubsub_patterns = listCreate();
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCount);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
|
||||
readQueryFromClient, c) == AE_ERR) {
|
||||
freeClient(c);
|
||||
return NULL;
|
||||
}
|
||||
listAddNodeTail(server.clients,c);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
@@ -2923,7 +2948,8 @@ static robj *createStringObject(char *ptr, size_t len) {
|
||||
|
||||
static robj *createStringObjectFromLongLong(long long value) {
|
||||
robj *o;
|
||||
if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
|
||||
if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
|
||||
pthread_equal(pthread_self(),server.mainthread)) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
o = shared.integers[value];
|
||||
} else {
|
||||
@@ -3127,6 +3153,7 @@ static int deleteKey(redisDb *db, robj *key) {
|
||||
* it's count. This may happen when we get the object reference directly
|
||||
* from the hash table with dictRandomKey() or dict iterators */
|
||||
incrRefCount(key);
|
||||
if (server.vm_enabled) handleClientsBlockedOnSwappedKey(db,key);
|
||||
if (dictSize(db->expires)) dictDelete(db->expires,key);
|
||||
retval = dictDelete(db->dict,key);
|
||||
decrRefCount(key);
|
||||
@@ -3175,8 +3202,15 @@ static robj *tryObjectEncoding(robj *o) {
|
||||
/* Check if we can represent this string as a long integer */
|
||||
if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return o;
|
||||
|
||||
/* Ok, this object can be encoded */
|
||||
if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
|
||||
/* Ok, this object can be encoded...
|
||||
*
|
||||
* Can I use a shared object? Only if the object is inside a given
|
||||
* range and if this is the main thread, since when VM is enabled we
|
||||
* have the constraint that I/O thread should only handle non-shared
|
||||
* objects, in order to avoid race conditions (we don't have per-object
|
||||
* locking). */
|
||||
if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
|
||||
pthread_equal(pthread_self(),server.mainthread)) {
|
||||
decrRefCount(o);
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
@@ -3272,7 +3306,7 @@ static int getDoubleFromObject(robj *o, double *target) {
|
||||
redisAssert(o->type == REDIS_STRING);
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
value = strtod(o->ptr, &eptr);
|
||||
if (eptr[0] != '\0') return REDIS_ERR;
|
||||
if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR;
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
} else {
|
||||
@@ -3774,6 +3808,7 @@ static int rdbSaveBackground(char *filename) {
|
||||
|
||||
if (server.bgsavechildpid != -1) return REDIS_ERR;
|
||||
if (server.vm_enabled) waitEmptyIOJobsQueue();
|
||||
server.dirty_before_bgsave = server.dirty;
|
||||
if ((childpid = fork()) == 0) {
|
||||
/* Child */
|
||||
if (server.vm_enabled) vmReopenSwapFile();
|
||||
@@ -4057,7 +4092,6 @@ static int rdbLoad(char *filename) {
|
||||
redisDb *db = server.db+0;
|
||||
char buf[1024];
|
||||
time_t expiretime, now = time(NULL);
|
||||
long long loadedkeys = 0;
|
||||
|
||||
fp = fopen(filename,"r");
|
||||
if (!fp) return REDIS_ERR;
|
||||
@@ -4076,6 +4110,7 @@ static int rdbLoad(char *filename) {
|
||||
}
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
int force_swapout;
|
||||
|
||||
expiretime = -1;
|
||||
/* Read type. */
|
||||
@@ -4114,7 +4149,6 @@ static int rdbLoad(char *filename) {
|
||||
redisLog(REDIS_WARNING,"Loading DB, duplicated key (%s) found! Unrecoverable error, exiting now.", key->ptr);
|
||||
exit(1);
|
||||
}
|
||||
loadedkeys++;
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(db,key,expiretime);
|
||||
|
||||
@@ -4132,17 +4166,30 @@ static int rdbLoad(char *filename) {
|
||||
if (de) {
|
||||
key = dictGetEntryKey(de);
|
||||
val = dictGetEntryVal(de);
|
||||
|
||||
if (val->refcount != 1) continue;
|
||||
|
||||
if (vmSwapObjectBlocking(key,val) == REDIS_OK) {
|
||||
dictGetEntryVal(de) = NULL;
|
||||
/* Unshare the key if needed */
|
||||
if (key->refcount != 1) {
|
||||
robj *newkey = dupStringObject(key);
|
||||
decrRefCount(key);
|
||||
key = dictGetEntryKey(de) = newkey;
|
||||
}
|
||||
|
||||
if (vmSwapObjectBlocking(key,val) == REDIS_OK)
|
||||
dictGetEntryVal(de) = NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Flush data on disk once 32 MB of additional RAM are used... */
|
||||
force_swapout = 0;
|
||||
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
|
||||
force_swapout = 1;
|
||||
|
||||
/* If we have still some hope of having some value fitting memory
|
||||
* then we try random sampling. */
|
||||
if (!swap_all_values && server.vm_enabled && (loadedkeys % 5000) == 0) {
|
||||
if (!swap_all_values && server.vm_enabled && force_swapout) {
|
||||
while (zmalloc_used_memory() > server.vm_max_memory) {
|
||||
if (vmSwapOneObjectBlocking() == REDIS_ERR) break;
|
||||
}
|
||||
@@ -4174,7 +4221,7 @@ static int prepareForShutdown() {
|
||||
/* Append only file: fsync() the AOF and exit */
|
||||
fsync(server.appendfd);
|
||||
if (server.vm_enabled) unlink(server.vm_swap_file);
|
||||
} else {
|
||||
} else if (server.saveparamslen > 0) {
|
||||
/* Snapshotting. Perform a SYNC SAVE and exit */
|
||||
if (rdbSave(server.dbfilename) == REDIS_OK) {
|
||||
if (server.daemonize)
|
||||
@@ -4189,6 +4236,8 @@ static int prepareForShutdown() {
|
||||
redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Not saving DB.");
|
||||
}
|
||||
redisLog(REDIS_WARNING,"Server exit now, bye bye...");
|
||||
return REDIS_OK;
|
||||
@@ -4875,9 +4924,9 @@ static void lrangeCommand(redisClient *c) {
|
||||
if (start < 0) start = llen+start;
|
||||
if (end < 0) end = llen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
|
||||
/* indexes sanity checks */
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
/* Out of range start or start > end result in empty list */
|
||||
addReply(c,shared.emptymultibulk);
|
||||
@@ -4914,9 +4963,9 @@ static void ltrimCommand(redisClient *c) {
|
||||
if (start < 0) start = llen+start;
|
||||
if (end < 0) end = llen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
|
||||
/* indexes sanity checks */
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
/* Out of range start or start > end result in empty list */
|
||||
ltrim = llen;
|
||||
@@ -5738,6 +5787,15 @@ static void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scor
|
||||
} else {
|
||||
*score = scoreval;
|
||||
}
|
||||
if (isnan(*score)) {
|
||||
addReplySds(c,
|
||||
sdsnew("-ERR resulting score is not a number (NaN)\r\n"));
|
||||
zfree(score);
|
||||
/* Note that we don't need to check if the zset may be empty and
|
||||
* should be removed here, as we can only obtain Nan as score if
|
||||
* there was already an element in the sorted set. */
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
*score = scoreval;
|
||||
}
|
||||
@@ -5785,15 +5843,13 @@ static void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scor
|
||||
|
||||
static void zaddCommand(redisClient *c) {
|
||||
double scoreval;
|
||||
|
||||
if (getDoubleFromObjectOrReply(c, c->argv[2], &scoreval, NULL) != REDIS_OK) return;
|
||||
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
|
||||
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
|
||||
}
|
||||
|
||||
static void zincrbyCommand(redisClient *c) {
|
||||
double scoreval;
|
||||
|
||||
if (getDoubleFromObjectOrReply(c, c->argv[2], &scoreval, NULL) != REDIS_OK) return;
|
||||
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
|
||||
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
|
||||
}
|
||||
|
||||
@@ -5867,9 +5923,9 @@ static void zremrangebyrankCommand(redisClient *c) {
|
||||
if (start < 0) start = llen+start;
|
||||
if (end < 0) end = llen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
|
||||
/* indexes sanity checks */
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
@@ -5905,6 +5961,10 @@ static int qsortCompareZsetopsrcByCardinality(const void *s1, const void *s2) {
|
||||
inline static void zunionInterAggregate(double *target, double val, int aggregate) {
|
||||
if (aggregate == REDIS_AGGR_SUM) {
|
||||
*target = *target + val;
|
||||
/* The result of adding two doubles is NaN when one variable
|
||||
* is +inf and the other is -inf. When these numbers are added,
|
||||
* we maintain the convention of the result being 0.0. */
|
||||
if (isnan(*target)) *target = 0.0;
|
||||
} else if (aggregate == REDIS_AGGR_MIN) {
|
||||
*target = val < *target ? val : *target;
|
||||
} else if (aggregate == REDIS_AGGR_MAX) {
|
||||
@@ -5964,8 +6024,12 @@ static void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
if (remaining >= (zsetnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) {
|
||||
j++; remaining--;
|
||||
for (i = 0; i < zsetnum; i++, j++, remaining--) {
|
||||
if (getDoubleFromObjectOrReply(c, c->argv[j], &src[i].weight, NULL) != REDIS_OK)
|
||||
if (getDoubleFromObjectOrReply(c,c->argv[j],&src[i].weight,
|
||||
"weight value is not a double") != REDIS_OK)
|
||||
{
|
||||
zfree(src);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) {
|
||||
j++; remaining--;
|
||||
@@ -6064,7 +6128,7 @@ static void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION);
|
||||
}
|
||||
|
||||
deleteKey(c->db,dstkey);
|
||||
if (deleteKey(c->db,dstkey)) server.dirty++;
|
||||
if (dstzset->zsl->length) {
|
||||
dictAdd(c->db->dict,dstkey,dstobj);
|
||||
incrRefCount(dstkey);
|
||||
@@ -6117,11 +6181,10 @@ static void zrangeGenericCommand(redisClient *c, int reverse) {
|
||||
if (start < 0) start = llen+start;
|
||||
if (end < 0) end = llen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
|
||||
/* indexes sanity checks */
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
/* Out of range start or start > end result in empty list */
|
||||
addReply(c,shared.emptymultibulk);
|
||||
return;
|
||||
}
|
||||
@@ -6646,6 +6709,7 @@ static void hmgetCommand(redisClient *c) {
|
||||
o = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (o != NULL && o->type != REDIS_HASH) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Note the check for o != NULL happens inside the loop. This is
|
||||
@@ -7575,6 +7639,22 @@ static void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeou
|
||||
list *l;
|
||||
int j;
|
||||
|
||||
/* Never block for keys when the AOF is being replayed.
|
||||
*
|
||||
* When a BPOP is issued against an expiring list, the list is expired
|
||||
* by means of the delete-on-write semantic, which causes the BPOP
|
||||
* command to be written to the AOF. Then, the BPOP ends up in a blocking
|
||||
* state and waits for a PUSH on any given key from another client.
|
||||
*
|
||||
* On replay, the expiring list will also be expired (if it isn't already),
|
||||
* and the fake AOF client will block for a push. When multiple BPOPs
|
||||
* (issued by multiple clients) are written to the AOF, this can cause the
|
||||
* same blocking code to be executed against the single fake AOF client,
|
||||
* which in turn can place the client in the list(s) of blocking clients
|
||||
* *multiple times*. This state should be prevented, so simply skip
|
||||
* blocking for the fake AOF client. */
|
||||
if (c->fd < 0) return;
|
||||
|
||||
c->blockingkeys = zmalloc(sizeof(robj*)*numkeys);
|
||||
c->blockingkeysnum = numkeys;
|
||||
c->blockingto = timeout;
|
||||
@@ -7668,9 +7748,20 @@ static int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
|
||||
/* Blocking RPOP/LPOP */
|
||||
static 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) {
|
||||
addReplySds(c,sdsnew("-ERR timeout is negative\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (j = 1; j < c->argc-1; j++) {
|
||||
o = lookupKeyWrite(c->db,c->argv[j]);
|
||||
if (o != NULL) {
|
||||
@@ -7710,8 +7801,16 @@ static void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we are inside a MULTI/EXEC and the list is empty the only thing
|
||||
* we can do is treating it as a timeout (even with timeout 0). */
|
||||
if (c->flags & REDIS_MULTI) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the list is empty or the key does not exists we must block */
|
||||
timeout = strtol(c->argv[c->argc-1]->ptr,NULL,10);
|
||||
timeout = lltimeout;
|
||||
if (timeout > 0) timeout += time(NULL);
|
||||
blockForKeys(c,c->argv+1,c->argc-2,timeout);
|
||||
}
|
||||
@@ -7754,7 +7853,7 @@ static int syncRead(int fd, char *ptr, ssize_t size, int timeout) {
|
||||
while(size) {
|
||||
if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) {
|
||||
nread = read(fd,ptr,size);
|
||||
if (nread == -1) return -1;
|
||||
if (nread <= 0) return -1;
|
||||
ptr += nread;
|
||||
size -= nread;
|
||||
totread += nread;
|
||||
@@ -8032,9 +8131,9 @@ static int syncWithMaster(void) {
|
||||
int nread, nwritten;
|
||||
|
||||
nread = read(fd,buf,(dumpsize < 1024)?dumpsize:1024);
|
||||
if (nread == -1) {
|
||||
if (nread <= 0) {
|
||||
redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s",
|
||||
strerror(errno));
|
||||
(nread == -1) ? strerror(errno) : "connection lost");
|
||||
close(fd);
|
||||
close(dfd);
|
||||
return REDIS_ERR;
|
||||
@@ -8149,6 +8248,7 @@ static void freeMemoryIfNeeded(void) {
|
||||
}
|
||||
}
|
||||
deleteKey(server.db+j,minkey);
|
||||
server.stat_expiredkeys++;
|
||||
}
|
||||
}
|
||||
if (!freed) return; /* nothing to free... */
|
||||
@@ -8316,7 +8416,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
struct redisClient *fakeClient;
|
||||
FILE *fp = fopen(filename,"r");
|
||||
struct redis_stat sb;
|
||||
unsigned long long loadedkeys = 0;
|
||||
int appendonly = server.appendonly;
|
||||
|
||||
if (redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0)
|
||||
@@ -8339,6 +8438,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
char buf[128];
|
||||
sds argsds;
|
||||
struct redisCommand *cmd;
|
||||
int force_swapout;
|
||||
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
if (feof(fp))
|
||||
@@ -8379,8 +8479,11 @@ int loadAppendOnlyFile(char *filename) {
|
||||
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
||||
zfree(argv);
|
||||
/* Handle swapping while loading big datasets when VM is on */
|
||||
loadedkeys++;
|
||||
if (server.vm_enabled && (loadedkeys % 5000) == 0) {
|
||||
force_swapout = 0;
|
||||
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
|
||||
force_swapout = 1;
|
||||
|
||||
if (server.vm_enabled && force_swapout) {
|
||||
while (zmalloc_used_memory() > server.vm_max_memory) {
|
||||
if (vmSwapOneObjectBlocking() == REDIS_ERR) break;
|
||||
}
|
||||
@@ -8589,10 +8692,10 @@ static int rewriteAppendOnlyFile(char *filename) {
|
||||
while((p = zipmapNext(p,&field,&flen,&val,&vlen)) != NULL) {
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,key) == 0) goto werr;
|
||||
if (fwriteBulkString(fp,(char*)field,flen) == -1)
|
||||
return -1;
|
||||
if (fwriteBulkString(fp,(char*)val,vlen) == -1)
|
||||
return -1;
|
||||
if (fwriteBulkString(fp,(char*)field,flen) == 0)
|
||||
goto werr;
|
||||
if (fwriteBulkString(fp,(char*)val,vlen) == 0)
|
||||
goto werr;
|
||||
}
|
||||
} else {
|
||||
dictIterator *di = dictGetIterator(o->ptr);
|
||||
@@ -8604,8 +8707,8 @@ static int rewriteAppendOnlyFile(char *filename) {
|
||||
|
||||
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,key) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,field) == -1) return -1;
|
||||
if (fwriteBulkObject(fp,val) == -1) return -1;
|
||||
if (fwriteBulkObject(fp,field) == 0) goto werr;
|
||||
if (fwriteBulkObject(fp,val) == 0) goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
@@ -8857,6 +8960,11 @@ static void vmInit(void) {
|
||||
/* LZF requires a lot of stack */
|
||||
pthread_attr_init(&server.io_threads_attr);
|
||||
pthread_attr_getstacksize(&server.io_threads_attr, &stacksize);
|
||||
|
||||
/* Solaris may report a stacksize of 0, let's set it to 1 otherwise 115
|
||||
* multiplying it by 2 in the while loop later will not really help ;) */
|
||||
if (!stacksize) stacksize = 1;
|
||||
|
||||
while (stacksize < REDIS_THREAD_STACK_SIZE) stacksize *= 2;
|
||||
pthread_attr_setstacksize(&server.io_threads_attr, stacksize);
|
||||
/* Listen for events in the threaded I/O pipe */
|
||||
@@ -9225,6 +9333,7 @@ static int vmSwapOneObject(int usethreads) {
|
||||
if (maxtries) i--; /* don't count this try */
|
||||
continue;
|
||||
}
|
||||
val->vm.atime = key->vm.atime; /* atime is updated on key object */
|
||||
swappability = computeObjectSwappability(val);
|
||||
if (!best || swappability > best_swappability) {
|
||||
best = de;
|
||||
@@ -9304,7 +9413,15 @@ static void freeIOJob(iojob *j) {
|
||||
|
||||
/* Every time a thread finished a Job, it writes a byte into the write side
|
||||
* of an unix pipe in order to "awake" the main thread, and this function
|
||||
* is called. */
|
||||
* is called.
|
||||
*
|
||||
* Note that this is called both by the event loop, when a I/O thread
|
||||
* sends a byte in the notification pipe, and is also directly called from
|
||||
* waitEmptyIOJobsQueue().
|
||||
*
|
||||
* In the latter case we don't want to swap more, so we use the
|
||||
* "privdata" argument setting it to a not NULL value to signal this
|
||||
* condition. */
|
||||
static void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
|
||||
int mask)
|
||||
{
|
||||
@@ -9314,6 +9431,8 @@ static void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
if (privdata != NULL) trytoswap = 0; /* check the comments above... */
|
||||
|
||||
/* For every byte we read in the read side of the pipe, there is one
|
||||
* I/O job completed to process. */
|
||||
while((retval = read(fd,buf,1)) == 1) {
|
||||
@@ -9623,7 +9742,8 @@ static void waitEmptyIOJobsQueue(void) {
|
||||
io_processed_len = listLength(server.io_processed);
|
||||
unlockThreadedIO();
|
||||
if (io_processed_len) {
|
||||
vmThreadedIOCompletedJob(NULL,server.io_ready_pipe_read,NULL,0);
|
||||
vmThreadedIOCompletedJob(NULL,server.io_ready_pipe_read,
|
||||
(void*)0xdeadbeef,0);
|
||||
usleep(1000); /* 1 millisecond */
|
||||
} else {
|
||||
usleep(10000); /* 10 milliseconds */
|
||||
@@ -9833,6 +9953,11 @@ static int dontWaitForSwappedKey(redisClient *c, robj *key) {
|
||||
listIter li;
|
||||
struct dictEntry *de;
|
||||
|
||||
/* The key object might be destroyed when deleted from the c->io_keys
|
||||
* list (and the "key" argument is physically the same object as the
|
||||
* object inside the list), so we need to protect it. */
|
||||
incrRefCount(key);
|
||||
|
||||
/* Remove the key from the list of keys this client is waiting for. */
|
||||
listRewind(c->io_keys,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
@@ -9841,18 +9966,19 @@ static int dontWaitForSwappedKey(redisClient *c, robj *key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(ln != NULL);
|
||||
redisAssert(ln != NULL);
|
||||
|
||||
/* Remove the client form the key => waiting clients map. */
|
||||
de = dictFind(c->db->io_keys,key);
|
||||
assert(de != NULL);
|
||||
redisAssert(de != NULL);
|
||||
l = dictGetEntryVal(de);
|
||||
ln = listSearchKey(l,c);
|
||||
assert(ln != NULL);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(l,ln);
|
||||
if (listLength(l) == 0)
|
||||
dictDelete(c->db->io_keys,key);
|
||||
|
||||
decrRefCount(key);
|
||||
return listLength(c->io_keys) == 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -357,3 +357,92 @@ sds sdsfromlonglong(long long value) {
|
||||
p++;
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
/* Split a line into arguments, where every argument can be in the
|
||||
* following programming-language REPL-alike form:
|
||||
*
|
||||
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
|
||||
*
|
||||
* The number of arguments is stored into *argc, and an array
|
||||
* of sds is returned. The caller should sdsfree() all the returned
|
||||
* strings and finally zfree() the array itself.
|
||||
*
|
||||
* Note that sdscatrepr() is able to convert back a string into
|
||||
* a quoted string in the same format sdssplitargs() is able to parse.
|
||||
*/
|
||||
sds *sdssplitargs(char *line, int *argc) {
|
||||
char *p = line;
|
||||
char *current = NULL;
|
||||
char **vector = NULL;
|
||||
|
||||
*argc = 0;
|
||||
while(1) {
|
||||
/* skip blanks */
|
||||
while(*p && isspace(*p)) p++;
|
||||
if (*p) {
|
||||
/* get a token */
|
||||
int inq=0; /* set to 1 if we are in "quotes" */
|
||||
int done=0;
|
||||
|
||||
if (current == NULL) current = sdsempty();
|
||||
while(!done) {
|
||||
if (inq) {
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
char c;
|
||||
|
||||
p++;
|
||||
switch(*p) {
|
||||
case 'n': c = '\n'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'b': c = '\b'; break;
|
||||
case 'a': c = '\a'; break;
|
||||
default: c = *p; break;
|
||||
}
|
||||
current = sdscatlen(current,&c,1);
|
||||
} else if (*p == '"') {
|
||||
/* closing quote must be followed by a space */
|
||||
if (*(p+1) && !isspace(*(p+1))) goto err;
|
||||
done=1;
|
||||
} else if (!*p) {
|
||||
/* unterminated quotes */
|
||||
goto err;
|
||||
} else {
|
||||
current = sdscatlen(current,p,1);
|
||||
}
|
||||
} else {
|
||||
switch(*p) {
|
||||
case ' ':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\0':
|
||||
done=1;
|
||||
break;
|
||||
case '"':
|
||||
inq=1;
|
||||
break;
|
||||
default:
|
||||
current = sdscatlen(current,p,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
|
||||
err:
|
||||
while((*argc)--)
|
||||
sdsfree(vector[*argc]);
|
||||
zfree(vector);
|
||||
if (current) sdsfree(current);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -69,5 +69,6 @@ void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds *sdssplitargs(char *line, int *argc);
|
||||
|
||||
#endif
|
||||
|
||||
-2331
File diff suppressed because it is too large
Load Diff
+60
-58
@@ -1,4 +1,4 @@
|
||||
set defaults [list [list appendonly yes] [list appendfilename appendonly.aof]]
|
||||
set defaults { appendonly {yes} appendfilename {appendonly.aof} }
|
||||
set server_path [tmpdir server.aof]
|
||||
set aof_path "$server_path/appendonly.aof"
|
||||
|
||||
@@ -16,65 +16,67 @@ proc create_aof {code} {
|
||||
|
||||
proc start_server_aof {overrides code} {
|
||||
upvar defaults defaults srv srv server_path server_path
|
||||
set _defaults $defaults
|
||||
set srv [start_server default.conf [lappend _defaults $overrides]]
|
||||
set config [concat $defaults $overrides]
|
||||
set srv [start_server [list overrides $config]]
|
||||
uplevel 1 $code
|
||||
kill_server $srv
|
||||
}
|
||||
|
||||
## Test the server doesn't start when the AOF contains an unfinished MULTI
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
append_to_aof [formatCommand multi]
|
||||
append_to_aof [formatCommand set bar world]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Unfinished MULTI: Server should not have been started} {
|
||||
is_alive $srv
|
||||
} {0}
|
||||
|
||||
test {Unfinished MULTI: Server should have logged an error} {
|
||||
exec cat [dict get $srv stdout] | tail -n1
|
||||
} {*Unexpected end of file reading the append only file*}
|
||||
}
|
||||
|
||||
## Test that the server exits when the AOF contains a short read
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
append_to_aof [string range [formatCommand set bar world] 0 end-1]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Short read: Server should not have been started} {
|
||||
is_alive $srv
|
||||
} {0}
|
||||
|
||||
test {Short read: Server should have logged an error} {
|
||||
exec cat [dict get $srv stdout] | tail -n1
|
||||
} {*Bad file format reading the append only file*}
|
||||
}
|
||||
|
||||
## Test that redis-check-aof indeed sees this AOF is not valid
|
||||
test {Short read: Utility should confirm the AOF is not valid} {
|
||||
catch {
|
||||
exec ./redis-check-aof $aof_path
|
||||
} str
|
||||
set _ $str
|
||||
} {*not valid*}
|
||||
|
||||
test {Short read: Utility should be able to fix the AOF} {
|
||||
exec echo y | ./redis-check-aof --fix $aof_path
|
||||
} {*Successfully truncated AOF*}
|
||||
|
||||
## Test that the server can be started using the truncated AOF
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Fixed AOF: Server should have been started} {
|
||||
is_alive $srv
|
||||
} {1}
|
||||
|
||||
test {Fixed AOF: Keyspace should contain values that were parsable} {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
list [$client get foo] [$client get bar]
|
||||
} {hello {}}
|
||||
tags {"aof"} {
|
||||
## Test the server doesn't start when the AOF contains an unfinished MULTI
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
append_to_aof [formatCommand multi]
|
||||
append_to_aof [formatCommand set bar world]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Unfinished MULTI: Server should not have been started} {
|
||||
is_alive $srv
|
||||
} {0}
|
||||
|
||||
test {Unfinished MULTI: Server should have logged an error} {
|
||||
exec cat [dict get $srv stdout] | tail -n1
|
||||
} {*Unexpected end of file reading the append only file*}
|
||||
}
|
||||
|
||||
## Test that the server exits when the AOF contains a short read
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
append_to_aof [string range [formatCommand set bar world] 0 end-1]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Short read: Server should not have been started} {
|
||||
is_alive $srv
|
||||
} {0}
|
||||
|
||||
test {Short read: Server should have logged an error} {
|
||||
exec cat [dict get $srv stdout] | tail -n1
|
||||
} {*Bad file format reading the append only file*}
|
||||
}
|
||||
|
||||
## Test that redis-check-aof indeed sees this AOF is not valid
|
||||
test {Short read: Utility should confirm the AOF is not valid} {
|
||||
catch {
|
||||
exec ./redis-check-aof $aof_path
|
||||
} str
|
||||
set _ $str
|
||||
} {*not valid*}
|
||||
|
||||
test {Short read: Utility should be able to fix the AOF} {
|
||||
exec echo y | ./redis-check-aof --fix $aof_path
|
||||
} {*Successfully truncated AOF*}
|
||||
|
||||
## Test that the server can be started using the truncated AOF
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Fixed AOF: Server should have been started} {
|
||||
is_alive $srv
|
||||
} {1}
|
||||
|
||||
test {Fixed AOF: Keyspace should contain values that were parsable} {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
list [$client get foo] [$client get bar]
|
||||
} {hello {}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
start_server default.conf {} {
|
||||
start_server {tags {"repl"}} {
|
||||
r set mykey foo
|
||||
|
||||
start_server default.conf {} {
|
||||
start_server {} {
|
||||
test {Second server should have role master at first} {
|
||||
s role
|
||||
} {master}
|
||||
|
||||
+18
-9
@@ -32,6 +32,7 @@ namespace eval redis {}
|
||||
set ::redis::id 0
|
||||
array set ::redis::fd {}
|
||||
array set ::redis::blocking {}
|
||||
array set ::redis::deferred {}
|
||||
array set ::redis::callback {}
|
||||
array set ::redis::state {} ;# State in non-blocking reply reading
|
||||
array set ::redis::statestack {} ;# Stack of states, for nested mbulks
|
||||
@@ -55,12 +56,13 @@ foreach redis_multibulk_cmd {
|
||||
unset redis_bulk_cmd
|
||||
unset redis_multibulk_cmd
|
||||
|
||||
proc redis {{server 127.0.0.1} {port 6379}} {
|
||||
proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
|
||||
set fd [socket $server $port]
|
||||
fconfigure $fd -translation binary
|
||||
set id [incr ::redis::id]
|
||||
set ::redis::fd($id) $fd
|
||||
set ::redis::blocking($id) 1
|
||||
set ::redis::deferred($id) $defer
|
||||
::redis::redis_reset_state $id
|
||||
interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
|
||||
}
|
||||
@@ -68,6 +70,7 @@ proc redis {{server 127.0.0.1} {port 6379}} {
|
||||
proc ::redis::__dispatch__ {id method args} {
|
||||
set fd $::redis::fd($id)
|
||||
set blocking $::redis::blocking($id)
|
||||
set deferred $::redis::deferred($id)
|
||||
if {$blocking == 0} {
|
||||
if {[llength $args] == 0} {
|
||||
error "Please provide a callback in non-blocking mode"
|
||||
@@ -95,14 +98,16 @@ proc ::redis::__dispatch__ {id method args} {
|
||||
append cmd [join $args]
|
||||
::redis::redis_writenl $fd $cmd
|
||||
}
|
||||
if {$blocking} {
|
||||
::redis::redis_read_reply $fd
|
||||
} else {
|
||||
# Every well formed reply read will pop an element from this
|
||||
# list and use it as a callback. So pipelining is supported
|
||||
# in non blocking mode.
|
||||
lappend ::redis::callback($id) $callback
|
||||
fileevent $fd readable [list ::redis::redis_readable $fd $id]
|
||||
if {!$deferred} {
|
||||
if {$blocking} {
|
||||
::redis::redis_read_reply $fd
|
||||
} else {
|
||||
# Every well formed reply read will pop an element from this
|
||||
# list and use it as a callback. So pipelining is supported
|
||||
# in non blocking mode.
|
||||
lappend ::redis::callback($id) $callback
|
||||
fileevent $fd readable [list ::redis::redis_readable $fd $id]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uplevel 1 [list ::redis::__method__$method $id $fd] $args
|
||||
@@ -114,6 +119,10 @@ proc ::redis::__method__blocking {id fd val} {
|
||||
fconfigure $fd -blocking $val
|
||||
}
|
||||
|
||||
proc ::redis::__method__read {id fd} {
|
||||
::redis::redis_read_reply $fd
|
||||
}
|
||||
|
||||
proc ::redis::__method__close {id fd} {
|
||||
catch {close $fd}
|
||||
catch {unset ::redis::fd($id)}
|
||||
|
||||
+66
-14
@@ -1,3 +1,6 @@
|
||||
set ::global_overrides {}
|
||||
set ::tags {}
|
||||
|
||||
proc error_and_quit {config_file error} {
|
||||
puts "!!COULD NOT START REDIS-SERVER\n"
|
||||
puts "CONFIGURATION:"
|
||||
@@ -27,11 +30,15 @@ proc kill_server config {
|
||||
set pid [dict get $config pid]
|
||||
|
||||
# check for leaks
|
||||
catch {
|
||||
if {[string match {*Darwin*} [exec uname -a]]} {
|
||||
test "Check for memory leaks (pid $pid)" {
|
||||
exec leaks $pid
|
||||
} {*0 leaks*}
|
||||
if {![dict exists $config "skipleaks"]} {
|
||||
catch {
|
||||
if {[string match {*Darwin*} [exec uname -a]]} {
|
||||
tags {"leaks"} {
|
||||
test "Check for memory leaks (pid $pid)" {
|
||||
exec leaks $pid
|
||||
} {*0 leaks*}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,9 +85,35 @@ proc ping_server {host port} {
|
||||
return $retval
|
||||
}
|
||||
|
||||
set ::global_overrides {}
|
||||
proc start_server {filename overrides {code undefined}} {
|
||||
set data [split [exec cat "tests/assets/$filename"] "\n"]
|
||||
# doesn't really belong here, but highly coupled to code in start_server
|
||||
proc tags {tags code} {
|
||||
set ::tags [concat $::tags $tags]
|
||||
uplevel 1 $code
|
||||
set ::tags [lrange $::tags 0 end-[llength $tags]]
|
||||
}
|
||||
|
||||
proc start_server {options {code undefined}} {
|
||||
# setup defaults
|
||||
set baseconfig "default.conf"
|
||||
set overrides {}
|
||||
set tags {}
|
||||
|
||||
# parse options
|
||||
foreach {option value} $options {
|
||||
switch $option {
|
||||
"config" {
|
||||
set baseconfig $value }
|
||||
"overrides" {
|
||||
set overrides $value }
|
||||
"tags" {
|
||||
set tags $value
|
||||
set ::tags [concat $::tags $value] }
|
||||
default {
|
||||
error "Unknown option $option" }
|
||||
}
|
||||
}
|
||||
|
||||
set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
|
||||
set config {}
|
||||
foreach line $data {
|
||||
if {[string length $line] > 0 && [string index $line 0] ne "#"} {
|
||||
@@ -98,9 +131,7 @@ proc start_server {filename overrides {code undefined}} {
|
||||
dict set config port [incr ::port]
|
||||
|
||||
# apply overrides from global space and arguments
|
||||
foreach override [concat $::global_overrides $overrides] {
|
||||
set directive [lrange $override 0 0]
|
||||
set arguments [lrange $override 1 end]
|
||||
foreach {directive arguments} [concat $::global_overrides $overrides] {
|
||||
dict set config $directive $arguments
|
||||
}
|
||||
|
||||
@@ -177,19 +208,40 @@ proc start_server {filename overrides {code undefined}} {
|
||||
lappend ::servers $srv
|
||||
|
||||
# execute provided block
|
||||
set curnum $::testnum
|
||||
catch { uplevel 1 $code } err
|
||||
if {$curnum == $::testnum} {
|
||||
# don't check for leaks when no tests were executed
|
||||
dict set srv "skipleaks" 1
|
||||
}
|
||||
|
||||
# pop the server object
|
||||
set ::servers [lrange $::servers 0 end-1]
|
||||
|
||||
kill_server $srv
|
||||
|
||||
if {[string length $err] > 0} {
|
||||
# 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 {$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
|
||||
} else {
|
||||
set ::tags [lrange $::tags 0 end-[llength $tags]]
|
||||
set _ $srv
|
||||
}
|
||||
}
|
||||
|
||||
+66
-15
@@ -2,27 +2,78 @@ set ::passed 0
|
||||
set ::failed 0
|
||||
set ::testnum 0
|
||||
|
||||
proc test {name code okpattern} {
|
||||
proc assert_match {pattern value} {
|
||||
if {![string match $pattern $value]} {
|
||||
puts "!! ERROR\nExpected '$value' to match '$pattern'"
|
||||
error "assertion"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_equal {expected value} {
|
||||
if {$expected ne $value} {
|
||||
puts "!! ERROR\nExpected '$value' to be equal to '$expected'"
|
||||
error "assertion"
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_encoding {enc key} {
|
||||
assert_match "* encoding:$enc *" [r debug object $key]
|
||||
}
|
||||
|
||||
proc assert_type {type key} {
|
||||
assert_equal $type [r type $key]
|
||||
}
|
||||
|
||||
proc test {name code {okpattern notspecified}} {
|
||||
# abort if tagged with a tag to deny
|
||||
foreach tag $::denytags {
|
||||
if {[lsearch $::tags $tag] >= 0} {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# check if tagged with at least 1 tag to allow when there *is* a list
|
||||
# of tags to allow, because default policy is to run everything
|
||||
if {[llength $::allowtags] > 0} {
|
||||
set matched 0
|
||||
foreach tag $::allowtags {
|
||||
if {[lsearch $::tags $tag] >= 0} {
|
||||
incr matched
|
||||
}
|
||||
}
|
||||
if {$matched < 1} {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
incr ::testnum
|
||||
# if {$::testnum < $::first || $::testnum > $::last} return
|
||||
puts -nonewline [format "#%03d %-68s " $::testnum $name]
|
||||
flush stdout
|
||||
if {[catch {set retval [uplevel 1 $code]} error]} {
|
||||
puts "ERROR\n\nLogged warnings:"
|
||||
foreach file [glob tests/tmp/server.[pid].*/stdout] {
|
||||
set warnings [warnings_from_file $file]
|
||||
if {[string length $warnings] > 0} {
|
||||
puts $warnings
|
||||
}
|
||||
if {$error eq "assertion"} {
|
||||
incr ::failed
|
||||
} else {
|
||||
puts "EXCEPTION"
|
||||
puts "\nCaught error: $error"
|
||||
error "exception"
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
if {$okpattern eq $retval || [string match $okpattern $retval]} {
|
||||
puts "PASSED"
|
||||
incr ::passed
|
||||
} else {
|
||||
puts "!! ERROR expected\n'$okpattern'\nbut got\n'$retval'"
|
||||
incr ::failed
|
||||
if {$okpattern eq "notspecified" || $okpattern eq $retval || [string match $okpattern $retval]} {
|
||||
puts "PASSED"
|
||||
incr ::passed
|
||||
} else {
|
||||
puts "!! ERROR expected\n'$okpattern'\nbut got\n'$retval'"
|
||||
incr ::failed
|
||||
}
|
||||
}
|
||||
if {$::traceleaks} {
|
||||
if {![string match {*0 leaks*} [exec leaks redis-server]]} {
|
||||
|
||||
+49
-5
@@ -13,9 +13,10 @@ set ::host 127.0.0.1
|
||||
set ::port 16379
|
||||
set ::traceleaks 0
|
||||
set ::valgrind 0
|
||||
set ::denytags {}
|
||||
set ::allowtags {}
|
||||
|
||||
proc execute_tests name {
|
||||
set cur $::testnum
|
||||
source "tests/$name.tcl"
|
||||
}
|
||||
|
||||
@@ -40,6 +41,22 @@ proc r {args} {
|
||||
[srv $level "client"] {*}$args
|
||||
}
|
||||
|
||||
proc redis_deferring_client {args} {
|
||||
set level 0
|
||||
if {[llength $args] > 0 && [string is integer [lindex $args 0]]} {
|
||||
set level [lindex $args 0]
|
||||
set args [lrange $args 1 end]
|
||||
}
|
||||
|
||||
# create client that defers reading reply
|
||||
set client [redis [srv $level "host"] [srv $level "port"] 1]
|
||||
|
||||
# select the right db and read the response (OK)
|
||||
$client select 9
|
||||
$client read
|
||||
return $client
|
||||
}
|
||||
|
||||
# Provide easy access to INFO properties. Same semantic as "proc r".
|
||||
proc s {args} {
|
||||
set level 0
|
||||
@@ -81,13 +98,40 @@ proc main {} {
|
||||
execute_tests "unit/sort"
|
||||
execute_tests "unit/expire"
|
||||
execute_tests "unit/other"
|
||||
|
||||
|
||||
cleanup
|
||||
puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed"
|
||||
if {$::failed > 0} {
|
||||
puts "\n*** WARNING!!! $::failed FAILED TESTS ***\n"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
}
|
||||
|
||||
main
|
||||
# parse arguments
|
||||
for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
set opt [lindex $argv $j]
|
||||
set arg [lindex $argv [expr $j+1]]
|
||||
if {$opt eq {--tags}} {
|
||||
foreach tag $arg {
|
||||
if {[string index $tag 0] eq "-"} {
|
||||
lappend ::denytags [string range $tag 1 end]
|
||||
} else {
|
||||
lappend ::allowtags $tag
|
||||
}
|
||||
}
|
||||
incr j
|
||||
} else {
|
||||
puts "Wrong argument: $opt"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {{requirepass foobar}} {
|
||||
start_server {tags {"auth"} overrides {requirepass foobar}} {
|
||||
test {AUTH fails when a wrong password is given} {
|
||||
catch {r auth wrong!} err
|
||||
format $err
|
||||
|
||||
+40
-38
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {} {
|
||||
start_server {tags {"basic"}} {
|
||||
test {DEL all keys to start with a clean DB} {
|
||||
foreach key [r keys *] {r del $key}
|
||||
r dbsize
|
||||
@@ -52,46 +52,48 @@ start_server default.conf {} {
|
||||
r get foo
|
||||
} [string repeat "abcd" 1000000]
|
||||
|
||||
test {Very big payload random access} {
|
||||
set err {}
|
||||
array set payload {}
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set size [expr 1+[randomInt 100000]]
|
||||
set buf [string repeat "pl-$j" $size]
|
||||
set payload($j) $buf
|
||||
r set bigpayload_$j $buf
|
||||
}
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
set index [randomInt 100]
|
||||
set buf [r get bigpayload_$index]
|
||||
if {$buf != $payload($index)} {
|
||||
set err "Values differ: I set '$payload($index)' but I read back '$buf'"
|
||||
break
|
||||
tags {"slow"} {
|
||||
test {Very big payload random access} {
|
||||
set err {}
|
||||
array set payload {}
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set size [expr 1+[randomInt 100000]]
|
||||
set buf [string repeat "pl-$j" $size]
|
||||
set payload($j) $buf
|
||||
r set bigpayload_$j $buf
|
||||
}
|
||||
}
|
||||
unset payload
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
test {SET 10000 numeric keys and access all them in reverse order} {
|
||||
set err {}
|
||||
for {set x 0} {$x < 10000} {incr x} {
|
||||
r set $x $x
|
||||
}
|
||||
set sum 0
|
||||
for {set x 9999} {$x >= 0} {incr x -1} {
|
||||
set val [r get $x]
|
||||
if {$val ne $x} {
|
||||
set err "Eleemnt at position $x is $val instead of $x"
|
||||
break
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
set index [randomInt 100]
|
||||
set buf [r get bigpayload_$index]
|
||||
if {$buf != $payload($index)} {
|
||||
set err "Values differ: I set '$payload($index)' but I read back '$buf'"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
unset payload
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
test {DBSIZE should be 10101 now} {
|
||||
r dbsize
|
||||
} {10101}
|
||||
test {SET 10000 numeric keys and access all them in reverse order} {
|
||||
set err {}
|
||||
for {set x 0} {$x < 10000} {incr x} {
|
||||
r set $x $x
|
||||
}
|
||||
set sum 0
|
||||
for {set x 9999} {$x >= 0} {incr x -1} {
|
||||
set val [r get $x]
|
||||
if {$val ne $x} {
|
||||
set err "Eleemnt at position $x is $val instead of $x"
|
||||
break
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
test {DBSIZE should be 10101 now} {
|
||||
r dbsize
|
||||
} {10101}
|
||||
}
|
||||
|
||||
test {INCR against non existing key} {
|
||||
set res {}
|
||||
|
||||
+13
-9
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {} {
|
||||
start_server {tags {"expire"}} {
|
||||
test {EXPIRE - don't set timeouts multiple times} {
|
||||
r set x foobar
|
||||
set v1 [r expire x 5]
|
||||
@@ -12,10 +12,12 @@ start_server default.conf {} {
|
||||
r get x
|
||||
} {foobar}
|
||||
|
||||
test {EXPIRE - After 6 seconds the key should no longer be here} {
|
||||
after 6000
|
||||
list [r get x] [r exists x]
|
||||
} {{} 0}
|
||||
tags {"slow"} {
|
||||
test {EXPIRE - After 6 seconds the key should no longer be here} {
|
||||
after 6000
|
||||
list [r get x] [r exists x]
|
||||
} {{} 0}
|
||||
}
|
||||
|
||||
test {EXPIRE - Delete on write policy} {
|
||||
r del x
|
||||
@@ -46,10 +48,12 @@ start_server default.conf {} {
|
||||
r get y
|
||||
} {foo}
|
||||
|
||||
test {SETEX - Wait for the key to expire} {
|
||||
after 3000
|
||||
r get y
|
||||
} {}
|
||||
tags {"slow"} {
|
||||
test {SETEX - Wait for the key to expire} {
|
||||
after 3000
|
||||
r get y
|
||||
} {}
|
||||
}
|
||||
|
||||
test {SETEX - Wrong time parameter} {
|
||||
catch {r setex z -10 foo} e
|
||||
|
||||
+16
-14
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {} {
|
||||
start_server {} {
|
||||
test {SAVE - make sure there are all the types as values} {
|
||||
# Wait for a background saving in progress to terminate
|
||||
waitForBgsave r
|
||||
@@ -12,20 +12,22 @@ start_server default.conf {} {
|
||||
r save
|
||||
} {OK}
|
||||
|
||||
foreach fuzztype {binary alpha compr} {
|
||||
test "FUZZ stresser with data model $fuzztype" {
|
||||
set err 0
|
||||
for {set i 0} {$i < 10000} {incr i} {
|
||||
set fuzz [randstring 0 512 $fuzztype]
|
||||
r set foo $fuzz
|
||||
set got [r get foo]
|
||||
if {$got ne $fuzz} {
|
||||
set err [list $fuzz $got]
|
||||
break
|
||||
tags {"slow"} {
|
||||
foreach fuzztype {binary alpha compr} {
|
||||
test "FUZZ stresser with data model $fuzztype" {
|
||||
set err 0
|
||||
for {set i 0} {$i < 10000} {incr i} {
|
||||
set fuzz [randstring 0 512 $fuzztype]
|
||||
r set foo $fuzz
|
||||
set got [r get foo]
|
||||
if {$got ne $fuzz} {
|
||||
set err [list $fuzz $got]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {0}
|
||||
set _ $err
|
||||
} {0}
|
||||
}
|
||||
}
|
||||
|
||||
test {BGSAVE} {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {} {
|
||||
start_server {} {
|
||||
test {Handle an empty query well} {
|
||||
set fd [r channel]
|
||||
puts -nonewline $fd "\r\n"
|
||||
|
||||
+114
-112
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {} {
|
||||
start_server {tags {"sort"}} {
|
||||
test {SORT ALPHA against integer encoded strings} {
|
||||
r del mylist
|
||||
r lpush mylist 2
|
||||
@@ -8,130 +8,132 @@ start_server default.conf {} {
|
||||
r sort mylist alpha
|
||||
} {1 10 2 3}
|
||||
|
||||
test {Create a random list and a random set} {
|
||||
set tosort {}
|
||||
array set seenrand {}
|
||||
for {set i 0} {$i < 10000} {incr i} {
|
||||
while 1 {
|
||||
# Make sure all the weights are different because
|
||||
# Redis does not use a stable sort but Tcl does.
|
||||
randpath {
|
||||
set rint [expr int(rand()*1000000)]
|
||||
} {
|
||||
set rint [expr rand()]
|
||||
}
|
||||
if {![info exists seenrand($rint)]} break
|
||||
}
|
||||
set seenrand($rint) x
|
||||
r lpush tosort $i
|
||||
r sadd tosort-set $i
|
||||
r set weight_$i $rint
|
||||
r hset wobj_$i weight $rint
|
||||
lappend tosort [list $i $rint]
|
||||
}
|
||||
set sorted [lsort -index 1 -real $tosort]
|
||||
tags {"slow"} {
|
||||
set res {}
|
||||
for {set i 0} {$i < 10000} {incr i} {
|
||||
lappend res [lindex $sorted $i 0]
|
||||
}
|
||||
format {}
|
||||
} {}
|
||||
|
||||
test {SORT with BY against the newly created list} {
|
||||
r sort tosort {BY weight_*}
|
||||
} $res
|
||||
|
||||
test {SORT with BY (hash field) against the newly created list} {
|
||||
r sort tosort {BY wobj_*->weight}
|
||||
} $res
|
||||
|
||||
test {SORT with GET (key+hash) with sanity check of each element (list)} {
|
||||
set err {}
|
||||
set l1 [r sort tosort GET # GET weight_*]
|
||||
set l2 [r sort tosort GET # GET wobj_*->weight]
|
||||
foreach {id1 w1} $l1 {id2 w2} $l2 {
|
||||
set realweight [r get weight_$id1]
|
||||
if {$id1 != $id2} {
|
||||
set err "ID mismatch $id1 != $id2"
|
||||
break
|
||||
test {Create a random list and a random set} {
|
||||
set tosort {}
|
||||
array set seenrand {}
|
||||
for {set i 0} {$i < 10000} {incr i} {
|
||||
while 1 {
|
||||
# Make sure all the weights are different because
|
||||
# Redis does not use a stable sort but Tcl does.
|
||||
randpath {
|
||||
set rint [expr int(rand()*1000000)]
|
||||
} {
|
||||
set rint [expr rand()]
|
||||
}
|
||||
if {![info exists seenrand($rint)]} break
|
||||
}
|
||||
set seenrand($rint) x
|
||||
r lpush tosort $i
|
||||
r sadd tosort-set $i
|
||||
r set weight_$i $rint
|
||||
r hset wobj_$i weight $rint
|
||||
lappend tosort [list $i $rint]
|
||||
}
|
||||
if {$realweight != $w1 || $realweight != $w2} {
|
||||
set err "Weights mismatch! w1: $w1 w2: $w2 real: $realweight"
|
||||
break
|
||||
set sorted [lsort -index 1 -real $tosort]
|
||||
for {set i 0} {$i < 10000} {incr i} {
|
||||
lappend res [lindex $sorted $i 0]
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
format {}
|
||||
} {}
|
||||
|
||||
test {SORT with BY, but against the newly created set} {
|
||||
r sort tosort-set {BY weight_*}
|
||||
} $res
|
||||
test {SORT with BY against the newly created list} {
|
||||
r sort tosort {BY weight_*}
|
||||
} $res
|
||||
|
||||
test {SORT with BY (hash field), but against the newly created set} {
|
||||
r sort tosort-set {BY wobj_*->weight}
|
||||
} $res
|
||||
test {SORT with BY (hash field) against the newly created list} {
|
||||
r sort tosort {BY wobj_*->weight}
|
||||
} $res
|
||||
|
||||
test {SORT with BY and STORE against the newly created list} {
|
||||
r sort tosort {BY weight_*} store sort-res
|
||||
r lrange sort-res 0 -1
|
||||
} $res
|
||||
test {SORT with GET (key+hash) with sanity check of each element (list)} {
|
||||
set err {}
|
||||
set l1 [r sort tosort GET # GET weight_*]
|
||||
set l2 [r sort tosort GET # GET wobj_*->weight]
|
||||
foreach {id1 w1} $l1 {id2 w2} $l2 {
|
||||
set realweight [r get weight_$id1]
|
||||
if {$id1 != $id2} {
|
||||
set err "ID mismatch $id1 != $id2"
|
||||
break
|
||||
}
|
||||
if {$realweight != $w1 || $realweight != $w2} {
|
||||
set err "Weights mismatch! w1: $w1 w2: $w2 real: $realweight"
|
||||
break
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
test {SORT with BY (hash field) and STORE against the newly created list} {
|
||||
r sort tosort {BY wobj_*->weight} store sort-res
|
||||
r lrange sort-res 0 -1
|
||||
} $res
|
||||
test {SORT with BY, but against the newly created set} {
|
||||
r sort tosort-set {BY weight_*}
|
||||
} $res
|
||||
|
||||
test {SORT direct, numeric, against the newly created list} {
|
||||
r sort tosort
|
||||
} [lsort -integer $res]
|
||||
test {SORT with BY (hash field), but against the newly created set} {
|
||||
r sort tosort-set {BY wobj_*->weight}
|
||||
} $res
|
||||
|
||||
test {SORT decreasing sort} {
|
||||
r sort tosort {DESC}
|
||||
} [lsort -decreasing -integer $res]
|
||||
test {SORT with BY and STORE against the newly created list} {
|
||||
r sort tosort {BY weight_*} store sort-res
|
||||
r lrange sort-res 0 -1
|
||||
} $res
|
||||
|
||||
test {SORT speed, sorting 10000 elements list using BY, 100 times} {
|
||||
set start [clock clicks -milliseconds]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
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
|
||||
format {}
|
||||
} {}
|
||||
test {SORT with BY (hash field) and STORE against the newly created list} {
|
||||
r sort tosort {BY wobj_*->weight} store sort-res
|
||||
r lrange sort-res 0 -1
|
||||
} $res
|
||||
|
||||
test {SORT speed, as above but against hash field} {
|
||||
set start [clock clicks -milliseconds]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
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
|
||||
format {}
|
||||
} {}
|
||||
test {SORT direct, numeric, against the newly created list} {
|
||||
r sort tosort
|
||||
} [lsort -integer $res]
|
||||
|
||||
test {SORT speed, sorting 10000 elements list directly, 100 times} {
|
||||
set start [clock clicks -milliseconds]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
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
|
||||
format {}
|
||||
} {}
|
||||
test {SORT decreasing sort} {
|
||||
r sort tosort {DESC}
|
||||
} [lsort -decreasing -integer $res]
|
||||
|
||||
test {SORT speed, pseudo-sorting 10000 elements list, BY <const>, 100 times} {
|
||||
set start [clock clicks -milliseconds]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
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
|
||||
format {}
|
||||
} {}
|
||||
test {SORT speed, sorting 10000 elements list using BY, 100 times} {
|
||||
set start [clock clicks -milliseconds]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
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
|
||||
format {}
|
||||
} {}
|
||||
|
||||
test {SORT speed, as above but against hash field} {
|
||||
set start [clock clicks -milliseconds]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
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
|
||||
format {}
|
||||
} {}
|
||||
|
||||
test {SORT speed, sorting 10000 elements list directly, 100 times} {
|
||||
set start [clock clicks -milliseconds]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
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
|
||||
format {}
|
||||
} {}
|
||||
|
||||
test {SORT speed, pseudo-sorting 10000 elements list, BY <const>, 100 times} {
|
||||
set start [clock clicks -milliseconds]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
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
|
||||
format {}
|
||||
} {}
|
||||
}
|
||||
|
||||
test {SORT regression for issue #19, sorting floats} {
|
||||
r flushdb
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {} {
|
||||
start_server {tags {"hash"}} {
|
||||
test {HSET/HLEN - Small hash creation} {
|
||||
array set smallhash {}
|
||||
for {set i 0} {$i < 8} {incr i} {
|
||||
@@ -140,6 +140,11 @@ start_server default.conf {} {
|
||||
set _ $rv
|
||||
} {{{} {}} {{} {}} {{} {}}}
|
||||
|
||||
test {HMGET against wrong type} {
|
||||
r set wrongtype somevalue
|
||||
assert_error "*wrong*" {r hmget wrongtype field1 field2}
|
||||
}
|
||||
|
||||
test {HMGET - small hash} {
|
||||
set keys {}
|
||||
set vals {}
|
||||
|
||||
+128
-1
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {} {
|
||||
start_server {tags {"list"}} {
|
||||
test {Basic LPUSH, RPUSH, LLENGTH, LINDEX} {
|
||||
set res [r lpush mylist a]
|
||||
append res [r lpush mylist b]
|
||||
@@ -20,6 +20,124 @@ start_server default.conf {} {
|
||||
r exists mylist
|
||||
} {0}
|
||||
|
||||
proc create_list {key entries} {
|
||||
r del $key
|
||||
foreach entry $entries { r rpush $key $entry }
|
||||
}
|
||||
|
||||
test "BLPOP, BRPOP: single existing list" {
|
||||
set rd [redis_deferring_client]
|
||||
create_list blist {a b c d}
|
||||
|
||||
$rd blpop blist 1
|
||||
assert_equal {blist a} [$rd read]
|
||||
$rd brpop blist 1
|
||||
assert_equal {blist d} [$rd read]
|
||||
|
||||
$rd blpop blist 1
|
||||
assert_equal {blist b} [$rd read]
|
||||
$rd brpop blist 1
|
||||
assert_equal {blist c} [$rd read]
|
||||
}
|
||||
|
||||
test "BLPOP, BRPOP: multiple existing lists" {
|
||||
set rd [redis_deferring_client]
|
||||
create_list blist1 {a b c}
|
||||
create_list blist2 {d e f}
|
||||
|
||||
$rd blpop blist1 blist2 1
|
||||
assert_equal {blist1 a} [$rd read]
|
||||
$rd brpop blist1 blist2 1
|
||||
assert_equal {blist1 c} [$rd read]
|
||||
assert_equal 1 [r llen blist1]
|
||||
assert_equal 3 [r llen blist2]
|
||||
|
||||
$rd blpop blist2 blist1 1
|
||||
assert_equal {blist2 d} [$rd read]
|
||||
$rd brpop blist2 blist1 1
|
||||
assert_equal {blist2 f} [$rd read]
|
||||
assert_equal 1 [r llen blist1]
|
||||
assert_equal 1 [r llen blist2]
|
||||
}
|
||||
|
||||
test "BLPOP, BRPOP: second list has an entry" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist1
|
||||
create_list blist2 {d e f}
|
||||
|
||||
$rd blpop blist1 blist2 1
|
||||
assert_equal {blist2 d} [$rd read]
|
||||
$rd brpop blist1 blist2 1
|
||||
assert_equal {blist2 f} [$rd read]
|
||||
assert_equal 0 [r llen blist1]
|
||||
assert_equal 1 [r llen blist2]
|
||||
}
|
||||
|
||||
foreach {pop} {BLPOP BRPOP} {
|
||||
test "$pop: with single empty list argument" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist1
|
||||
$rd $pop blist1 1
|
||||
r rpush blist1 foo
|
||||
assert_equal {blist1 foo} [$rd read]
|
||||
assert_equal 0 [r exists blist1]
|
||||
}
|
||||
|
||||
test "$pop: with negative timeout" {
|
||||
set rd [redis_deferring_client]
|
||||
$rd $pop blist1 -1
|
||||
assert_error "ERR*is negative*" {$rd read}
|
||||
}
|
||||
|
||||
test "$pop: with non-integer timeout" {
|
||||
set rd [redis_deferring_client]
|
||||
$rd $pop blist1 1.1
|
||||
assert_error "ERR*not an integer*" {$rd read}
|
||||
}
|
||||
|
||||
test "$pop: with zero timeout should block indefinitely" {
|
||||
# To test this, use a timeout of 0 and wait a second.
|
||||
# The blocking pop should still be waiting for a push.
|
||||
set rd [redis_deferring_client]
|
||||
$rd $pop blist1 0
|
||||
after 1000
|
||||
r rpush blist1 foo
|
||||
assert_equal {blist1 foo} [$rd read]
|
||||
}
|
||||
|
||||
test "$pop: second argument is not a list" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist1 blist2
|
||||
r set blist2 nolist
|
||||
$rd $pop blist1 blist2 1
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
}
|
||||
|
||||
test "$pop: timeout" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist1 blist2
|
||||
$rd $pop blist1 blist2 1
|
||||
assert_equal {} [$rd read]
|
||||
}
|
||||
|
||||
test "$pop: arguments are empty" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist1 blist2
|
||||
|
||||
$rd $pop blist1 blist2 1
|
||||
r rpush blist1 foo
|
||||
assert_equal {blist1 foo} [$rd read]
|
||||
assert_equal 0 [r exists blist1]
|
||||
assert_equal 0 [r exists blist2]
|
||||
|
||||
$rd $pop blist1 blist2 1
|
||||
r rpush blist2 foo
|
||||
assert_equal {blist2 foo} [$rd read]
|
||||
assert_equal 0 [r exists blist1]
|
||||
assert_equal 0 [r exists blist2]
|
||||
}
|
||||
}
|
||||
|
||||
test {Create a long list and check every single element with LINDEX} {
|
||||
set ok 0
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
@@ -210,6 +328,10 @@ start_server default.conf {} {
|
||||
r lrange mylist -1000 1000
|
||||
} {0 1 2 3 4 5 6 7 8 9}
|
||||
|
||||
test {LRANGE out of range negative end index} {
|
||||
list [r lrange mylist 0 -10] [r lrange mylist 0 -11]
|
||||
} {0 {}}
|
||||
|
||||
test {LRANGE against non existing key} {
|
||||
r lrange nosuchkey 0 1
|
||||
} {}
|
||||
@@ -223,6 +345,11 @@ start_server default.conf {} {
|
||||
r lrange mylist 0 -1
|
||||
} {99 98 97 96 95}
|
||||
|
||||
test {LTRIM with out of range negative end index} {
|
||||
r ltrim mylist 0 -6
|
||||
r llen mylist
|
||||
} {0}
|
||||
|
||||
test {LTRIM stress testing} {
|
||||
set mylist {}
|
||||
set err {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
start_server default.conf {} {
|
||||
start_server {tags {"set"}} {
|
||||
test {SADD, SCARD, SISMEMBER, SMEMBERS basics} {
|
||||
r sadd myset foo
|
||||
r sadd myset bar
|
||||
|
||||
+258
-130
@@ -1,4 +1,11 @@
|
||||
start_server default.conf {} {
|
||||
start_server {tags {"zset"}} {
|
||||
proc create_zset {key items} {
|
||||
r del $key
|
||||
foreach {score entry} $items {
|
||||
r zadd $key $score $entry
|
||||
}
|
||||
}
|
||||
|
||||
test {ZSET basic ZADD and score update} {
|
||||
r zadd ztmp 10 x
|
||||
r zadd ztmp 20 y
|
||||
@@ -17,6 +24,66 @@ start_server default.conf {} {
|
||||
r zcard ztmp-blabla
|
||||
} {0}
|
||||
|
||||
test "ZRANGE basics" {
|
||||
r del ztmp
|
||||
r zadd ztmp 1 a
|
||||
r zadd ztmp 2 b
|
||||
r zadd ztmp 3 c
|
||||
r zadd ztmp 4 d
|
||||
|
||||
assert_equal {a b c d} [r zrange ztmp 0 -1]
|
||||
assert_equal {a b c} [r zrange ztmp 0 -2]
|
||||
assert_equal {b c d} [r zrange ztmp 1 -1]
|
||||
assert_equal {b c} [r zrange ztmp 1 -2]
|
||||
assert_equal {c d} [r zrange ztmp -2 -1]
|
||||
assert_equal {c} [r zrange ztmp -2 -2]
|
||||
|
||||
# out of range start index
|
||||
assert_equal {a b c} [r zrange ztmp -5 2]
|
||||
assert_equal {a b} [r zrange ztmp -5 1]
|
||||
assert_equal {} [r zrange ztmp 5 -1]
|
||||
assert_equal {} [r zrange ztmp 5 -2]
|
||||
|
||||
# out of range end index
|
||||
assert_equal {a b c d} [r zrange ztmp 0 5]
|
||||
assert_equal {b c d} [r zrange ztmp 1 5]
|
||||
assert_equal {} [r zrange ztmp 0 -5]
|
||||
assert_equal {} [r zrange ztmp 1 -5]
|
||||
|
||||
# withscores
|
||||
assert_equal {a 1 b 2 c 3 d 4} [r zrange ztmp 0 -1 withscores]
|
||||
}
|
||||
|
||||
test "ZREVRANGE basics" {
|
||||
r del ztmp
|
||||
r zadd ztmp 1 a
|
||||
r zadd ztmp 2 b
|
||||
r zadd ztmp 3 c
|
||||
r zadd ztmp 4 d
|
||||
|
||||
assert_equal {d c b a} [r zrevrange ztmp 0 -1]
|
||||
assert_equal {d c b} [r zrevrange ztmp 0 -2]
|
||||
assert_equal {c b a} [r zrevrange ztmp 1 -1]
|
||||
assert_equal {c b} [r zrevrange ztmp 1 -2]
|
||||
assert_equal {b a} [r zrevrange ztmp -2 -1]
|
||||
assert_equal {b} [r zrevrange ztmp -2 -2]
|
||||
|
||||
# out of range start index
|
||||
assert_equal {d c b} [r zrevrange ztmp -5 2]
|
||||
assert_equal {d c} [r zrevrange ztmp -5 1]
|
||||
assert_equal {} [r zrevrange ztmp 5 -1]
|
||||
assert_equal {} [r zrevrange ztmp 5 -2]
|
||||
|
||||
# out of range end index
|
||||
assert_equal {d c b a} [r zrevrange ztmp 0 5]
|
||||
assert_equal {c b a} [r zrevrange ztmp 1 5]
|
||||
assert_equal {} [r zrevrange ztmp 0 -5]
|
||||
assert_equal {} [r zrevrange ztmp 1 -5]
|
||||
|
||||
# withscores
|
||||
assert_equal {d 4 c 3 b 2 a 1} [r zrevrange ztmp 0 -1 withscores]
|
||||
}
|
||||
|
||||
test {ZRANK basics} {
|
||||
r zadd zranktmp 10 x
|
||||
r zadd zranktmp 20 y
|
||||
@@ -69,15 +136,6 @@ start_server default.conf {} {
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
test {ZRANGE and ZREVRANGE basics} {
|
||||
list [r zrange ztmp 0 -1] [r zrevrange ztmp 0 -1] \
|
||||
[r zrange ztmp 1 -1] [r zrevrange ztmp 1 -1]
|
||||
} {{y x z} {z x y} {x z} {x y}}
|
||||
|
||||
test {ZRANGE WITHSCORES} {
|
||||
r zrange ztmp 0 -1 withscores
|
||||
} {y 1 x 10 z 30}
|
||||
|
||||
test {ZSETs stress tester - sorting is working well?} {
|
||||
set delta 0
|
||||
for {set test 0} {$test < 2} {incr test} {
|
||||
@@ -162,85 +220,87 @@ start_server default.conf {} {
|
||||
r zrangebyscore zset 2 4 withscores
|
||||
} {b 2 c 3 d 4}
|
||||
|
||||
test {ZRANGEBYSCORE fuzzy test, 100 ranges in 1000 elements sorted set} {
|
||||
set err {}
|
||||
r del zset
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
r zadd zset [expr rand()] $i
|
||||
}
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
set min [expr rand()]
|
||||
set max [expr rand()]
|
||||
if {$min > $max} {
|
||||
set aux $min
|
||||
set min $max
|
||||
set max $aux
|
||||
tags {"slow"} {
|
||||
test {ZRANGEBYSCORE fuzzy test, 100 ranges in 1000 elements sorted set} {
|
||||
set err {}
|
||||
r del zset
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
r zadd zset [expr rand()] $i
|
||||
}
|
||||
set low [r zrangebyscore zset -inf $min]
|
||||
set ok [r zrangebyscore zset $min $max]
|
||||
set high [r zrangebyscore zset $max +inf]
|
||||
set lowx [r zrangebyscore zset -inf ($min]
|
||||
set okx [r zrangebyscore zset ($min ($max]
|
||||
set highx [r zrangebyscore zset ($max +inf]
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
set min [expr rand()]
|
||||
set max [expr rand()]
|
||||
if {$min > $max} {
|
||||
set aux $min
|
||||
set min $max
|
||||
set max $aux
|
||||
}
|
||||
set low [r zrangebyscore zset -inf $min]
|
||||
set ok [r zrangebyscore zset $min $max]
|
||||
set high [r zrangebyscore zset $max +inf]
|
||||
set lowx [r zrangebyscore zset -inf ($min]
|
||||
set okx [r zrangebyscore zset ($min ($max]
|
||||
set highx [r zrangebyscore zset ($max +inf]
|
||||
|
||||
if {[r zcount zset -inf $min] != [llength $low]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset $min $max] != [llength $ok]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset $max +inf] != [llength $high]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset -inf ($min] != [llength $lowx]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset ($min ($max] != [llength $okx]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset ($max +inf] != [llength $highx]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset -inf $min] != [llength $low]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset $min $max] != [llength $ok]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset $max +inf] != [llength $high]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset -inf ($min] != [llength $lowx]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset ($min ($max] != [llength $okx]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
if {[r zcount zset ($max +inf] != [llength $highx]} {
|
||||
append err "Error, len does not match zcount\n"
|
||||
}
|
||||
|
||||
foreach x $low {
|
||||
set score [r zscore zset $x]
|
||||
if {$score > $min} {
|
||||
append err "Error, score for $x is $score > $min\n"
|
||||
foreach x $low {
|
||||
set score [r zscore zset $x]
|
||||
if {$score > $min} {
|
||||
append err "Error, score for $x is $score > $min\n"
|
||||
}
|
||||
}
|
||||
foreach x $lowx {
|
||||
set score [r zscore zset $x]
|
||||
if {$score >= $min} {
|
||||
append err "Error, score for $x is $score >= $min\n"
|
||||
}
|
||||
}
|
||||
foreach x $ok {
|
||||
set score [r zscore zset $x]
|
||||
if {$score < $min || $score > $max} {
|
||||
append err "Error, score for $x is $score outside $min-$max range\n"
|
||||
}
|
||||
}
|
||||
foreach x $okx {
|
||||
set score [r zscore zset $x]
|
||||
if {$score <= $min || $score >= $max} {
|
||||
append err "Error, score for $x is $score outside $min-$max open range\n"
|
||||
}
|
||||
}
|
||||
foreach x $high {
|
||||
set score [r zscore zset $x]
|
||||
if {$score < $max} {
|
||||
append err "Error, score for $x is $score < $max\n"
|
||||
}
|
||||
}
|
||||
foreach x $highx {
|
||||
set score [r zscore zset $x]
|
||||
if {$score <= $max} {
|
||||
append err "Error, score for $x is $score <= $max\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach x $lowx {
|
||||
set score [r zscore zset $x]
|
||||
if {$score >= $min} {
|
||||
append err "Error, score for $x is $score >= $min\n"
|
||||
}
|
||||
}
|
||||
foreach x $ok {
|
||||
set score [r zscore zset $x]
|
||||
if {$score < $min || $score > $max} {
|
||||
append err "Error, score for $x is $score outside $min-$max range\n"
|
||||
}
|
||||
}
|
||||
foreach x $okx {
|
||||
set score [r zscore zset $x]
|
||||
if {$score <= $min || $score >= $max} {
|
||||
append err "Error, score for $x is $score outside $min-$max open range\n"
|
||||
}
|
||||
}
|
||||
foreach x $high {
|
||||
set score [r zscore zset $x]
|
||||
if {$score < $max} {
|
||||
append err "Error, score for $x is $score < $max\n"
|
||||
}
|
||||
}
|
||||
foreach x $highx {
|
||||
set score [r zscore zset $x]
|
||||
if {$score <= $max} {
|
||||
append err "Error, score for $x is $score <= $max\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
set _ $err
|
||||
} {}
|
||||
}
|
||||
|
||||
test {ZRANGEBYSCORE with LIMIT} {
|
||||
r del zset
|
||||
@@ -286,15 +346,32 @@ start_server default.conf {} {
|
||||
list [r zremrangebyscore zset -inf +inf] [r zrange zset 0 -1]
|
||||
} {5 {}}
|
||||
|
||||
test {ZREMRANGEBYRANK basics} {
|
||||
r del zset
|
||||
r zadd zset 1 a
|
||||
r zadd zset 2 b
|
||||
r zadd zset 3 c
|
||||
r zadd zset 4 d
|
||||
r zadd zset 5 e
|
||||
list [r zremrangebyrank zset 1 3] [r zrange zset 0 -1]
|
||||
} {3 {a e}}
|
||||
test "ZREMRANGEBYRANK basics" {
|
||||
proc remrangebyrank {min max} {
|
||||
create_zset zset {1 a 2 b 3 c 4 d 5 e}
|
||||
r zremrangebyrank zset $min $max
|
||||
}
|
||||
|
||||
# inner range
|
||||
assert_equal 3 [remrangebyrank 1 3]
|
||||
assert_equal {a e} [r zrange zset 0 -1]
|
||||
|
||||
# start underflow
|
||||
assert_equal 1 [remrangebyrank -10 0]
|
||||
assert_equal {b c d e} [r zrange zset 0 -1]
|
||||
|
||||
# start overflow
|
||||
assert_equal 0 [remrangebyrank 10 -1]
|
||||
assert_equal {a b c d e} [r zrange zset 0 -1]
|
||||
|
||||
# end underflow
|
||||
assert_equal 0 [remrangebyrank 0 -10]
|
||||
assert_equal {a b c d e} [r zrange zset 0 -1]
|
||||
|
||||
# end overflow
|
||||
assert_equal 5 [remrangebyrank 0 10]
|
||||
assert_equal {} [r zrange zset 0 -1]
|
||||
}
|
||||
|
||||
test {ZUNIONSTORE against non-existing key doesn't set destination} {
|
||||
r del zseta
|
||||
@@ -340,45 +417,96 @@ start_server default.conf {} {
|
||||
list [r zinterstore zsetc 2 zseta zsetb aggregate max] [r zrange zsetc 0 -1 withscores]
|
||||
} {2 {b 2 c 3}}
|
||||
|
||||
test {ZSETs skiplist implementation backlink consistency test} {
|
||||
set diff 0
|
||||
set elements 10000
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
r zadd myzset [expr rand()] "Element-$j"
|
||||
r zrem myzset "Element-[expr int(rand()*$elements)]"
|
||||
}
|
||||
set l1 [r zrange myzset 0 -1]
|
||||
set l2 [r zrevrange myzset 0 -1]
|
||||
for {set j 0} {$j < [llength $l1]} {incr j} {
|
||||
if {[lindex $l1 $j] ne [lindex $l2 end-$j]} {
|
||||
incr diff
|
||||
}
|
||||
}
|
||||
format $diff
|
||||
} {0}
|
||||
foreach cmd {ZUNIONSTORE ZINTERSTORE} {
|
||||
test "$cmd with +inf/-inf scores" {
|
||||
r del zsetinf1 zsetinf2
|
||||
|
||||
test {ZSETs ZRANK augmented skip list stress testing} {
|
||||
set err {}
|
||||
r del myzset
|
||||
for {set k 0} {$k < 10000} {incr k} {
|
||||
set i [expr {$k%1000}]
|
||||
if {[expr rand()] < .2} {
|
||||
r zrem myzset $i
|
||||
} else {
|
||||
set score [expr rand()]
|
||||
r zadd myzset $score $i
|
||||
r zadd zsetinf1 +inf key
|
||||
r zadd zsetinf2 +inf key
|
||||
r $cmd zsetinf3 2 zsetinf1 zsetinf2
|
||||
assert_equal inf [r zscore zsetinf3 key]
|
||||
|
||||
r zadd zsetinf1 -inf key
|
||||
r zadd zsetinf2 +inf key
|
||||
r $cmd zsetinf3 2 zsetinf1 zsetinf2
|
||||
assert_equal 0 [r zscore zsetinf3 key]
|
||||
|
||||
r zadd zsetinf1 +inf key
|
||||
r zadd zsetinf2 -inf key
|
||||
r $cmd zsetinf3 2 zsetinf1 zsetinf2
|
||||
assert_equal 0 [r zscore zsetinf3 key]
|
||||
|
||||
r zadd zsetinf1 -inf key
|
||||
r zadd zsetinf2 -inf key
|
||||
r $cmd zsetinf3 2 zsetinf1 zsetinf2
|
||||
assert_equal -inf [r zscore zsetinf3 key]
|
||||
}
|
||||
|
||||
test "$cmd with NaN weights" {
|
||||
r del zsetinf1 zsetinf2
|
||||
|
||||
r zadd zsetinf1 1.0 key
|
||||
r zadd zsetinf2 1.0 key
|
||||
assert_error "*weight value is not a double*" {
|
||||
r $cmd zsetinf3 2 zsetinf1 zsetinf2 weights nan nan
|
||||
}
|
||||
set card [r zcard myzset]
|
||||
if {$card > 0} {
|
||||
set index [randomInt $card]
|
||||
set ele [lindex [r zrange myzset $index $index] 0]
|
||||
set rank [r zrank myzset $ele]
|
||||
if {$rank != $index} {
|
||||
set err "$ele RANK is wrong! ($rank != $index)"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tags {"slow"} {
|
||||
test {ZSETs skiplist implementation backlink consistency test} {
|
||||
set diff 0
|
||||
set elements 10000
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
r zadd myzset [expr rand()] "Element-$j"
|
||||
r zrem myzset "Element-[expr int(rand()*$elements)]"
|
||||
}
|
||||
set l1 [r zrange myzset 0 -1]
|
||||
set l2 [r zrevrange myzset 0 -1]
|
||||
for {set j 0} {$j < [llength $l1]} {incr j} {
|
||||
if {[lindex $l1 $j] ne [lindex $l2 end-$j]} {
|
||||
incr diff
|
||||
}
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
format $diff
|
||||
} {0}
|
||||
|
||||
test {ZSETs ZRANK augmented skip list stress testing} {
|
||||
set err {}
|
||||
r del myzset
|
||||
for {set k 0} {$k < 10000} {incr k} {
|
||||
set i [expr {$k%1000}]
|
||||
if {[expr rand()] < .2} {
|
||||
r zrem myzset $i
|
||||
} else {
|
||||
set score [expr rand()]
|
||||
r zadd myzset $score $i
|
||||
}
|
||||
set card [r zcard myzset]
|
||||
if {$card > 0} {
|
||||
set index [randomInt $card]
|
||||
set ele [lindex [r zrange myzset $index $index] 0]
|
||||
set rank [r zrank myzset $ele]
|
||||
if {$rank != $index} {
|
||||
set err "$ele RANK is wrong! ($rank != $index)"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
}
|
||||
|
||||
test {ZSET element can't be set to NaN with ZADD} {
|
||||
assert_error "*not a double*" {r zadd myzset nan abc}
|
||||
}
|
||||
|
||||
test {ZSET element can't be set to NaN with ZINCRBY} {
|
||||
assert_error "*not a double*" {r zadd myzset nan abc}
|
||||
}
|
||||
|
||||
test {ZINCRBY calls leading to NaN result in error} {
|
||||
r zincrby myzset +inf abc
|
||||
assert_error "*NaN*" {r zincrby myzset -inf abc}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,14 @@ case "$1" in
|
||||
then
|
||||
echo -n "$PIDFILE does not exist, process is not running\n"
|
||||
else
|
||||
PID=$(cat $PIDFILE)
|
||||
echo -n "Stopping ...\n"
|
||||
echo -n "Sending SHUTDOWN\r\n" | nc localhost $REDISPORT &
|
||||
PID=$(cat $PIDFILE)
|
||||
echo -n "SHUTDOWN\r\n" | nc localhost $REDISPORT &
|
||||
while [ -x /proc/${PIDFILE} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
done
|
||||
rm $PIDFILE
|
||||
echo "Redis stopped"
|
||||
fi
|
||||
;;
|
||||
|
||||
Reference in New Issue
Block a user