Compare commits

..
107 Commits
Author SHA1 Message Date
antirez 7b7f987e91 Redis 1.3.6 2010-03-18 16:55:49 +01:00
antirez 12f72a714c test-redis.tcl dataset digest function Hash support 2010-03-18 03:27:08 +01:00
antirez ad6de43c80 zipmap fix for large values 2010-03-18 03:23:44 +01:00
antirez db5946fc27 Optimization fixed and re-activated 2010-03-18 03:06:08 +01:00
antirez bf0da6174c reverted an optimization that makes Redis not stable 2010-03-18 02:59:28 +01:00
antirez 288799e04a Fixed redis-cli auth code 2010-03-18 02:51:09 +01:00
antirez 2a1198b4c4 HDEL fix, an optimization for comparison of objects in hash table lookups when they are integer encoding 2010-03-17 19:26:53 +01:00
antirez a4c507866c Version is now 1.3.5 2010-03-17 18:30:15 +01:00
antirez 4583c4f0ea Merged Pietern patch for VM key args helper function. Fixed an obvious bug in the redis-cli passwd auth stuff 2010-03-17 17:14:07 +01:00
antirez 17d68f9c99 Merge branch 'aggregates' of git://github.com/pietern/redis 2010-03-17 16:59:29 +01:00
root fdfdae0f3a Added Authentication to redis-cli.c using -a switch
Update usage
fixed Makefile to delete redis-check-dump during make clean
2010-03-17 09:41:02 -04:00
antirez a86f14b190 HEXISTS and tests implemented 2010-03-17 13:34:27 +01:00
antirez 11d9d1e3b1 More hash tests 2010-03-17 12:36:27 +01:00
antirez 516977de15 better HSET test 2010-03-17 02:06:56 +01:00
antirez 2069d06a0b Fixed a bug in HSET, a memory leak, and a theoretical bug in dict.c 2010-03-17 02:00:03 +01:00
antirez b1d9c91ca8 More Hash tests 2010-03-17 01:40:05 +01:00
Pieter Noordhuis 76583ea455 added preloading keys from VM when using ZINTER or ZUNION 2010-03-16 20:43:49 +01:00
Pieter Noordhuis d2764cd692 added explicit AGGREGATE [SUM|MIN|MAX] option to ZUNION/ZINTER 2010-03-16 20:34:45 +01:00
antirez 164ee59564 HGET fix for integer encoded field against zipmap encoded hash 2010-03-16 18:44:37 +01:00
antirez 23d8214891 zrevrank support in redis-cli 2010-03-16 16:20:35 +01:00
antirez 78409a0f84 HKEYS / HVALS / HGETALL 2010-03-16 16:09:58 +01:00
antirez 6d4fb107b2 Solved a memory leak with Hashes 2010-03-16 01:11:23 +01:00
antirez dd88747b78 pretty big refactoring 2010-03-15 23:59:40 +01:00
antirez 92b27fe946 An interesting refactoring + more expressive internal API 2010-03-15 22:46:20 +01:00
antirez 653c9240b7 Fixed the same problem in ZREVRANK 2010-03-15 19:33:26 +01:00
antirez cc3b01c30a Fixed a ZRANK bug 2010-03-15 19:33:01 +01:00
antirez bae2c7ec6b zipmap to hash conversion in HSET 2010-03-15 18:28:12 +01:00
antirez 55a8298fcf max zipmap entries and max zipmap value parameters added into INFO output 2010-03-14 13:58:28 +01:00
antirez 07efaf7467 HDEL and some improvement in DEBUG OBJECT command 2010-03-14 13:54:31 +01:00
antirez 9c8e3cee26 Append only file support for hashes 2010-03-14 13:40:41 +01:00
Pieter Noordhuis 08af4d5c96 utility to check rdb files for unprocessable opcodes 2010-03-13 15:57:00 +01:00
antirez 5de9ad7c85 A minor fix and a few debug messages removed 2010-03-12 23:46:27 +01:00
antirez 8c5abee892 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-12 11:38:53 +01:00
antirez bcd1190644 Fix for HGET against non Hash type, debug messages used to understand a bit better a corrupted rdb file 2010-03-11 16:10:36 +01:00
Pieter Noordhuis b9eed483ae fix: use zmalloc instead of malloc 2010-03-09 17:32:24 +01:00
antirez c1d57a1063 Merged zsetops branch from Pietern 2010-03-09 16:25:55 +01:00
antirez ee5cfe3853 Merged ZREMBYRANK from Pietern 2010-03-09 16:22:50 +01:00
antirez e3f46030fc Merged ZREVRANK from Pietern 2010-03-09 16:19:33 +01:00
Pieter Noordhuis 8f92e76831 use a struct to store both a dict and its weight for ZUNION and ZINTER, so qsort can be applied 2010-03-09 16:12:34 +01:00
antirez ada386b218 Hash auto conversion from zipmap to hash table, type fixed for hashes, hash loading from disk 2010-03-09 13:18:49 +01:00
Pieter Noordhuis 2830ca539b replaced ZMERGE by ZUNION and ZINTER. note: key preloading by the VM does not yet work 2010-03-09 12:38:50 +01:00
antirez b1befe6a8e Hashes saving / fixes 2010-03-08 21:33:07 +01:00
Pieter Noordhuis b287c9bb2b use ZMERGE as starting point 2010-03-08 21:27:26 +01:00
antirez cbba7dd7c9 HSET fixes, now the new pointer is stored back in the object pointer field 2010-03-07 23:41:48 +01:00
Pieter Noordhuis 798d9e5531 added ZREVRANK 2010-03-07 20:35:53 +01:00
antirez 18e61fa2ff Fix for replicaiton with over 2GB dump file initial SYNC stage 2010-03-06 12:08:22 +01:00
antirez 978c2c944c first implementation of HSET/HSET. More work needed 2010-03-06 01:56:16 +01:00
antirez 5234952bee zipmaps functions to get, iterate, test for existence. Initial works for Hash data type 2010-03-05 14:04:17 +01:00
antirez b1ad58ed2f redis-benchmark now implements Set commands benchmarks 2010-03-04 23:05:12 +01:00
antirez 66ef8da009 zipmap iteration code 2010-03-04 19:45:15 +01:00
Pieter Noordhuis 841053366f moved code to delete a single node from a zset to a separate function 2010-03-04 17:55:16 +01:00
Pieter Noordhuis f84d393374 rename zslDeleteRange to zslDeleteRangeByScore (to differentiate between deleting using score or rank) 2010-03-04 17:50:25 +01:00
Pieter Noordhuis 2424490f9b use 1-based rank across zsl*Rank functions consistently 2010-03-04 17:48:31 +01:00
Pieter Noordhuis 9212eafd5d implemented ZREMBYRANK 2010-03-04 17:42:33 +01:00
antirez 94e543b5c2 A fix for initialization of augmented skip lists 2010-03-04 17:00:06 +01:00
antirez ddfaca9d81 A fix for an invalid access when VM is disabled 2010-03-04 16:48:26 +01:00
antirez 947efa8d6e Merge branch 'zsl-get-rank' of git://github.com/pietern/redis 2010-03-04 16:43:50 +01:00
antirez d239ec5960 redis-cli now runs in interactive mode if no command is provided 2010-03-04 16:36:30 +01:00
Pieter Noordhuis a50ea45c84 merged memory reduction patch 2010-03-04 16:35:57 +01:00
antirez 520b5a33ae Now list push commands return the length of the new list, thanks to Gustavo Picon 2010-03-04 16:16:01 +01:00
Pieter Noordhuis edb519581a first check if starting point is trivial (head or tail) before applying log(N) search 2010-03-04 14:23:59 +01:00
Pieter Noordhuis e74825c24f use rank to find starting point for ZRANGE and ZREVRANGE 2010-03-04 14:16:07 +01:00
Pieter Noordhuis 27b0ccca71 lookup rank of a zset entry in a different function 2010-03-04 13:35:46 +01:00
antirez 8fe7fad771 SUBSTR fix for integer encoded vals 2010-03-04 13:10:50 +01:00
Pieter Noordhuis 67cac14343 fix ZRANK (realize that rank is 1-based due to the skip list header) 2010-03-04 01:33:37 +01:00
antirez 3919155375 initial implementation of SUBSTR 2010-03-03 23:15:52 +01:00
antirez 9693e4f0f0 TODO updated 2010-03-03 20:13:42 +01:00
antirez 386b43c6b3 fpurge call removed from redis-cli 2010-03-03 20:08:16 +01:00
antirez 3589e1a7e5 ZRANK stress tester 2010-03-03 19:44:03 +01:00
Pieter Noordhuis 2b37892e9b use less memory as element->span[0] will always be 1; any level 0 skip list is essentially a linked list 2010-03-03 19:04:38 +01:00
Pieter Noordhuis 912b9165a9 rank is very unlikely to overflow integer range 2010-03-03 18:40:42 +01:00
Pieter Noordhuis 709d0a1bbc x->backward never equals zsl->header 2010-03-03 18:35:56 +01:00
Pieter Noordhuis 69d95c3e1c initial implementation for augmented zsets and the zrank command 2010-03-03 18:21:02 +01:00
antirez cd5a96eeda zipampDel() implemented 2010-03-03 16:59:44 +01:00
antirez 2f4d2242ea added quit and exit commands to redis-cli in order to quit the interactive mode 2010-03-03 14:41:47 +01:00
antirez 52d23ddbea Merge remote branch 'djanowski/interactive' 2010-03-03 14:39:03 +01:00
Damian Janowski & Michel Martens 6fa246220b Add support for MULTI/EXEC. 2010-03-02 16:24:21 -03:00
Damian Janowski & Michel Martens e3c7f0027f Remove trailing newline in interactive mode. 2010-03-02 15:47:31 -03:00
antirez 54bac49d92 minor fix for a Linux warning 2010-03-02 17:42:22 +01:00
Michel Martens & Damian Janowski 6cf5882c56 Add interactive mode to redis-cli. 2010-03-02 12:14:14 -03:00
antirez ed7451548a 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 00:57:09 +01:00
antirez ba76a8f901 Fixed a subtle VM bug... was not flushing the buffer so the child process read truncated data 2010-03-02 00:52:50 +01:00
antirez a3f9eec291 KEYS now returns a multi bulk reply 2010-03-01 23:46:24 +01:00
Damian Janowski 18b6cb7643 Add DISCARD command to discard queued MULTI commands. 2010-03-01 23:29:48 +01:00
antirez c8c72447f6 Swappability bug due to a typo fixed thanks to code review by Felix Geisendörfer @felixge 2010-03-01 22:15:49 +01:00
antirez be0af2f0ec minor fixes for zipmap.c 2010-02-28 18:05:25 +01:00
antirez 8ec0832157 first zipmap fix of a long sequence in the days to come ;) 2010-02-27 18:13:55 +01:00
antirez eb46f4bd7b initial zipmap.c implementation 2010-02-27 18:07:46 +01:00
antirez 5921aa36ed Bug #169 fixed (BLOP/BRPOP interrupted connections are not cleared from the queue) 2010-02-27 12:26:08 +01:00
antirez 9ebed7cf06 Fixed 32bit make target to work on Linux out of the box 2010-02-22 17:36:54 +01:00
antirez 12d090d2d0 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-19 11:23:57 +01:00
antirez ce8330208a Saner VM defaults for redis.conf 2010-02-10 15:59:09 +01:00
antirez 7c775e0943 VM now is able to block clients on swapped keys for all the commands 2010-02-09 14:01:49 +01:00
antirez 223a059192 ZCOUNT and ZRANGEBYSCORE new tests 2010-02-07 23:54:22 +01:00
antirez f44dd42872 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 21:52:35 +01:00
antirez 3a3978b10b WITHSCORES in ZRANGEBYSCORE thanks to Sam Hendley 2010-02-07 10:32:28 +01:00
Sam Hendley 0500ef2736 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 18:41:25 -05:00
antirez 59146ef3c1 DEBUG OBJECT provide info about serialized object length even when VM is disabled 2010-02-06 18:53:39 +01:00
antirez 6766f45ef2 multi bulk requests in redis-benchmark, default fsync policy changed to everysec, added a prefix character for DEBUG logs 2010-02-06 13:39:07 +01:00
antirez 3c290b9b52 APPEND tests 2010-02-04 16:38:33 +01:00
antirez 4b00bebd3d APPEND command 2010-02-04 15:55:52 +01:00
antirez ed9e496634 Faster version of the function hashing possibly encoded objects, leading to a general speed gain when working with Sets of integers 2010-02-02 12:19:24 +01:00
antirez 3c68de9b01 faster Set loading time from .rdb file resizing the hash table to the right size before loading elements 2010-02-02 12:05:15 +01:00
antirez 9651a78787 Log time taken to load the DB at startup, in seconds 2010-02-02 12:01:53 +01:00
antirez 478c2c6f5b 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-31 16:03:44 +01:00
antirez d5d55fc319 loading side of the threaded VM 2010-01-28 10:12:04 -05:00
antirez a544018d04 TODO cahnges 2010-01-26 05:02:27 -05:00
31 changed files with 3855 additions and 888 deletions
+1
View File
@@ -5,6 +5,7 @@
redis-cli
redis-server
redis-benchmark
redis-check-dump
doc-tools
mkrelease.sh
release
+106
View File
@@ -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 -5
View File
@@ -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"
+18 -9
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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
+10 -2
View File
@@ -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;
}
+4 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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
+9 -3
View File
@@ -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;
}
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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();
+671
View File
@@ -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
View File
@@ -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);
+1919 -789
View File
File diff suppressed because it is too large Load Diff
+31 -16
View File
@@ -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 -1
View File
@@ -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
+2 -2
View File
@@ -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) {}
}
+2 -2
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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 {}
+457
View File
@@ -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
+48
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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