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,314 @@
|
|
1
|
+
#include "redis.h"
|
2
|
+
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
|
3
|
+
|
4
|
+
#include <arpa/inet.h>
|
5
|
+
|
6
|
+
/* ================================= Debugging ============================== */
|
7
|
+
|
8
|
+
/* Compute the sha1 of string at 's' with 'len' bytes long.
|
9
|
+
* The SHA1 is then xored againt the string pointed by digest.
|
10
|
+
* Since xor is commutative, this operation is used in order to
|
11
|
+
* "add" digests relative to unordered elements.
|
12
|
+
*
|
13
|
+
* So digest(a,b,c,d) will be the same of digest(b,a,c,d) */
|
14
|
+
void xorDigest(unsigned char *digest, void *ptr, size_t len) {
|
15
|
+
SHA1_CTX ctx;
|
16
|
+
unsigned char hash[20], *s = ptr;
|
17
|
+
int j;
|
18
|
+
|
19
|
+
SHA1Init(&ctx);
|
20
|
+
SHA1Update(&ctx,s,len);
|
21
|
+
SHA1Final(hash,&ctx);
|
22
|
+
|
23
|
+
for (j = 0; j < 20; j++)
|
24
|
+
digest[j] ^= hash[j];
|
25
|
+
}
|
26
|
+
|
27
|
+
void xorObjectDigest(unsigned char *digest, robj *o) {
|
28
|
+
o = getDecodedObject(o);
|
29
|
+
xorDigest(digest,o->ptr,sdslen(o->ptr));
|
30
|
+
decrRefCount(o);
|
31
|
+
}
|
32
|
+
|
33
|
+
/* This function instead of just computing the SHA1 and xoring it
|
34
|
+
* against diget, also perform the digest of "digest" itself and
|
35
|
+
* replace the old value with the new one.
|
36
|
+
*
|
37
|
+
* So the final digest will be:
|
38
|
+
*
|
39
|
+
* digest = SHA1(digest xor SHA1(data))
|
40
|
+
*
|
41
|
+
* This function is used every time we want to preserve the order so
|
42
|
+
* that digest(a,b,c,d) will be different than digest(b,c,d,a)
|
43
|
+
*
|
44
|
+
* Also note that mixdigest("foo") followed by mixdigest("bar")
|
45
|
+
* will lead to a different digest compared to "fo", "obar".
|
46
|
+
*/
|
47
|
+
void mixDigest(unsigned char *digest, void *ptr, size_t len) {
|
48
|
+
SHA1_CTX ctx;
|
49
|
+
char *s = ptr;
|
50
|
+
|
51
|
+
xorDigest(digest,s,len);
|
52
|
+
SHA1Init(&ctx);
|
53
|
+
SHA1Update(&ctx,digest,20);
|
54
|
+
SHA1Final(digest,&ctx);
|
55
|
+
}
|
56
|
+
|
57
|
+
void mixObjectDigest(unsigned char *digest, robj *o) {
|
58
|
+
o = getDecodedObject(o);
|
59
|
+
mixDigest(digest,o->ptr,sdslen(o->ptr));
|
60
|
+
decrRefCount(o);
|
61
|
+
}
|
62
|
+
|
63
|
+
/* Compute the dataset digest. Since keys, sets elements, hashes elements
|
64
|
+
* are not ordered, we use a trick: every aggregate digest is the xor
|
65
|
+
* of the digests of their elements. This way the order will not change
|
66
|
+
* the result. For list instead we use a feedback entering the output digest
|
67
|
+
* as input in order to ensure that a different ordered list will result in
|
68
|
+
* a different digest. */
|
69
|
+
void computeDatasetDigest(unsigned char *final) {
|
70
|
+
unsigned char digest[20];
|
71
|
+
char buf[128];
|
72
|
+
dictIterator *di = NULL;
|
73
|
+
dictEntry *de;
|
74
|
+
int j;
|
75
|
+
uint32_t aux;
|
76
|
+
|
77
|
+
memset(final,0,20); /* Start with a clean result */
|
78
|
+
|
79
|
+
for (j = 0; j < server.dbnum; j++) {
|
80
|
+
redisDb *db = server.db+j;
|
81
|
+
|
82
|
+
if (dictSize(db->dict) == 0) continue;
|
83
|
+
di = dictGetSafeIterator(db->dict);
|
84
|
+
|
85
|
+
/* hash the DB id, so the same dataset moved in a different
|
86
|
+
* DB will lead to a different digest */
|
87
|
+
aux = htonl(j);
|
88
|
+
mixDigest(final,&aux,sizeof(aux));
|
89
|
+
|
90
|
+
/* Iterate this DB writing every entry */
|
91
|
+
while((de = dictNext(di)) != NULL) {
|
92
|
+
sds key;
|
93
|
+
robj *keyobj, *o;
|
94
|
+
time_t expiretime;
|
95
|
+
|
96
|
+
memset(digest,0,20); /* This key-val digest */
|
97
|
+
key = dictGetEntryKey(de);
|
98
|
+
keyobj = createStringObject(key,sdslen(key));
|
99
|
+
|
100
|
+
mixDigest(digest,key,sdslen(key));
|
101
|
+
|
102
|
+
/* Make sure the key is loaded if VM is active */
|
103
|
+
o = lookupKeyRead(db,keyobj);
|
104
|
+
if (o == NULL) {
|
105
|
+
/* Key expired on lookup? Try the next one. */
|
106
|
+
decrRefCount(keyobj);
|
107
|
+
continue;
|
108
|
+
}
|
109
|
+
|
110
|
+
aux = htonl(o->type);
|
111
|
+
mixDigest(digest,&aux,sizeof(aux));
|
112
|
+
expiretime = getExpire(db,keyobj);
|
113
|
+
|
114
|
+
/* Save the key and associated value */
|
115
|
+
if (o->type == REDIS_STRING) {
|
116
|
+
mixObjectDigest(digest,o);
|
117
|
+
} else if (o->type == REDIS_LIST) {
|
118
|
+
listTypeIterator *li = listTypeInitIterator(o,0,REDIS_TAIL);
|
119
|
+
listTypeEntry entry;
|
120
|
+
while(listTypeNext(li,&entry)) {
|
121
|
+
robj *eleobj = listTypeGet(&entry);
|
122
|
+
mixObjectDigest(digest,eleobj);
|
123
|
+
decrRefCount(eleobj);
|
124
|
+
}
|
125
|
+
listTypeReleaseIterator(li);
|
126
|
+
} else if (o->type == REDIS_SET) {
|
127
|
+
setTypeIterator *si = setTypeInitIterator(o);
|
128
|
+
robj *ele;
|
129
|
+
while((ele = setTypeNextObject(si)) != NULL) {
|
130
|
+
xorObjectDigest(digest,ele);
|
131
|
+
decrRefCount(ele);
|
132
|
+
}
|
133
|
+
setTypeReleaseIterator(si);
|
134
|
+
} else if (o->type == REDIS_ZSET) {
|
135
|
+
zset *zs = o->ptr;
|
136
|
+
dictIterator *di = dictGetIterator(zs->dict);
|
137
|
+
dictEntry *de;
|
138
|
+
|
139
|
+
while((de = dictNext(di)) != NULL) {
|
140
|
+
robj *eleobj = dictGetEntryKey(de);
|
141
|
+
double *score = dictGetEntryVal(de);
|
142
|
+
unsigned char eledigest[20];
|
143
|
+
|
144
|
+
snprintf(buf,sizeof(buf),"%.17g",*score);
|
145
|
+
memset(eledigest,0,20);
|
146
|
+
mixObjectDigest(eledigest,eleobj);
|
147
|
+
mixDigest(eledigest,buf,strlen(buf));
|
148
|
+
xorDigest(digest,eledigest,20);
|
149
|
+
}
|
150
|
+
dictReleaseIterator(di);
|
151
|
+
} else if (o->type == REDIS_HASH) {
|
152
|
+
hashTypeIterator *hi;
|
153
|
+
robj *obj;
|
154
|
+
|
155
|
+
hi = hashTypeInitIterator(o);
|
156
|
+
while (hashTypeNext(hi) != REDIS_ERR) {
|
157
|
+
unsigned char eledigest[20];
|
158
|
+
|
159
|
+
memset(eledigest,0,20);
|
160
|
+
obj = hashTypeCurrentObject(hi,REDIS_HASH_KEY);
|
161
|
+
mixObjectDigest(eledigest,obj);
|
162
|
+
decrRefCount(obj);
|
163
|
+
obj = hashTypeCurrentObject(hi,REDIS_HASH_VALUE);
|
164
|
+
mixObjectDigest(eledigest,obj);
|
165
|
+
decrRefCount(obj);
|
166
|
+
xorDigest(digest,eledigest,20);
|
167
|
+
}
|
168
|
+
hashTypeReleaseIterator(hi);
|
169
|
+
} else {
|
170
|
+
redisPanic("Unknown object type");
|
171
|
+
}
|
172
|
+
/* If the key has an expire, add it to the mix */
|
173
|
+
if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
|
174
|
+
/* We can finally xor the key-val digest to the final digest */
|
175
|
+
xorDigest(final,digest,20);
|
176
|
+
decrRefCount(keyobj);
|
177
|
+
}
|
178
|
+
dictReleaseIterator(di);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
void debugCommand(redisClient *c) {
|
183
|
+
if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
184
|
+
*((char*)-1) = 'x';
|
185
|
+
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
186
|
+
if (rdbSave(server.dbfilename) != REDIS_OK) {
|
187
|
+
addReply(c,shared.err);
|
188
|
+
return;
|
189
|
+
}
|
190
|
+
emptyDb();
|
191
|
+
if (rdbLoad(server.dbfilename) != REDIS_OK) {
|
192
|
+
addReply(c,shared.err);
|
193
|
+
return;
|
194
|
+
}
|
195
|
+
redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD");
|
196
|
+
addReply(c,shared.ok);
|
197
|
+
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
|
198
|
+
emptyDb();
|
199
|
+
if (loadAppendOnlyFile(server.appendfilename) != REDIS_OK) {
|
200
|
+
addReply(c,shared.err);
|
201
|
+
return;
|
202
|
+
}
|
203
|
+
redisLog(REDIS_WARNING,"Append Only File loaded by DEBUG LOADAOF");
|
204
|
+
addReply(c,shared.ok);
|
205
|
+
} else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) {
|
206
|
+
dictEntry *de = dictFind(c->db->dict,c->argv[2]->ptr);
|
207
|
+
robj *val;
|
208
|
+
|
209
|
+
if (!de) {
|
210
|
+
addReply(c,shared.nokeyerr);
|
211
|
+
return;
|
212
|
+
}
|
213
|
+
val = dictGetEntryVal(de);
|
214
|
+
if (!server.vm_enabled || (val->storage == REDIS_VM_MEMORY ||
|
215
|
+
val->storage == REDIS_VM_SWAPPING)) {
|
216
|
+
char *strenc;
|
217
|
+
|
218
|
+
strenc = strEncoding(val->encoding);
|
219
|
+
addReplyStatusFormat(c,
|
220
|
+
"Value at:%p refcount:%d "
|
221
|
+
"encoding:%s serializedlength:%lld "
|
222
|
+
"lru:%d lru_seconds_idle:%lu",
|
223
|
+
(void*)val, val->refcount,
|
224
|
+
strenc, (long long) rdbSavedObjectLen(val),
|
225
|
+
val->lru, estimateObjectIdleTime(val));
|
226
|
+
} else {
|
227
|
+
vmpointer *vp = (vmpointer*) val;
|
228
|
+
addReplyStatusFormat(c,
|
229
|
+
"Value swapped at: page %llu "
|
230
|
+
"using %llu pages",
|
231
|
+
(unsigned long long) vp->page,
|
232
|
+
(unsigned long long) vp->usedpages);
|
233
|
+
}
|
234
|
+
} else if (!strcasecmp(c->argv[1]->ptr,"swapin") && c->argc == 3) {
|
235
|
+
lookupKeyRead(c->db,c->argv[2]);
|
236
|
+
addReply(c,shared.ok);
|
237
|
+
} else if (!strcasecmp(c->argv[1]->ptr,"swapout") && c->argc == 3) {
|
238
|
+
dictEntry *de = dictFind(c->db->dict,c->argv[2]->ptr);
|
239
|
+
robj *val;
|
240
|
+
vmpointer *vp;
|
241
|
+
|
242
|
+
if (!server.vm_enabled) {
|
243
|
+
addReplyError(c,"Virtual Memory is disabled");
|
244
|
+
return;
|
245
|
+
}
|
246
|
+
if (!de) {
|
247
|
+
addReply(c,shared.nokeyerr);
|
248
|
+
return;
|
249
|
+
}
|
250
|
+
val = dictGetEntryVal(de);
|
251
|
+
/* Swap it */
|
252
|
+
if (val->storage != REDIS_VM_MEMORY) {
|
253
|
+
addReplyError(c,"This key is not in memory");
|
254
|
+
} else if (val->refcount != 1) {
|
255
|
+
addReplyError(c,"Object is shared");
|
256
|
+
} else if ((vp = vmSwapObjectBlocking(val)) != NULL) {
|
257
|
+
dictGetEntryVal(de) = vp;
|
258
|
+
addReply(c,shared.ok);
|
259
|
+
} else {
|
260
|
+
addReply(c,shared.err);
|
261
|
+
}
|
262
|
+
} else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) {
|
263
|
+
long keys, j;
|
264
|
+
robj *key, *val;
|
265
|
+
char buf[128];
|
266
|
+
|
267
|
+
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK)
|
268
|
+
return;
|
269
|
+
for (j = 0; j < keys; j++) {
|
270
|
+
snprintf(buf,sizeof(buf),"key:%lu",j);
|
271
|
+
key = createStringObject(buf,strlen(buf));
|
272
|
+
if (lookupKeyRead(c->db,key) != NULL) {
|
273
|
+
decrRefCount(key);
|
274
|
+
continue;
|
275
|
+
}
|
276
|
+
snprintf(buf,sizeof(buf),"value:%lu",j);
|
277
|
+
val = createStringObject(buf,strlen(buf));
|
278
|
+
dbAdd(c->db,key,val);
|
279
|
+
decrRefCount(key);
|
280
|
+
}
|
281
|
+
addReply(c,shared.ok);
|
282
|
+
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
|
283
|
+
unsigned char digest[20];
|
284
|
+
sds d = sdsempty();
|
285
|
+
int j;
|
286
|
+
|
287
|
+
computeDatasetDigest(digest);
|
288
|
+
for (j = 0; j < 20; j++)
|
289
|
+
d = sdscatprintf(d, "%02x",digest[j]);
|
290
|
+
addReplyStatus(c,d);
|
291
|
+
sdsfree(d);
|
292
|
+
} else {
|
293
|
+
addReplyError(c,
|
294
|
+
"Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
|
295
|
+
}
|
296
|
+
}
|
297
|
+
|
298
|
+
void _redisAssert(char *estr, char *file, int line) {
|
299
|
+
redisLog(REDIS_WARNING,"=== ASSERTION FAILED ===");
|
300
|
+
redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
|
301
|
+
#ifdef HAVE_BACKTRACE
|
302
|
+
redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)");
|
303
|
+
*((char*)-1) = 'x';
|
304
|
+
#endif
|
305
|
+
}
|
306
|
+
|
307
|
+
void _redisPanic(char *msg, char *file, int line) {
|
308
|
+
redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue");
|
309
|
+
redisLog(REDIS_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line);
|
310
|
+
#ifdef HAVE_BACKTRACE
|
311
|
+
redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)");
|
312
|
+
*((char*)-1) = 'x';
|
313
|
+
#endif
|
314
|
+
}
|
@@ -0,0 +1,721 @@
|
|
1
|
+
/* Hash Tables Implementation.
|
2
|
+
*
|
3
|
+
* This file implements in memory hash tables with insert/del/replace/find/
|
4
|
+
* get-random-element operations. Hash tables will auto resize if needed
|
5
|
+
* tables of power of two in size are used, collisions are handled by
|
6
|
+
* chaining. See the source code for more information... :)
|
7
|
+
*
|
8
|
+
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
9
|
+
* All rights reserved.
|
10
|
+
*
|
11
|
+
* Redistribution and use in source and binary forms, with or without
|
12
|
+
* modification, are permitted provided that the following conditions are met:
|
13
|
+
*
|
14
|
+
* * Redistributions of source code must retain the above copyright notice,
|
15
|
+
* this list of conditions and the following disclaimer.
|
16
|
+
* * Redistributions in binary form must reproduce the above copyright
|
17
|
+
* notice, this list of conditions and the following disclaimer in the
|
18
|
+
* documentation and/or other materials provided with the distribution.
|
19
|
+
* * Neither the name of Redis nor the names of its contributors may be used
|
20
|
+
* to endorse or promote products derived from this software without
|
21
|
+
* specific prior written permission.
|
22
|
+
*
|
23
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33
|
+
* POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
*/
|
35
|
+
|
36
|
+
#include "fmacros.h"
|
37
|
+
|
38
|
+
#include <stdio.h>
|
39
|
+
#include <stdlib.h>
|
40
|
+
#include <string.h>
|
41
|
+
#include <stdarg.h>
|
42
|
+
#include <assert.h>
|
43
|
+
#include <limits.h>
|
44
|
+
#include <sys/time.h>
|
45
|
+
#include <ctype.h>
|
46
|
+
|
47
|
+
#include "dict.h"
|
48
|
+
#include "zmalloc.h"
|
49
|
+
|
50
|
+
/* Using dictEnableResize() / dictDisableResize() we make possible to
|
51
|
+
* enable/disable resizing of the hash table as needed. This is very important
|
52
|
+
* for Redis, as we use copy-on-write and don't want to move too much memory
|
53
|
+
* around when there is a child performing saving operations.
|
54
|
+
*
|
55
|
+
* Note that even when dict_can_resize is set to 0, not all resizes are
|
56
|
+
* prevented: an hash table is still allowed to grow if the ratio between
|
57
|
+
* the number of elements and the buckets > dict_force_resize_ratio. */
|
58
|
+
static int dict_can_resize = 1;
|
59
|
+
static unsigned int dict_force_resize_ratio = 5;
|
60
|
+
|
61
|
+
/* -------------------------- private prototypes ---------------------------- */
|
62
|
+
|
63
|
+
static int _dictExpandIfNeeded(dict *ht);
|
64
|
+
static unsigned long _dictNextPower(unsigned long size);
|
65
|
+
static int _dictKeyIndex(dict *ht, const void *key);
|
66
|
+
static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
|
67
|
+
|
68
|
+
/* -------------------------- hash functions -------------------------------- */
|
69
|
+
|
70
|
+
/* Thomas Wang's 32 bit Mix Function */
|
71
|
+
unsigned int dictIntHashFunction(unsigned int key)
|
72
|
+
{
|
73
|
+
key += ~(key << 15);
|
74
|
+
key ^= (key >> 10);
|
75
|
+
key += (key << 3);
|
76
|
+
key ^= (key >> 6);
|
77
|
+
key += ~(key << 11);
|
78
|
+
key ^= (key >> 16);
|
79
|
+
return key;
|
80
|
+
}
|
81
|
+
|
82
|
+
/* Identity hash function for integer keys */
|
83
|
+
unsigned int dictIdentityHashFunction(unsigned int key)
|
84
|
+
{
|
85
|
+
return key;
|
86
|
+
}
|
87
|
+
|
88
|
+
/* Generic hash function (a popular one from Bernstein).
|
89
|
+
* I tested a few and this was the best. */
|
90
|
+
unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
|
91
|
+
unsigned int hash = 5381;
|
92
|
+
|
93
|
+
while (len--)
|
94
|
+
hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
|
95
|
+
return hash;
|
96
|
+
}
|
97
|
+
|
98
|
+
/* And a case insensitive version */
|
99
|
+
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len) {
|
100
|
+
unsigned int hash = 5381;
|
101
|
+
|
102
|
+
while (len--)
|
103
|
+
hash = ((hash << 5) + hash) + (tolower(*buf++)); /* hash * 33 + c */
|
104
|
+
return hash;
|
105
|
+
}
|
106
|
+
|
107
|
+
/* ----------------------------- API implementation ------------------------- */
|
108
|
+
|
109
|
+
/* Reset an hashtable already initialized with ht_init().
|
110
|
+
* NOTE: This function should only called by ht_destroy(). */
|
111
|
+
static void _dictReset(dictht *ht)
|
112
|
+
{
|
113
|
+
ht->table = NULL;
|
114
|
+
ht->size = 0;
|
115
|
+
ht->sizemask = 0;
|
116
|
+
ht->used = 0;
|
117
|
+
}
|
118
|
+
|
119
|
+
/* Create a new hash table */
|
120
|
+
dict *dictCreate(dictType *type,
|
121
|
+
void *privDataPtr)
|
122
|
+
{
|
123
|
+
dict *d = zmalloc(sizeof(*d));
|
124
|
+
|
125
|
+
_dictInit(d,type,privDataPtr);
|
126
|
+
return d;
|
127
|
+
}
|
128
|
+
|
129
|
+
/* Initialize the hash table */
|
130
|
+
int _dictInit(dict *d, dictType *type,
|
131
|
+
void *privDataPtr)
|
132
|
+
{
|
133
|
+
_dictReset(&d->ht[0]);
|
134
|
+
_dictReset(&d->ht[1]);
|
135
|
+
d->type = type;
|
136
|
+
d->privdata = privDataPtr;
|
137
|
+
d->rehashidx = -1;
|
138
|
+
d->iterators = 0;
|
139
|
+
return DICT_OK;
|
140
|
+
}
|
141
|
+
|
142
|
+
/* Resize the table to the minimal size that contains all the elements,
|
143
|
+
* but with the invariant of a USER/BUCKETS ratio near to <= 1 */
|
144
|
+
int dictResize(dict *d)
|
145
|
+
{
|
146
|
+
int minimal;
|
147
|
+
|
148
|
+
if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
|
149
|
+
minimal = d->ht[0].used;
|
150
|
+
if (minimal < DICT_HT_INITIAL_SIZE)
|
151
|
+
minimal = DICT_HT_INITIAL_SIZE;
|
152
|
+
return dictExpand(d, minimal);
|
153
|
+
}
|
154
|
+
|
155
|
+
/* Expand or create the hashtable */
|
156
|
+
int dictExpand(dict *d, unsigned long size)
|
157
|
+
{
|
158
|
+
dictht n; /* the new hashtable */
|
159
|
+
unsigned long realsize = _dictNextPower(size);
|
160
|
+
|
161
|
+
/* the size is invalid if it is smaller than the number of
|
162
|
+
* elements already inside the hashtable */
|
163
|
+
if (dictIsRehashing(d) || d->ht[0].used > size)
|
164
|
+
return DICT_ERR;
|
165
|
+
|
166
|
+
/* Allocate the new hashtable and initialize all pointers to NULL */
|
167
|
+
n.size = realsize;
|
168
|
+
n.sizemask = realsize-1;
|
169
|
+
n.table = zcalloc(realsize*sizeof(dictEntry*));
|
170
|
+
n.used = 0;
|
171
|
+
|
172
|
+
/* Is this the first initialization? If so it's not really a rehashing
|
173
|
+
* we just set the first hash table so that it can accept keys. */
|
174
|
+
if (d->ht[0].table == NULL) {
|
175
|
+
d->ht[0] = n;
|
176
|
+
return DICT_OK;
|
177
|
+
}
|
178
|
+
|
179
|
+
/* Prepare a second hash table for incremental rehashing */
|
180
|
+
d->ht[1] = n;
|
181
|
+
d->rehashidx = 0;
|
182
|
+
return DICT_OK;
|
183
|
+
}
|
184
|
+
|
185
|
+
/* Performs N steps of incremental rehashing. Returns 1 if there are still
|
186
|
+
* keys to move from the old to the new hash table, otherwise 0 is returned.
|
187
|
+
* Note that a rehashing step consists in moving a bucket (that may have more
|
188
|
+
* thank one key as we use chaining) from the old to the new hash table. */
|
189
|
+
int dictRehash(dict *d, int n) {
|
190
|
+
if (!dictIsRehashing(d)) return 0;
|
191
|
+
|
192
|
+
while(n--) {
|
193
|
+
dictEntry *de, *nextde;
|
194
|
+
|
195
|
+
/* Check if we already rehashed the whole table... */
|
196
|
+
if (d->ht[0].used == 0) {
|
197
|
+
zfree(d->ht[0].table);
|
198
|
+
d->ht[0] = d->ht[1];
|
199
|
+
_dictReset(&d->ht[1]);
|
200
|
+
d->rehashidx = -1;
|
201
|
+
return 0;
|
202
|
+
}
|
203
|
+
|
204
|
+
/* Note that rehashidx can't overflow as we are sure there are more
|
205
|
+
* elements because ht[0].used != 0 */
|
206
|
+
while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
|
207
|
+
de = d->ht[0].table[d->rehashidx];
|
208
|
+
/* Move all the keys in this bucket from the old to the new hash HT */
|
209
|
+
while(de) {
|
210
|
+
unsigned int h;
|
211
|
+
|
212
|
+
nextde = de->next;
|
213
|
+
/* Get the index in the new hash table */
|
214
|
+
h = dictHashKey(d, de->key) & d->ht[1].sizemask;
|
215
|
+
de->next = d->ht[1].table[h];
|
216
|
+
d->ht[1].table[h] = de;
|
217
|
+
d->ht[0].used--;
|
218
|
+
d->ht[1].used++;
|
219
|
+
de = nextde;
|
220
|
+
}
|
221
|
+
d->ht[0].table[d->rehashidx] = NULL;
|
222
|
+
d->rehashidx++;
|
223
|
+
}
|
224
|
+
return 1;
|
225
|
+
}
|
226
|
+
|
227
|
+
long long timeInMilliseconds(void) {
|
228
|
+
struct timeval tv;
|
229
|
+
|
230
|
+
gettimeofday(&tv,NULL);
|
231
|
+
return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000);
|
232
|
+
}
|
233
|
+
|
234
|
+
/* Rehash for an amount of time between ms milliseconds and ms+1 milliseconds */
|
235
|
+
int dictRehashMilliseconds(dict *d, int ms) {
|
236
|
+
long long start = timeInMilliseconds();
|
237
|
+
int rehashes = 0;
|
238
|
+
|
239
|
+
while(dictRehash(d,100)) {
|
240
|
+
rehashes += 100;
|
241
|
+
if (timeInMilliseconds()-start > ms) break;
|
242
|
+
}
|
243
|
+
return rehashes;
|
244
|
+
}
|
245
|
+
|
246
|
+
/* This function performs just a step of rehashing, and only if there are
|
247
|
+
* no safe iterators bound to our hash table. When we have iterators in the
|
248
|
+
* middle of a rehashing we can't mess with the two hash tables otherwise
|
249
|
+
* some element can be missed or duplicated.
|
250
|
+
*
|
251
|
+
* This function is called by common lookup or update operations in the
|
252
|
+
* dictionary so that the hash table automatically migrates from H1 to H2
|
253
|
+
* while it is actively used. */
|
254
|
+
static void _dictRehashStep(dict *d) {
|
255
|
+
if (d->iterators == 0) dictRehash(d,1);
|
256
|
+
}
|
257
|
+
|
258
|
+
/* Add an element to the target hash table */
|
259
|
+
int dictAdd(dict *d, void *key, void *val)
|
260
|
+
{
|
261
|
+
int index;
|
262
|
+
dictEntry *entry;
|
263
|
+
dictht *ht;
|
264
|
+
|
265
|
+
if (dictIsRehashing(d)) _dictRehashStep(d);
|
266
|
+
|
267
|
+
/* Get the index of the new element, or -1 if
|
268
|
+
* the element already exists. */
|
269
|
+
if ((index = _dictKeyIndex(d, key)) == -1)
|
270
|
+
return DICT_ERR;
|
271
|
+
|
272
|
+
/* Allocates the memory and stores key */
|
273
|
+
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
|
274
|
+
entry = zmalloc(sizeof(*entry));
|
275
|
+
entry->next = ht->table[index];
|
276
|
+
ht->table[index] = entry;
|
277
|
+
ht->used++;
|
278
|
+
|
279
|
+
/* Set the hash entry fields. */
|
280
|
+
dictSetHashKey(d, entry, key);
|
281
|
+
dictSetHashVal(d, entry, val);
|
282
|
+
return DICT_OK;
|
283
|
+
}
|
284
|
+
|
285
|
+
/* Add an element, discarding the old if the key already exists.
|
286
|
+
* Return 1 if the key was added from scratch, 0 if there was already an
|
287
|
+
* element with such key and dictReplace() just performed a value update
|
288
|
+
* operation. */
|
289
|
+
int dictReplace(dict *d, void *key, void *val)
|
290
|
+
{
|
291
|
+
dictEntry *entry, auxentry;
|
292
|
+
|
293
|
+
/* Try to add the element. If the key
|
294
|
+
* does not exists dictAdd will suceed. */
|
295
|
+
if (dictAdd(d, key, val) == DICT_OK)
|
296
|
+
return 1;
|
297
|
+
/* It already exists, get the entry */
|
298
|
+
entry = dictFind(d, key);
|
299
|
+
/* Free the old value and set the new one */
|
300
|
+
/* Set the new value and free the old one. Note that it is important
|
301
|
+
* to do that in this order, as the value may just be exactly the same
|
302
|
+
* as the previous one. In this context, think to reference counting,
|
303
|
+
* you want to increment (set), and then decrement (free), and not the
|
304
|
+
* reverse. */
|
305
|
+
auxentry = *entry;
|
306
|
+
dictSetHashVal(d, entry, val);
|
307
|
+
dictFreeEntryVal(d, &auxentry);
|
308
|
+
return 0;
|
309
|
+
}
|
310
|
+
|
311
|
+
/* Search and remove an element */
|
312
|
+
static int dictGenericDelete(dict *d, const void *key, int nofree)
|
313
|
+
{
|
314
|
+
unsigned int h, idx;
|
315
|
+
dictEntry *he, *prevHe;
|
316
|
+
int table;
|
317
|
+
|
318
|
+
if (d->ht[0].size == 0) return DICT_ERR; /* d->ht[0].table is NULL */
|
319
|
+
if (dictIsRehashing(d)) _dictRehashStep(d);
|
320
|
+
h = dictHashKey(d, key);
|
321
|
+
|
322
|
+
for (table = 0; table <= 1; table++) {
|
323
|
+
idx = h & d->ht[table].sizemask;
|
324
|
+
he = d->ht[table].table[idx];
|
325
|
+
prevHe = NULL;
|
326
|
+
while(he) {
|
327
|
+
if (dictCompareHashKeys(d, key, he->key)) {
|
328
|
+
/* Unlink the element from the list */
|
329
|
+
if (prevHe)
|
330
|
+
prevHe->next = he->next;
|
331
|
+
else
|
332
|
+
d->ht[table].table[idx] = he->next;
|
333
|
+
if (!nofree) {
|
334
|
+
dictFreeEntryKey(d, he);
|
335
|
+
dictFreeEntryVal(d, he);
|
336
|
+
}
|
337
|
+
zfree(he);
|
338
|
+
d->ht[table].used--;
|
339
|
+
return DICT_OK;
|
340
|
+
}
|
341
|
+
prevHe = he;
|
342
|
+
he = he->next;
|
343
|
+
}
|
344
|
+
if (!dictIsRehashing(d)) break;
|
345
|
+
}
|
346
|
+
return DICT_ERR; /* not found */
|
347
|
+
}
|
348
|
+
|
349
|
+
int dictDelete(dict *ht, const void *key) {
|
350
|
+
return dictGenericDelete(ht,key,0);
|
351
|
+
}
|
352
|
+
|
353
|
+
int dictDeleteNoFree(dict *ht, const void *key) {
|
354
|
+
return dictGenericDelete(ht,key,1);
|
355
|
+
}
|
356
|
+
|
357
|
+
/* Destroy an entire dictionary */
|
358
|
+
int _dictClear(dict *d, dictht *ht)
|
359
|
+
{
|
360
|
+
unsigned long i;
|
361
|
+
|
362
|
+
/* Free all the elements */
|
363
|
+
for (i = 0; i < ht->size && ht->used > 0; i++) {
|
364
|
+
dictEntry *he, *nextHe;
|
365
|
+
|
366
|
+
if ((he = ht->table[i]) == NULL) continue;
|
367
|
+
while(he) {
|
368
|
+
nextHe = he->next;
|
369
|
+
dictFreeEntryKey(d, he);
|
370
|
+
dictFreeEntryVal(d, he);
|
371
|
+
zfree(he);
|
372
|
+
ht->used--;
|
373
|
+
he = nextHe;
|
374
|
+
}
|
375
|
+
}
|
376
|
+
/* Free the table and the allocated cache structure */
|
377
|
+
zfree(ht->table);
|
378
|
+
/* Re-initialize the table */
|
379
|
+
_dictReset(ht);
|
380
|
+
return DICT_OK; /* never fails */
|
381
|
+
}
|
382
|
+
|
383
|
+
/* Clear & Release the hash table */
|
384
|
+
void dictRelease(dict *d)
|
385
|
+
{
|
386
|
+
_dictClear(d,&d->ht[0]);
|
387
|
+
_dictClear(d,&d->ht[1]);
|
388
|
+
zfree(d);
|
389
|
+
}
|
390
|
+
|
391
|
+
dictEntry *dictFind(dict *d, const void *key)
|
392
|
+
{
|
393
|
+
dictEntry *he;
|
394
|
+
unsigned int h, idx, table;
|
395
|
+
|
396
|
+
if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */
|
397
|
+
if (dictIsRehashing(d)) _dictRehashStep(d);
|
398
|
+
h = dictHashKey(d, key);
|
399
|
+
for (table = 0; table <= 1; table++) {
|
400
|
+
idx = h & d->ht[table].sizemask;
|
401
|
+
he = d->ht[table].table[idx];
|
402
|
+
while(he) {
|
403
|
+
if (dictCompareHashKeys(d, key, he->key))
|
404
|
+
return he;
|
405
|
+
he = he->next;
|
406
|
+
}
|
407
|
+
if (!dictIsRehashing(d)) return NULL;
|
408
|
+
}
|
409
|
+
return NULL;
|
410
|
+
}
|
411
|
+
|
412
|
+
void *dictFetchValue(dict *d, const void *key) {
|
413
|
+
dictEntry *he;
|
414
|
+
|
415
|
+
he = dictFind(d,key);
|
416
|
+
return he ? dictGetEntryVal(he) : NULL;
|
417
|
+
}
|
418
|
+
|
419
|
+
dictIterator *dictGetIterator(dict *d)
|
420
|
+
{
|
421
|
+
dictIterator *iter = zmalloc(sizeof(*iter));
|
422
|
+
|
423
|
+
iter->d = d;
|
424
|
+
iter->table = 0;
|
425
|
+
iter->index = -1;
|
426
|
+
iter->safe = 0;
|
427
|
+
iter->entry = NULL;
|
428
|
+
iter->nextEntry = NULL;
|
429
|
+
return iter;
|
430
|
+
}
|
431
|
+
|
432
|
+
dictIterator *dictGetSafeIterator(dict *d) {
|
433
|
+
dictIterator *i = dictGetIterator(d);
|
434
|
+
|
435
|
+
i->safe = 1;
|
436
|
+
return i;
|
437
|
+
}
|
438
|
+
|
439
|
+
dictEntry *dictNext(dictIterator *iter)
|
440
|
+
{
|
441
|
+
while (1) {
|
442
|
+
if (iter->entry == NULL) {
|
443
|
+
dictht *ht = &iter->d->ht[iter->table];
|
444
|
+
if (iter->safe && iter->index == -1 && iter->table == 0)
|
445
|
+
iter->d->iterators++;
|
446
|
+
iter->index++;
|
447
|
+
if (iter->index >= (signed) ht->size) {
|
448
|
+
if (dictIsRehashing(iter->d) && iter->table == 0) {
|
449
|
+
iter->table++;
|
450
|
+
iter->index = 0;
|
451
|
+
ht = &iter->d->ht[1];
|
452
|
+
} else {
|
453
|
+
break;
|
454
|
+
}
|
455
|
+
}
|
456
|
+
iter->entry = ht->table[iter->index];
|
457
|
+
} else {
|
458
|
+
iter->entry = iter->nextEntry;
|
459
|
+
}
|
460
|
+
if (iter->entry) {
|
461
|
+
/* We need to save the 'next' here, the iterator user
|
462
|
+
* may delete the entry we are returning. */
|
463
|
+
iter->nextEntry = iter->entry->next;
|
464
|
+
return iter->entry;
|
465
|
+
}
|
466
|
+
}
|
467
|
+
return NULL;
|
468
|
+
}
|
469
|
+
|
470
|
+
void dictReleaseIterator(dictIterator *iter)
|
471
|
+
{
|
472
|
+
if (iter->safe && !(iter->index == -1 && iter->table == 0))
|
473
|
+
iter->d->iterators--;
|
474
|
+
zfree(iter);
|
475
|
+
}
|
476
|
+
|
477
|
+
/* Return a random entry from the hash table. Useful to
|
478
|
+
* implement randomized algorithms */
|
479
|
+
dictEntry *dictGetRandomKey(dict *d)
|
480
|
+
{
|
481
|
+
dictEntry *he, *orighe;
|
482
|
+
unsigned int h;
|
483
|
+
int listlen, listele;
|
484
|
+
|
485
|
+
if (dictSize(d) == 0) return NULL;
|
486
|
+
if (dictIsRehashing(d)) _dictRehashStep(d);
|
487
|
+
if (dictIsRehashing(d)) {
|
488
|
+
do {
|
489
|
+
h = random() % (d->ht[0].size+d->ht[1].size);
|
490
|
+
he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
|
491
|
+
d->ht[0].table[h];
|
492
|
+
} while(he == NULL);
|
493
|
+
} else {
|
494
|
+
do {
|
495
|
+
h = random() & d->ht[0].sizemask;
|
496
|
+
he = d->ht[0].table[h];
|
497
|
+
} while(he == NULL);
|
498
|
+
}
|
499
|
+
|
500
|
+
/* Now we found a non empty bucket, but it is a linked
|
501
|
+
* list and we need to get a random element from the list.
|
502
|
+
* The only sane way to do so is counting the elements and
|
503
|
+
* select a random index. */
|
504
|
+
listlen = 0;
|
505
|
+
orighe = he;
|
506
|
+
while(he) {
|
507
|
+
he = he->next;
|
508
|
+
listlen++;
|
509
|
+
}
|
510
|
+
listele = random() % listlen;
|
511
|
+
he = orighe;
|
512
|
+
while(listele--) he = he->next;
|
513
|
+
return he;
|
514
|
+
}
|
515
|
+
|
516
|
+
/* ------------------------- private functions ------------------------------ */
|
517
|
+
|
518
|
+
/* Expand the hash table if needed */
|
519
|
+
static int _dictExpandIfNeeded(dict *d)
|
520
|
+
{
|
521
|
+
/* Incremental rehashing already in progress. Return. */
|
522
|
+
if (dictIsRehashing(d)) return DICT_OK;
|
523
|
+
|
524
|
+
/* If the hash table is empty expand it to the intial size. */
|
525
|
+
if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
|
526
|
+
|
527
|
+
/* If we reached the 1:1 ratio, and we are allowed to resize the hash
|
528
|
+
* table (global setting) or we should avoid it but the ratio between
|
529
|
+
* elements/buckets is over the "safe" threshold, we resize doubling
|
530
|
+
* the number of buckets. */
|
531
|
+
if (d->ht[0].used >= d->ht[0].size &&
|
532
|
+
(dict_can_resize ||
|
533
|
+
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
|
534
|
+
{
|
535
|
+
return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?
|
536
|
+
d->ht[0].size : d->ht[0].used)*2);
|
537
|
+
}
|
538
|
+
return DICT_OK;
|
539
|
+
}
|
540
|
+
|
541
|
+
/* Our hash table capability is a power of two */
|
542
|
+
static unsigned long _dictNextPower(unsigned long size)
|
543
|
+
{
|
544
|
+
unsigned long i = DICT_HT_INITIAL_SIZE;
|
545
|
+
|
546
|
+
if (size >= LONG_MAX) return LONG_MAX;
|
547
|
+
while(1) {
|
548
|
+
if (i >= size)
|
549
|
+
return i;
|
550
|
+
i *= 2;
|
551
|
+
}
|
552
|
+
}
|
553
|
+
|
554
|
+
/* Returns the index of a free slot that can be populated with
|
555
|
+
* an hash entry for the given 'key'.
|
556
|
+
* If the key already exists, -1 is returned.
|
557
|
+
*
|
558
|
+
* Note that if we are in the process of rehashing the hash table, the
|
559
|
+
* index is always returned in the context of the second (new) hash table. */
|
560
|
+
static int _dictKeyIndex(dict *d, const void *key)
|
561
|
+
{
|
562
|
+
unsigned int h, idx, table;
|
563
|
+
dictEntry *he;
|
564
|
+
|
565
|
+
/* Expand the hashtable if needed */
|
566
|
+
if (_dictExpandIfNeeded(d) == DICT_ERR)
|
567
|
+
return -1;
|
568
|
+
/* Compute the key hash value */
|
569
|
+
h = dictHashKey(d, key);
|
570
|
+
for (table = 0; table <= 1; table++) {
|
571
|
+
idx = h & d->ht[table].sizemask;
|
572
|
+
/* Search if this slot does not already contain the given key */
|
573
|
+
he = d->ht[table].table[idx];
|
574
|
+
while(he) {
|
575
|
+
if (dictCompareHashKeys(d, key, he->key))
|
576
|
+
return -1;
|
577
|
+
he = he->next;
|
578
|
+
}
|
579
|
+
if (!dictIsRehashing(d)) break;
|
580
|
+
}
|
581
|
+
return idx;
|
582
|
+
}
|
583
|
+
|
584
|
+
void dictEmpty(dict *d) {
|
585
|
+
_dictClear(d,&d->ht[0]);
|
586
|
+
_dictClear(d,&d->ht[1]);
|
587
|
+
d->rehashidx = -1;
|
588
|
+
d->iterators = 0;
|
589
|
+
}
|
590
|
+
|
591
|
+
#define DICT_STATS_VECTLEN 50
|
592
|
+
static void _dictPrintStatsHt(dictht *ht) {
|
593
|
+
unsigned long i, slots = 0, chainlen, maxchainlen = 0;
|
594
|
+
unsigned long totchainlen = 0;
|
595
|
+
unsigned long clvector[DICT_STATS_VECTLEN];
|
596
|
+
|
597
|
+
if (ht->used == 0) {
|
598
|
+
printf("No stats available for empty dictionaries\n");
|
599
|
+
return;
|
600
|
+
}
|
601
|
+
|
602
|
+
for (i = 0; i < DICT_STATS_VECTLEN; i++) clvector[i] = 0;
|
603
|
+
for (i = 0; i < ht->size; i++) {
|
604
|
+
dictEntry *he;
|
605
|
+
|
606
|
+
if (ht->table[i] == NULL) {
|
607
|
+
clvector[0]++;
|
608
|
+
continue;
|
609
|
+
}
|
610
|
+
slots++;
|
611
|
+
/* For each hash entry on this slot... */
|
612
|
+
chainlen = 0;
|
613
|
+
he = ht->table[i];
|
614
|
+
while(he) {
|
615
|
+
chainlen++;
|
616
|
+
he = he->next;
|
617
|
+
}
|
618
|
+
clvector[(chainlen < DICT_STATS_VECTLEN) ? chainlen : (DICT_STATS_VECTLEN-1)]++;
|
619
|
+
if (chainlen > maxchainlen) maxchainlen = chainlen;
|
620
|
+
totchainlen += chainlen;
|
621
|
+
}
|
622
|
+
printf("Hash table stats:\n");
|
623
|
+
printf(" table size: %ld\n", ht->size);
|
624
|
+
printf(" number of elements: %ld\n", ht->used);
|
625
|
+
printf(" different slots: %ld\n", slots);
|
626
|
+
printf(" max chain length: %ld\n", maxchainlen);
|
627
|
+
printf(" avg chain length (counted): %.02f\n", (float)totchainlen/slots);
|
628
|
+
printf(" avg chain length (computed): %.02f\n", (float)ht->used/slots);
|
629
|
+
printf(" Chain length distribution:\n");
|
630
|
+
for (i = 0; i < DICT_STATS_VECTLEN-1; i++) {
|
631
|
+
if (clvector[i] == 0) continue;
|
632
|
+
printf(" %s%ld: %ld (%.02f%%)\n",(i == DICT_STATS_VECTLEN-1)?">= ":"", i, clvector[i], ((float)clvector[i]/ht->size)*100);
|
633
|
+
}
|
634
|
+
}
|
635
|
+
|
636
|
+
void dictPrintStats(dict *d) {
|
637
|
+
_dictPrintStatsHt(&d->ht[0]);
|
638
|
+
if (dictIsRehashing(d)) {
|
639
|
+
printf("-- Rehashing into ht[1]:\n");
|
640
|
+
_dictPrintStatsHt(&d->ht[1]);
|
641
|
+
}
|
642
|
+
}
|
643
|
+
|
644
|
+
void dictEnableResize(void) {
|
645
|
+
dict_can_resize = 1;
|
646
|
+
}
|
647
|
+
|
648
|
+
void dictDisableResize(void) {
|
649
|
+
dict_can_resize = 0;
|
650
|
+
}
|
651
|
+
|
652
|
+
#if 0
|
653
|
+
|
654
|
+
/* The following are just example hash table types implementations.
|
655
|
+
* Not useful for Redis so they are commented out.
|
656
|
+
*/
|
657
|
+
|
658
|
+
/* ----------------------- StringCopy Hash Table Type ------------------------*/
|
659
|
+
|
660
|
+
static unsigned int _dictStringCopyHTHashFunction(const void *key)
|
661
|
+
{
|
662
|
+
return dictGenHashFunction(key, strlen(key));
|
663
|
+
}
|
664
|
+
|
665
|
+
static void *_dictStringDup(void *privdata, const void *key)
|
666
|
+
{
|
667
|
+
int len = strlen(key);
|
668
|
+
char *copy = zmalloc(len+1);
|
669
|
+
DICT_NOTUSED(privdata);
|
670
|
+
|
671
|
+
memcpy(copy, key, len);
|
672
|
+
copy[len] = '\0';
|
673
|
+
return copy;
|
674
|
+
}
|
675
|
+
|
676
|
+
static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
|
677
|
+
const void *key2)
|
678
|
+
{
|
679
|
+
DICT_NOTUSED(privdata);
|
680
|
+
|
681
|
+
return strcmp(key1, key2) == 0;
|
682
|
+
}
|
683
|
+
|
684
|
+
static void _dictStringDestructor(void *privdata, void *key)
|
685
|
+
{
|
686
|
+
DICT_NOTUSED(privdata);
|
687
|
+
|
688
|
+
zfree(key);
|
689
|
+
}
|
690
|
+
|
691
|
+
dictType dictTypeHeapStringCopyKey = {
|
692
|
+
_dictStringCopyHTHashFunction, /* hash function */
|
693
|
+
_dictStringDup, /* key dup */
|
694
|
+
NULL, /* val dup */
|
695
|
+
_dictStringCopyHTKeyCompare, /* key compare */
|
696
|
+
_dictStringDestructor, /* key destructor */
|
697
|
+
NULL /* val destructor */
|
698
|
+
};
|
699
|
+
|
700
|
+
/* This is like StringCopy but does not auto-duplicate the key.
|
701
|
+
* It's used for intepreter's shared strings. */
|
702
|
+
dictType dictTypeHeapStrings = {
|
703
|
+
_dictStringCopyHTHashFunction, /* hash function */
|
704
|
+
NULL, /* key dup */
|
705
|
+
NULL, /* val dup */
|
706
|
+
_dictStringCopyHTKeyCompare, /* key compare */
|
707
|
+
_dictStringDestructor, /* key destructor */
|
708
|
+
NULL /* val destructor */
|
709
|
+
};
|
710
|
+
|
711
|
+
/* This is like StringCopy but also automatically handle dynamic
|
712
|
+
* allocated C strings as values. */
|
713
|
+
dictType dictTypeHeapStringCopyKeyValue = {
|
714
|
+
_dictStringCopyHTHashFunction, /* hash function */
|
715
|
+
_dictStringDup, /* key dup */
|
716
|
+
_dictStringDup, /* val dup */
|
717
|
+
_dictStringCopyHTKeyCompare, /* key compare */
|
718
|
+
_dictStringDestructor, /* key destructor */
|
719
|
+
_dictStringDestructor, /* val destructor */
|
720
|
+
};
|
721
|
+
#endif
|