Compare commits
107
Commits
vm-playpen
..
1.3.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b7f987e91 | ||
|
|
12f72a714c | ||
|
|
ad6de43c80 | ||
|
|
db5946fc27 | ||
|
|
bf0da6174c | ||
|
|
288799e04a | ||
|
|
2a1198b4c4 | ||
|
|
a4c507866c | ||
|
|
4583c4f0ea | ||
|
|
17d68f9c99 | ||
|
|
fdfdae0f3a | ||
|
|
a86f14b190 | ||
|
|
11d9d1e3b1 | ||
|
|
516977de15 | ||
|
|
2069d06a0b | ||
|
|
b1d9c91ca8 | ||
|
|
76583ea455 | ||
|
|
d2764cd692 | ||
|
|
164ee59564 | ||
|
|
23d8214891 | ||
|
|
78409a0f84 | ||
|
|
6d4fb107b2 | ||
|
|
dd88747b78 | ||
|
|
92b27fe946 | ||
|
|
653c9240b7 | ||
|
|
cc3b01c30a | ||
|
|
bae2c7ec6b | ||
|
|
55a8298fcf | ||
|
|
07efaf7467 | ||
|
|
9c8e3cee26 | ||
|
|
08af4d5c96 | ||
|
|
5de9ad7c85 | ||
|
|
8c5abee892 | ||
|
|
bcd1190644 | ||
|
|
b9eed483ae | ||
|
|
c1d57a1063 | ||
|
|
ee5cfe3853 | ||
|
|
e3f46030fc | ||
|
|
8f92e76831 | ||
|
|
ada386b218 | ||
|
|
2830ca539b | ||
|
|
b1befe6a8e | ||
|
|
b287c9bb2b | ||
|
|
cbba7dd7c9 | ||
|
|
798d9e5531 | ||
|
|
18e61fa2ff | ||
|
|
978c2c944c | ||
|
|
5234952bee | ||
|
|
b1ad58ed2f | ||
|
|
66ef8da009 | ||
|
|
841053366f | ||
|
|
f84d393374 | ||
|
|
2424490f9b | ||
|
|
9212eafd5d | ||
|
|
94e543b5c2 | ||
|
|
ddfaca9d81 | ||
|
|
947efa8d6e | ||
|
|
d239ec5960 | ||
|
|
a50ea45c84 | ||
|
|
520b5a33ae | ||
|
|
edb519581a | ||
|
|
e74825c24f | ||
|
|
27b0ccca71 | ||
|
|
8fe7fad771 | ||
|
|
67cac14343 | ||
|
|
3919155375 | ||
|
|
9693e4f0f0 | ||
|
|
386b43c6b3 | ||
|
|
3589e1a7e5 | ||
|
|
2b37892e9b | ||
|
|
912b9165a9 | ||
|
|
709d0a1bbc | ||
|
|
69d95c3e1c | ||
|
|
cd5a96eeda | ||
|
|
2f4d2242ea | ||
|
|
52d23ddbea | ||
|
|
6fa246220b | ||
|
|
e3c7f0027f | ||
|
|
54bac49d92 | ||
|
|
6cf5882c56 | ||
|
|
ed7451548a | ||
|
|
ba76a8f901 | ||
|
|
a3f9eec291 | ||
|
|
18b6cb7643 | ||
|
|
c8c72447f6 | ||
|
|
be0af2f0ec | ||
|
|
8ec0832157 | ||
|
|
eb46f4bd7b | ||
|
|
5921aa36ed | ||
|
|
9ebed7cf06 | ||
|
|
12d090d2d0 | ||
|
|
ce8330208a | ||
|
|
7c775e0943 | ||
|
|
223a059192 | ||
|
|
f44dd42872 | ||
|
|
3a3978b10b | ||
|
|
0500ef2736 | ||
|
|
59146ef3c1 | ||
|
|
6766f45ef2 | ||
|
|
3c290b9b52 | ||
|
|
4b00bebd3d | ||
|
|
ed9e496634 | ||
|
|
3c68de9b01 | ||
|
|
9651a78787 | ||
|
|
478c2c6f5b | ||
|
|
d5d55fc319 | ||
|
|
a544018d04 |
@@ -5,6 +5,7 @@
|
||||
redis-cli
|
||||
redis-server
|
||||
redis-benchmark
|
||||
redis-check-dump
|
||||
doc-tools
|
||||
mkrelease.sh
|
||||
release
|
||||
|
||||
@@ -1,3 +1,109 @@
|
||||
2010-03-18 reverted an optimization that makes Redis not stable
|
||||
2010-03-18 Fixed redis-cli auth code
|
||||
2010-03-17 HDEL fix, an optimization for comparison of objects in hash table lookups when they are integer encoding
|
||||
2010-03-17 Version is now 1.3.5
|
||||
2010-03-17 Merged Pietern patch for VM key args helper function. Fixed an obvious bug in the redis-cli passwd auth stuff
|
||||
2010-03-17 Merge branch 'aggregates' of git://github.com/pietern/redis
|
||||
2010-03-17 Added Authentication to redis-cli.c using -a switch Update usage fixed Makefile to delete redis-check-dump during make clean
|
||||
2010-03-17 HEXISTS and tests implemented
|
||||
2010-03-17 More hash tests
|
||||
2010-03-17 better HSET test
|
||||
2010-03-17 Fixed a bug in HSET, a memory leak, and a theoretical bug in dict.c
|
||||
2010-03-17 More Hash tests
|
||||
2010-03-13 added preloading keys from VM when using ZINTER or ZUNION
|
||||
2010-03-13 added explicit AGGREGATE [SUM|MIN|MAX] option to ZUNION/ZINTER
|
||||
2010-03-16 HGET fix for integer encoded field against zipmap encoded hash
|
||||
2010-03-16 zrevrank support in redis-cli
|
||||
2010-03-16 HKEYS / HVALS / HGETALL
|
||||
2010-03-16 Solved a memory leak with Hashes
|
||||
2010-03-15 pretty big refactoring
|
||||
2010-03-15 An interesting refactoring + more expressive internal API
|
||||
2010-03-15 Fixed the same problem in ZREVRANK
|
||||
2010-03-15 Fixed a ZRANK bug
|
||||
2010-03-15 zipmap to hash conversion in HSET
|
||||
2010-03-14 max zipmap entries and max zipmap value parameters added into INFO output
|
||||
2010-03-14 HDEL and some improvement in DEBUG OBJECT command
|
||||
2010-03-14 Append only file support for hashes
|
||||
2010-03-13 utility to check rdb files for unprocessable opcodes
|
||||
2010-03-12 A minor fix and a few debug messages removed
|
||||
2010-03-12 Applied the replication bug patch provided by Jeremy Zawodny, removing temp file collision after the slave got the dump.rdb file in the SYNC stage
|
||||
2010-03-11 Fix for HGET against non Hash type, debug messages used to understand a bit better a corrupted rdb file
|
||||
2010-03-09 fix: use zmalloc instead of malloc
|
||||
2010-03-09 Merged zsetops branch from Pietern
|
||||
2010-03-09 Merged ZREMBYRANK from Pietern
|
||||
2010-03-09 Merged ZREVRANK from Pietern
|
||||
2010-03-09 use a struct to store both a dict and its weight for ZUNION and ZINTER, so qsort can be applied
|
||||
2010-03-09 Hash auto conversion from zipmap to hash table, type fixed for hashes, hash loading from disk
|
||||
2010-03-09 replaced ZMERGE by ZUNION and ZINTER. note: key preloading by the VM does not yet work
|
||||
2010-03-08 Hashes saving / fixes
|
||||
2010-03-08 use ZMERGE as starting point
|
||||
2010-03-07 HSET fixes, now the new pointer is stored back in the object pointer field
|
||||
2010-03-07 added ZREVRANK
|
||||
2010-03-06 Fix for replicaiton with over 2GB dump file initial SYNC stage
|
||||
2010-03-06 first implementation of HSET/HSET. More work needed
|
||||
2010-03-05 zipmaps functions to get, iterate, test for existence. Initial works for Hash data type
|
||||
2010-03-04 redis-benchmark now implements Set commands benchmarks
|
||||
2010-03-04 zipmap iteration code
|
||||
2010-03-04 moved code to delete a single node from a zset to a separate function
|
||||
2010-03-04 rename zslDeleteRange to zslDeleteRangeByScore (to differentiate between deleting using score or rank)
|
||||
2010-03-04 use 1-based rank across zsl*Rank functions consistently
|
||||
2010-03-04 implemented ZREMBYRANK
|
||||
2010-03-04 A fix for initialization of augmented skip lists
|
||||
2010-03-04 A fix for an invalid access when VM is disabled
|
||||
2010-03-04 Merge branch 'zsl-get-rank' of git://github.com/pietern/redis
|
||||
2010-03-04 redis-cli now runs in interactive mode if no command is provided
|
||||
2010-03-04 merged memory reduction patch
|
||||
2010-03-04 Now list push commands return the length of the new list, thanks to Gustavo Picon
|
||||
2010-03-04 first check if starting point is trivial (head or tail) before applying log(N) search
|
||||
2010-03-04 use rank to find starting point for ZRANGE and ZREVRANGE
|
||||
2010-03-04 lookup rank of a zset entry in a different function
|
||||
2010-03-04 SUBSTR fix for integer encoded vals
|
||||
2010-03-04 fix ZRANK (realize that rank is 1-based due to the skip list header)
|
||||
2010-03-03 initial implementation of SUBSTR
|
||||
2010-03-03 TODO updated
|
||||
2010-03-03 fpurge call removed from redis-cli
|
||||
2010-03-03 ZRANK stress tester
|
||||
2010-03-03 use less memory as element->span[0] will always be 1; any level 0 skip list is essentially a linked list
|
||||
2010-03-03 rank is very unlikely to overflow integer range
|
||||
2010-03-03 x->backward never equals zsl->header
|
||||
2010-03-03 initial implementation for augmented zsets and the zrank command
|
||||
2010-03-03 zipampDel() implemented
|
||||
2010-03-03 added quit and exit commands to redis-cli in order to quit the interactive mode
|
||||
2010-03-03 Merge remote branch 'djanowski/interactive'
|
||||
2010-03-02 Add support for MULTI/EXEC.
|
||||
2010-03-02 Remove trailing newline in interactive mode.
|
||||
2010-03-02 minor fix for a Linux warning
|
||||
2010-03-02 Add interactive mode to redis-cli.
|
||||
2010-03-02 Better to increment the version minor number when a VM bug is fixed... it will be simpler to understand what's going on when users will report problems with the INFO trace.
|
||||
2010-03-02 Fixed a subtle VM bug... was not flushing the buffer so the child process read truncated data
|
||||
2010-03-01 KEYS now returns a multi bulk reply
|
||||
2010-02-27 Add DISCARD command to discard queued MULTI commands.
|
||||
2010-03-01 Swappability bug due to a typo fixed thanks to code review by Felix Geisendörfer @felixge
|
||||
2010-02-28 minor fixes for zipmap.c
|
||||
2010-02-27 first zipmap fix of a long sequence in the days to come ;)
|
||||
2010-02-27 initial zipmap.c implementation
|
||||
2010-02-27 Bug #169 fixed (BLOP/BRPOP interrupted connections are not cleared from the queue)
|
||||
2010-02-22 Fixed 32bit make target to work on Linux out of the box
|
||||
2010-02-19 A problem with replication with multiple slaves connectiong to a single master fixed. It was due to a typo, and reported on github by the user micmac. Also the copyright year fixed from many files.
|
||||
2010-02-10 Saner VM defaults for redis.conf
|
||||
2010-02-09 VM now is able to block clients on swapped keys for all the commands
|
||||
2010-02-07 ZCOUNT and ZRANGEBYSCORE new tests
|
||||
2010-02-07 ZRANGEBYSCORE now supports open intervals, prefixing double values with a open paren. Added ZCOUNT that can count the elements inside an interval of scores, this supports open intervals too
|
||||
2010-02-07 WITHSCORES in ZRANGEBYSCORE thanks to Sam Hendley
|
||||
2010-02-06 Added "withscores" option to zrangebyscore command. Based on withscores support in zrange function, ugliest part was the argument parsing to handle using it with the limit option.
|
||||
2010-02-06 DEBUG OBJECT provide info about serialized object length even when VM is disabled
|
||||
2010-02-06 multi bulk requests in redis-benchmark, default fsync policy changed to everysec, added a prefix character for DEBUG logs
|
||||
2010-02-04 APPEND tests
|
||||
2010-02-04 APPEND command
|
||||
2010-02-02 Faster version of the function hashing possibly encoded objects, leading to a general speed gain when working with Sets of integers
|
||||
2010-02-02 faster Set loading time from .rdb file resizing the hash table to the right size before loading elements
|
||||
2010-02-02 Log time taken to load the DB at startup, in seconds
|
||||
2010-01-31 Fixed VM corruption due to child fclosing the VM file directly or indirectly calling exit(), now replaced with _exit() in all the sensible places. Masked a few signals from IO threads.
|
||||
2010-01-28 loading side of the threaded VM
|
||||
2010-01-26 TODO cahnges
|
||||
2010-01-23 Fixed memory human style memory reporting, removed server.usedmemory, now zmalloc_used_memory() is used always.
|
||||
2010-01-22 VM tuning thanks to redis-stat vmstat. Now it performs much better under high load
|
||||
2010-01-21 Changelog updated
|
||||
2010-01-21 REDIS_MAX_COMPLETED_JOBS_PROCESSED is now in percentage, not number of jobs. Moved a debugging message a few lines forward as it was called where a few logged parameters where invalid, leading to a crash
|
||||
2010-01-20 fixed a deadlock caused by too much finished processes in queue so that I/O clients writing to the wirte side of the pipe used to awake the main thread where blocking. Then a BGSAVE started waiting for the last active thread to finish, condition impossible because all the I/O threads where blocking on threads. Takes this as a note to myself...
|
||||
2010-01-20 ae.c event loop does no longer support exception notifications, as they are fully pointless. Also a theoretical bug that never happens in practice fixed.
|
||||
|
||||
@@ -14,15 +14,17 @@ endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
|
||||
DEBUG?= -g -rdynamic -ggdb
|
||||
|
||||
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o
|
||||
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
|
||||
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
|
||||
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
|
||||
|
||||
PRGNAME = redis-server
|
||||
BENCHPRGNAME = redis-benchmark
|
||||
CLIPRGNAME = redis-cli
|
||||
CHECKDUMPPRGNAME = redis-check-dump
|
||||
|
||||
all: redis-server redis-benchmark redis-cli
|
||||
all: redis-server redis-benchmark redis-cli redis-check-dump
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
adlist.o: adlist.c adlist.h zmalloc.h
|
||||
@@ -39,8 +41,9 @@ redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
|
||||
zmalloc.h
|
||||
redis-cli.o: redis-cli.c fmacros.h anet.h sds.h adlist.h zmalloc.h
|
||||
redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \
|
||||
adlist.h zmalloc.h lzf.h pqsort.h staticsymbols.h
|
||||
adlist.h zmalloc.h lzf.h pqsort.h zipmap.h staticsymbols.h
|
||||
sds.o: sds.c sds.h zmalloc.h
|
||||
zipmap.o: zipmap.c zmalloc.h
|
||||
zmalloc.o: zmalloc.c config.h
|
||||
|
||||
redis-server: $(OBJ)
|
||||
@@ -57,11 +60,14 @@ redis-benchmark: $(BENCHOBJ)
|
||||
redis-cli: $(CLIOBJ)
|
||||
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ)
|
||||
|
||||
redis-check-dump: $(CHECKDUMPOBJ)
|
||||
$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ)
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
clean:
|
||||
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) *.o *.gcda *.gcno *.gcov
|
||||
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
@@ -79,7 +85,10 @@ log:
|
||||
git log '--pretty=format:%ad %s' --date=short > Changelog
|
||||
|
||||
32bit:
|
||||
make ARCH="-arch i386"
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
make ARCH="-m32"
|
||||
|
||||
gprof:
|
||||
make PROF="-pg"
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
Redis TODO and Roadmap
|
||||
|
||||
VERSION 1.4 TODO (Hash type)
|
||||
============================
|
||||
VERSION 2.0 TODO
|
||||
================
|
||||
|
||||
* BRPOPLPUSH
|
||||
* List ops like L/RPUSH L/RPOP should return the new list length.
|
||||
* Save dataset / fsync() on SIGTERM
|
||||
* MULTI/EXEC should support the "EXEC FSYNC" form?
|
||||
* BLPOP & C. tests (write a non blocking Tcl client as first step)
|
||||
* ZCOUNT sortedset min max
|
||||
* ZRANK: http://docs.google.com/viewer?a=v&q=cache:tCQaP3ZeN4YJ:courses.csail.mit.edu/6.046/spring04/handouts/ps5-sol.pdf+skip+list+rank+operation+augmented&hl=en&pid=bl&srcid=ADGEEShXuNjTcZyXw_1cq9OaWpSXy3PprjXqVzmM-LE0ETFznLyrDXJKQ_mBPNT10R8ErkoiXD9JbMw_FaoHmOA4yoGVrA7tZWiy393JwfCwuewuP93sjbkzZ_gnEp83jYhPYjThaIzw&sig=AHIEtbRF0GkYCdYRFtTJBE69senXZwFY0w
|
||||
* Once ZRANK is implemented, change the implementation of ZCOUNT to use the augmented skiplist in order to be much faster.
|
||||
* Write doc for ZCOUNT, and for open / closed intervals of sorted sets range operations.
|
||||
|
||||
Virtual Memory sub-TODO:
|
||||
* Check if the page selection algorithm is working well
|
||||
* Divide swappability of objects by refcount
|
||||
* Use multiple open FDs against the VM file, one for thread.
|
||||
* it should be possible to give the vm-max-memory option in megabyte, gigabyte, ..., just using 2GB, 100MB, and so forth.
|
||||
* Try to understand what can be moved into I/O threads that currently is instead handled by the main thread. For instance swapping file table scannig to find contiguous page could be a potential candidate (but I'm not convinced it's a good idea, better to improve the algorithm, for instance double the fast forward at every step?).
|
||||
* Possibly decrRefCount() against swapped objects can be moved into I/O threads, as it's a slow operation against million elements list, and in general consumes CPU time that can be consumed by other threads (and cores).
|
||||
* EXISTS should avoid loading the object if possible without too make the code too specialized.
|
||||
* vm-min-age <seconds> option
|
||||
* Make sure objects loaded from the VM are specially encoded when possible.
|
||||
* Check what happens performance-wise if instead to create threads again and again the same threads are reused forever. Note: this requires a way to disable this clients in the child, but waiting for empty new jobs queue can be enough.
|
||||
* Sets of integers are slow to load, for a number of reasons. Fix it. (use slow_sets.rdb file for debugging). (p.s. this was now partially fixed).
|
||||
* On EXEC try to block the client until relevant keys are loaded.
|
||||
|
||||
* Hashes (HSET, HGET, HDEL, HEXISTS, HLEN, ...).
|
||||
* Hashes (GET/SET/DEL/INCRBY/EXISTS/FIELDS/LEN/MSET/MGET). Special encoding for hashes with less than N elements.
|
||||
* Write documentation for APPEND
|
||||
* Implement LEN, SUBSTR, PEEK, POKE, SETBIT, GETBIT
|
||||
|
||||
VERSION 2.2 TODO (Fault tolerant sharding)
|
||||
===========================================
|
||||
@@ -36,11 +48,6 @@ VERSION 2.4 TODO (Optimizations and latency)
|
||||
* Use epool and alike to rewrite ae.c for Linux and other platforms suppporting fater-than-select() mutiplexing APIs.
|
||||
* Implement an UDP interface for low-latency GET/SET operations.
|
||||
|
||||
VERSION 2.6 TODO (Optimizations and latency)
|
||||
============================================
|
||||
|
||||
* JSON command able to access data serialized in JSON format. For instance if I've a key foobar with a json object I can alter the "name" file using somthing like: "JSON SET foobar name Kevin". We should have GET and INCRBY as well.
|
||||
|
||||
OTHER IMPORTANT THINGS THAT WILL BE ADDED BUT I'M NOT SURE WHEN
|
||||
===============================================================
|
||||
|
||||
@@ -49,14 +56,16 @@ BIG ONES:
|
||||
* Specially encoded memory-saving integer sets.
|
||||
* A command to export a JSON dump (there should be mostly working patch needing major reworking).
|
||||
* Specially encoded sets of integers (this includes a big refactoring providing an higher level layer for Sets manipulation)
|
||||
* ZRANK: http://docs.google.com/viewer?a=v&q=cache:tCQaP3ZeN4YJ:courses.csail.mit.edu/6.046/spring04/handouts/ps5-sol.pdf+skip+list+rank+operation+augmented&hl=en&pid=bl&srcid=ADGEEShXuNjTcZyXw_1cq9OaWpSXy3PprjXqVzmM-LE0ETFznLyrDXJKQ_mBPNT10R8ErkoiXD9JbMw_FaoHmOA4yoGVrA7tZWiy393JwfCwuewuP93sjbkzZ_gnEp83jYhPYjThaIzw&sig=AHIEtbRF0GkYCdYRFtTJBE69senXZwFY0w
|
||||
|
||||
SMALL ONES:
|
||||
|
||||
* If sizeof(double) == sizeof(void*) we could store the double value of sorted sets directly in place of the pointer instead of allocating it in the heap.
|
||||
* Delete on writes against expire policy should only happen after argument parsing for commands doing their own arg parsing stuff.
|
||||
* Give errors when incrementing a key that does not look like an integer, when providing as a sorted set score something can't be parsed as a double, and so forth.
|
||||
* MSADD (n keys) (n values). See this thread in the Redis google group: http://groups.google.com/group/redis-db/browse_thread/thread/e766d84eb375cd41
|
||||
* Don't save empty lists / sets / zsets on disk with snapshotting.
|
||||
* Remove keys when a list / set / zset reaches length of 0.
|
||||
* An option to exec a command slave-side if the master connection is lost: even cooler: if the script returns "0" the slave elects itself as master, otherwise continue trying to reconnect.
|
||||
|
||||
THE "MAYBE" TODO LIST: things that may or may not get implemented
|
||||
=================================================================
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* adlist.c - A generic doubly linked list implementation
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* adlist.h - A generic doubly linked list implementation
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
|
||||
* it in form of a library for easy reuse.
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -62,6 +62,7 @@ aeEventLoop *aeCreateEventLoop(void) {
|
||||
eventLoop->timeEventNextId = 0;
|
||||
eventLoop->stop = 0;
|
||||
eventLoop->maxfd = -1;
|
||||
eventLoop->beforesleep = NULL;
|
||||
if (aeApiCreate(eventLoop) == -1) {
|
||||
zfree(eventLoop);
|
||||
return NULL;
|
||||
@@ -373,10 +374,17 @@ int aeWait(int fd, int mask, long long milliseconds) {
|
||||
|
||||
void aeMain(aeEventLoop *eventLoop) {
|
||||
eventLoop->stop = 0;
|
||||
while (!eventLoop->stop)
|
||||
while (!eventLoop->stop) {
|
||||
if (eventLoop->beforesleep != NULL)
|
||||
eventLoop->beforesleep(eventLoop);
|
||||
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
|
||||
}
|
||||
}
|
||||
|
||||
char *aeGetApiName(void) {
|
||||
return aeApiName();
|
||||
}
|
||||
|
||||
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) {
|
||||
eventLoop->beforesleep = beforesleep;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
|
||||
* it in form of a library for easy reuse.
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -58,6 +58,7 @@ struct aeEventLoop;
|
||||
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
|
||||
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
|
||||
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
|
||||
typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop);
|
||||
|
||||
/* File event structure */
|
||||
typedef struct aeFileEvent {
|
||||
@@ -93,6 +94,7 @@ typedef struct aeEventLoop {
|
||||
aeTimeEvent *timeEventHead;
|
||||
int stop;
|
||||
void *apidata; /* This is used for polling API specific data */
|
||||
aeBeforeSleepProc *beforesleep;
|
||||
} aeEventLoop;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -110,5 +112,6 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags);
|
||||
int aeWait(int fd, int mask, long long milliseconds);
|
||||
void aeMain(aeEventLoop *eventLoop);
|
||||
char *aeGetApiName(void);
|
||||
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep);
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/* Linux epoll(2) based ae.c module
|
||||
* Copyright (C) 2009 Salvatore Sanfilippo - antirez@gmail.com
|
||||
* Copyright (C) 2009-2010 Salvatore Sanfilippo - antirez@gmail.com
|
||||
* Released under the BSD license. See the COPYING file for more info. */
|
||||
|
||||
#include <sys/epoll.h>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/* Select()-based ae.c module
|
||||
* Copyright (C) 2009 Salvatore Sanfilippo - antirez@gmail.com
|
||||
* Copyright (C) 2009-2010 Salvatore Sanfilippo - antirez@gmail.com
|
||||
* Released under the BSD license. See the COPYING file for more info. */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* anet.c -- Basic TCP socket stuff made a bit less boring
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* anet.c -- Basic TCP socket stuff made a bit less boring
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* tables of power of two in size are used, collisions are handled by
|
||||
* chaining. See the source code for more information... :)
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -232,7 +232,7 @@ int dictAdd(dict *ht, void *key, void *val)
|
||||
* operation. */
|
||||
int dictReplace(dict *ht, void *key, void *val)
|
||||
{
|
||||
dictEntry *entry;
|
||||
dictEntry *entry, auxentry;
|
||||
|
||||
/* Try to add the element. If the key
|
||||
* does not exists dictAdd will suceed. */
|
||||
@@ -241,8 +241,14 @@ int dictReplace(dict *ht, void *key, void *val)
|
||||
/* It already exists, get the entry */
|
||||
entry = dictFind(ht, key);
|
||||
/* Free the old value and set the new one */
|
||||
dictFreeEntryVal(ht, entry);
|
||||
/* Set the new value and free the old one. Note that it is important
|
||||
* to do that in this order, as the value may just be exactly the same
|
||||
* as the previous one. In this context, think to reference counting,
|
||||
* you want to increment (set), and then decrement (free), and not the
|
||||
* reverse. */
|
||||
auxentry = *entry;
|
||||
dictSetHashVal(ht, entry, val);
|
||||
dictFreeEntryVal(ht, &auxentry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* tables of power of two in size are used, collisions are handled by
|
||||
* chaining. See the source code for more information... :)
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* The following is the NetBSD libc qsort implementation modified in order to
|
||||
* support partial sorting of ranges for Redis.
|
||||
*
|
||||
* Copyright(C) 2009 Salvatore Sanfilippo. All rights reserved.
|
||||
* Copyright(C) 2009-2010 Salvatore Sanfilippo. All rights reserved.
|
||||
*
|
||||
* The original copyright notice follows. */
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* The following is the NetBSD libc qsort implementation modified in order to
|
||||
* support partial sorting of ranges for Redis.
|
||||
*
|
||||
* Copyright(C) 2009 Salvatore Sanfilippo. All rights reserved.
|
||||
* Copyright(C) 2009-2010 Salvatore Sanfilippo. All rights reserved.
|
||||
*
|
||||
* See the pqsort.c file for the original copyright notice. */
|
||||
|
||||
|
||||
+31
-4
@@ -1,6 +1,6 @@
|
||||
/* Redis benchmark utility.
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -464,7 +464,7 @@ void parseOptions(int argc, char **argv) {
|
||||
printf(" -n <requests> Total number of requests (default 10000)\n");
|
||||
printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
|
||||
printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
|
||||
printf(" -r <keyspacelen> Use random keys for SET/GET/INCR\n");
|
||||
printf(" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n");
|
||||
printf(" Using this option the benchmark will get/set keys\n");
|
||||
printf(" in the form mykey_rand000000012456 instead of constant\n");
|
||||
printf(" keys, the <keyspacelen> argument determines the max\n");
|
||||
@@ -525,6 +525,24 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
do {
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
c->obuf = sdscat(c->obuf,"PING\r\n");
|
||||
prepareClientForReply(c,REPLY_RETCODE);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("PING");
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
|
||||
prepareClientForReply(c,REPLY_RETCODE);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("PING (multi bulk)");
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
@@ -580,11 +598,20 @@ int main(int argc, char **argv) {
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
c->obuf = sdscat(c->obuf,"PING\r\n");
|
||||
c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n");
|
||||
prepareClientForReply(c,REPLY_RETCODE);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("PING");
|
||||
endBenchmark("SADD");
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
if (!c) exit(1);
|
||||
c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
|
||||
prepareClientForReply(c,REPLY_BULK);
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark("SPOP");
|
||||
|
||||
prepareForBenchmark();
|
||||
c = createClient();
|
||||
|
||||
@@ -0,0 +1,671 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "lzf.h"
|
||||
|
||||
/* Object types */
|
||||
#define REDIS_STRING 0
|
||||
#define REDIS_LIST 1
|
||||
#define REDIS_SET 2
|
||||
#define REDIS_ZSET 3
|
||||
#define REDIS_HASH 4
|
||||
|
||||
/* Objects encoding. Some kind of objects like Strings and Hashes can be
|
||||
* internally represented in multiple ways. The 'encoding' field of the object
|
||||
* is set to one of this fields for this object. */
|
||||
#define REDIS_ENCODING_RAW 0 /* Raw representation */
|
||||
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
|
||||
#define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */
|
||||
#define REDIS_ENCODING_HT 3 /* Encoded as an hash table */
|
||||
|
||||
/* Object types only used for dumping to disk */
|
||||
#define REDIS_EXPIRETIME 253
|
||||
#define REDIS_SELECTDB 254
|
||||
#define REDIS_EOF 255
|
||||
|
||||
/* Defines related to the dump file format. To store 32 bits lengths for short
|
||||
* keys requires a lot of space, so we check the most significant 2 bits of
|
||||
* the first byte to interpreter the length:
|
||||
*
|
||||
* 00|000000 => if the two MSB are 00 the len is the 6 bits of this byte
|
||||
* 01|000000 00000000 => 01, the len is 14 byes, 6 bits + 8 bits of next byte
|
||||
* 10|000000 [32 bit integer] => if it's 01, a full 32 bit len will follow
|
||||
* 11|000000 this means: specially encoded object will follow. The six bits
|
||||
* number specify the kind of object that follows.
|
||||
* See the REDIS_RDB_ENC_* defines.
|
||||
*
|
||||
* Lenghts up to 63 are stored using a single byte, most DB keys, and may
|
||||
* values, will fit inside. */
|
||||
#define REDIS_RDB_6BITLEN 0
|
||||
#define REDIS_RDB_14BITLEN 1
|
||||
#define REDIS_RDB_32BITLEN 2
|
||||
#define REDIS_RDB_ENCVAL 3
|
||||
#define REDIS_RDB_LENERR UINT_MAX
|
||||
|
||||
/* When a length of a string object stored on disk has the first two bits
|
||||
* set, the remaining two bits specify a special encoding for the object
|
||||
* accordingly to the following defines: */
|
||||
#define REDIS_RDB_ENC_INT8 0 /* 8 bit signed integer */
|
||||
#define REDIS_RDB_ENC_INT16 1 /* 16 bit signed integer */
|
||||
#define REDIS_RDB_ENC_INT32 2 /* 32 bit signed integer */
|
||||
#define REDIS_RDB_ENC_LZF 3 /* string compressed with FASTLZ */
|
||||
|
||||
#define ERROR(...) { \
|
||||
printf(__VA_ARGS__); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
/* data type to hold offset in file and size */
|
||||
typedef struct {
|
||||
void *data;
|
||||
unsigned long size;
|
||||
unsigned long offset;
|
||||
} pos;
|
||||
|
||||
static unsigned char level = 0;
|
||||
static pos positions[16];
|
||||
|
||||
#define CURR_OFFSET (positions[level].offset)
|
||||
|
||||
/* Hold a stack of errors */
|
||||
typedef struct {
|
||||
char error[16][1024];
|
||||
unsigned long offset[16];
|
||||
unsigned int level;
|
||||
} errors_t;
|
||||
static errors_t errors;
|
||||
|
||||
#define SHIFT_ERROR(provided_offset, ...) { \
|
||||
sprintf(errors.error[errors.level], __VA_ARGS__); \
|
||||
errors.offset[errors.level] = provided_offset; \
|
||||
errors.level++; \
|
||||
}
|
||||
|
||||
/* Data type to hold opcode with optional key name an success status */
|
||||
typedef struct {
|
||||
char* key;
|
||||
int type;
|
||||
char success;
|
||||
} entry;
|
||||
|
||||
/* Global vars that are actally used as constants. The following double
|
||||
* values are used for double on-disk serialization, and are initialized
|
||||
* at runtime to avoid strange compiler optimizations. */
|
||||
static double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
|
||||
/* store string types for output */
|
||||
static char types[256][16];
|
||||
|
||||
/* when number of bytes to read is negative, do a peek */
|
||||
int readBytes(void *target, long num) {
|
||||
char peek = (num < 0) ? 1 : 0;
|
||||
num = (num < 0) ? -num : num;
|
||||
|
||||
pos p = positions[level];
|
||||
if (p.offset + num > p.size) {
|
||||
return 0;
|
||||
} else {
|
||||
memcpy(target, (void*)((unsigned long)p.data + p.offset), num);
|
||||
if (!peek) positions[level].offset += num;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int processHeader() {
|
||||
char buf[10] = "_________";
|
||||
int dump_version;
|
||||
|
||||
if (!readBytes(buf, 9)) {
|
||||
ERROR("Cannot read header\n");
|
||||
}
|
||||
|
||||
/* expect the first 5 bytes to equal REDIS */
|
||||
if (memcmp(buf,"REDIS",5) != 0) {
|
||||
ERROR("Wrong signature in header\n");
|
||||
}
|
||||
|
||||
dump_version = (int)strtol(buf + 5, NULL, 10);
|
||||
if (dump_version != 1) {
|
||||
ERROR("Unknown RDB format version: %d\n", dump_version);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int loadType(entry *e) {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
|
||||
/* this byte needs to qualify as type */
|
||||
unsigned char t;
|
||||
if (readBytes(&t, 1)) {
|
||||
if (t <= 4 || t >= 253) {
|
||||
e->type = t;
|
||||
return 1;
|
||||
} else {
|
||||
SHIFT_ERROR(offset, "Unknown type (0x%02x)", t);
|
||||
}
|
||||
} else {
|
||||
SHIFT_ERROR(offset, "Could not read type");
|
||||
}
|
||||
|
||||
/* failure */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int peekType() {
|
||||
unsigned char t;
|
||||
if (readBytes(&t, -1) && (t <= 4 || t >= 253)) return t;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* discard time, just consume the bytes */
|
||||
int processTime() {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
unsigned char t[4];
|
||||
if (readBytes(t, 4)) {
|
||||
return 1;
|
||||
} else {
|
||||
SHIFT_ERROR(offset, "Could not read time");
|
||||
}
|
||||
|
||||
/* failure */
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t loadLength(int *isencoded) {
|
||||
unsigned char buf[2];
|
||||
uint32_t len;
|
||||
int type;
|
||||
|
||||
if (isencoded) *isencoded = 0;
|
||||
if (!readBytes(buf, 1)) return REDIS_RDB_LENERR;
|
||||
type = (buf[0] & 0xC0) >> 6;
|
||||
if (type == REDIS_RDB_6BITLEN) {
|
||||
/* Read a 6 bit len */
|
||||
return buf[0] & 0x3F;
|
||||
} else if (type == REDIS_RDB_ENCVAL) {
|
||||
/* Read a 6 bit len encoding type */
|
||||
if (isencoded) *isencoded = 1;
|
||||
return buf[0] & 0x3F;
|
||||
} else if (type == REDIS_RDB_14BITLEN) {
|
||||
/* Read a 14 bit len */
|
||||
if (!readBytes(buf+1,1)) return REDIS_RDB_LENERR;
|
||||
return ((buf[0] & 0x3F) << 8) | buf[1];
|
||||
} else {
|
||||
/* Read a 32 bit len */
|
||||
if (!readBytes(&len, 4)) return REDIS_RDB_LENERR;
|
||||
return (unsigned int)ntohl(len);
|
||||
}
|
||||
}
|
||||
|
||||
char *loadIntegerObject(int enctype) {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
unsigned char enc[4];
|
||||
long long val;
|
||||
|
||||
if (enctype == REDIS_RDB_ENC_INT8) {
|
||||
uint8_t v;
|
||||
if (!readBytes(enc, 1)) return NULL;
|
||||
v = enc[0];
|
||||
val = (int8_t)v;
|
||||
} else if (enctype == REDIS_RDB_ENC_INT16) {
|
||||
uint16_t v;
|
||||
if (!readBytes(enc, 2)) return NULL;
|
||||
v = enc[0]|(enc[1]<<8);
|
||||
val = (int16_t)v;
|
||||
} else if (enctype == REDIS_RDB_ENC_INT32) {
|
||||
uint32_t v;
|
||||
if (!readBytes(enc, 4)) return NULL;
|
||||
v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24);
|
||||
val = (int32_t)v;
|
||||
} else {
|
||||
SHIFT_ERROR(offset, "Unknown integer encoding (0x%02x)", enctype);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* convert val into string */
|
||||
char *buf;
|
||||
buf = malloc(sizeof(char) * 128);
|
||||
sprintf(buf, "%lld", val);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char* loadLzfStringObject() {
|
||||
unsigned int slen, clen;
|
||||
char *c, *s;
|
||||
|
||||
if ((clen = loadLength(NULL)) == REDIS_RDB_LENERR) return NULL;
|
||||
if ((slen = loadLength(NULL)) == REDIS_RDB_LENERR) return NULL;
|
||||
|
||||
c = malloc(clen);
|
||||
if (!readBytes(c, clen)) {
|
||||
free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = malloc(slen+1);
|
||||
if (lzf_decompress(c,clen,s,slen) == 0) {
|
||||
free(c); free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(c);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* returns NULL when not processable, char* when valid */
|
||||
char* loadStringObject() {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
int isencoded;
|
||||
uint32_t len;
|
||||
|
||||
len = loadLength(&isencoded);
|
||||
if (isencoded) {
|
||||
switch(len) {
|
||||
case REDIS_RDB_ENC_INT8:
|
||||
case REDIS_RDB_ENC_INT16:
|
||||
case REDIS_RDB_ENC_INT32:
|
||||
return loadIntegerObject(len);
|
||||
case REDIS_RDB_ENC_LZF:
|
||||
return loadLzfStringObject();
|
||||
default:
|
||||
/* unknown encoding */
|
||||
SHIFT_ERROR(offset, "Unknown string encoding (0x%02x)", len);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == REDIS_RDB_LENERR) return NULL;
|
||||
|
||||
char *buf = malloc(sizeof(char) * (len+1));
|
||||
buf[len] = '\0';
|
||||
if (!readBytes(buf, len)) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
int processStringObject(char** store) {
|
||||
unsigned long offset = CURR_OFFSET;
|
||||
char *key = loadStringObject();
|
||||
if (key == NULL) {
|
||||
SHIFT_ERROR(offset, "Error reading string object");
|
||||
free(key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (store != NULL) {
|
||||
*store = key;
|
||||
} else {
|
||||
free(key);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
double* loadDoubleValue() {
|
||||
char buf[256];
|
||||
unsigned char len;
|
||||
double* val;
|
||||
|
||||
if (!readBytes(&len,1)) return NULL;
|
||||
|
||||
val = malloc(sizeof(double));
|
||||
switch(len) {
|
||||
case 255: *val = R_NegInf; return val;
|
||||
case 254: *val = R_PosInf; return val;
|
||||
case 253: *val = R_Nan; return val;
|
||||
default:
|
||||
if (!readBytes(buf, len)) {
|
||||
free(val);
|
||||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
sscanf(buf, "%lg", val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
int processDoubleValue(double** store) {
|
||||
unsigned long offset = CURR_OFFSET;
|
||||
double *val = loadDoubleValue();
|
||||
if (val == NULL) {
|
||||
SHIFT_ERROR(offset, "Error reading double value");
|
||||
free(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (store != NULL) {
|
||||
*store = val;
|
||||
} else {
|
||||
free(val);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int loadPair(entry *e) {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
uint32_t i;
|
||||
|
||||
/* read key first */
|
||||
char *key;
|
||||
if (processStringObject(&key)) {
|
||||
e->key = key;
|
||||
} else {
|
||||
SHIFT_ERROR(offset, "Error reading entry key");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t length = 0;
|
||||
if (e->type == REDIS_LIST ||
|
||||
e->type == REDIS_SET ||
|
||||
e->type == REDIS_ZSET ||
|
||||
e->type == REDIS_HASH) {
|
||||
if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) {
|
||||
SHIFT_ERROR(offset, "Error reading %s length", types[e->type]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch(e->type) {
|
||||
case REDIS_STRING:
|
||||
if (!processStringObject(NULL)) {
|
||||
SHIFT_ERROR(offset, "Error reading entry value");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case REDIS_LIST:
|
||||
case REDIS_SET:
|
||||
for (i = 0; i < length; i++) {
|
||||
offset = CURR_OFFSET;
|
||||
if (!processStringObject(NULL)) {
|
||||
SHIFT_ERROR(offset, "Error reading element at index %d (length: %d)", i, length);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REDIS_ZSET:
|
||||
for (i = 0; i < length; i++) {
|
||||
offset = CURR_OFFSET;
|
||||
if (!processStringObject(NULL)) {
|
||||
SHIFT_ERROR(offset, "Error reading element key at index %d (length: %d)", i, length);
|
||||
return 0;
|
||||
}
|
||||
offset = CURR_OFFSET;
|
||||
if (!processDoubleValue(NULL)) {
|
||||
SHIFT_ERROR(offset, "Error reading element value at index %d (length: %d)", i, length);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REDIS_HASH:
|
||||
for (i = 0; i < length; i++) {
|
||||
offset = CURR_OFFSET;
|
||||
if (!processStringObject(NULL)) {
|
||||
SHIFT_ERROR(offset, "Error reading element key at index %d (length: %d)", i, length);
|
||||
return 0;
|
||||
}
|
||||
offset = CURR_OFFSET;
|
||||
if (!processStringObject(NULL)) {
|
||||
SHIFT_ERROR(offset, "Error reading element value at index %d (length: %d)", i, length);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
SHIFT_ERROR(offset, "Type not implemented");
|
||||
return 0;
|
||||
}
|
||||
/* because we're done, we assume success */
|
||||
e->success = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
entry loadEntry() {
|
||||
entry e = { NULL, -1, 0 };
|
||||
uint32_t length, offset[4];
|
||||
|
||||
/* reset error container */
|
||||
errors.level = 0;
|
||||
|
||||
offset[0] = CURR_OFFSET;
|
||||
if (!loadType(&e)) {
|
||||
return e;
|
||||
}
|
||||
|
||||
offset[1] = CURR_OFFSET;
|
||||
if (e.type == REDIS_SELECTDB) {
|
||||
if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) {
|
||||
SHIFT_ERROR(offset[1], "Error reading database number");
|
||||
return e;
|
||||
}
|
||||
if (length > 63) {
|
||||
SHIFT_ERROR(offset[1], "Database number out of range (%d)", length);
|
||||
return e;
|
||||
}
|
||||
} else if (e.type == REDIS_EOF) {
|
||||
if (positions[level].offset < positions[level].size) {
|
||||
SHIFT_ERROR(offset[0], "Unexpected EOF");
|
||||
} else {
|
||||
e.success = 1;
|
||||
}
|
||||
return e;
|
||||
} else {
|
||||
/* optionally consume expire */
|
||||
if (e.type == REDIS_EXPIRETIME) {
|
||||
if (!processTime()) return e;
|
||||
if (!loadType(&e)) return e;
|
||||
}
|
||||
|
||||
offset[1] = CURR_OFFSET;
|
||||
if (!loadPair(&e)) {
|
||||
SHIFT_ERROR(offset[1], "Error for type %s", types[e.type]);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
/* all entries are followed by a valid type:
|
||||
* e.g. a new entry, SELECTDB, EXPIRE, EOF */
|
||||
offset[2] = CURR_OFFSET;
|
||||
if (peekType() == -1) {
|
||||
SHIFT_ERROR(offset[2], "Followed by invalid type");
|
||||
SHIFT_ERROR(offset[0], "Error for type %s", types[e.type]);
|
||||
e.success = 0;
|
||||
} else {
|
||||
e.success = 1;
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
void printCentered(int indent, int width, char* body) {
|
||||
char head[256], tail[256];
|
||||
memset(head, '\0', 256);
|
||||
memset(tail, '\0', 256);
|
||||
|
||||
memset(head, '=', indent);
|
||||
memset(tail, '=', width - 2 - indent - strlen(body));
|
||||
printf("%s %s %s\n", head, body, tail);
|
||||
}
|
||||
|
||||
void printValid(int ops, int bytes) {
|
||||
char body[80];
|
||||
sprintf(body, "Processed %d valid opcodes (in %d bytes)", ops, bytes);
|
||||
printCentered(4, 80, body);
|
||||
}
|
||||
|
||||
void printSkipped(int bytes, int offset) {
|
||||
char body[80];
|
||||
sprintf(body, "Skipped %d bytes (resuming at 0x%08x)", bytes, offset);
|
||||
printCentered(4, 80, body);
|
||||
}
|
||||
|
||||
void printErrorStack(entry *e) {
|
||||
unsigned int i;
|
||||
char body[64];
|
||||
|
||||
if (e->type == -1) {
|
||||
sprintf(body, "Error trace");
|
||||
} else if (e->type >= 253) {
|
||||
sprintf(body, "Error trace (%s)", types[e->type]);
|
||||
} else if (!e->key) {
|
||||
sprintf(body, "Error trace (%s: (unknown))", types[e->type]);
|
||||
} else {
|
||||
char tmp[41];
|
||||
strncpy(tmp, e->key, 40);
|
||||
|
||||
/* display truncation at the last 3 chars */
|
||||
if (strlen(e->key) > 40) {
|
||||
memset(&tmp[37], '.', 3);
|
||||
}
|
||||
|
||||
/* display unprintable characters as ? */
|
||||
for (i = 0; i < strlen(tmp); i++) {
|
||||
if (tmp[i] <= 32) tmp[i] = '?';
|
||||
}
|
||||
sprintf(body, "Error trace (%s: %s)", types[e->type], tmp);
|
||||
}
|
||||
|
||||
printCentered(4, 80, body);
|
||||
|
||||
/* display error stack */
|
||||
for (i = 0; i < errors.level; i++) {
|
||||
printf("0x%08lx - %s\n", errors.offset[i], errors.error[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void process() {
|
||||
int i, num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
|
||||
entry entry;
|
||||
processHeader();
|
||||
|
||||
level = 1;
|
||||
while(positions[0].offset < positions[0].size) {
|
||||
positions[1] = positions[0];
|
||||
|
||||
entry = loadEntry();
|
||||
if (!entry.success) {
|
||||
printValid(num_valid_ops, num_valid_bytes);
|
||||
printErrorStack(&entry);
|
||||
num_errors++;
|
||||
num_valid_ops = 0;
|
||||
num_valid_bytes = 0;
|
||||
|
||||
/* search for next valid entry */
|
||||
unsigned long offset = positions[0].offset + 1;
|
||||
while (!entry.success && offset < positions[0].size) {
|
||||
positions[1].offset = offset;
|
||||
|
||||
/* find 3 consecutive valid entries */
|
||||
for (i = 0; i < 3; i++) {
|
||||
entry = loadEntry();
|
||||
if (!entry.success) break;
|
||||
}
|
||||
/* check if we found 3 consecutive valid entries */
|
||||
if (i < 3) {
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
/* print how many bytes we have skipped to find a new valid opcode */
|
||||
if (offset < positions[0].size) {
|
||||
printSkipped(offset - positions[0].offset, offset);
|
||||
}
|
||||
|
||||
positions[0].offset = offset;
|
||||
} else {
|
||||
num_valid_ops++;
|
||||
num_valid_bytes += positions[1].offset - positions[0].offset;
|
||||
|
||||
/* advance position */
|
||||
positions[0] = positions[1];
|
||||
}
|
||||
}
|
||||
|
||||
/* because there is another potential error,
|
||||
* print how many valid ops we have processed */
|
||||
printValid(num_valid_ops, num_valid_bytes);
|
||||
|
||||
/* expect an eof */
|
||||
if (entry.type != REDIS_EOF) {
|
||||
/* last byte should be EOF, add error */
|
||||
errors.level = 0;
|
||||
SHIFT_ERROR(positions[0].offset, "Expected EOF, got %s", types[entry.type]);
|
||||
|
||||
/* this is an EOF error so reset type */
|
||||
entry.type = -1;
|
||||
printErrorStack(&entry);
|
||||
|
||||
num_errors++;
|
||||
}
|
||||
|
||||
/* print summary on errors */
|
||||
if (num_errors > 0) {
|
||||
printf("\n");
|
||||
printf("Total unprocessable opcodes: %d\n", num_errors);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* expect the first argument to be the dump file */
|
||||
if (argc <= 1) {
|
||||
printf("Usage: %s <dump.rdb>\n", argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int fd;
|
||||
unsigned long size;
|
||||
struct stat stat;
|
||||
void *data;
|
||||
|
||||
fd = open(argv[1], O_RDONLY);
|
||||
if (fd < 1) {
|
||||
ERROR("Cannot open file: %s\n", argv[1]);
|
||||
}
|
||||
if (fstat(fd, &stat) == -1) {
|
||||
ERROR("Cannot stat: %s\n", argv[1]);
|
||||
} else {
|
||||
size = stat.st_size;
|
||||
}
|
||||
|
||||
data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
ERROR("Cannot mmap: %s\n", argv[1]);
|
||||
}
|
||||
|
||||
/* Initialize static vars */
|
||||
positions[0].data = data;
|
||||
positions[0].size = size;
|
||||
positions[0].offset = 0;
|
||||
errors.level = 0;
|
||||
|
||||
/* Object types */
|
||||
sprintf(types[REDIS_STRING], "STRING");
|
||||
sprintf(types[REDIS_LIST], "LIST");
|
||||
sprintf(types[REDIS_SET], "SET");
|
||||
sprintf(types[REDIS_ZSET], "ZSET");
|
||||
sprintf(types[REDIS_HASH], "HASH");
|
||||
|
||||
/* Object types only used for dumping to disk */
|
||||
sprintf(types[REDIS_EXPIRETIME], "EXPIRETIME");
|
||||
sprintf(types[REDIS_SELECTDB], "SELECTDB");
|
||||
sprintf(types[REDIS_EOF], "EOF");
|
||||
|
||||
/* Double constants initialization */
|
||||
R_Zero = 0.0;
|
||||
R_PosInf = 1.0/R_Zero;
|
||||
R_NegInf = -1.0/R_Zero;
|
||||
R_Nan = R_Zero/R_Zero;
|
||||
|
||||
process();
|
||||
|
||||
munmap(data, size);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
+110
-28
@@ -1,6 +1,6 @@
|
||||
/* Redis CLI (command line interface)
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -51,6 +51,8 @@ static struct config {
|
||||
int hostport;
|
||||
long repeat;
|
||||
int dbnum;
|
||||
int interactive;
|
||||
char *auth;
|
||||
} config;
|
||||
|
||||
struct redisCommand {
|
||||
@@ -60,9 +62,12 @@ struct redisCommand {
|
||||
};
|
||||
|
||||
static struct redisCommand cmdTable[] = {
|
||||
{"auth",2,REDIS_CMD_INLINE},
|
||||
{"get",2,REDIS_CMD_INLINE},
|
||||
{"set",3,REDIS_CMD_BULK},
|
||||
{"setnx",3,REDIS_CMD_BULK},
|
||||
{"append",3,REDIS_CMD_BULK},
|
||||
{"substr",4,REDIS_CMD_INLINE},
|
||||
{"del",-2,REDIS_CMD_INLINE},
|
||||
{"exists",2,REDIS_CMD_INLINE},
|
||||
{"incr",2,REDIS_CMD_INLINE},
|
||||
@@ -98,8 +103,13 @@ static struct redisCommand cmdTable[] = {
|
||||
{"zincrby",4,REDIS_CMD_BULK},
|
||||
{"zrem",3,REDIS_CMD_BULK},
|
||||
{"zremrangebyscore",4,REDIS_CMD_INLINE},
|
||||
{"zmerge",-3,REDIS_CMD_INLINE},
|
||||
{"zmergeweighed",-4,REDIS_CMD_INLINE},
|
||||
{"zrange",-4,REDIS_CMD_INLINE},
|
||||
{"zrank",3,REDIS_CMD_BULK},
|
||||
{"zrevrank",3,REDIS_CMD_BULK},
|
||||
{"zrangebyscore",-4,REDIS_CMD_INLINE},
|
||||
{"zcount",4,REDIS_CMD_INLINE},
|
||||
{"zrevrange",-4,REDIS_CMD_INLINE},
|
||||
{"zcard",2,REDIS_CMD_INLINE},
|
||||
{"zscore",3,REDIS_CMD_BULK},
|
||||
@@ -135,6 +145,17 @@ static struct redisCommand cmdTable[] = {
|
||||
{"mset",-3,REDIS_CMD_MULTIBULK},
|
||||
{"msetnx",-3,REDIS_CMD_MULTIBULK},
|
||||
{"monitor",1,REDIS_CMD_INLINE},
|
||||
{"multi",1,REDIS_CMD_INLINE},
|
||||
{"exec",1,REDIS_CMD_INLINE},
|
||||
{"discard",1,REDIS_CMD_INLINE},
|
||||
{"hset",4,REDIS_CMD_MULTIBULK},
|
||||
{"hget",3,REDIS_CMD_BULK},
|
||||
{"hdel",3,REDIS_CMD_BULK},
|
||||
{"hlen",2,REDIS_CMD_INLINE},
|
||||
{"hkeys",2,REDIS_CMD_INLINE},
|
||||
{"hvals",2,REDIS_CMD_INLINE},
|
||||
{"hgetall",2,REDIS_CMD_INLINE},
|
||||
{"hexists",3,REDIS_CMD_BULK},
|
||||
{NULL,0,0}
|
||||
};
|
||||
|
||||
@@ -152,14 +173,16 @@ static struct redisCommand *lookupCommand(char *name) {
|
||||
|
||||
static int cliConnect(void) {
|
||||
char err[ANET_ERR_LEN];
|
||||
int fd;
|
||||
static int fd = ANET_ERR;
|
||||
|
||||
fd = anetTcpConnect(err,config.hostip,config.hostport);
|
||||
if (fd == ANET_ERR) {
|
||||
fprintf(stderr,"Connect: %s\n",err);
|
||||
return -1;
|
||||
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);
|
||||
return -1;
|
||||
}
|
||||
anetTcpNoDelay(NULL,fd);
|
||||
}
|
||||
anetTcpNoDelay(NULL,fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
@@ -264,8 +287,7 @@ static int cliReadReply(int fd) {
|
||||
}
|
||||
}
|
||||
|
||||
static int selectDb(int fd)
|
||||
{
|
||||
static int selectDb(int fd) {
|
||||
int retval;
|
||||
sds cmd;
|
||||
char type;
|
||||
@@ -280,8 +302,7 @@ static int selectDb(int fd)
|
||||
if (type <= 0 || type != '+') return 1;
|
||||
retval = cliReadSingleLineReply(fd,1);
|
||||
if (retval) {
|
||||
close(fd);
|
||||
return retval;
|
||||
return retval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -311,7 +332,7 @@ static int cliSendCommand(int argc, char **argv) {
|
||||
fprintf(stderr,"Error setting DB num\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
while(config.repeat--) {
|
||||
/* Build the command to send */
|
||||
cmd = sdsempty();
|
||||
@@ -348,11 +369,9 @@ static int cliSendCommand(int argc, char **argv) {
|
||||
|
||||
retval = cliReadReply(fd);
|
||||
if (retval) {
|
||||
close(fd);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -361,7 +380,7 @@ static int parseOptions(int argc, char **argv) {
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
int lastarg = i==argc-1;
|
||||
|
||||
|
||||
if (!strcmp(argv[i],"-h") && !lastarg) {
|
||||
char *ip = zmalloc(32);
|
||||
if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
|
||||
@@ -381,6 +400,11 @@ static int parseOptions(int argc, char **argv) {
|
||||
} else if (!strcmp(argv[i],"-n") && !lastarg) {
|
||||
config.dbnum = atoi(argv[i+1]);
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
||||
config.auth = argv[i+1];
|
||||
i++;
|
||||
} else if (!strcmp(argv[i],"-i")) {
|
||||
config.interactive = 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -406,17 +430,76 @@ static sds readArgFromStdin(void) {
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
fprintf(stderr, "usage: redis-cli [-h host] [-p port] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
|
||||
fprintf(stderr, "usage: echo \"argN\" | redis-cli [-h host] [-p port] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
|
||||
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, "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");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Turn the plain C strings into Sds strings */
|
||||
static char **convertToSds(int count, char** args) {
|
||||
int j;
|
||||
char **sds = zmalloc(sizeof(char*)*count+1);
|
||||
|
||||
for(j = 0; j < count; j++)
|
||||
sds[j] = sdsnew(args[j]);
|
||||
|
||||
return sds;
|
||||
}
|
||||
|
||||
static char *prompt(char *line, int size) {
|
||||
char *retval;
|
||||
|
||||
do {
|
||||
printf(">> ");
|
||||
retval = fgets(line, size, stdin);
|
||||
} while (retval && *line == '\n');
|
||||
line[strlen(line) - 1] = '\0';
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void repl() {
|
||||
int size = 4096, max = size >> 1, argc;
|
||||
char buffer[size];
|
||||
char *line = buffer;
|
||||
char **ap, *args[max];
|
||||
|
||||
if (config.auth != NULL) {
|
||||
char *authargv[2];
|
||||
|
||||
authargv[0] = "AUTH";
|
||||
authargv[1] = config.auth;
|
||||
cliSendCommand(2, convertToSds(2, authargv));
|
||||
}
|
||||
|
||||
while (prompt(line, size)) {
|
||||
argc = 0;
|
||||
|
||||
for (ap = args; (*ap = strsep(&line, " \t")) != NULL;) {
|
||||
if (**ap != '\0') {
|
||||
if (argc >= max) break;
|
||||
if (strcasecmp(*ap,"quit") == 0 || strcasecmp(*ap,"exit") == 0)
|
||||
exit(0);
|
||||
ap++;
|
||||
argc++;
|
||||
}
|
||||
}
|
||||
|
||||
config.repeat = 1;
|
||||
cliSendCommand(argc, convertToSds(argc, args));
|
||||
line = buffer;
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg, j;
|
||||
int firstarg;
|
||||
char **argvcopy;
|
||||
struct redisCommand *rc;
|
||||
|
||||
@@ -424,25 +507,24 @@ int main(int argc, char **argv) {
|
||||
config.hostport = 6379;
|
||||
config.repeat = 1;
|
||||
config.dbnum = 0;
|
||||
config.interactive = 0;
|
||||
config.auth = NULL;
|
||||
|
||||
firstarg = parseOptions(argc,argv);
|
||||
argc -= firstarg;
|
||||
argv += firstarg;
|
||||
|
||||
/* Turn the plain C strings into Sds strings */
|
||||
argvcopy = zmalloc(sizeof(char*)*argc+1);
|
||||
for(j = 0; j < argc; j++)
|
||||
argvcopy[j] = sdsnew(argv[j]);
|
||||
|
||||
if (argc < 1) usage();
|
||||
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++;
|
||||
}
|
||||
if (rc->arity > 0 && argc == rc->arity-1) {
|
||||
sds lastarg = readArgFromStdin();
|
||||
argvcopy[argc] = lastarg;
|
||||
argc++;
|
||||
}
|
||||
}
|
||||
|
||||
return cliSendCommand(argc, argvcopy);
|
||||
|
||||
+31
-16
@@ -158,14 +158,18 @@ appendonly no
|
||||
# always: fsync after every write to the append only log . Slow, Safest.
|
||||
# everysec: fsync only if one second passed since the last fsync. Compromise.
|
||||
#
|
||||
# The default is "always" that's the safer of the options. It's up to you to
|
||||
# understand if you can relax this to "everysec" that will fsync every second
|
||||
# or to "no" that will let the operating system flush the output buffer when
|
||||
# it want, for better performances (but if you can live with the idea of
|
||||
# some data loss consider the default persistence mode that's snapshotting).
|
||||
# The default is "everysec" that's usually the right compromise between
|
||||
# speed and data safety. It's up to you to understand if you can relax this to
|
||||
# "no" that will will let the operating system flush the output buffer when
|
||||
# it wants, for better performances (but if you can live with the idea of
|
||||
# some data loss consider the default persistence mode that's snapshotting),
|
||||
# or on the contrary, use "always" that's very slow but a bit safer than
|
||||
# everysec.
|
||||
#
|
||||
# If unsure, use "everysec".
|
||||
|
||||
appendfsync always
|
||||
# appendfsync everysec
|
||||
# appendfsync always
|
||||
appendfsync everysec
|
||||
# appendfsync no
|
||||
|
||||
################################ VIRTUAL MEMORY ###############################
|
||||
@@ -193,17 +197,21 @@ vm-enabled no
|
||||
#
|
||||
# Useless to say, the best kind of disk for a Redis swap file (that's accessed
|
||||
# at random) is a Solid State Disk (SSD).
|
||||
#
|
||||
# *** WARNING *** if you are using a shared hosting the default of putting
|
||||
# the swap file under /tmp is not secure. Create a dir with access granted
|
||||
# only to Redis user and configure Redis to create the swap file there.
|
||||
vm-swap-file /tmp/redis-%p.vm
|
||||
|
||||
# vm-max-memory configures the VM to use at max the specified amount of
|
||||
# RAM. Everything that deos not fit will be swapped on disk *if* possible, that
|
||||
# is, if there is still enough contiguous space in the swap file.
|
||||
#
|
||||
# With vm-max-memory 10000000 the system will swap almost everything it
|
||||
# can. Not a good default, just specify the max amount of RAM you can
|
||||
# in bytes, but it's better to leave some margin. For instance specify
|
||||
# an amount of RAM that's more or less 80% of your free RAM.
|
||||
vm-max-memory 10000000
|
||||
# With vm-max-memory 0 the system will swap everything it can. Not a good
|
||||
# default, just specify the max amount of RAM you can in bytes, but it's
|
||||
# better to leave some margin. For instance specify an amount of RAM
|
||||
# that's more or less between 60 and 80% of your free RAM.
|
||||
vm-max-memory 0
|
||||
|
||||
# Redis swap files is split into pages. An object can be saved using multiple
|
||||
# contiguous pages, but pages can't be shared between different objects.
|
||||
@@ -214,7 +222,7 @@ vm-max-memory 10000000
|
||||
# If you use a lot of small objects, use a page size of 64 or 32 bytes.
|
||||
# If you use a lot of big objects, use a bigger page size.
|
||||
# If unsure, use the default :)
|
||||
vm-page-size 256
|
||||
vm-page-size 32
|
||||
|
||||
# Number of total memory pages in the swap file.
|
||||
# Given that the page table (a bitmap of free/used pages) is taken in memory,
|
||||
@@ -222,12 +230,12 @@ vm-page-size 256
|
||||
#
|
||||
# The total swap size is vm-page-size * vm-pages
|
||||
#
|
||||
# With the default of 256-bytes memory pages and 104857600 pages Redis will
|
||||
# use a 25 GB swap file, that will use roughly 13 MB of RAM for the page table.
|
||||
# With the default of 32-bytes memory pages and 134217728 pages Redis will
|
||||
# use a 4 GB swap file, that will use 16 MB of RAM for the page table.
|
||||
#
|
||||
# It's better to use the smallest acceptable value for your application,
|
||||
# but the default is large in order to work in most conditions.
|
||||
vm-pages 104857600
|
||||
vm-pages 134217728
|
||||
|
||||
# Max number of VM I/O threads running at the same time.
|
||||
# This threads are used to read/write data from/to swap file, since they
|
||||
@@ -263,3 +271,10 @@ glueoutputbuf yes
|
||||
# your development environment so that we can test it better.
|
||||
shareobjects no
|
||||
shareobjectspoolsize 1024
|
||||
|
||||
# Hashes are encoded in a special way (much more memory efficient) when they
|
||||
# have at max a given numer of elements, and the biggest element does not
|
||||
# exceed a given threshold. You can configure this limits with the following
|
||||
# configuration directives.
|
||||
hash-max-zipmap-entries 64
|
||||
hash-max-zipmap-value 512
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -20,14 +20,14 @@ array set ::redis::multibulkarg {}
|
||||
|
||||
# Flag commands requiring last argument as a bulk write operation
|
||||
foreach redis_bulk_cmd {
|
||||
set setnx rpush lpush lset lrem sadd srem sismember echo getset smove zadd zrem zscore zincrby
|
||||
set setnx rpush lpush lset lrem sadd srem sismember echo getset smove zadd zrem zscore zincrby append zrank zrevrank hget hdel hexists
|
||||
} {
|
||||
set ::redis::bulkarg($redis_bulk_cmd) {}
|
||||
}
|
||||
|
||||
# Flag commands requiring last argument as a bulk write operation
|
||||
foreach redis_multibulk_cmd {
|
||||
mset msetnx
|
||||
mset msetnx hset
|
||||
} {
|
||||
set ::redis::multibulkarg($redis_multibulk_cmd) {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SDSLib, A C dynamic strings library
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -159,7 +159,7 @@ sds sdscpy(sds s, char *t) {
|
||||
sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char *buf, *t;
|
||||
size_t buflen = 32;
|
||||
size_t buflen = 16;
|
||||
|
||||
while(1) {
|
||||
buf = zmalloc(buflen);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SDSLib, A C dynamic strings library
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
+39
-2
@@ -3,24 +3,33 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"_redisAssert",(unsigned long)_redisAssert},
|
||||
{"acceptHandler",(unsigned long)acceptHandler},
|
||||
{"addReply",(unsigned long)addReply},
|
||||
{"addReplyBulk",(unsigned long)addReplyBulk},
|
||||
{"addReplyBulkLen",(unsigned long)addReplyBulkLen},
|
||||
{"addReplyDouble",(unsigned long)addReplyDouble},
|
||||
{"addReplyLong",(unsigned long)addReplyLong},
|
||||
{"addReplySds",(unsigned long)addReplySds},
|
||||
{"addReplyUlong",(unsigned long)addReplyUlong},
|
||||
{"aofRemoveTempFile",(unsigned long)aofRemoveTempFile},
|
||||
{"appendCommand",(unsigned long)appendCommand},
|
||||
{"appendServerSaveParams",(unsigned long)appendServerSaveParams},
|
||||
{"authCommand",(unsigned long)authCommand},
|
||||
{"beforeSleep",(unsigned long)beforeSleep},
|
||||
{"bgrewriteaofCommand",(unsigned long)bgrewriteaofCommand},
|
||||
{"bgsaveCommand",(unsigned long)bgsaveCommand},
|
||||
{"blockClientOnSwappedKeys",(unsigned long)blockClientOnSwappedKeys},
|
||||
{"blockForKeys",(unsigned long)blockForKeys},
|
||||
{"blockingPopGenericCommand",(unsigned long)blockingPopGenericCommand},
|
||||
{"blpopCommand",(unsigned long)blpopCommand},
|
||||
{"brpopCommand",(unsigned long)brpopCommand},
|
||||
{"bytesToHuman",(unsigned long)bytesToHuman},
|
||||
{"call",(unsigned long)call},
|
||||
{"checkType",(unsigned long)checkType},
|
||||
{"closeTimedoutClients",(unsigned long)closeTimedoutClients},
|
||||
{"compareStringObjects",(unsigned long)compareStringObjects},
|
||||
{"computeObjectSwappability",(unsigned long)computeObjectSwappability},
|
||||
{"convertToRealHash",(unsigned long)convertToRealHash},
|
||||
{"createClient",(unsigned long)createClient},
|
||||
{"createHashObject",(unsigned long)createHashObject},
|
||||
{"createListObject",(unsigned long)createListObject},
|
||||
{"createObject",(unsigned long)createObject},
|
||||
{"createSetObject",(unsigned long)createSetObject},
|
||||
@@ -43,6 +52,8 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"dictObjKeyCompare",(unsigned long)dictObjKeyCompare},
|
||||
{"dictRedisObjectDestructor",(unsigned long)dictRedisObjectDestructor},
|
||||
{"dictVanillaFree",(unsigned long)dictVanillaFree},
|
||||
{"discardCommand",(unsigned long)discardCommand},
|
||||
{"dontWaitForSwappedKey",(unsigned long)dontWaitForSwappedKey},
|
||||
{"dupClientReplyValue",(unsigned long)dupClientReplyValue},
|
||||
{"dupStringObject",(unsigned long)dupStringObject},
|
||||
{"echoCommand",(unsigned long)echoCommand},
|
||||
@@ -68,10 +79,13 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"freeSetObject",(unsigned long)freeSetObject},
|
||||
{"freeStringObject",(unsigned long)freeStringObject},
|
||||
{"freeZsetObject",(unsigned long)freeZsetObject},
|
||||
{"fwriteBulk",(unsigned long)fwriteBulk},
|
||||
{"fwriteBulkDouble",(unsigned long)fwriteBulkDouble},
|
||||
{"fwriteBulkLong",(unsigned long)fwriteBulkLong},
|
||||
{"fwriteBulkObject",(unsigned long)fwriteBulkObject},
|
||||
{"fwriteBulkString",(unsigned long)fwriteBulkString},
|
||||
{"genRedisInfoString",(unsigned long)genRedisInfoString},
|
||||
{"genericHgetallCommand",(unsigned long)genericHgetallCommand},
|
||||
{"genericZrangebyscoreCommand",(unsigned long)genericZrangebyscoreCommand},
|
||||
{"getCommand",(unsigned long)getCommand},
|
||||
{"getDecodedObject",(unsigned long)getDecodedObject},
|
||||
{"getExpire",(unsigned long)getExpire},
|
||||
@@ -79,8 +93,17 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"getMcontextEip",(unsigned long)getMcontextEip},
|
||||
{"getsetCommand",(unsigned long)getsetCommand},
|
||||
{"glueReplyBuffersIfNeeded",(unsigned long)glueReplyBuffersIfNeeded},
|
||||
{"handleClientsBlockedOnSwappedKey",(unsigned long)handleClientsBlockedOnSwappedKey},
|
||||
{"handleClientsWaitingListPush",(unsigned long)handleClientsWaitingListPush},
|
||||
{"hdelCommand",(unsigned long)hdelCommand},
|
||||
{"hexistsCommand",(unsigned long)hexistsCommand},
|
||||
{"hgetCommand",(unsigned long)hgetCommand},
|
||||
{"hgetallCommand",(unsigned long)hgetallCommand},
|
||||
{"hkeysCommand",(unsigned long)hkeysCommand},
|
||||
{"hlenCommand",(unsigned long)hlenCommand},
|
||||
{"hsetCommand",(unsigned long)hsetCommand},
|
||||
{"htNeedsResize",(unsigned long)htNeedsResize},
|
||||
{"hvalsCommand",(unsigned long)hvalsCommand},
|
||||
{"incrCommand",(unsigned long)incrCommand},
|
||||
{"incrDecrCommand",(unsigned long)incrDecrCommand},
|
||||
{"incrRefCount",(unsigned long)incrRefCount},
|
||||
@@ -99,7 +122,9 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"lookupKey",(unsigned long)lookupKey},
|
||||
{"lookupKeyByPattern",(unsigned long)lookupKeyByPattern},
|
||||
{"lookupKeyRead",(unsigned long)lookupKeyRead},
|
||||
{"lookupKeyReadOrReply",(unsigned long)lookupKeyReadOrReply},
|
||||
{"lookupKeyWrite",(unsigned long)lookupKeyWrite},
|
||||
{"lookupKeyWriteOrReply",(unsigned long)lookupKeyWriteOrReply},
|
||||
{"lpopCommand",(unsigned long)lpopCommand},
|
||||
{"lpushCommand",(unsigned long)lpushCommand},
|
||||
{"lrangeCommand",(unsigned long)lrangeCommand},
|
||||
@@ -120,6 +145,7 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"processInputBuffer",(unsigned long)processInputBuffer},
|
||||
{"pushGenericCommand",(unsigned long)pushGenericCommand},
|
||||
{"qsortCompareSetsByCardinality",(unsigned long)qsortCompareSetsByCardinality},
|
||||
{"qsortCompareZsetopsrcByCardinality",(unsigned long)qsortCompareZsetopsrcByCardinality},
|
||||
{"queueIOJob",(unsigned long)queueIOJob},
|
||||
{"queueMultiCommand",(unsigned long)queueMultiCommand},
|
||||
{"randomkeyCommand",(unsigned long)randomkeyCommand},
|
||||
@@ -139,8 +165,8 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"rdbSaveLen",(unsigned long)rdbSaveLen},
|
||||
{"rdbSaveLzfStringObject",(unsigned long)rdbSaveLzfStringObject},
|
||||
{"rdbSaveObject",(unsigned long)rdbSaveObject},
|
||||
{"rdbSaveRawString",(unsigned long)rdbSaveRawString},
|
||||
{"rdbSaveStringObject",(unsigned long)rdbSaveStringObject},
|
||||
{"rdbSaveStringObjectRaw",(unsigned long)rdbSaveStringObjectRaw},
|
||||
{"rdbSaveTime",(unsigned long)rdbSaveTime},
|
||||
{"rdbSaveType",(unsigned long)rdbSaveType},
|
||||
{"rdbSavedObjectLen",(unsigned long)rdbSavedObjectLen},
|
||||
@@ -192,6 +218,7 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"srandmemberCommand",(unsigned long)srandmemberCommand},
|
||||
{"sremCommand",(unsigned long)sremCommand},
|
||||
{"stringObjectLen",(unsigned long)stringObjectLen},
|
||||
{"substrCommand",(unsigned long)substrCommand},
|
||||
{"sunionCommand",(unsigned long)sunionCommand},
|
||||
{"sunionDiffGenericCommand",(unsigned long)sunionDiffGenericCommand},
|
||||
{"sunionstoreCommand",(unsigned long)sunionstoreCommand},
|
||||
@@ -231,17 +258,24 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"vmThreadedIOCompletedJob",(unsigned long)vmThreadedIOCompletedJob},
|
||||
{"vmWriteObjectOnSwap",(unsigned long)vmWriteObjectOnSwap},
|
||||
{"waitEmptyIOJobsQueue",(unsigned long)waitEmptyIOJobsQueue},
|
||||
{"waitForSwappedKey",(unsigned long)waitForSwappedKey},
|
||||
{"yesnotoi",(unsigned long)yesnotoi},
|
||||
{"zaddCommand",(unsigned long)zaddCommand},
|
||||
{"zaddGenericCommand",(unsigned long)zaddGenericCommand},
|
||||
{"zcardCommand",(unsigned long)zcardCommand},
|
||||
{"zcountCommand",(unsigned long)zcountCommand},
|
||||
{"zincrbyCommand",(unsigned long)zincrbyCommand},
|
||||
{"zinterCommand",(unsigned long)zinterCommand},
|
||||
{"zrangeCommand",(unsigned long)zrangeCommand},
|
||||
{"zrangeGenericCommand",(unsigned long)zrangeGenericCommand},
|
||||
{"zrangebyscoreCommand",(unsigned long)zrangebyscoreCommand},
|
||||
{"zrankCommand",(unsigned long)zrankCommand},
|
||||
{"zrankGenericCommand",(unsigned long)zrankGenericCommand},
|
||||
{"zremCommand",(unsigned long)zremCommand},
|
||||
{"zremrangebyrankCommand",(unsigned long)zremrangebyrankCommand},
|
||||
{"zremrangebyscoreCommand",(unsigned long)zremrangebyscoreCommand},
|
||||
{"zrevrangeCommand",(unsigned long)zrevrangeCommand},
|
||||
{"zrevrankCommand",(unsigned long)zrevrankCommand},
|
||||
{"zscoreCommand",(unsigned long)zscoreCommand},
|
||||
{"zslCreate",(unsigned long)zslCreate},
|
||||
{"zslCreateNode",(unsigned long)zslCreateNode},
|
||||
@@ -251,5 +285,8 @@ static struct redisFunctionSym symsTable[] = {
|
||||
{"zslFreeNode",(unsigned long)zslFreeNode},
|
||||
{"zslInsert",(unsigned long)zslInsert},
|
||||
{"zslRandomLevel",(unsigned long)zslRandomLevel},
|
||||
{"zunionCommand",(unsigned long)zunionCommand},
|
||||
{"zunionInterBlockClientOnSwappedKeys",(unsigned long)zunionInterBlockClientOnSwappedKeys},
|
||||
{"zunionInterGenericCommand",(unsigned long)zunionInterGenericCommand},
|
||||
{NULL,0}
|
||||
};
|
||||
|
||||
+370
-12
@@ -125,6 +125,7 @@ proc randomKey {} {
|
||||
proc createComplexDataset {r ops} {
|
||||
for {set j 0} {$j < $ops} {incr j} {
|
||||
set k [randomKey]
|
||||
set f [randomValue]
|
||||
set v [randomValue]
|
||||
randpath {
|
||||
set d [expr {rand()}]
|
||||
@@ -150,6 +151,8 @@ proc createComplexDataset {r ops} {
|
||||
$r sadd $k $v
|
||||
} {
|
||||
$r zadd $k $d $v
|
||||
} {
|
||||
$r hset $k $f $v
|
||||
}
|
||||
set t [$r type $k]
|
||||
}
|
||||
@@ -173,12 +176,16 @@ proc createComplexDataset {r ops} {
|
||||
randpath {$r zadd $k $d $v} \
|
||||
{$r zrem $k $v}
|
||||
}
|
||||
{hash} {
|
||||
randpath {$r hset $k $f $v} \
|
||||
{$r hdel $k $f}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc datasetDigest r {
|
||||
set keys [lsort [split [$r keys *] " "]]
|
||||
set keys [lsort [$r keys *]]
|
||||
set digest {}
|
||||
foreach k $keys {
|
||||
set t [$r type $k]
|
||||
@@ -203,8 +210,14 @@ proc datasetDigest r {
|
||||
} else {
|
||||
set aux [::sha1::sha1 -hex [$r zrange $k 0 -1]]
|
||||
}
|
||||
} {hash} {
|
||||
if {[$r hlen $k] == 0} {
|
||||
set aux {}
|
||||
} else {
|
||||
set aux [::sha1::sha1 -hex [lsort [$r hgetall $k]]]
|
||||
}
|
||||
} default {
|
||||
error "Type not supported"
|
||||
error "Type not supported: $t"
|
||||
}
|
||||
}
|
||||
if {$aux eq {}} continue
|
||||
@@ -406,15 +419,20 @@ proc main {server port} {
|
||||
} {1}
|
||||
|
||||
test {Basic LPUSH, RPUSH, LLENGTH, LINDEX} {
|
||||
$r lpush mylist a
|
||||
$r lpush mylist b
|
||||
$r rpush mylist c
|
||||
set res [$r llen mylist]
|
||||
set res [$r lpush mylist a]
|
||||
append res [$r lpush mylist b]
|
||||
append res [$r rpush mylist c]
|
||||
append res [$r llen mylist]
|
||||
append res [$r rpush anotherlist d]
|
||||
append res [$r lpush anotherlist e]
|
||||
append res [$r llen anotherlist]
|
||||
append res [$r lindex mylist 0]
|
||||
append res [$r lindex mylist 1]
|
||||
append res [$r lindex mylist 2]
|
||||
append res [$r lindex anotherlist 0]
|
||||
append res [$r lindex anotherlist 1]
|
||||
list $res [$r lindex mylist 100]
|
||||
} {3bac {}}
|
||||
} {1233122baced {}}
|
||||
|
||||
test {DEL a list} {
|
||||
$r del mylist
|
||||
@@ -1192,6 +1210,22 @@ proc main {server port} {
|
||||
$r zcard ztmp-blabla
|
||||
} {0}
|
||||
|
||||
test {ZRANK basics} {
|
||||
$r zadd zranktmp 10 x
|
||||
$r zadd zranktmp 20 y
|
||||
$r zadd zranktmp 30 z
|
||||
list [$r zrank zranktmp x] [$r zrank zranktmp y] [$r zrank zranktmp z]
|
||||
} {0 1 2}
|
||||
|
||||
test {ZREVRANK basics} {
|
||||
list [$r zrevrank zranktmp x] [$r zrevrank zranktmp y] [$r zrevrank zranktmp z]
|
||||
} {2 1 0}
|
||||
|
||||
test {ZRANK - after deletion} {
|
||||
$r zrem zranktmp y
|
||||
list [$r zrank zranktmp x] [$r zrank zranktmp z]
|
||||
} {0 1}
|
||||
|
||||
test {ZSCORE} {
|
||||
set aux {}
|
||||
set err {}
|
||||
@@ -1300,15 +1334,26 @@ proc main {server port} {
|
||||
list $v1 $v2 [$r zscore zset foo] [$r zscore zset bar]
|
||||
} {{bar foo} {foo bar} -2 6}
|
||||
|
||||
test {ZRANGEBYSCORE basics} {
|
||||
test {ZRANGEBYSCORE and ZCOUNT 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
|
||||
$r zrangebyscore zset 2 4
|
||||
} {b c d}
|
||||
list [$r zrangebyscore zset 2 4] [$r zrangebyscore zset (2 (4] \
|
||||
[$r zcount zset 2 4] [$r zcount zset (2 (4]
|
||||
} {{b c d} c 3 1}
|
||||
|
||||
test {ZRANGEBYSCORE withscores} {
|
||||
$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
|
||||
$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 {}
|
||||
@@ -1327,24 +1372,65 @@ proc main {server port} {
|
||||
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"
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
@@ -1363,7 +1449,17 @@ proc main {server port} {
|
||||
[$r zrangebyscore zset 0 10 LIMIT 20 10]
|
||||
} {{a b} {c d e} {c d e} {}}
|
||||
|
||||
test {ZREMRANGE basics} {
|
||||
test {ZRANGEBYSCORE with LIMIT and withscores} {
|
||||
$r del zset
|
||||
$r zadd zset 10 a
|
||||
$r zadd zset 20 b
|
||||
$r zadd zset 30 c
|
||||
$r zadd zset 40 d
|
||||
$r zadd zset 50 e
|
||||
$r zrangebyscore zset 20 50 LIMIT 2 3 withscores
|
||||
} {d 40 e 50}
|
||||
|
||||
test {ZREMRANGEBYSCORE basics} {
|
||||
$r del zset
|
||||
$r zadd zset 1 a
|
||||
$r zadd zset 2 b
|
||||
@@ -1373,7 +1469,7 @@ proc main {server port} {
|
||||
list [$r zremrangebyscore zset 2 4] [$r zrange zset 0 -1]
|
||||
} {3 {a e}}
|
||||
|
||||
test {ZREMRANGE from -inf to +inf} {
|
||||
test {ZREMRANGEBYSCORE from -inf to +inf} {
|
||||
$r del zset
|
||||
$r zadd zset 1 a
|
||||
$r zadd zset 2 b
|
||||
@@ -1383,6 +1479,55 @@ proc main {server port} {
|
||||
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 {ZUNION basics} {
|
||||
$r del zseta zsetb zsetc
|
||||
$r zadd zseta 1 a
|
||||
$r zadd zseta 2 b
|
||||
$r zadd zseta 3 c
|
||||
$r zadd zsetb 1 b
|
||||
$r zadd zsetb 2 c
|
||||
$r zadd zsetb 3 d
|
||||
list [$r zunion zsetc 2 zseta zsetb] [$r zrange zsetc 0 -1 withscores]
|
||||
} {4 {a 1 b 3 d 3 c 5}}
|
||||
|
||||
test {ZUNION with weights} {
|
||||
list [$r zunion zsetc 2 zseta zsetb weights 2 3] [$r zrange zsetc 0 -1 withscores]
|
||||
} {4 {a 2 b 7 d 9 c 12}}
|
||||
|
||||
test {ZUNION with AGGREGATE MIN} {
|
||||
list [$r zunion zsetc 2 zseta zsetb aggregate min] [$r zrange zsetc 0 -1 withscores]
|
||||
} {4 {a 1 b 1 c 2 d 3}}
|
||||
|
||||
test {ZUNION with AGGREGATE MAX} {
|
||||
list [$r zunion zsetc 2 zseta zsetb aggregate max] [$r zrange zsetc 0 -1 withscores]
|
||||
} {4 {a 1 b 2 c 3 d 3}}
|
||||
|
||||
test {ZINTER basics} {
|
||||
list [$r zinter zsetc 2 zseta zsetb] [$r zrange zsetc 0 -1 withscores]
|
||||
} {2 {b 3 c 5}}
|
||||
|
||||
test {ZINTER with weights} {
|
||||
list [$r zinter zsetc 2 zseta zsetb weights 2 3] [$r zrange zsetc 0 -1 withscores]
|
||||
} {2 {b 7 c 12}}
|
||||
|
||||
test {ZINTER with AGGREGATE MIN} {
|
||||
list [$r zinter zsetc 2 zseta zsetb aggregate min] [$r zrange zsetc 0 -1 withscores]
|
||||
} {2 {b 1 c 2}}
|
||||
|
||||
test {ZINTER with AGGREGATE MAX} {
|
||||
list [$r zinter zsetc 2 zseta zsetb aggregate max] [$r zrange zsetc 0 -1 withscores]
|
||||
} {2 {b 2 c 3}}
|
||||
|
||||
test {SORT against sorted sets} {
|
||||
$r del zset
|
||||
$r zadd zset 1 a
|
||||
@@ -1404,6 +1549,158 @@ proc main {server port} {
|
||||
$r zrange zset 0 -1
|
||||
} {min c a b d max}
|
||||
|
||||
test {HSET/HLEN - Small hash creation} {
|
||||
array set smallhash {}
|
||||
for {set i 0} {$i < 8} {incr i} {
|
||||
set key [randstring 0 8 alpha]
|
||||
set val [randstring 0 8 alpha]
|
||||
if {[info exists smallhash($key)]} {
|
||||
incr i -1
|
||||
continue
|
||||
}
|
||||
$r hset smallhash $key $val
|
||||
set smallhash($key) $val
|
||||
}
|
||||
list [$r hlen smallhash]
|
||||
} {8}
|
||||
|
||||
test {Is the small hash encoded with a zipmap?} {
|
||||
$r debug object smallhash
|
||||
} {*zipmap*}
|
||||
|
||||
test {HSET/HLEN - Big hash creation} {
|
||||
array set bighash {}
|
||||
for {set i 0} {$i < 1024} {incr i} {
|
||||
set key [randstring 0 8 alpha]
|
||||
set val [randstring 0 8 alpha]
|
||||
if {[info exists bighash($key)]} {
|
||||
incr i -1
|
||||
continue
|
||||
}
|
||||
$r hset bighash $key $val
|
||||
set bighash($key) $val
|
||||
}
|
||||
list [$r hlen bighash]
|
||||
} {1024}
|
||||
|
||||
test {Is the big hash encoded with a zipmap?} {
|
||||
$r debug object bighash
|
||||
} {*hashtable*}
|
||||
|
||||
test {HGET against the small hash} {
|
||||
set err {}
|
||||
foreach k [array names smallhash *] {
|
||||
if {$smallhash($k) ne [$r hget smallhash $k]} {
|
||||
set err "$smallhash($k) != [$r hget smallhash $k]"
|
||||
break
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
test {HGET against the big hash} {
|
||||
set err {}
|
||||
foreach k [array names bighash *] {
|
||||
if {$bighash($k) ne [$r hget bighash $k]} {
|
||||
set err "$bighash($k) != [$r hget bighash $k]"
|
||||
break
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
test {HSET in update and insert mode} {
|
||||
set rv {}
|
||||
set k [lindex [array names smallhash *] 0]
|
||||
lappend rv [$r hset smallhash $k newval1]
|
||||
set smallhash($k) newval1
|
||||
lappend rv [$r hget smallhash $k]
|
||||
lappend rv [$r hset smallhash __foobar123__ newval]
|
||||
set k [lindex [array names bighash *] 0]
|
||||
lappend rv [$r hset bighash $k newval2]
|
||||
set bighash($k) newval2
|
||||
lappend rv [$r hget bighash $k]
|
||||
lappend rv [$r hset bighash __foobar123__ newval]
|
||||
lappend rv [$r hdel smallhash __foobar123__]
|
||||
lappend rv [$r hdel bighash __foobar123__]
|
||||
set _ $rv
|
||||
} {0 newval1 1 0 newval2 1 1 1}
|
||||
|
||||
test {HGET against non existing key} {
|
||||
set rv {}
|
||||
lappend rv [$r hget smallhash __123123123__]
|
||||
lappend rv [$r hget bighash __123123123__]
|
||||
set _ $rv
|
||||
} {{} {}}
|
||||
|
||||
test {HKEYS - small hash} {
|
||||
lsort [$r hkeys smallhash]
|
||||
} [lsort [array names smallhash *]]
|
||||
|
||||
test {HKEYS - big hash} {
|
||||
lsort [$r hkeys bighash]
|
||||
} [lsort [array names bighash *]]
|
||||
|
||||
test {HVALS - small hash} {
|
||||
set vals {}
|
||||
foreach {k v} [array get smallhash] {
|
||||
lappend vals $v
|
||||
}
|
||||
set _ [lsort $vals]
|
||||
} [lsort [$r hvals smallhash]]
|
||||
|
||||
test {HVALS - big hash} {
|
||||
set vals {}
|
||||
foreach {k v} [array get bighash] {
|
||||
lappend vals $v
|
||||
}
|
||||
set _ [lsort $vals]
|
||||
} [lsort [$r hvals bighash]]
|
||||
|
||||
test {HGETALL - small hash} {
|
||||
lsort [$r hgetall smallhash]
|
||||
} [lsort [array get smallhash]]
|
||||
|
||||
test {HGETALL - big hash} {
|
||||
lsort [$r hgetall bighash]
|
||||
} [lsort [array get bighash]]
|
||||
|
||||
test {HDEL and return value} {
|
||||
set rv {}
|
||||
lappend rv [$r hdel smallhash nokey]
|
||||
lappend rv [$r hdel bighash nokey]
|
||||
set k [lindex [array names smallhash *] 0]
|
||||
lappend rv [$r hdel smallhash $k]
|
||||
lappend rv [$r hdel smallhash $k]
|
||||
lappend rv [$r hget smallhash $k]
|
||||
unset smallhash($k)
|
||||
set k [lindex [array names bighash *] 0]
|
||||
lappend rv [$r hdel bighash $k]
|
||||
lappend rv [$r hdel bighash $k]
|
||||
lappend rv [$r hget bighash $k]
|
||||
unset bighash($k)
|
||||
set _ $rv
|
||||
} {0 0 1 0 {} 1 0 {}}
|
||||
|
||||
test {HEXISTS} {
|
||||
set rv {}
|
||||
set k [lindex [array names smallhash *] 0]
|
||||
lappend rv [$r hexists smallhash $k]
|
||||
lappend rv [$r hexists smallhash nokey]
|
||||
set k [lindex [array names bighash *] 0]
|
||||
lappend rv [$r hexists bighash $k]
|
||||
lappend rv [$r hexists bighash nokey]
|
||||
} {1 0 1 0}
|
||||
|
||||
test {Is a zipmap encoded Hash promoted on big payload?} {
|
||||
$r hset smallhash foo [string repeat a 1024]
|
||||
$r debug object smallhash
|
||||
} {*hashtable*}
|
||||
|
||||
# TODO:
|
||||
# Randomized test, small and big
|
||||
# .rdb / AOF consistency test should include hashes
|
||||
|
||||
test {EXPIRE - don't set timeouts multiple times} {
|
||||
$r set x foobar
|
||||
set v1 [$r expire x 5]
|
||||
@@ -1454,6 +1751,31 @@ proc main {server port} {
|
||||
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
|
||||
} {}
|
||||
|
||||
foreach fuzztype {binary alpha compr} {
|
||||
test "FUZZ stresser with data model $fuzztype" {
|
||||
set err 0
|
||||
@@ -1471,6 +1793,7 @@ proc main {server port} {
|
||||
}
|
||||
|
||||
test {BGSAVE} {
|
||||
waitForBgsave $r
|
||||
$r flushdb
|
||||
$r save
|
||||
$r set x 10
|
||||
@@ -1600,6 +1923,41 @@ proc main {server port} {
|
||||
list $v1 $v2 $v3
|
||||
} {QUEUED QUEUED {{a b c} PONG}}
|
||||
|
||||
test {DISCARD} {
|
||||
$r del mylist
|
||||
$r rpush mylist a
|
||||
$r rpush mylist b
|
||||
$r rpush mylist c
|
||||
$r multi
|
||||
set v1 [$r del mylist]
|
||||
set v2 [$r discard]
|
||||
set v3 [$r lrange mylist 0 -1]
|
||||
list $v1 $v2 $v3
|
||||
} {QUEUED OK {a b c}}
|
||||
|
||||
test {APPEND basics} {
|
||||
list [$r append foo bar] [$r get foo] \
|
||||
[$r append foo 100] [$r get foo]
|
||||
} {3 bar 6 bar100}
|
||||
|
||||
test {APPEND fuzzing} {
|
||||
set err {}
|
||||
foreach type {binary alpha compr} {
|
||||
set buf {}
|
||||
$r del x
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
set bin [randstring 0 10 $type]
|
||||
append buf $bin
|
||||
$r append x $bin
|
||||
}
|
||||
if {$buf != [$r get x]} {
|
||||
set err "Expected '$buf' found '[$r get x]'"
|
||||
break
|
||||
}
|
||||
}
|
||||
set _ $err
|
||||
} {}
|
||||
|
||||
# Leave the user with a clean DB before to exit
|
||||
test {FLUSHDB} {
|
||||
set aux {}
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
/* String -> String Map data structure optimized for size.
|
||||
* This file implements a data structure mapping strings to other strings
|
||||
* implementing an O(n) lookup data structure designed to be very memory
|
||||
* efficient.
|
||||
*
|
||||
* The Redis Hash type uses this data structure for hashes composed of a small
|
||||
* number of elements, to switch to an hash table once a given number of
|
||||
* elements is reached.
|
||||
*
|
||||
* Given that many times Redis Hashes are used to represent objects composed
|
||||
* of few fields, this is a very big win in terms of used memory.
|
||||
*
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* Memory layout of a zipmap, for the map "foo" => "bar", "hello" => "world":
|
||||
*
|
||||
* <status><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"
|
||||
*
|
||||
* <status> is 1 byte status. Currently only 1 bit is used: if the least
|
||||
* significant bit is set, it means the zipmap needs to be defragmented.
|
||||
*
|
||||
* <len> is the length of the following string (key or value).
|
||||
* <len> lengths are encoded in a single value or in a 5 bytes value.
|
||||
* If the first byte value (as an unsigned 8 bit value) is between 0 and
|
||||
* 252, it's a single-byte length. If it is 253 then a four bytes unsigned
|
||||
* integer follows (in the host byte ordering). A value fo 255 is used to
|
||||
* signal the end of the hash. The special value 254 is used to mark
|
||||
* empty space that can be used to add new key/value pairs.
|
||||
*
|
||||
* <free> is the number of free unused bytes
|
||||
* after the string, resulting from modification of values associated to a
|
||||
* key (for instance if "foo" is set to "bar', and later "foo" will be se to
|
||||
* "hi", I'll have a free byte to use if the value will enlarge again later,
|
||||
* or even in order to add a key/value pair if it fits.
|
||||
*
|
||||
* <free> is always an unsigned 8 bit number, because if after an
|
||||
* update operation there are more than a few free bytes, they'll be converted
|
||||
* into empty space prefixed by the special value 254.
|
||||
*
|
||||
* The most compact representation of the above two elements hash is actually:
|
||||
*
|
||||
* "\x00\x03foo\x03\x00bar\x05hello\x05\x00world\xff"
|
||||
*
|
||||
* Empty space is marked using a 254 bytes + a <len> (coded as already
|
||||
* specified). The length includes the 254 bytes in the count and the
|
||||
* space taken by the <len> field. So for instance removing the "foo" key
|
||||
* from the zipmap above will lead to the following representation:
|
||||
*
|
||||
* "\x00\xfd\x10........\x05hello\x05\x00world\xff"
|
||||
*
|
||||
* Note that because empty space, keys, values, are all prefixed length
|
||||
* "objects", the lookup will take O(N) where N is the numeber of elements
|
||||
* in the zipmap and *not* the number of bytes needed to represent the zipmap.
|
||||
* This lowers the constant times considerably.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "zmalloc.h"
|
||||
|
||||
#define ZIPMAP_BIGLEN 253
|
||||
#define ZIPMAP_EMPTY 254
|
||||
#define ZIPMAP_END 255
|
||||
|
||||
#define ZIPMAP_STATUS_FRAGMENTED 1
|
||||
|
||||
/* The following defines the max value for the <free> field described in the
|
||||
* comments above, that is, the max number of trailing bytes in a value. */
|
||||
#define ZIPMAP_VALUE_MAX_FREE 5
|
||||
|
||||
/* The following macro returns the number of bytes needed to encode the length
|
||||
* for the integer value _l, that is, 1 byte for lengths < ZIPMAP_BIGLEN and
|
||||
* 5 bytes for all the other lengths. */
|
||||
#define ZIPMAP_LEN_BYTES(_l) (((_l) < ZIPMAP_BIGLEN) ? 1 : sizeof(unsigned int)+1)
|
||||
|
||||
/* Create a new empty zipmap. */
|
||||
unsigned char *zipmapNew(void) {
|
||||
unsigned char *zm = zmalloc(2);
|
||||
|
||||
zm[0] = 0; /* Status */
|
||||
zm[1] = ZIPMAP_END;
|
||||
return zm;
|
||||
}
|
||||
|
||||
/* Decode the encoded length pointed by 'p' */
|
||||
static unsigned int zipmapDecodeLength(unsigned char *p) {
|
||||
unsigned int len = *p;
|
||||
|
||||
if (len < ZIPMAP_BIGLEN) return len;
|
||||
memcpy(&len,p+1,sizeof(unsigned int));
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Encode the length 'l' writing it in 'p'. If p is NULL it just returns
|
||||
* the amount of bytes required to encode such a length. */
|
||||
static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) {
|
||||
if (p == NULL) {
|
||||
return ZIPMAP_LEN_BYTES(len);
|
||||
} else {
|
||||
if (len < ZIPMAP_BIGLEN) {
|
||||
p[0] = len;
|
||||
return 1;
|
||||
} else {
|
||||
p[0] = ZIPMAP_BIGLEN;
|
||||
memcpy(p+1,&len,sizeof(len));
|
||||
return 1+sizeof(len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Search for a matching key, returning a pointer to the entry inside the
|
||||
* zipmap. Returns NULL if the key is not found.
|
||||
*
|
||||
* If NULL is returned, and totlen is not NULL, it is set to the entire
|
||||
* size of the zimap, so that the calling function will be able to
|
||||
* reallocate the original zipmap to make room for more entries.
|
||||
*
|
||||
* If NULL is returned, and freeoff and freelen are not NULL, they are set
|
||||
* to the offset of the first empty space that can hold '*freelen' bytes
|
||||
* (freelen is an integer pointer used both to signal the required length
|
||||
* and to get the reply from the function). If there is not a suitable
|
||||
* free space block to hold the requested bytes, *freelen is set to 0. */
|
||||
static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned int *totlen, unsigned int *freeoff, unsigned int *freelen) {
|
||||
unsigned char *p = zm+1;
|
||||
unsigned int l;
|
||||
unsigned int reqfreelen = 0; /* initialized just to prevent warning */
|
||||
|
||||
if (freelen) {
|
||||
reqfreelen = *freelen;
|
||||
*freelen = 0;
|
||||
assert(reqfreelen != 0);
|
||||
}
|
||||
while(*p != ZIPMAP_END) {
|
||||
if (*p == ZIPMAP_EMPTY) {
|
||||
l = zipmapDecodeLength(p+1);
|
||||
/* if the user want a free space report, and this space is
|
||||
* enough, and we did't already found a suitable space... */
|
||||
if (freelen && l >= reqfreelen && *freelen == 0) {
|
||||
*freelen = l;
|
||||
*freeoff = p-zm;
|
||||
}
|
||||
p += l;
|
||||
zm[0] |= ZIPMAP_STATUS_FRAGMENTED;
|
||||
} else {
|
||||
unsigned char free;
|
||||
|
||||
/* Match or skip the key */
|
||||
l = zipmapDecodeLength(p);
|
||||
if (l == klen && !memcmp(p+1,key,l)) return p;
|
||||
p += zipmapEncodeLength(NULL,l) + l;
|
||||
/* Skip the value as well */
|
||||
l = zipmapDecodeLength(p);
|
||||
p += zipmapEncodeLength(NULL,l);
|
||||
free = p[0];
|
||||
p += l+1+free; /* +1 to skip the free byte */
|
||||
}
|
||||
}
|
||||
if (totlen != NULL) *totlen = (unsigned int)(p-zm)+1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static unsigned long zipmapRequiredLength(unsigned int klen, unsigned int vlen) {
|
||||
unsigned int l;
|
||||
|
||||
l = klen+vlen+3;
|
||||
if (klen >= ZIPMAP_BIGLEN) l += 4;
|
||||
if (vlen >= ZIPMAP_BIGLEN) l += 4;
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Return the total amount used by a key (encoded length + payload) */
|
||||
static unsigned int zipmapRawKeyLength(unsigned char *p) {
|
||||
unsigned int l = zipmapDecodeLength(p);
|
||||
|
||||
return zipmapEncodeLength(NULL,l) + l;
|
||||
}
|
||||
|
||||
/* Return the total amount used by a value
|
||||
* (encoded length + single byte free count + payload) */
|
||||
static unsigned int zipmapRawValueLength(unsigned char *p) {
|
||||
unsigned int l = zipmapDecodeLength(p);
|
||||
unsigned int used;
|
||||
|
||||
used = zipmapEncodeLength(NULL,l);
|
||||
used += p[used] + 1 + l;
|
||||
return used;
|
||||
}
|
||||
|
||||
/* If 'p' points to a key, this function returns the total amount of
|
||||
* bytes used to store this entry (entry = key + associated value + trailing
|
||||
* free space if any). */
|
||||
static unsigned int zipmapRawEntryLength(unsigned char *p) {
|
||||
unsigned int l = zipmapRawKeyLength(p);
|
||||
|
||||
return l + zipmapRawValueLength(p+l);
|
||||
}
|
||||
|
||||
/* Set key to value, creating the key if it does not already exist.
|
||||
* If 'update' is not NULL, *update is set to 1 if the key was
|
||||
* already preset, otherwise to 0. */
|
||||
unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update) {
|
||||
unsigned int oldlen = 0, freeoff = 0, freelen;
|
||||
unsigned int reqlen = zipmapRequiredLength(klen,vlen);
|
||||
unsigned int empty, vempty;
|
||||
unsigned char *p;
|
||||
|
||||
freelen = reqlen;
|
||||
if (update) *update = 0;
|
||||
p = zipmapLookupRaw(zm,key,klen,&oldlen,&freeoff,&freelen);
|
||||
if (p == NULL && freelen == 0) {
|
||||
/* Key not found, and not space for the new key. Enlarge */
|
||||
zm = zrealloc(zm,oldlen+reqlen);
|
||||
p = zm+oldlen-1;
|
||||
zm[oldlen+reqlen-1] = ZIPMAP_END;
|
||||
freelen = reqlen;
|
||||
} else if (p == NULL) {
|
||||
/* Key not found, but there is enough free space. */
|
||||
p = zm+freeoff;
|
||||
/* note: freelen is already set in this case */
|
||||
} else {
|
||||
unsigned char *b = p;
|
||||
|
||||
/* Key found. Is there enough space for the new value? */
|
||||
/* Compute the total length: */
|
||||
if (update) *update = 1;
|
||||
freelen = zipmapRawKeyLength(b);
|
||||
b += freelen;
|
||||
freelen += zipmapRawValueLength(b);
|
||||
if (freelen < reqlen) {
|
||||
/* Mark this entry as free and recurse */
|
||||
p[0] = ZIPMAP_EMPTY;
|
||||
zipmapEncodeLength(p+1,freelen);
|
||||
zm[0] |= ZIPMAP_STATUS_FRAGMENTED;
|
||||
return zipmapSet(zm,key,klen,val,vlen,NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Ok we have a suitable block where to write the new key/value
|
||||
* entry. */
|
||||
empty = freelen-reqlen;
|
||||
/* If there is too much free space mark it as a free block instead
|
||||
* of adding it as trailing empty space for the value, as we want
|
||||
* zipmaps to be very space efficient. */
|
||||
if (empty > ZIPMAP_VALUE_MAX_FREE) {
|
||||
unsigned char *e;
|
||||
|
||||
e = p+reqlen;
|
||||
e[0] = ZIPMAP_EMPTY;
|
||||
zipmapEncodeLength(e+1,empty);
|
||||
vempty = 0;
|
||||
zm[0] |= ZIPMAP_STATUS_FRAGMENTED;
|
||||
} else {
|
||||
vempty = empty;
|
||||
}
|
||||
|
||||
/* Just write the key + value and we are done. */
|
||||
/* Key: */
|
||||
p += zipmapEncodeLength(p,klen);
|
||||
memcpy(p,key,klen);
|
||||
p += klen;
|
||||
/* Value: */
|
||||
p += zipmapEncodeLength(p,vlen);
|
||||
*p++ = vempty;
|
||||
memcpy(p,val,vlen);
|
||||
return zm;
|
||||
}
|
||||
|
||||
/* Remove the specified key. If 'deleted' is not NULL the pointed integer is
|
||||
* set to 0 if the key was not found, to 1 if it was found and deleted. */
|
||||
unsigned char *zipmapDel(unsigned char *zm, unsigned char *key, unsigned int klen, int *deleted) {
|
||||
unsigned char *p = zipmapLookupRaw(zm,key,klen,NULL,NULL,NULL);
|
||||
if (p) {
|
||||
unsigned int freelen = zipmapRawEntryLength(p);
|
||||
|
||||
p[0] = ZIPMAP_EMPTY;
|
||||
zipmapEncodeLength(p+1,freelen);
|
||||
zm[0] |= ZIPMAP_STATUS_FRAGMENTED;
|
||||
if (deleted) *deleted = 1;
|
||||
} else {
|
||||
if (deleted) *deleted = 0;
|
||||
}
|
||||
return zm;
|
||||
}
|
||||
|
||||
/* Call it before to iterate trought elements via zipmapNext() */
|
||||
unsigned char *zipmapRewind(unsigned char *zm) {
|
||||
return zm+1;
|
||||
}
|
||||
|
||||
/* This function is used to iterate through all the zipmap elements.
|
||||
* In the first call the first argument is the pointer to the zipmap + 1.
|
||||
* In the next calls what zipmapNext returns is used as first argument.
|
||||
* Example:
|
||||
*
|
||||
* unsigned char *i = zipmapRewind(my_zipmap);
|
||||
* while((i = zipmapNext(i,&key,&klen,&value,&vlen)) != NULL) {
|
||||
* printf("%d bytes key at $p\n", klen, key);
|
||||
* printf("%d bytes value at $p\n", vlen, value);
|
||||
* }
|
||||
*/
|
||||
unsigned char *zipmapNext(unsigned char *zm, unsigned char **key, unsigned int *klen, unsigned char **value, unsigned int *vlen) {
|
||||
while(zm[0] == ZIPMAP_EMPTY)
|
||||
zm += zipmapDecodeLength(zm+1);
|
||||
if (zm[0] == ZIPMAP_END) return NULL;
|
||||
if (key) {
|
||||
*key = zm;
|
||||
*klen = zipmapDecodeLength(zm);
|
||||
*key += ZIPMAP_LEN_BYTES(*klen);
|
||||
}
|
||||
zm += zipmapRawKeyLength(zm);
|
||||
if (value) {
|
||||
*value = zm+1;
|
||||
*vlen = zipmapDecodeLength(zm);
|
||||
*value += ZIPMAP_LEN_BYTES(*vlen);
|
||||
}
|
||||
zm += zipmapRawValueLength(zm);
|
||||
return zm;
|
||||
}
|
||||
|
||||
/* Search a key and retrieve the pointer and len of the associated value.
|
||||
* If the key is found the function returns 1, otherwise 0. */
|
||||
int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen) {
|
||||
unsigned char *p;
|
||||
|
||||
if ((p = zipmapLookupRaw(zm,key,klen,NULL,NULL,NULL)) == NULL) return 0;
|
||||
p += zipmapRawKeyLength(p);
|
||||
*vlen = zipmapDecodeLength(p);
|
||||
*value = p + ZIPMAP_LEN_BYTES(*vlen) + 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Return 1 if the key exists, otherwise 0 is returned. */
|
||||
int zipmapExists(unsigned char *zm, unsigned char *key, unsigned int klen) {
|
||||
return zipmapLookupRaw(zm,key,klen,NULL,NULL,NULL) != NULL;
|
||||
}
|
||||
|
||||
/* Return the number of entries inside a zipmap */
|
||||
unsigned int zipmapLen(unsigned char *zm) {
|
||||
unsigned char *p = zipmapRewind(zm);
|
||||
unsigned int len = 0;
|
||||
|
||||
while((p = zipmapNext(p,NULL,NULL,NULL,NULL)) != NULL) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
void zipmapRepr(unsigned char *p) {
|
||||
unsigned int l;
|
||||
|
||||
printf("{status %u}",*p++);
|
||||
while(1) {
|
||||
if (p[0] == ZIPMAP_END) {
|
||||
printf("{end}");
|
||||
break;
|
||||
} else if (p[0] == ZIPMAP_EMPTY) {
|
||||
l = zipmapDecodeLength(p+1);
|
||||
printf("{%u empty block}", l);
|
||||
p += l;
|
||||
} else {
|
||||
unsigned char e;
|
||||
|
||||
l = zipmapDecodeLength(p);
|
||||
printf("{key %u}",l);
|
||||
p += zipmapEncodeLength(NULL,l);
|
||||
fwrite(p,l,1,stdout);
|
||||
p += l;
|
||||
|
||||
l = zipmapDecodeLength(p);
|
||||
printf("{value %u}",l);
|
||||
p += zipmapEncodeLength(NULL,l);
|
||||
e = *p++;
|
||||
fwrite(p,l,1,stdout);
|
||||
p += l+e;
|
||||
if (e) {
|
||||
printf("[");
|
||||
while(e--) printf(".");
|
||||
printf("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#ifdef ZIPMAP_TEST_MAIN
|
||||
int main(void) {
|
||||
unsigned char *zm;
|
||||
|
||||
zm = zipmapNew();
|
||||
|
||||
zm = zipmapSet(zm,(unsigned char*) "name",4, (unsigned char*) "foo",3,NULL);
|
||||
zm = zipmapSet(zm,(unsigned char*) "surname",7, (unsigned char*) "foo",3,NULL);
|
||||
zm = zipmapSet(zm,(unsigned char*) "age",3, (unsigned char*) "foo",3,NULL);
|
||||
zipmapRepr(zm);
|
||||
exit(1);
|
||||
|
||||
zm = zipmapSet(zm,(unsigned char*) "hello",5, (unsigned char*) "world!",6,NULL);
|
||||
zm = zipmapSet(zm,(unsigned char*) "foo",3, (unsigned char*) "bar",3,NULL);
|
||||
zm = zipmapSet(zm,(unsigned char*) "foo",3, (unsigned char*) "!",1,NULL);
|
||||
zipmapRepr(zm);
|
||||
zm = zipmapSet(zm,(unsigned char*) "foo",3, (unsigned char*) "12345",5,NULL);
|
||||
zipmapRepr(zm);
|
||||
zm = zipmapSet(zm,(unsigned char*) "new",3, (unsigned char*) "xx",2,NULL);
|
||||
zm = zipmapSet(zm,(unsigned char*) "noval",5, (unsigned char*) "",0,NULL);
|
||||
zipmapRepr(zm);
|
||||
zm = zipmapDel(zm,(unsigned char*) "new",3,NULL);
|
||||
zipmapRepr(zm);
|
||||
printf("\nPerform a direct lookup:\n");
|
||||
{
|
||||
unsigned char *value;
|
||||
unsigned int vlen;
|
||||
|
||||
if (zipmapGet(zm,(unsigned char*) "foo",3,&value,&vlen)) {
|
||||
printf(" foo is associated to the %d bytes value: %.*s\n",
|
||||
vlen, vlen, value);
|
||||
}
|
||||
}
|
||||
printf("\nIterate trought elements:\n");
|
||||
{
|
||||
unsigned char *i = zipmapRewind(zm);
|
||||
unsigned char *key, *value;
|
||||
unsigned int klen, vlen;
|
||||
|
||||
while((i = zipmapNext(i,&key,&klen,&value,&vlen)) != NULL) {
|
||||
printf(" %d:%.*s => %d:%.*s\n", klen, klen, key, vlen, vlen, value);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
/* String -> String Map data structure optimized for size.
|
||||
*
|
||||
* See zipmap.c for more info.
|
||||
*
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _ZIMMAP_H
|
||||
#define _ZIPMAP_H
|
||||
|
||||
unsigned char *zipmapNew(void);
|
||||
unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update);
|
||||
unsigned char *zipmapDel(unsigned char *zm, unsigned char *key, unsigned int klen, int *deleted);
|
||||
unsigned char *zipmapRewind(unsigned char *zm);
|
||||
unsigned char *zipmapNext(unsigned char *zm, unsigned char **key, unsigned int *klen, unsigned char **value, unsigned int *vlen);
|
||||
int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen);
|
||||
int zipmapExists(unsigned char *zm, unsigned char *key, unsigned int klen);
|
||||
unsigned int zipmapLen(unsigned char *zm);
|
||||
void zipmapRepr(unsigned char *p);
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
/* zmalloc - total amount of allocated memory aware version of malloc()
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* zmalloc - total amount of allocated memory aware version of malloc()
|
||||
*
|
||||
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
Reference in New Issue
Block a user