redis-server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +38 -0
- data/README.md +33 -0
- data/bin/redis +114 -0
- data/redis/Makefile +5 -0
- data/redis/extconf.rb +3 -0
- data/redis/redis-2.2.11/00-RELEASENOTES +199 -0
- data/redis/redis-2.2.11/BUGS +1 -0
- data/redis/redis-2.2.11/CONTRIBUTING +13 -0
- data/redis/redis-2.2.11/COPYING +10 -0
- data/redis/redis-2.2.11/Changelog +1032 -0
- data/redis/redis-2.2.11/INSTALL +30 -0
- data/redis/redis-2.2.11/Makefile +22 -0
- data/redis/redis-2.2.11/README +83 -0
- data/redis/redis-2.2.11/TODO +4 -0
- data/redis/redis-2.2.11/client-libraries/README +11 -0
- data/redis/redis-2.2.11/deps/hiredis/COPYING +10 -0
- data/redis/redis-2.2.11/deps/hiredis/Makefile +115 -0
- data/redis/redis-2.2.11/deps/hiredis/README.md +311 -0
- data/redis/redis-2.2.11/deps/hiredis/TODO +2 -0
- data/redis/redis-2.2.11/deps/hiredis/adapters/ae.h +95 -0
- data/redis/redis-2.2.11/deps/hiredis/adapters/libev.h +113 -0
- data/redis/redis-2.2.11/deps/hiredis/adapters/libevent.h +76 -0
- data/redis/redis-2.2.11/deps/hiredis/async.c +321 -0
- data/redis/redis-2.2.11/deps/hiredis/async.h +112 -0
- data/redis/redis-2.2.11/deps/hiredis/example-ae.c +53 -0
- data/redis/redis-2.2.11/deps/hiredis/example-libev.c +47 -0
- data/redis/redis-2.2.11/deps/hiredis/example-libevent.c +48 -0
- data/redis/redis-2.2.11/deps/hiredis/example.c +67 -0
- data/redis/redis-2.2.11/deps/hiredis/fmacros.h +15 -0
- data/redis/redis-2.2.11/deps/hiredis/hiredis.c +1058 -0
- data/redis/redis-2.2.11/deps/hiredis/hiredis.h +170 -0
- data/redis/redis-2.2.11/deps/hiredis/net.c +170 -0
- data/redis/redis-2.2.11/deps/hiredis/net.h +43 -0
- data/redis/redis-2.2.11/deps/hiredis/sds.c +479 -0
- data/redis/redis-2.2.11/deps/hiredis/sds.h +77 -0
- data/redis/redis-2.2.11/deps/hiredis/test.c +479 -0
- data/redis/redis-2.2.11/deps/hiredis/util.h +40 -0
- data/redis/redis-2.2.11/deps/linenoise/Makefile +10 -0
- data/redis/redis-2.2.11/deps/linenoise/README.markdown +45 -0
- data/redis/redis-2.2.11/deps/linenoise/example.c +27 -0
- data/redis/redis-2.2.11/deps/linenoise/linenoise.c +609 -0
- data/redis/redis-2.2.11/deps/linenoise/linenoise.h +55 -0
- data/redis/redis-2.2.11/design-documents/REDIS-CLUSTER +214 -0
- data/redis/redis-2.2.11/design-documents/REDIS-CLUSTER-2 +343 -0
- data/redis/redis-2.2.11/doc/AppendCommand.html +48 -0
- data/redis/redis-2.2.11/doc/AppendOnlyFileHowto.html +41 -0
- data/redis/redis-2.2.11/doc/AuthCommand.html +39 -0
- data/redis/redis-2.2.11/doc/Benchmarks.html +129 -0
- data/redis/redis-2.2.11/doc/BgrewriteaofCommand.html +41 -0
- data/redis/redis-2.2.11/doc/BgsaveCommand.html +39 -0
- data/redis/redis-2.2.11/doc/BlpopCommand.html +51 -0
- data/redis/redis-2.2.11/doc/BrpoplpushCommand.html +39 -0
- data/redis/redis-2.2.11/doc/CommandReference.html +47 -0
- data/redis/redis-2.2.11/doc/Comparisons.html +42 -0
- data/redis/redis-2.2.11/doc/ConfigCommand.html +76 -0
- data/redis/redis-2.2.11/doc/Configuration.html +38 -0
- data/redis/redis-2.2.11/doc/ConnectionHandlingSidebar.html +36 -0
- data/redis/redis-2.2.11/doc/ControlCommandsSidebar.html +36 -0
- data/redis/redis-2.2.11/doc/Credits.html +38 -0
- data/redis/redis-2.2.11/doc/DbsizeCommand.html +38 -0
- data/redis/redis-2.2.11/doc/DelCommand.html +41 -0
- data/redis/redis-2.2.11/doc/DesignPatterns.html +37 -0
- data/redis/redis-2.2.11/doc/EventLibray.html +44 -0
- data/redis/redis-2.2.11/doc/ExistsCommand.html +42 -0
- data/redis/redis-2.2.11/doc/ExpireCommand.html +96 -0
- data/redis/redis-2.2.11/doc/FAQ.html +70 -0
- data/redis/redis-2.2.11/doc/Features.html +38 -0
- data/redis/redis-2.2.11/doc/FlushallCommand.html +39 -0
- data/redis/redis-2.2.11/doc/FlushdbCommand.html +39 -0
- data/redis/redis-2.2.11/doc/FromSqlToDataStructures.html +37 -0
- data/redis/redis-2.2.11/doc/GenericCommandsSidebar.html +36 -0
- data/redis/redis-2.2.11/doc/GetCommand.html +39 -0
- data/redis/redis-2.2.11/doc/GetbitCommand.html +39 -0
- data/redis/redis-2.2.11/doc/GetsetCommand.html +38 -0
- data/redis/redis-2.2.11/doc/HackingStrings.html +83 -0
- data/redis/redis-2.2.11/doc/HashCommandsSidebar.html +36 -0
- data/redis/redis-2.2.11/doc/Hashes.html +37 -0
- data/redis/redis-2.2.11/doc/HdelCommand.html +39 -0
- data/redis/redis-2.2.11/doc/HexistsCommand.html +39 -0
- data/redis/redis-2.2.11/doc/HgetCommand.html +39 -0
- data/redis/redis-2.2.11/doc/HgetallCommand.html +40 -0
- data/redis/redis-2.2.11/doc/HincrbyCommand.html +45 -0
- data/redis/redis-2.2.11/doc/HlenCommand.html +38 -0
- data/redis/redis-2.2.11/doc/HmgetCommand.html +40 -0
- data/redis/redis-2.2.11/doc/HmsetCommand.html +40 -0
- data/redis/redis-2.2.11/doc/HsetCommand.html +40 -0
- data/redis/redis-2.2.11/doc/HsetnxCommand.html +41 -0
- data/redis/redis-2.2.11/doc/IncrCommand.html +43 -0
- data/redis/redis-2.2.11/doc/InfoCommand.html +48 -0
- data/redis/redis-2.2.11/doc/IntroductionToRedisDataTypes.html +152 -0
- data/redis/redis-2.2.11/doc/KeysCommand.html +43 -0
- data/redis/redis-2.2.11/doc/LastsaveCommand.html +39 -0
- data/redis/redis-2.2.11/doc/LindexCommand.html +40 -0
- data/redis/redis-2.2.11/doc/ListCommandsSidebar.html +36 -0
- data/redis/redis-2.2.11/doc/Lists.html +42 -0
- data/redis/redis-2.2.11/doc/LlenCommand.html +41 -0
- data/redis/redis-2.2.11/doc/LpopCommand.html +41 -0
- data/redis/redis-2.2.11/doc/LrangeCommand.html +47 -0
- data/redis/redis-2.2.11/doc/LremCommand.html +41 -0
- data/redis/redis-2.2.11/doc/LsetCommand.html +38 -0
- data/redis/redis-2.2.11/doc/LtrimCommand.html +47 -0
- data/redis/redis-2.2.11/doc/MgetCommand.html +52 -0
- data/redis/redis-2.2.11/doc/MonitorCommand.html +63 -0
- data/redis/redis-2.2.11/doc/MoveCommand.html +42 -0
- data/redis/redis-2.2.11/doc/MsetCommand.html +44 -0
- data/redis/redis-2.2.11/doc/MultiExecCommand.html +166 -0
- data/redis/redis-2.2.11/doc/NonexistentCommands.html +51 -0
- data/redis/redis-2.2.11/doc/ObjectHashMappers.html +39 -0
- data/redis/redis-2.2.11/doc/Pipelining.html +36 -0
- data/redis/redis-2.2.11/doc/ProgrammingExamples.html +38 -0
- data/redis/redis-2.2.11/doc/ProtocolSpecification.html +137 -0
- data/redis/redis-2.2.11/doc/PublishSubscribe.html +115 -0
- data/redis/redis-2.2.11/doc/QuickStart.html +68 -0
- data/redis/redis-2.2.11/doc/QuitCommand.html +38 -0
- data/redis/redis-2.2.11/doc/README.html +119 -0
- data/redis/redis-2.2.11/doc/RandomkeyCommand.html +39 -0
- data/redis/redis-2.2.11/doc/Redis0100ChangeLog.html +67 -0
- data/redis/redis-2.2.11/doc/Redis0900ChangeLog.html +56 -0
- data/redis/redis-2.2.11/doc/RedisBigData.html +61 -0
- data/redis/redis-2.2.11/doc/RedisCLI.html +37 -0
- data/redis/redis-2.2.11/doc/RedisEventLibrary.html +70 -0
- data/redis/redis-2.2.11/doc/RedisGuides.html +37 -0
- data/redis/redis-2.2.11/doc/RedisInternals.html +38 -0
- data/redis/redis-2.2.11/doc/RedisPipelining.html +93 -0
- data/redis/redis-2.2.11/doc/RedisStatus.html +56 -0
- data/redis/redis-2.2.11/doc/Redis_1_2_0_Changelog.html +40 -0
- data/redis/redis-2.2.11/doc/Redis_2_0_0_Changelog.html +62 -0
- data/redis/redis-2.2.11/doc/Redis_2_0_Whats_new.html +59 -0
- data/redis/redis-2.2.11/doc/RenameCommand.html +39 -0
- data/redis/redis-2.2.11/doc/RenamenxCommand.html +42 -0
- data/redis/redis-2.2.11/doc/ReplicationHowto.html +43 -0
- data/redis/redis-2.2.11/doc/ReplyTypes.html +42 -0
- data/redis/redis-2.2.11/doc/RoadMap.html +38 -0
- data/redis/redis-2.2.11/doc/RpoplpushCommand.html +44 -0
- data/redis/redis-2.2.11/doc/RpushCommand.html +40 -0
- data/redis/redis-2.2.11/doc/SaddCommand.html +41 -0
- data/redis/redis-2.2.11/doc/SaveCommand.html +39 -0
- data/redis/redis-2.2.11/doc/ScardCommand.html +41 -0
- data/redis/redis-2.2.11/doc/SdiffCommand.html +45 -0
- data/redis/redis-2.2.11/doc/SdiffstoreCommand.html +38 -0
- data/redis/redis-2.2.11/doc/SelectCommand.html +39 -0
- data/redis/redis-2.2.11/doc/SetCommand.html +39 -0
- data/redis/redis-2.2.11/doc/SetCommandsSidebar.html +36 -0
- data/redis/redis-2.2.11/doc/SetbitCommand.html +45 -0
- data/redis/redis-2.2.11/doc/SetexCommand.html +42 -0
- data/redis/redis-2.2.11/doc/SetnxCommand.html +51 -0
- data/redis/redis-2.2.11/doc/SetrangeCommand.html +58 -0
- data/redis/redis-2.2.11/doc/Sets.html +36 -0
- data/redis/redis-2.2.11/doc/ShutdownCommand.html +39 -0
- data/redis/redis-2.2.11/doc/SideBar.html +36 -0
- data/redis/redis-2.2.11/doc/SinterCommand.html +40 -0
- data/redis/redis-2.2.11/doc/SinterstoreCommand.html +39 -0
- data/redis/redis-2.2.11/doc/SismemberCommand.html +42 -0
- data/redis/redis-2.2.11/doc/SlaveofCommand.html +41 -0
- data/redis/redis-2.2.11/doc/SmembersCommand.html +38 -0
- data/redis/redis-2.2.11/doc/SmoveCommand.html +44 -0
- data/redis/redis-2.2.11/doc/SortCommand.html +75 -0
- data/redis/redis-2.2.11/doc/SortedSetCommandsSidebar.html +36 -0
- data/redis/redis-2.2.11/doc/SortedSets.html +36 -0
- data/redis/redis-2.2.11/doc/Speed.html +38 -0
- data/redis/redis-2.2.11/doc/SponsorshipHistory.html +38 -0
- data/redis/redis-2.2.11/doc/SpopCommand.html +40 -0
- data/redis/redis-2.2.11/doc/SrandmemberCommand.html +40 -0
- data/redis/redis-2.2.11/doc/SremCommand.html +42 -0
- data/redis/redis-2.2.11/doc/StringCommandsSidebar.html +36 -0
- data/redis/redis-2.2.11/doc/Strings.html +37 -0
- data/redis/redis-2.2.11/doc/StrlenCommand.html +39 -0
- data/redis/redis-2.2.11/doc/SubstrCommand.html +52 -0
- data/redis/redis-2.2.11/doc/SunionCommand.html +40 -0
- data/redis/redis-2.2.11/doc/SunionstoreCommand.html +38 -0
- data/redis/redis-2.2.11/doc/SupportedLanguages.html +60 -0
- data/redis/redis-2.2.11/doc/SupportedPlatforms.html +37 -0
- data/redis/redis-2.2.11/doc/TemplateCommand.html +38 -0
- data/redis/redis-2.2.11/doc/TtlCommand.html +38 -0
- data/redis/redis-2.2.11/doc/TwitterAlikeExample.html +250 -0
- data/redis/redis-2.2.11/doc/TypeCommand.html +46 -0
- data/redis/redis-2.2.11/doc/UnstableSource.html +39 -0
- data/redis/redis-2.2.11/doc/VirtualMemorySpecification.html +156 -0
- data/redis/redis-2.2.11/doc/VirtualMemoryUserGuide.html +66 -0
- data/redis/redis-2.2.11/doc/ZaddCommand.html +43 -0
- data/redis/redis-2.2.11/doc/ZcardCommand.html +41 -0
- data/redis/redis-2.2.11/doc/ZincrbyCommand.html +42 -0
- data/redis/redis-2.2.11/doc/ZrangeCommand.html +42 -0
- data/redis/redis-2.2.11/doc/ZrangebyscoreCommand.html +77 -0
- data/redis/redis-2.2.11/doc/ZrankCommand.html +43 -0
- data/redis/redis-2.2.11/doc/ZremCommand.html +42 -0
- data/redis/redis-2.2.11/doc/ZremrangebyrankCommand.html +39 -0
- data/redis/redis-2.2.11/doc/ZremrangebyscoreCommand.html +39 -0
- data/redis/redis-2.2.11/doc/ZscoreCommand.html +41 -0
- data/redis/redis-2.2.11/doc/ZunionCommand.html +42 -0
- data/redis/redis-2.2.11/doc/ZunionstoreCommand.html +43 -0
- data/redis/redis-2.2.11/doc/index.html +43 -0
- data/redis/redis-2.2.11/doc/redis.png +0 -0
- data/redis/redis-2.2.11/doc/style.css +25 -0
- data/redis/redis-2.2.11/redis.conf +417 -0
- data/redis/redis-2.2.11/src/Makefile +177 -0
- data/redis/redis-2.2.11/src/adlist.c +325 -0
- data/redis/redis-2.2.11/src/adlist.h +92 -0
- data/redis/redis-2.2.11/src/ae.c +390 -0
- data/redis/redis-2.2.11/src/ae.h +117 -0
- data/redis/redis-2.2.11/src/ae_epoll.c +91 -0
- data/redis/redis-2.2.11/src/ae_kqueue.c +93 -0
- data/redis/redis-2.2.11/src/ae_select.c +72 -0
- data/redis/redis-2.2.11/src/anet.c +347 -0
- data/redis/redis-2.2.11/src/anet.h +57 -0
- data/redis/redis-2.2.11/src/aof.c +675 -0
- data/redis/redis-2.2.11/src/config.c +627 -0
- data/redis/redis-2.2.11/src/config.h +64 -0
- data/redis/redis-2.2.11/src/db.c +543 -0
- data/redis/redis-2.2.11/src/debug.c +314 -0
- data/redis/redis-2.2.11/src/dict.c +721 -0
- data/redis/redis-2.2.11/src/dict.h +156 -0
- data/redis/redis-2.2.11/src/fmacros.h +15 -0
- data/redis/redis-2.2.11/src/help.h +638 -0
- data/redis/redis-2.2.11/src/intset.c +422 -0
- data/redis/redis-2.2.11/src/intset.h +19 -0
- data/redis/redis-2.2.11/src/lzf.h +100 -0
- data/redis/redis-2.2.11/src/lzfP.h +159 -0
- data/redis/redis-2.2.11/src/lzf_c.c +295 -0
- data/redis/redis-2.2.11/src/lzf_d.c +150 -0
- data/redis/redis-2.2.11/src/mkreleasehdr.sh +9 -0
- data/redis/redis-2.2.11/src/multi.c +268 -0
- data/redis/redis-2.2.11/src/networking.c +899 -0
- data/redis/redis-2.2.11/src/object.c +484 -0
- data/redis/redis-2.2.11/src/pqsort.c +197 -0
- data/redis/redis-2.2.11/src/pqsort.h +15 -0
- data/redis/redis-2.2.11/src/pubsub.c +267 -0
- data/redis/redis-2.2.11/src/rdb.c +1020 -0
- data/redis/redis-2.2.11/src/redis-benchmark.c +530 -0
- data/redis/redis-2.2.11/src/redis-check-aof.c +185 -0
- data/redis/redis-2.2.11/src/redis-check-dump.c +681 -0
- data/redis/redis-2.2.11/src/redis-cli.c +773 -0
- data/redis/redis-2.2.11/src/redis.c +1677 -0
- data/redis/redis-2.2.11/src/redis.h +1022 -0
- data/redis/redis-2.2.11/src/release.c +13 -0
- data/redis/redis-2.2.11/src/replication.c +557 -0
- data/redis/redis-2.2.11/src/sds.c +639 -0
- data/redis/redis-2.2.11/src/sds.h +78 -0
- data/redis/redis-2.2.11/src/sha1.c +276 -0
- data/redis/redis-2.2.11/src/sha1.h +17 -0
- data/redis/redis-2.2.11/src/solarisfixes.h +22 -0
- data/redis/redis-2.2.11/src/sort.c +389 -0
- data/redis/redis-2.2.11/src/syncio.c +154 -0
- data/redis/redis-2.2.11/src/t_hash.c +476 -0
- data/redis/redis-2.2.11/src/t_list.c +986 -0
- data/redis/redis-2.2.11/src/t_set.c +610 -0
- data/redis/redis-2.2.11/src/t_string.c +438 -0
- data/redis/redis-2.2.11/src/t_zset.c +1084 -0
- data/redis/redis-2.2.11/src/testhelp.h +54 -0
- data/redis/redis-2.2.11/src/util.c +243 -0
- data/redis/redis-2.2.11/src/valgrind.sup +5 -0
- data/redis/redis-2.2.11/src/version.h +1 -0
- data/redis/redis-2.2.11/src/vm.c +1149 -0
- data/redis/redis-2.2.11/src/ziplist.c +1323 -0
- data/redis/redis-2.2.11/src/ziplist.h +15 -0
- data/redis/redis-2.2.11/src/zipmap.c +455 -0
- data/redis/redis-2.2.11/src/zipmap.h +48 -0
- data/redis/redis-2.2.11/src/zmalloc.c +278 -0
- data/redis/redis-2.2.11/src/zmalloc.h +47 -0
- data/redis/redis-2.2.11/tests/assets/default.conf +308 -0
- data/redis/redis-2.2.11/tests/integration/aof.tcl +104 -0
- data/redis/redis-2.2.11/tests/integration/redis-cli.tcl +208 -0
- data/redis/redis-2.2.11/tests/integration/replication.tcl +98 -0
- data/redis/redis-2.2.11/tests/support/redis.tcl +241 -0
- data/redis/redis-2.2.11/tests/support/server.tcl +294 -0
- data/redis/redis-2.2.11/tests/support/test.tcl +190 -0
- data/redis/redis-2.2.11/tests/support/tmpfile.tcl +15 -0
- data/redis/redis-2.2.11/tests/support/util.tcl +296 -0
- data/redis/redis-2.2.11/tests/test_helper.tcl +221 -0
- data/redis/redis-2.2.11/tests/unit/auth.tcl +15 -0
- data/redis/redis-2.2.11/tests/unit/basic.tcl +616 -0
- data/redis/redis-2.2.11/tests/unit/cas.tcl +135 -0
- data/redis/redis-2.2.11/tests/unit/expire.tcl +74 -0
- data/redis/redis-2.2.11/tests/unit/other.tcl +240 -0
- data/redis/redis-2.2.11/tests/unit/printver.tcl +6 -0
- data/redis/redis-2.2.11/tests/unit/protocol.tcl +62 -0
- data/redis/redis-2.2.11/tests/unit/pubsub.tcl +195 -0
- data/redis/redis-2.2.11/tests/unit/quit.tcl +40 -0
- data/redis/redis-2.2.11/tests/unit/sort.tcl +189 -0
- data/redis/redis-2.2.11/tests/unit/type/hash.tcl +300 -0
- data/redis/redis-2.2.11/tests/unit/type/list.tcl +819 -0
- data/redis/redis-2.2.11/tests/unit/type/set.tcl +334 -0
- data/redis/redis-2.2.11/tests/unit/type/zset.tcl +587 -0
- data/redis/redis-2.2.11/utils/build-static-symbols.tcl +22 -0
- data/redis/redis-2.2.11/utils/generate-command-help.rb +112 -0
- data/redis/redis-2.2.11/utils/mktarball.sh +13 -0
- data/redis/redis-2.2.11/utils/redis-copy.rb +78 -0
- data/redis/redis-2.2.11/utils/redis-sha1.rb +52 -0
- data/redis/redis-2.2.11/utils/redis_init_script +42 -0
- metadata +362 -0
@@ -0,0 +1,438 @@
|
|
1
|
+
#include "redis.h"
|
2
|
+
|
3
|
+
/*-----------------------------------------------------------------------------
|
4
|
+
* String Commands
|
5
|
+
*----------------------------------------------------------------------------*/
|
6
|
+
|
7
|
+
static int checkStringLength(redisClient *c, long long size) {
|
8
|
+
if (size > 512*1024*1024) {
|
9
|
+
addReplyError(c,"string exceeds maximum allowed size (512MB)");
|
10
|
+
return REDIS_ERR;
|
11
|
+
}
|
12
|
+
return REDIS_OK;
|
13
|
+
}
|
14
|
+
|
15
|
+
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
|
16
|
+
int retval;
|
17
|
+
long seconds = 0; /* initialized to avoid an harmness warning */
|
18
|
+
|
19
|
+
if (expire) {
|
20
|
+
if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK)
|
21
|
+
return;
|
22
|
+
if (seconds <= 0) {
|
23
|
+
addReplyError(c,"invalid expire time in SETEX");
|
24
|
+
return;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
lookupKeyWrite(c->db,key); /* Force expire of old key if needed */
|
29
|
+
retval = dbAdd(c->db,key,val);
|
30
|
+
if (retval == REDIS_ERR) {
|
31
|
+
if (!nx) {
|
32
|
+
dbReplace(c->db,key,val);
|
33
|
+
incrRefCount(val);
|
34
|
+
} else {
|
35
|
+
addReply(c,shared.czero);
|
36
|
+
return;
|
37
|
+
}
|
38
|
+
} else {
|
39
|
+
incrRefCount(val);
|
40
|
+
}
|
41
|
+
touchWatchedKey(c->db,key);
|
42
|
+
server.dirty++;
|
43
|
+
removeExpire(c->db,key);
|
44
|
+
if (expire) setExpire(c->db,key,time(NULL)+seconds);
|
45
|
+
addReply(c, nx ? shared.cone : shared.ok);
|
46
|
+
}
|
47
|
+
|
48
|
+
void setCommand(redisClient *c) {
|
49
|
+
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
50
|
+
setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);
|
51
|
+
}
|
52
|
+
|
53
|
+
void setnxCommand(redisClient *c) {
|
54
|
+
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
55
|
+
setGenericCommand(c,1,c->argv[1],c->argv[2],NULL);
|
56
|
+
}
|
57
|
+
|
58
|
+
void setexCommand(redisClient *c) {
|
59
|
+
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
60
|
+
setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]);
|
61
|
+
}
|
62
|
+
|
63
|
+
int getGenericCommand(redisClient *c) {
|
64
|
+
robj *o;
|
65
|
+
|
66
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
|
67
|
+
return REDIS_OK;
|
68
|
+
|
69
|
+
if (o->type != REDIS_STRING) {
|
70
|
+
addReply(c,shared.wrongtypeerr);
|
71
|
+
return REDIS_ERR;
|
72
|
+
} else {
|
73
|
+
addReplyBulk(c,o);
|
74
|
+
return REDIS_OK;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
void getCommand(redisClient *c) {
|
79
|
+
getGenericCommand(c);
|
80
|
+
}
|
81
|
+
|
82
|
+
void getsetCommand(redisClient *c) {
|
83
|
+
if (getGenericCommand(c) == REDIS_ERR) return;
|
84
|
+
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
85
|
+
dbReplace(c->db,c->argv[1],c->argv[2]);
|
86
|
+
incrRefCount(c->argv[2]);
|
87
|
+
touchWatchedKey(c->db,c->argv[1]);
|
88
|
+
server.dirty++;
|
89
|
+
removeExpire(c->db,c->argv[1]);
|
90
|
+
}
|
91
|
+
|
92
|
+
static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
|
93
|
+
long long loffset;
|
94
|
+
char *err = "bit offset is not an integer or out of range";
|
95
|
+
|
96
|
+
if (getLongLongFromObjectOrReply(c,o,&loffset,err) != REDIS_OK)
|
97
|
+
return REDIS_ERR;
|
98
|
+
|
99
|
+
/* Limit offset to 512MB in bytes */
|
100
|
+
if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
|
101
|
+
{
|
102
|
+
addReplyError(c,err);
|
103
|
+
return REDIS_ERR;
|
104
|
+
}
|
105
|
+
|
106
|
+
*offset = (size_t)loffset;
|
107
|
+
return REDIS_OK;
|
108
|
+
}
|
109
|
+
|
110
|
+
void setbitCommand(redisClient *c) {
|
111
|
+
robj *o;
|
112
|
+
char *err = "bit is not an integer or out of range";
|
113
|
+
size_t bitoffset;
|
114
|
+
int byte, bit;
|
115
|
+
int byteval, bitval;
|
116
|
+
long on;
|
117
|
+
|
118
|
+
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
|
119
|
+
return;
|
120
|
+
|
121
|
+
if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != REDIS_OK)
|
122
|
+
return;
|
123
|
+
|
124
|
+
/* Bits can only be set or cleared... */
|
125
|
+
if (on & ~1) {
|
126
|
+
addReplyError(c,err);
|
127
|
+
return;
|
128
|
+
}
|
129
|
+
|
130
|
+
o = lookupKeyWrite(c->db,c->argv[1]);
|
131
|
+
if (o == NULL) {
|
132
|
+
o = createObject(REDIS_STRING,sdsempty());
|
133
|
+
dbAdd(c->db,c->argv[1],o);
|
134
|
+
} else {
|
135
|
+
if (checkType(c,o,REDIS_STRING)) return;
|
136
|
+
|
137
|
+
/* Create a copy when the object is shared or encoded. */
|
138
|
+
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
139
|
+
robj *decoded = getDecodedObject(o);
|
140
|
+
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
141
|
+
decrRefCount(decoded);
|
142
|
+
dbReplace(c->db,c->argv[1],o);
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
/* Grow sds value to the right length if necessary */
|
147
|
+
byte = bitoffset >> 3;
|
148
|
+
o->ptr = sdsgrowzero(o->ptr,byte+1);
|
149
|
+
|
150
|
+
/* Get current values */
|
151
|
+
byteval = ((char*)o->ptr)[byte];
|
152
|
+
bit = 7 - (bitoffset & 0x7);
|
153
|
+
bitval = byteval & (1 << bit);
|
154
|
+
|
155
|
+
/* Update byte with new bit value and return original value */
|
156
|
+
byteval &= ~(1 << bit);
|
157
|
+
byteval |= ((on & 0x1) << bit);
|
158
|
+
((char*)o->ptr)[byte] = byteval;
|
159
|
+
touchWatchedKey(c->db,c->argv[1]);
|
160
|
+
server.dirty++;
|
161
|
+
addReply(c, bitval ? shared.cone : shared.czero);
|
162
|
+
}
|
163
|
+
|
164
|
+
void getbitCommand(redisClient *c) {
|
165
|
+
robj *o;
|
166
|
+
char llbuf[32];
|
167
|
+
size_t bitoffset;
|
168
|
+
size_t byte, bit;
|
169
|
+
size_t bitval = 0;
|
170
|
+
|
171
|
+
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
|
172
|
+
return;
|
173
|
+
|
174
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
175
|
+
checkType(c,o,REDIS_STRING)) return;
|
176
|
+
|
177
|
+
byte = bitoffset >> 3;
|
178
|
+
bit = 7 - (bitoffset & 0x7);
|
179
|
+
if (o->encoding != REDIS_ENCODING_RAW) {
|
180
|
+
if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
|
181
|
+
bitval = llbuf[byte] & (1 << bit);
|
182
|
+
} else {
|
183
|
+
if (byte < sdslen(o->ptr))
|
184
|
+
bitval = ((char*)o->ptr)[byte] & (1 << bit);
|
185
|
+
}
|
186
|
+
|
187
|
+
addReply(c, bitval ? shared.cone : shared.czero);
|
188
|
+
}
|
189
|
+
|
190
|
+
void setrangeCommand(redisClient *c) {
|
191
|
+
robj *o;
|
192
|
+
long offset;
|
193
|
+
sds value = c->argv[3]->ptr;
|
194
|
+
|
195
|
+
if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
|
196
|
+
return;
|
197
|
+
|
198
|
+
if (offset < 0) {
|
199
|
+
addReplyError(c,"offset is out of range");
|
200
|
+
return;
|
201
|
+
}
|
202
|
+
|
203
|
+
o = lookupKeyWrite(c->db,c->argv[1]);
|
204
|
+
if (o == NULL) {
|
205
|
+
/* Return 0 when setting nothing on a non-existing string */
|
206
|
+
if (sdslen(value) == 0) {
|
207
|
+
addReply(c,shared.czero);
|
208
|
+
return;
|
209
|
+
}
|
210
|
+
|
211
|
+
/* Return when the resulting string exceeds allowed size */
|
212
|
+
if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
|
213
|
+
return;
|
214
|
+
|
215
|
+
o = createObject(REDIS_STRING,sdsempty());
|
216
|
+
dbAdd(c->db,c->argv[1],o);
|
217
|
+
} else {
|
218
|
+
size_t olen;
|
219
|
+
|
220
|
+
/* Key exists, check type */
|
221
|
+
if (checkType(c,o,REDIS_STRING))
|
222
|
+
return;
|
223
|
+
|
224
|
+
/* Return existing string length when setting nothing */
|
225
|
+
olen = stringObjectLen(o);
|
226
|
+
if (sdslen(value) == 0) {
|
227
|
+
addReplyLongLong(c,olen);
|
228
|
+
return;
|
229
|
+
}
|
230
|
+
|
231
|
+
/* Return when the resulting string exceeds allowed size */
|
232
|
+
if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
|
233
|
+
return;
|
234
|
+
|
235
|
+
/* Create a copy when the object is shared or encoded. */
|
236
|
+
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
237
|
+
robj *decoded = getDecodedObject(o);
|
238
|
+
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
239
|
+
decrRefCount(decoded);
|
240
|
+
dbReplace(c->db,c->argv[1],o);
|
241
|
+
}
|
242
|
+
}
|
243
|
+
|
244
|
+
if (sdslen(value) > 0) {
|
245
|
+
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
|
246
|
+
memcpy((char*)o->ptr+offset,value,sdslen(value));
|
247
|
+
touchWatchedKey(c->db,c->argv[1]);
|
248
|
+
server.dirty++;
|
249
|
+
}
|
250
|
+
addReplyLongLong(c,sdslen(o->ptr));
|
251
|
+
}
|
252
|
+
|
253
|
+
void getrangeCommand(redisClient *c) {
|
254
|
+
robj *o;
|
255
|
+
long start, end;
|
256
|
+
char *str, llbuf[32];
|
257
|
+
size_t strlen;
|
258
|
+
|
259
|
+
if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
|
260
|
+
return;
|
261
|
+
if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
|
262
|
+
return;
|
263
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
|
264
|
+
checkType(c,o,REDIS_STRING)) return;
|
265
|
+
|
266
|
+
if (o->encoding == REDIS_ENCODING_INT) {
|
267
|
+
str = llbuf;
|
268
|
+
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
269
|
+
} else {
|
270
|
+
str = o->ptr;
|
271
|
+
strlen = sdslen(str);
|
272
|
+
}
|
273
|
+
|
274
|
+
/* Convert negative indexes */
|
275
|
+
if (start < 0) start = strlen+start;
|
276
|
+
if (end < 0) end = strlen+end;
|
277
|
+
if (start < 0) start = 0;
|
278
|
+
if (end < 0) end = 0;
|
279
|
+
if ((unsigned)end >= strlen) end = strlen-1;
|
280
|
+
|
281
|
+
/* Precondition: end >= 0 && end < strlen, so the only condition where
|
282
|
+
* nothing can be returned is: start > end. */
|
283
|
+
if (start > end) {
|
284
|
+
addReply(c,shared.emptybulk);
|
285
|
+
} else {
|
286
|
+
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
void mgetCommand(redisClient *c) {
|
291
|
+
int j;
|
292
|
+
|
293
|
+
addReplyMultiBulkLen(c,c->argc-1);
|
294
|
+
for (j = 1; j < c->argc; j++) {
|
295
|
+
robj *o = lookupKeyRead(c->db,c->argv[j]);
|
296
|
+
if (o == NULL) {
|
297
|
+
addReply(c,shared.nullbulk);
|
298
|
+
} else {
|
299
|
+
if (o->type != REDIS_STRING) {
|
300
|
+
addReply(c,shared.nullbulk);
|
301
|
+
} else {
|
302
|
+
addReplyBulk(c,o);
|
303
|
+
}
|
304
|
+
}
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
void msetGenericCommand(redisClient *c, int nx) {
|
309
|
+
int j, busykeys = 0;
|
310
|
+
|
311
|
+
if ((c->argc % 2) == 0) {
|
312
|
+
addReplyError(c,"wrong number of arguments for MSET");
|
313
|
+
return;
|
314
|
+
}
|
315
|
+
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
|
316
|
+
* set nothing at all if at least one already key exists. */
|
317
|
+
if (nx) {
|
318
|
+
for (j = 1; j < c->argc; j += 2) {
|
319
|
+
if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
|
320
|
+
busykeys++;
|
321
|
+
}
|
322
|
+
}
|
323
|
+
}
|
324
|
+
if (busykeys) {
|
325
|
+
addReply(c, shared.czero);
|
326
|
+
return;
|
327
|
+
}
|
328
|
+
|
329
|
+
for (j = 1; j < c->argc; j += 2) {
|
330
|
+
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
|
331
|
+
dbReplace(c->db,c->argv[j],c->argv[j+1]);
|
332
|
+
incrRefCount(c->argv[j+1]);
|
333
|
+
removeExpire(c->db,c->argv[j]);
|
334
|
+
touchWatchedKey(c->db,c->argv[j]);
|
335
|
+
}
|
336
|
+
server.dirty += (c->argc-1)/2;
|
337
|
+
addReply(c, nx ? shared.cone : shared.ok);
|
338
|
+
}
|
339
|
+
|
340
|
+
void msetCommand(redisClient *c) {
|
341
|
+
msetGenericCommand(c,0);
|
342
|
+
}
|
343
|
+
|
344
|
+
void msetnxCommand(redisClient *c) {
|
345
|
+
msetGenericCommand(c,1);
|
346
|
+
}
|
347
|
+
|
348
|
+
void incrDecrCommand(redisClient *c, long long incr) {
|
349
|
+
long long value, oldvalue;
|
350
|
+
robj *o;
|
351
|
+
|
352
|
+
o = lookupKeyWrite(c->db,c->argv[1]);
|
353
|
+
if (o != NULL && checkType(c,o,REDIS_STRING)) return;
|
354
|
+
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
|
355
|
+
|
356
|
+
oldvalue = value;
|
357
|
+
value += incr;
|
358
|
+
if ((incr < 0 && value > oldvalue) || (incr > 0 && value < oldvalue)) {
|
359
|
+
addReplyError(c,"increment or decrement would overflow");
|
360
|
+
return;
|
361
|
+
}
|
362
|
+
o = createStringObjectFromLongLong(value);
|
363
|
+
dbReplace(c->db,c->argv[1],o);
|
364
|
+
touchWatchedKey(c->db,c->argv[1]);
|
365
|
+
server.dirty++;
|
366
|
+
addReply(c,shared.colon);
|
367
|
+
addReply(c,o);
|
368
|
+
addReply(c,shared.crlf);
|
369
|
+
}
|
370
|
+
|
371
|
+
void incrCommand(redisClient *c) {
|
372
|
+
incrDecrCommand(c,1);
|
373
|
+
}
|
374
|
+
|
375
|
+
void decrCommand(redisClient *c) {
|
376
|
+
incrDecrCommand(c,-1);
|
377
|
+
}
|
378
|
+
|
379
|
+
void incrbyCommand(redisClient *c) {
|
380
|
+
long long incr;
|
381
|
+
|
382
|
+
if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
|
383
|
+
incrDecrCommand(c,incr);
|
384
|
+
}
|
385
|
+
|
386
|
+
void decrbyCommand(redisClient *c) {
|
387
|
+
long long incr;
|
388
|
+
|
389
|
+
if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
|
390
|
+
incrDecrCommand(c,-incr);
|
391
|
+
}
|
392
|
+
|
393
|
+
void appendCommand(redisClient *c) {
|
394
|
+
size_t totlen;
|
395
|
+
robj *o, *append;
|
396
|
+
|
397
|
+
o = lookupKeyWrite(c->db,c->argv[1]);
|
398
|
+
if (o == NULL) {
|
399
|
+
/* Create the key */
|
400
|
+
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
401
|
+
dbAdd(c->db,c->argv[1],c->argv[2]);
|
402
|
+
incrRefCount(c->argv[2]);
|
403
|
+
totlen = stringObjectLen(c->argv[2]);
|
404
|
+
} else {
|
405
|
+
/* Key exists, check type */
|
406
|
+
if (checkType(c,o,REDIS_STRING))
|
407
|
+
return;
|
408
|
+
|
409
|
+
/* "append" is an argument, so always an sds */
|
410
|
+
append = c->argv[2];
|
411
|
+
totlen = stringObjectLen(o)+sdslen(append->ptr);
|
412
|
+
if (checkStringLength(c,totlen) != REDIS_OK)
|
413
|
+
return;
|
414
|
+
|
415
|
+
/* If the object is shared or encoded, we have to make a copy */
|
416
|
+
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
417
|
+
robj *decoded = getDecodedObject(o);
|
418
|
+
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
419
|
+
decrRefCount(decoded);
|
420
|
+
dbReplace(c->db,c->argv[1],o);
|
421
|
+
}
|
422
|
+
|
423
|
+
/* Append the value */
|
424
|
+
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
|
425
|
+
totlen = sdslen(o->ptr);
|
426
|
+
}
|
427
|
+
touchWatchedKey(c->db,c->argv[1]);
|
428
|
+
server.dirty++;
|
429
|
+
addReplyLongLong(c,totlen);
|
430
|
+
}
|
431
|
+
|
432
|
+
void strlenCommand(redisClient *c) {
|
433
|
+
robj *o;
|
434
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
435
|
+
checkType(c,o,REDIS_STRING)) return;
|
436
|
+
addReplyLongLong(c,stringObjectLen(o));
|
437
|
+
}
|
438
|
+
|
@@ -0,0 +1,1084 @@
|
|
1
|
+
#include "redis.h"
|
2
|
+
|
3
|
+
#include <math.h>
|
4
|
+
|
5
|
+
/*-----------------------------------------------------------------------------
|
6
|
+
* Sorted set API
|
7
|
+
*----------------------------------------------------------------------------*/
|
8
|
+
|
9
|
+
/* ZSETs are ordered sets using two data structures to hold the same elements
|
10
|
+
* in order to get O(log(N)) INSERT and REMOVE operations into a sorted
|
11
|
+
* data structure.
|
12
|
+
*
|
13
|
+
* The elements are added to an hash table mapping Redis objects to scores.
|
14
|
+
* At the same time the elements are added to a skip list mapping scores
|
15
|
+
* to Redis objects (so objects are sorted by scores in this "view"). */
|
16
|
+
|
17
|
+
/* This skiplist implementation is almost a C translation of the original
|
18
|
+
* algorithm described by William Pugh in "Skip Lists: A Probabilistic
|
19
|
+
* Alternative to Balanced Trees", modified in three ways:
|
20
|
+
* a) this implementation allows for repeated values.
|
21
|
+
* b) the comparison is not just by key (our 'score') but by satellite data.
|
22
|
+
* c) there is a back pointer, so it's a doubly linked list with the back
|
23
|
+
* pointers being only at "level 1". This allows to traverse the list
|
24
|
+
* from tail to head, useful for ZREVRANGE. */
|
25
|
+
|
26
|
+
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
|
27
|
+
zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
|
28
|
+
zn->score = score;
|
29
|
+
zn->obj = obj;
|
30
|
+
return zn;
|
31
|
+
}
|
32
|
+
|
33
|
+
zskiplist *zslCreate(void) {
|
34
|
+
int j;
|
35
|
+
zskiplist *zsl;
|
36
|
+
|
37
|
+
zsl = zmalloc(sizeof(*zsl));
|
38
|
+
zsl->level = 1;
|
39
|
+
zsl->length = 0;
|
40
|
+
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
|
41
|
+
for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
|
42
|
+
zsl->header->level[j].forward = NULL;
|
43
|
+
zsl->header->level[j].span = 0;
|
44
|
+
}
|
45
|
+
zsl->header->backward = NULL;
|
46
|
+
zsl->tail = NULL;
|
47
|
+
return zsl;
|
48
|
+
}
|
49
|
+
|
50
|
+
void zslFreeNode(zskiplistNode *node) {
|
51
|
+
decrRefCount(node->obj);
|
52
|
+
zfree(node);
|
53
|
+
}
|
54
|
+
|
55
|
+
void zslFree(zskiplist *zsl) {
|
56
|
+
zskiplistNode *node = zsl->header->level[0].forward, *next;
|
57
|
+
|
58
|
+
zfree(zsl->header);
|
59
|
+
while(node) {
|
60
|
+
next = node->level[0].forward;
|
61
|
+
zslFreeNode(node);
|
62
|
+
node = next;
|
63
|
+
}
|
64
|
+
zfree(zsl);
|
65
|
+
}
|
66
|
+
|
67
|
+
int zslRandomLevel(void) {
|
68
|
+
int level = 1;
|
69
|
+
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
|
70
|
+
level += 1;
|
71
|
+
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
|
72
|
+
}
|
73
|
+
|
74
|
+
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
|
75
|
+
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
|
76
|
+
unsigned int rank[ZSKIPLIST_MAXLEVEL];
|
77
|
+
int i, level;
|
78
|
+
|
79
|
+
x = zsl->header;
|
80
|
+
for (i = zsl->level-1; i >= 0; i--) {
|
81
|
+
/* store rank that is crossed to reach the insert position */
|
82
|
+
rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
|
83
|
+
while (x->level[i].forward &&
|
84
|
+
(x->level[i].forward->score < score ||
|
85
|
+
(x->level[i].forward->score == score &&
|
86
|
+
compareStringObjects(x->level[i].forward->obj,obj) < 0))) {
|
87
|
+
rank[i] += x->level[i].span;
|
88
|
+
x = x->level[i].forward;
|
89
|
+
}
|
90
|
+
update[i] = x;
|
91
|
+
}
|
92
|
+
/* we assume the key is not already inside, since we allow duplicated
|
93
|
+
* scores, and the re-insertion of score and redis object should never
|
94
|
+
* happpen since the caller of zslInsert() should test in the hash table
|
95
|
+
* if the element is already inside or not. */
|
96
|
+
level = zslRandomLevel();
|
97
|
+
if (level > zsl->level) {
|
98
|
+
for (i = zsl->level; i < level; i++) {
|
99
|
+
rank[i] = 0;
|
100
|
+
update[i] = zsl->header;
|
101
|
+
update[i]->level[i].span = zsl->length;
|
102
|
+
}
|
103
|
+
zsl->level = level;
|
104
|
+
}
|
105
|
+
x = zslCreateNode(level,score,obj);
|
106
|
+
for (i = 0; i < level; i++) {
|
107
|
+
x->level[i].forward = update[i]->level[i].forward;
|
108
|
+
update[i]->level[i].forward = x;
|
109
|
+
|
110
|
+
/* update span covered by update[i] as x is inserted here */
|
111
|
+
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
|
112
|
+
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
|
113
|
+
}
|
114
|
+
|
115
|
+
/* increment span for untouched levels */
|
116
|
+
for (i = level; i < zsl->level; i++) {
|
117
|
+
update[i]->level[i].span++;
|
118
|
+
}
|
119
|
+
|
120
|
+
x->backward = (update[0] == zsl->header) ? NULL : update[0];
|
121
|
+
if (x->level[0].forward)
|
122
|
+
x->level[0].forward->backward = x;
|
123
|
+
else
|
124
|
+
zsl->tail = x;
|
125
|
+
zsl->length++;
|
126
|
+
return x;
|
127
|
+
}
|
128
|
+
|
129
|
+
/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
|
130
|
+
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
|
131
|
+
int i;
|
132
|
+
for (i = 0; i < zsl->level; i++) {
|
133
|
+
if (update[i]->level[i].forward == x) {
|
134
|
+
update[i]->level[i].span += x->level[i].span - 1;
|
135
|
+
update[i]->level[i].forward = x->level[i].forward;
|
136
|
+
} else {
|
137
|
+
update[i]->level[i].span -= 1;
|
138
|
+
}
|
139
|
+
}
|
140
|
+
if (x->level[0].forward) {
|
141
|
+
x->level[0].forward->backward = x->backward;
|
142
|
+
} else {
|
143
|
+
zsl->tail = x->backward;
|
144
|
+
}
|
145
|
+
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
|
146
|
+
zsl->level--;
|
147
|
+
zsl->length--;
|
148
|
+
}
|
149
|
+
|
150
|
+
/* Delete an element with matching score/object from the skiplist. */
|
151
|
+
int zslDelete(zskiplist *zsl, double score, robj *obj) {
|
152
|
+
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
|
153
|
+
int i;
|
154
|
+
|
155
|
+
x = zsl->header;
|
156
|
+
for (i = zsl->level-1; i >= 0; i--) {
|
157
|
+
while (x->level[i].forward &&
|
158
|
+
(x->level[i].forward->score < score ||
|
159
|
+
(x->level[i].forward->score == score &&
|
160
|
+
compareStringObjects(x->level[i].forward->obj,obj) < 0)))
|
161
|
+
x = x->level[i].forward;
|
162
|
+
update[i] = x;
|
163
|
+
}
|
164
|
+
/* We may have multiple elements with the same score, what we need
|
165
|
+
* is to find the element with both the right score and object. */
|
166
|
+
x = x->level[0].forward;
|
167
|
+
if (x && score == x->score && equalStringObjects(x->obj,obj)) {
|
168
|
+
zslDeleteNode(zsl, x, update);
|
169
|
+
zslFreeNode(x);
|
170
|
+
return 1;
|
171
|
+
} else {
|
172
|
+
return 0; /* not found */
|
173
|
+
}
|
174
|
+
return 0; /* not found */
|
175
|
+
}
|
176
|
+
|
177
|
+
/* Struct to hold a inclusive/exclusive range spec. */
|
178
|
+
typedef struct {
|
179
|
+
double min, max;
|
180
|
+
int minex, maxex; /* are min or max exclusive? */
|
181
|
+
} zrangespec;
|
182
|
+
|
183
|
+
/* Delete all the elements with score between min and max from the skiplist.
|
184
|
+
* Min and mx are inclusive, so a score >= min || score <= max is deleted.
|
185
|
+
* Note that this function takes the reference to the hash table view of the
|
186
|
+
* sorted set, in order to remove the elements from the hash table too. */
|
187
|
+
unsigned long zslDeleteRangeByScore(zskiplist *zsl, zrangespec range, dict *dict) {
|
188
|
+
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
|
189
|
+
unsigned long removed = 0;
|
190
|
+
int i;
|
191
|
+
|
192
|
+
x = zsl->header;
|
193
|
+
for (i = zsl->level-1; i >= 0; i--) {
|
194
|
+
while (x->level[i].forward && (range.minex ?
|
195
|
+
x->level[i].forward->score <= range.min :
|
196
|
+
x->level[i].forward->score < range.min))
|
197
|
+
x = x->level[i].forward;
|
198
|
+
update[i] = x;
|
199
|
+
}
|
200
|
+
|
201
|
+
/* Current node is the last with score < or <= min. */
|
202
|
+
x = x->level[0].forward;
|
203
|
+
|
204
|
+
/* Delete nodes while in range. */
|
205
|
+
while (x && (range.maxex ? x->score < range.max : x->score <= range.max)) {
|
206
|
+
zskiplistNode *next = x->level[0].forward;
|
207
|
+
zslDeleteNode(zsl,x,update);
|
208
|
+
dictDelete(dict,x->obj);
|
209
|
+
zslFreeNode(x);
|
210
|
+
removed++;
|
211
|
+
x = next;
|
212
|
+
}
|
213
|
+
return removed;
|
214
|
+
}
|
215
|
+
|
216
|
+
/* Delete all the elements with rank between start and end from the skiplist.
|
217
|
+
* Start and end are inclusive. Note that start and end need to be 1-based */
|
218
|
+
unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned int end, dict *dict) {
|
219
|
+
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
|
220
|
+
unsigned long traversed = 0, removed = 0;
|
221
|
+
int i;
|
222
|
+
|
223
|
+
x = zsl->header;
|
224
|
+
for (i = zsl->level-1; i >= 0; i--) {
|
225
|
+
while (x->level[i].forward && (traversed + x->level[i].span) < start) {
|
226
|
+
traversed += x->level[i].span;
|
227
|
+
x = x->level[i].forward;
|
228
|
+
}
|
229
|
+
update[i] = x;
|
230
|
+
}
|
231
|
+
|
232
|
+
traversed++;
|
233
|
+
x = x->level[0].forward;
|
234
|
+
while (x && traversed <= end) {
|
235
|
+
zskiplistNode *next = x->level[0].forward;
|
236
|
+
zslDeleteNode(zsl,x,update);
|
237
|
+
dictDelete(dict,x->obj);
|
238
|
+
zslFreeNode(x);
|
239
|
+
removed++;
|
240
|
+
traversed++;
|
241
|
+
x = next;
|
242
|
+
}
|
243
|
+
return removed;
|
244
|
+
}
|
245
|
+
|
246
|
+
/* Find the first node having a score equal or greater than the specified one.
|
247
|
+
* Returns NULL if there is no match. */
|
248
|
+
zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) {
|
249
|
+
zskiplistNode *x;
|
250
|
+
int i;
|
251
|
+
|
252
|
+
x = zsl->header;
|
253
|
+
for (i = zsl->level-1; i >= 0; i--) {
|
254
|
+
while (x->level[i].forward && x->level[i].forward->score < score)
|
255
|
+
x = x->level[i].forward;
|
256
|
+
}
|
257
|
+
/* We may have multiple elements with the same score, what we need
|
258
|
+
* is to find the element with both the right score and object. */
|
259
|
+
return x->level[0].forward;
|
260
|
+
}
|
261
|
+
|
262
|
+
/* Find the rank for an element by both score and key.
|
263
|
+
* Returns 0 when the element cannot be found, rank otherwise.
|
264
|
+
* Note that the rank is 1-based due to the span of zsl->header to the
|
265
|
+
* first element. */
|
266
|
+
unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) {
|
267
|
+
zskiplistNode *x;
|
268
|
+
unsigned long rank = 0;
|
269
|
+
int i;
|
270
|
+
|
271
|
+
x = zsl->header;
|
272
|
+
for (i = zsl->level-1; i >= 0; i--) {
|
273
|
+
while (x->level[i].forward &&
|
274
|
+
(x->level[i].forward->score < score ||
|
275
|
+
(x->level[i].forward->score == score &&
|
276
|
+
compareStringObjects(x->level[i].forward->obj,o) <= 0))) {
|
277
|
+
rank += x->level[i].span;
|
278
|
+
x = x->level[i].forward;
|
279
|
+
}
|
280
|
+
|
281
|
+
/* x might be equal to zsl->header, so test if obj is non-NULL */
|
282
|
+
if (x->obj && equalStringObjects(x->obj,o)) {
|
283
|
+
return rank;
|
284
|
+
}
|
285
|
+
}
|
286
|
+
return 0;
|
287
|
+
}
|
288
|
+
|
289
|
+
/* Finds an element by its rank. The rank argument needs to be 1-based. */
|
290
|
+
zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {
|
291
|
+
zskiplistNode *x;
|
292
|
+
unsigned long traversed = 0;
|
293
|
+
int i;
|
294
|
+
|
295
|
+
x = zsl->header;
|
296
|
+
for (i = zsl->level-1; i >= 0; i--) {
|
297
|
+
while (x->level[i].forward && (traversed + x->level[i].span) <= rank)
|
298
|
+
{
|
299
|
+
traversed += x->level[i].span;
|
300
|
+
x = x->level[i].forward;
|
301
|
+
}
|
302
|
+
if (traversed == rank) {
|
303
|
+
return x;
|
304
|
+
}
|
305
|
+
}
|
306
|
+
return NULL;
|
307
|
+
}
|
308
|
+
|
309
|
+
/* Populate the rangespec according to the objects min and max. */
|
310
|
+
static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
|
311
|
+
char *eptr;
|
312
|
+
spec->minex = spec->maxex = 0;
|
313
|
+
|
314
|
+
/* Parse the min-max interval. If one of the values is prefixed
|
315
|
+
* by the "(" character, it's considered "open". For instance
|
316
|
+
* ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max
|
317
|
+
* ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */
|
318
|
+
if (min->encoding == REDIS_ENCODING_INT) {
|
319
|
+
spec->min = (long)min->ptr;
|
320
|
+
} else {
|
321
|
+
if (((char*)min->ptr)[0] == '(') {
|
322
|
+
spec->min = strtod((char*)min->ptr+1,&eptr);
|
323
|
+
if (eptr[0] != '\0' || isnan(spec->min)) return REDIS_ERR;
|
324
|
+
spec->minex = 1;
|
325
|
+
} else {
|
326
|
+
spec->min = strtod((char*)min->ptr,&eptr);
|
327
|
+
if (eptr[0] != '\0' || isnan(spec->min)) return REDIS_ERR;
|
328
|
+
}
|
329
|
+
}
|
330
|
+
if (max->encoding == REDIS_ENCODING_INT) {
|
331
|
+
spec->max = (long)max->ptr;
|
332
|
+
} else {
|
333
|
+
if (((char*)max->ptr)[0] == '(') {
|
334
|
+
spec->max = strtod((char*)max->ptr+1,&eptr);
|
335
|
+
if (eptr[0] != '\0' || isnan(spec->max)) return REDIS_ERR;
|
336
|
+
spec->maxex = 1;
|
337
|
+
} else {
|
338
|
+
spec->max = strtod((char*)max->ptr,&eptr);
|
339
|
+
if (eptr[0] != '\0' || isnan(spec->max)) return REDIS_ERR;
|
340
|
+
}
|
341
|
+
}
|
342
|
+
|
343
|
+
return REDIS_OK;
|
344
|
+
}
|
345
|
+
|
346
|
+
|
347
|
+
/*-----------------------------------------------------------------------------
|
348
|
+
* Sorted set commands
|
349
|
+
*----------------------------------------------------------------------------*/
|
350
|
+
|
351
|
+
/* This generic command implements both ZADD and ZINCRBY. */
|
352
|
+
void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int incr) {
|
353
|
+
robj *zsetobj;
|
354
|
+
zset *zs;
|
355
|
+
zskiplistNode *znode;
|
356
|
+
|
357
|
+
zsetobj = lookupKeyWrite(c->db,key);
|
358
|
+
if (zsetobj == NULL) {
|
359
|
+
zsetobj = createZsetObject();
|
360
|
+
dbAdd(c->db,key,zsetobj);
|
361
|
+
} else {
|
362
|
+
if (zsetobj->type != REDIS_ZSET) {
|
363
|
+
addReply(c,shared.wrongtypeerr);
|
364
|
+
return;
|
365
|
+
}
|
366
|
+
}
|
367
|
+
zs = zsetobj->ptr;
|
368
|
+
|
369
|
+
/* Since both ZADD and ZINCRBY are implemented here, we need to increment
|
370
|
+
* the score first by the current score if ZINCRBY is called. */
|
371
|
+
if (incr) {
|
372
|
+
/* Read the old score. If the element was not present starts from 0 */
|
373
|
+
dictEntry *de = dictFind(zs->dict,ele);
|
374
|
+
if (de != NULL)
|
375
|
+
score += *(double*)dictGetEntryVal(de);
|
376
|
+
|
377
|
+
if (isnan(score)) {
|
378
|
+
addReplyError(c,"resulting score is not a number (NaN)");
|
379
|
+
/* Note that we don't need to check if the zset may be empty and
|
380
|
+
* should be removed here, as we can only obtain Nan as score if
|
381
|
+
* there was already an element in the sorted set. */
|
382
|
+
return;
|
383
|
+
}
|
384
|
+
}
|
385
|
+
|
386
|
+
/* We need to remove and re-insert the element when it was already present
|
387
|
+
* in the dictionary, to update the skiplist. Note that we delay adding a
|
388
|
+
* pointer to the score because we want to reference the score in the
|
389
|
+
* skiplist node. */
|
390
|
+
if (dictAdd(zs->dict,ele,NULL) == DICT_OK) {
|
391
|
+
dictEntry *de;
|
392
|
+
|
393
|
+
/* New element */
|
394
|
+
incrRefCount(ele); /* added to hash */
|
395
|
+
znode = zslInsert(zs->zsl,score,ele);
|
396
|
+
incrRefCount(ele); /* added to skiplist */
|
397
|
+
|
398
|
+
/* Update the score in the dict entry */
|
399
|
+
de = dictFind(zs->dict,ele);
|
400
|
+
redisAssert(de != NULL);
|
401
|
+
dictGetEntryVal(de) = &znode->score;
|
402
|
+
touchWatchedKey(c->db,c->argv[1]);
|
403
|
+
server.dirty++;
|
404
|
+
if (incr)
|
405
|
+
addReplyDouble(c,score);
|
406
|
+
else
|
407
|
+
addReply(c,shared.cone);
|
408
|
+
} else {
|
409
|
+
dictEntry *de;
|
410
|
+
robj *curobj;
|
411
|
+
double *curscore;
|
412
|
+
int deleted;
|
413
|
+
|
414
|
+
/* Update score */
|
415
|
+
de = dictFind(zs->dict,ele);
|
416
|
+
redisAssert(de != NULL);
|
417
|
+
curobj = dictGetEntryKey(de);
|
418
|
+
curscore = dictGetEntryVal(de);
|
419
|
+
|
420
|
+
/* When the score is updated, reuse the existing string object to
|
421
|
+
* prevent extra alloc/dealloc of strings on ZINCRBY. */
|
422
|
+
if (score != *curscore) {
|
423
|
+
deleted = zslDelete(zs->zsl,*curscore,curobj);
|
424
|
+
redisAssert(deleted != 0);
|
425
|
+
znode = zslInsert(zs->zsl,score,curobj);
|
426
|
+
incrRefCount(curobj);
|
427
|
+
|
428
|
+
/* Update the score in the current dict entry */
|
429
|
+
dictGetEntryVal(de) = &znode->score;
|
430
|
+
touchWatchedKey(c->db,c->argv[1]);
|
431
|
+
server.dirty++;
|
432
|
+
}
|
433
|
+
if (incr)
|
434
|
+
addReplyDouble(c,score);
|
435
|
+
else
|
436
|
+
addReply(c,shared.czero);
|
437
|
+
}
|
438
|
+
}
|
439
|
+
|
440
|
+
void zaddCommand(redisClient *c) {
|
441
|
+
double scoreval;
|
442
|
+
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
|
443
|
+
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
444
|
+
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
|
445
|
+
}
|
446
|
+
|
447
|
+
void zincrbyCommand(redisClient *c) {
|
448
|
+
double scoreval;
|
449
|
+
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
|
450
|
+
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
451
|
+
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
|
452
|
+
}
|
453
|
+
|
454
|
+
void zremCommand(redisClient *c) {
|
455
|
+
robj *zsetobj;
|
456
|
+
zset *zs;
|
457
|
+
dictEntry *de;
|
458
|
+
double curscore;
|
459
|
+
int deleted;
|
460
|
+
|
461
|
+
if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
462
|
+
checkType(c,zsetobj,REDIS_ZSET)) return;
|
463
|
+
|
464
|
+
zs = zsetobj->ptr;
|
465
|
+
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
466
|
+
de = dictFind(zs->dict,c->argv[2]);
|
467
|
+
if (de == NULL) {
|
468
|
+
addReply(c,shared.czero);
|
469
|
+
return;
|
470
|
+
}
|
471
|
+
/* Delete from the skiplist */
|
472
|
+
curscore = *(double*)dictGetEntryVal(de);
|
473
|
+
deleted = zslDelete(zs->zsl,curscore,c->argv[2]);
|
474
|
+
redisAssert(deleted != 0);
|
475
|
+
|
476
|
+
/* Delete from the hash table */
|
477
|
+
dictDelete(zs->dict,c->argv[2]);
|
478
|
+
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
|
479
|
+
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
|
480
|
+
touchWatchedKey(c->db,c->argv[1]);
|
481
|
+
server.dirty++;
|
482
|
+
addReply(c,shared.cone);
|
483
|
+
}
|
484
|
+
|
485
|
+
void zremrangebyscoreCommand(redisClient *c) {
|
486
|
+
zrangespec range;
|
487
|
+
long deleted;
|
488
|
+
robj *o;
|
489
|
+
zset *zs;
|
490
|
+
|
491
|
+
/* Parse the range arguments. */
|
492
|
+
if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
|
493
|
+
addReplyError(c,"min or max is not a double");
|
494
|
+
return;
|
495
|
+
}
|
496
|
+
|
497
|
+
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
498
|
+
checkType(c,o,REDIS_ZSET)) return;
|
499
|
+
|
500
|
+
zs = o->ptr;
|
501
|
+
deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict);
|
502
|
+
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
|
503
|
+
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
|
504
|
+
if (deleted) touchWatchedKey(c->db,c->argv[1]);
|
505
|
+
server.dirty += deleted;
|
506
|
+
addReplyLongLong(c,deleted);
|
507
|
+
}
|
508
|
+
|
509
|
+
void zremrangebyrankCommand(redisClient *c) {
|
510
|
+
long start;
|
511
|
+
long end;
|
512
|
+
int llen;
|
513
|
+
long deleted;
|
514
|
+
robj *zsetobj;
|
515
|
+
zset *zs;
|
516
|
+
|
517
|
+
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
|
518
|
+
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
|
519
|
+
|
520
|
+
if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
521
|
+
checkType(c,zsetobj,REDIS_ZSET)) return;
|
522
|
+
zs = zsetobj->ptr;
|
523
|
+
llen = zs->zsl->length;
|
524
|
+
|
525
|
+
/* convert negative indexes */
|
526
|
+
if (start < 0) start = llen+start;
|
527
|
+
if (end < 0) end = llen+end;
|
528
|
+
if (start < 0) start = 0;
|
529
|
+
|
530
|
+
/* Invariant: start >= 0, so this test will be true when end < 0.
|
531
|
+
* The range is empty when start > end or start >= length. */
|
532
|
+
if (start > end || start >= llen) {
|
533
|
+
addReply(c,shared.czero);
|
534
|
+
return;
|
535
|
+
}
|
536
|
+
if (end >= llen) end = llen-1;
|
537
|
+
|
538
|
+
/* increment start and end because zsl*Rank functions
|
539
|
+
* use 1-based rank */
|
540
|
+
deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict);
|
541
|
+
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
|
542
|
+
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
|
543
|
+
if (deleted) touchWatchedKey(c->db,c->argv[1]);
|
544
|
+
server.dirty += deleted;
|
545
|
+
addReplyLongLong(c, deleted);
|
546
|
+
}
|
547
|
+
|
548
|
+
typedef struct {
|
549
|
+
dict *dict;
|
550
|
+
double weight;
|
551
|
+
} zsetopsrc;
|
552
|
+
|
553
|
+
int qsortCompareZsetopsrcByCardinality(const void *s1, const void *s2) {
|
554
|
+
zsetopsrc *d1 = (void*) s1, *d2 = (void*) s2;
|
555
|
+
unsigned long size1, size2;
|
556
|
+
size1 = d1->dict ? dictSize(d1->dict) : 0;
|
557
|
+
size2 = d2->dict ? dictSize(d2->dict) : 0;
|
558
|
+
return size1 - size2;
|
559
|
+
}
|
560
|
+
|
561
|
+
#define REDIS_AGGR_SUM 1
|
562
|
+
#define REDIS_AGGR_MIN 2
|
563
|
+
#define REDIS_AGGR_MAX 3
|
564
|
+
#define zunionInterDictValue(_e) (dictGetEntryVal(_e) == NULL ? 1.0 : *(double*)dictGetEntryVal(_e))
|
565
|
+
|
566
|
+
inline static void zunionInterAggregate(double *target, double val, int aggregate) {
|
567
|
+
if (aggregate == REDIS_AGGR_SUM) {
|
568
|
+
*target = *target + val;
|
569
|
+
/* The result of adding two doubles is NaN when one variable
|
570
|
+
* is +inf and the other is -inf. When these numbers are added,
|
571
|
+
* we maintain the convention of the result being 0.0. */
|
572
|
+
if (isnan(*target)) *target = 0.0;
|
573
|
+
} else if (aggregate == REDIS_AGGR_MIN) {
|
574
|
+
*target = val < *target ? val : *target;
|
575
|
+
} else if (aggregate == REDIS_AGGR_MAX) {
|
576
|
+
*target = val > *target ? val : *target;
|
577
|
+
} else {
|
578
|
+
/* safety net */
|
579
|
+
redisPanic("Unknown ZUNION/INTER aggregate type");
|
580
|
+
}
|
581
|
+
}
|
582
|
+
|
583
|
+
void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
584
|
+
int i, j, setnum;
|
585
|
+
int aggregate = REDIS_AGGR_SUM;
|
586
|
+
zsetopsrc *src;
|
587
|
+
robj *dstobj;
|
588
|
+
zset *dstzset;
|
589
|
+
zskiplistNode *znode;
|
590
|
+
dictIterator *di;
|
591
|
+
dictEntry *de;
|
592
|
+
int touched = 0;
|
593
|
+
|
594
|
+
/* expect setnum input keys to be given */
|
595
|
+
setnum = atoi(c->argv[2]->ptr);
|
596
|
+
if (setnum < 1) {
|
597
|
+
addReplyError(c,
|
598
|
+
"at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE");
|
599
|
+
return;
|
600
|
+
}
|
601
|
+
|
602
|
+
/* test if the expected number of keys would overflow */
|
603
|
+
if (3+setnum > c->argc) {
|
604
|
+
addReply(c,shared.syntaxerr);
|
605
|
+
return;
|
606
|
+
}
|
607
|
+
|
608
|
+
/* read keys to be used for input */
|
609
|
+
src = zmalloc(sizeof(zsetopsrc) * setnum);
|
610
|
+
for (i = 0, j = 3; i < setnum; i++, j++) {
|
611
|
+
robj *obj = lookupKeyWrite(c->db,c->argv[j]);
|
612
|
+
if (!obj) {
|
613
|
+
src[i].dict = NULL;
|
614
|
+
} else {
|
615
|
+
if (obj->type == REDIS_ZSET) {
|
616
|
+
src[i].dict = ((zset*)obj->ptr)->dict;
|
617
|
+
} else if (obj->type == REDIS_SET) {
|
618
|
+
if (obj->encoding == REDIS_ENCODING_INTSET)
|
619
|
+
setTypeConvert(obj, REDIS_ENCODING_HT);
|
620
|
+
|
621
|
+
redisAssert(obj->encoding == REDIS_ENCODING_HT);
|
622
|
+
src[i].dict = (obj->ptr);
|
623
|
+
} else {
|
624
|
+
zfree(src);
|
625
|
+
addReply(c,shared.wrongtypeerr);
|
626
|
+
return;
|
627
|
+
}
|
628
|
+
}
|
629
|
+
|
630
|
+
/* default all weights to 1 */
|
631
|
+
src[i].weight = 1.0;
|
632
|
+
}
|
633
|
+
|
634
|
+
/* parse optional extra arguments */
|
635
|
+
if (j < c->argc) {
|
636
|
+
int remaining = c->argc - j;
|
637
|
+
|
638
|
+
while (remaining) {
|
639
|
+
if (remaining >= (setnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) {
|
640
|
+
j++; remaining--;
|
641
|
+
for (i = 0; i < setnum; i++, j++, remaining--) {
|
642
|
+
if (getDoubleFromObjectOrReply(c,c->argv[j],&src[i].weight,
|
643
|
+
"weight value is not a double") != REDIS_OK)
|
644
|
+
{
|
645
|
+
zfree(src);
|
646
|
+
return;
|
647
|
+
}
|
648
|
+
}
|
649
|
+
} else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) {
|
650
|
+
j++; remaining--;
|
651
|
+
if (!strcasecmp(c->argv[j]->ptr,"sum")) {
|
652
|
+
aggregate = REDIS_AGGR_SUM;
|
653
|
+
} else if (!strcasecmp(c->argv[j]->ptr,"min")) {
|
654
|
+
aggregate = REDIS_AGGR_MIN;
|
655
|
+
} else if (!strcasecmp(c->argv[j]->ptr,"max")) {
|
656
|
+
aggregate = REDIS_AGGR_MAX;
|
657
|
+
} else {
|
658
|
+
zfree(src);
|
659
|
+
addReply(c,shared.syntaxerr);
|
660
|
+
return;
|
661
|
+
}
|
662
|
+
j++; remaining--;
|
663
|
+
} else {
|
664
|
+
zfree(src);
|
665
|
+
addReply(c,shared.syntaxerr);
|
666
|
+
return;
|
667
|
+
}
|
668
|
+
}
|
669
|
+
}
|
670
|
+
|
671
|
+
/* sort sets from the smallest to largest, this will improve our
|
672
|
+
* algorithm's performance */
|
673
|
+
qsort(src,setnum,sizeof(zsetopsrc),qsortCompareZsetopsrcByCardinality);
|
674
|
+
|
675
|
+
dstobj = createZsetObject();
|
676
|
+
dstzset = dstobj->ptr;
|
677
|
+
|
678
|
+
if (op == REDIS_OP_INTER) {
|
679
|
+
/* skip going over all entries if the smallest zset is NULL or empty */
|
680
|
+
if (src[0].dict && dictSize(src[0].dict) > 0) {
|
681
|
+
/* precondition: as src[0].dict is non-empty and the zsets are ordered
|
682
|
+
* from small to large, all src[i > 0].dict are non-empty too */
|
683
|
+
di = dictGetIterator(src[0].dict);
|
684
|
+
while((de = dictNext(di)) != NULL) {
|
685
|
+
double score, value;
|
686
|
+
|
687
|
+
score = src[0].weight * zunionInterDictValue(de);
|
688
|
+
for (j = 1; j < setnum; j++) {
|
689
|
+
dictEntry *other;
|
690
|
+
|
691
|
+
/* If it's the same dictionary don't lookup as we are not
|
692
|
+
* in the context of a safe iterator. It's the same
|
693
|
+
* dictionary so we are sure the element is inside.
|
694
|
+
* This happens on SINTERSTORE dest 2 mykey mykey. */
|
695
|
+
if (src[j].dict == src[0].dict) {
|
696
|
+
other = de;
|
697
|
+
} else {
|
698
|
+
other = dictFind(src[j].dict,dictGetEntryKey(de));
|
699
|
+
}
|
700
|
+
|
701
|
+
if (other) {
|
702
|
+
value = src[j].weight * zunionInterDictValue(other);
|
703
|
+
zunionInterAggregate(&score,value,aggregate);
|
704
|
+
} else {
|
705
|
+
break;
|
706
|
+
}
|
707
|
+
}
|
708
|
+
|
709
|
+
/* Only continue when present in every source dict. */
|
710
|
+
if (j == setnum) {
|
711
|
+
robj *o = dictGetEntryKey(de);
|
712
|
+
znode = zslInsert(dstzset->zsl,score,o);
|
713
|
+
incrRefCount(o); /* added to skiplist */
|
714
|
+
dictAdd(dstzset->dict,o,&znode->score);
|
715
|
+
incrRefCount(o); /* added to dictionary */
|
716
|
+
}
|
717
|
+
}
|
718
|
+
dictReleaseIterator(di);
|
719
|
+
}
|
720
|
+
} else if (op == REDIS_OP_UNION) {
|
721
|
+
for (i = 0; i < setnum; i++) {
|
722
|
+
if (!src[i].dict) continue;
|
723
|
+
|
724
|
+
di = dictGetIterator(src[i].dict);
|
725
|
+
while((de = dictNext(di)) != NULL) {
|
726
|
+
double score, value;
|
727
|
+
|
728
|
+
/* skip key when already processed */
|
729
|
+
if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL)
|
730
|
+
continue;
|
731
|
+
|
732
|
+
/* initialize score */
|
733
|
+
score = src[i].weight * zunionInterDictValue(de);
|
734
|
+
|
735
|
+
/* because the zsets are sorted by size, its only possible
|
736
|
+
* for sets at larger indices to hold this entry */
|
737
|
+
for (j = (i+1); j < setnum; j++) {
|
738
|
+
/* It is not safe to access the zset we are
|
739
|
+
* iterating, so explicitly check for equal object. */
|
740
|
+
if (src[j].dict == src[i].dict) {
|
741
|
+
value = src[i].weight * zunionInterDictValue(de);
|
742
|
+
zunionInterAggregate(&score,value,aggregate);
|
743
|
+
} else {
|
744
|
+
dictEntry *other;
|
745
|
+
|
746
|
+
other = dictFind(src[j].dict,dictGetEntryKey(de));
|
747
|
+
if (other) {
|
748
|
+
value = src[j].weight * zunionInterDictValue(other);
|
749
|
+
zunionInterAggregate(&score,value,aggregate);
|
750
|
+
}
|
751
|
+
}
|
752
|
+
}
|
753
|
+
|
754
|
+
robj *o = dictGetEntryKey(de);
|
755
|
+
znode = zslInsert(dstzset->zsl,score,o);
|
756
|
+
incrRefCount(o); /* added to skiplist */
|
757
|
+
dictAdd(dstzset->dict,o,&znode->score);
|
758
|
+
incrRefCount(o); /* added to dictionary */
|
759
|
+
}
|
760
|
+
dictReleaseIterator(di);
|
761
|
+
}
|
762
|
+
} else {
|
763
|
+
/* unknown operator */
|
764
|
+
redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION);
|
765
|
+
}
|
766
|
+
|
767
|
+
if (dbDelete(c->db,dstkey)) {
|
768
|
+
touchWatchedKey(c->db,dstkey);
|
769
|
+
touched = 1;
|
770
|
+
server.dirty++;
|
771
|
+
}
|
772
|
+
if (dstzset->zsl->length) {
|
773
|
+
dbAdd(c->db,dstkey,dstobj);
|
774
|
+
addReplyLongLong(c, dstzset->zsl->length);
|
775
|
+
if (!touched) touchWatchedKey(c->db,dstkey);
|
776
|
+
server.dirty++;
|
777
|
+
} else {
|
778
|
+
decrRefCount(dstobj);
|
779
|
+
addReply(c, shared.czero);
|
780
|
+
}
|
781
|
+
zfree(src);
|
782
|
+
}
|
783
|
+
|
784
|
+
void zunionstoreCommand(redisClient *c) {
|
785
|
+
zunionInterGenericCommand(c,c->argv[1], REDIS_OP_UNION);
|
786
|
+
}
|
787
|
+
|
788
|
+
void zinterstoreCommand(redisClient *c) {
|
789
|
+
zunionInterGenericCommand(c,c->argv[1], REDIS_OP_INTER);
|
790
|
+
}
|
791
|
+
|
792
|
+
void zrangeGenericCommand(redisClient *c, int reverse) {
|
793
|
+
robj *o;
|
794
|
+
long start;
|
795
|
+
long end;
|
796
|
+
int withscores = 0;
|
797
|
+
int llen;
|
798
|
+
int rangelen, j;
|
799
|
+
zset *zsetobj;
|
800
|
+
zskiplist *zsl;
|
801
|
+
zskiplistNode *ln;
|
802
|
+
robj *ele;
|
803
|
+
|
804
|
+
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
|
805
|
+
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
|
806
|
+
|
807
|
+
if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) {
|
808
|
+
withscores = 1;
|
809
|
+
} else if (c->argc >= 5) {
|
810
|
+
addReply(c,shared.syntaxerr);
|
811
|
+
return;
|
812
|
+
}
|
813
|
+
|
814
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|
815
|
+
|| checkType(c,o,REDIS_ZSET)) return;
|
816
|
+
zsetobj = o->ptr;
|
817
|
+
zsl = zsetobj->zsl;
|
818
|
+
llen = zsl->length;
|
819
|
+
|
820
|
+
/* convert negative indexes */
|
821
|
+
if (start < 0) start = llen+start;
|
822
|
+
if (end < 0) end = llen+end;
|
823
|
+
if (start < 0) start = 0;
|
824
|
+
|
825
|
+
/* Invariant: start >= 0, so this test will be true when end < 0.
|
826
|
+
* The range is empty when start > end or start >= length. */
|
827
|
+
if (start > end || start >= llen) {
|
828
|
+
addReply(c,shared.emptymultibulk);
|
829
|
+
return;
|
830
|
+
}
|
831
|
+
if (end >= llen) end = llen-1;
|
832
|
+
rangelen = (end-start)+1;
|
833
|
+
|
834
|
+
/* check if starting point is trivial, before searching
|
835
|
+
* the element in log(N) time */
|
836
|
+
if (reverse) {
|
837
|
+
ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start);
|
838
|
+
} else {
|
839
|
+
ln = start == 0 ?
|
840
|
+
zsl->header->level[0].forward : zslGetElementByRank(zsl, start+1);
|
841
|
+
}
|
842
|
+
|
843
|
+
/* Return the result in form of a multi-bulk reply */
|
844
|
+
addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen);
|
845
|
+
for (j = 0; j < rangelen; j++) {
|
846
|
+
ele = ln->obj;
|
847
|
+
addReplyBulk(c,ele);
|
848
|
+
if (withscores)
|
849
|
+
addReplyDouble(c,ln->score);
|
850
|
+
ln = reverse ? ln->backward : ln->level[0].forward;
|
851
|
+
}
|
852
|
+
}
|
853
|
+
|
854
|
+
void zrangeCommand(redisClient *c) {
|
855
|
+
zrangeGenericCommand(c,0);
|
856
|
+
}
|
857
|
+
|
858
|
+
void zrevrangeCommand(redisClient *c) {
|
859
|
+
zrangeGenericCommand(c,1);
|
860
|
+
}
|
861
|
+
|
862
|
+
/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE and ZCOUNT.
|
863
|
+
* If "justcount", only the number of elements in the range is returned. */
|
864
|
+
void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
|
865
|
+
zrangespec range;
|
866
|
+
robj *o, *emptyreply;
|
867
|
+
zset *zsetobj;
|
868
|
+
zskiplist *zsl;
|
869
|
+
zskiplistNode *ln;
|
870
|
+
int offset = 0, limit = -1;
|
871
|
+
int withscores = 0;
|
872
|
+
unsigned long rangelen = 0;
|
873
|
+
void *replylen = NULL;
|
874
|
+
|
875
|
+
/* Parse the range arguments. */
|
876
|
+
if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
|
877
|
+
addReplyError(c,"min or max is not a double");
|
878
|
+
return;
|
879
|
+
}
|
880
|
+
|
881
|
+
/* Parse optional extra arguments. Note that ZCOUNT will exactly have
|
882
|
+
* 4 arguments, so we'll never enter the following code path. */
|
883
|
+
if (c->argc > 4) {
|
884
|
+
int remaining = c->argc - 4;
|
885
|
+
int pos = 4;
|
886
|
+
|
887
|
+
while (remaining) {
|
888
|
+
if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) {
|
889
|
+
pos++; remaining--;
|
890
|
+
withscores = 1;
|
891
|
+
} else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
|
892
|
+
offset = atoi(c->argv[pos+1]->ptr);
|
893
|
+
limit = atoi(c->argv[pos+2]->ptr);
|
894
|
+
pos += 3; remaining -= 3;
|
895
|
+
} else {
|
896
|
+
addReply(c,shared.syntaxerr);
|
897
|
+
return;
|
898
|
+
}
|
899
|
+
}
|
900
|
+
}
|
901
|
+
|
902
|
+
/* Ok, lookup the key and get the range */
|
903
|
+
emptyreply = justcount ? shared.czero : shared.emptymultibulk;
|
904
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL ||
|
905
|
+
checkType(c,o,REDIS_ZSET)) return;
|
906
|
+
zsetobj = o->ptr;
|
907
|
+
zsl = zsetobj->zsl;
|
908
|
+
|
909
|
+
/* If reversed, assume the elements are sorted from high to low score. */
|
910
|
+
ln = zslFirstWithScore(zsl,range.min);
|
911
|
+
if (reverse) {
|
912
|
+
/* If range.min is out of range, ln will be NULL and we need to use
|
913
|
+
* the tail of the skiplist as first node of the range. */
|
914
|
+
if (ln == NULL) ln = zsl->tail;
|
915
|
+
|
916
|
+
/* zslFirstWithScore returns the first element with where with
|
917
|
+
* score >= range.min, so backtrack to make sure the element we use
|
918
|
+
* here has score <= range.min. */
|
919
|
+
while (ln && ln->score > range.min) ln = ln->backward;
|
920
|
+
|
921
|
+
/* Move to the right element according to the range spec. */
|
922
|
+
if (range.minex) {
|
923
|
+
/* Find last element with score < range.min */
|
924
|
+
while (ln && ln->score == range.min) ln = ln->backward;
|
925
|
+
} else {
|
926
|
+
/* Find last element with score <= range.min */
|
927
|
+
while (ln && ln->level[0].forward &&
|
928
|
+
ln->level[0].forward->score == range.min)
|
929
|
+
ln = ln->level[0].forward;
|
930
|
+
}
|
931
|
+
} else {
|
932
|
+
if (range.minex) {
|
933
|
+
/* Find first element with score > range.min */
|
934
|
+
while (ln && ln->score == range.min) ln = ln->level[0].forward;
|
935
|
+
}
|
936
|
+
}
|
937
|
+
|
938
|
+
/* No "first" element in the specified interval. */
|
939
|
+
if (ln == NULL) {
|
940
|
+
addReply(c,emptyreply);
|
941
|
+
return;
|
942
|
+
}
|
943
|
+
|
944
|
+
/* We don't know in advance how many matching elements there
|
945
|
+
* are in the list, so we push this object that will represent
|
946
|
+
* the multi-bulk length in the output buffer, and will "fix"
|
947
|
+
* it later */
|
948
|
+
if (!justcount)
|
949
|
+
replylen = addDeferredMultiBulkLength(c);
|
950
|
+
|
951
|
+
/* If there is an offset, just traverse the number of elements without
|
952
|
+
* checking the score because that is done in the next loop. */
|
953
|
+
while(ln && offset--) {
|
954
|
+
if (reverse)
|
955
|
+
ln = ln->backward;
|
956
|
+
else
|
957
|
+
ln = ln->level[0].forward;
|
958
|
+
}
|
959
|
+
|
960
|
+
while (ln && limit--) {
|
961
|
+
/* Check if this this element is in range. */
|
962
|
+
if (reverse) {
|
963
|
+
if (range.maxex) {
|
964
|
+
/* Element should have score > range.max */
|
965
|
+
if (ln->score <= range.max) break;
|
966
|
+
} else {
|
967
|
+
/* Element should have score >= range.max */
|
968
|
+
if (ln->score < range.max) break;
|
969
|
+
}
|
970
|
+
} else {
|
971
|
+
if (range.maxex) {
|
972
|
+
/* Element should have score < range.max */
|
973
|
+
if (ln->score >= range.max) break;
|
974
|
+
} else {
|
975
|
+
/* Element should have score <= range.max */
|
976
|
+
if (ln->score > range.max) break;
|
977
|
+
}
|
978
|
+
}
|
979
|
+
|
980
|
+
/* Do our magic */
|
981
|
+
rangelen++;
|
982
|
+
if (!justcount) {
|
983
|
+
addReplyBulk(c,ln->obj);
|
984
|
+
if (withscores)
|
985
|
+
addReplyDouble(c,ln->score);
|
986
|
+
}
|
987
|
+
|
988
|
+
if (reverse)
|
989
|
+
ln = ln->backward;
|
990
|
+
else
|
991
|
+
ln = ln->level[0].forward;
|
992
|
+
}
|
993
|
+
|
994
|
+
if (justcount) {
|
995
|
+
addReplyLongLong(c,(long)rangelen);
|
996
|
+
} else {
|
997
|
+
setDeferredMultiBulkLength(c,replylen,
|
998
|
+
withscores ? (rangelen*2) : rangelen);
|
999
|
+
}
|
1000
|
+
}
|
1001
|
+
|
1002
|
+
void zrangebyscoreCommand(redisClient *c) {
|
1003
|
+
genericZrangebyscoreCommand(c,0,0);
|
1004
|
+
}
|
1005
|
+
|
1006
|
+
void zrevrangebyscoreCommand(redisClient *c) {
|
1007
|
+
genericZrangebyscoreCommand(c,1,0);
|
1008
|
+
}
|
1009
|
+
|
1010
|
+
void zcountCommand(redisClient *c) {
|
1011
|
+
genericZrangebyscoreCommand(c,0,1);
|
1012
|
+
}
|
1013
|
+
|
1014
|
+
void zcardCommand(redisClient *c) {
|
1015
|
+
robj *o;
|
1016
|
+
zset *zs;
|
1017
|
+
|
1018
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
1019
|
+
checkType(c,o,REDIS_ZSET)) return;
|
1020
|
+
|
1021
|
+
zs = o->ptr;
|
1022
|
+
addReplyLongLong(c,zs->zsl->length);
|
1023
|
+
}
|
1024
|
+
|
1025
|
+
void zscoreCommand(redisClient *c) {
|
1026
|
+
robj *o;
|
1027
|
+
zset *zs;
|
1028
|
+
dictEntry *de;
|
1029
|
+
|
1030
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
1031
|
+
checkType(c,o,REDIS_ZSET)) return;
|
1032
|
+
|
1033
|
+
zs = o->ptr;
|
1034
|
+
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
1035
|
+
de = dictFind(zs->dict,c->argv[2]);
|
1036
|
+
if (!de) {
|
1037
|
+
addReply(c,shared.nullbulk);
|
1038
|
+
} else {
|
1039
|
+
double *score = dictGetEntryVal(de);
|
1040
|
+
|
1041
|
+
addReplyDouble(c,*score);
|
1042
|
+
}
|
1043
|
+
}
|
1044
|
+
|
1045
|
+
void zrankGenericCommand(redisClient *c, int reverse) {
|
1046
|
+
robj *o;
|
1047
|
+
zset *zs;
|
1048
|
+
zskiplist *zsl;
|
1049
|
+
dictEntry *de;
|
1050
|
+
unsigned long rank;
|
1051
|
+
double *score;
|
1052
|
+
|
1053
|
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
1054
|
+
checkType(c,o,REDIS_ZSET)) return;
|
1055
|
+
|
1056
|
+
zs = o->ptr;
|
1057
|
+
zsl = zs->zsl;
|
1058
|
+
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
1059
|
+
de = dictFind(zs->dict,c->argv[2]);
|
1060
|
+
if (!de) {
|
1061
|
+
addReply(c,shared.nullbulk);
|
1062
|
+
return;
|
1063
|
+
}
|
1064
|
+
|
1065
|
+
score = dictGetEntryVal(de);
|
1066
|
+
rank = zslGetRank(zsl, *score, c->argv[2]);
|
1067
|
+
if (rank) {
|
1068
|
+
if (reverse) {
|
1069
|
+
addReplyLongLong(c, zsl->length - rank);
|
1070
|
+
} else {
|
1071
|
+
addReplyLongLong(c, rank-1);
|
1072
|
+
}
|
1073
|
+
} else {
|
1074
|
+
addReply(c,shared.nullbulk);
|
1075
|
+
}
|
1076
|
+
}
|
1077
|
+
|
1078
|
+
void zrankCommand(redisClient *c) {
|
1079
|
+
zrankGenericCommand(c, 0);
|
1080
|
+
}
|
1081
|
+
|
1082
|
+
void zrevrankCommand(redisClient *c) {
|
1083
|
+
zrankGenericCommand(c, 1);
|
1084
|
+
}
|