Implement serialization of HSET
This commit is contained in:
parent
d5cea3f5f3
commit
a845e9bce1
62
README.md
62
README.md
|
@ -38,7 +38,7 @@ cd build-opt && ninja dragonfly
|
||||||
|
|
||||||
for more options, run `./dragonfly --help`
|
for more options, run `./dragonfly --help`
|
||||||
|
|
||||||
## Milestone Egg 🥚
|
## Milestone - Source Available
|
||||||
|
|
||||||
API 1.0
|
API 1.0
|
||||||
- [X] String family
|
- [X] String family
|
||||||
|
@ -128,27 +128,8 @@ API 1.0
|
||||||
- [ ] RANDOMKEY
|
- [ ] RANDOMKEY
|
||||||
- [ ] MOVE
|
- [ ] MOVE
|
||||||
|
|
||||||
In addition, we want to support efficient expiry (TTL) and cache eviction algorithms.
|
|
||||||
We should implement basic memory management support. For Master/Slave replication we should design
|
|
||||||
a distributed log format.
|
|
||||||
|
|
||||||
### Memchache API
|
|
||||||
- [X] set
|
|
||||||
- [X] get
|
|
||||||
- [X] replace
|
|
||||||
- [X] add
|
|
||||||
- [X] stats (partial)
|
|
||||||
- [x] append
|
|
||||||
- [x] prepend
|
|
||||||
- [x] delete
|
|
||||||
- [x] flush_all
|
|
||||||
- [x] incr
|
|
||||||
- [x] decr
|
|
||||||
- [x] version
|
|
||||||
- [x] quit
|
|
||||||
|
|
||||||
API 2.0
|
API 2.0
|
||||||
- [ ] List Family
|
- [X] List Family
|
||||||
- [X] BLPOP
|
- [X] BLPOP
|
||||||
- [X] BRPOP
|
- [X] BRPOP
|
||||||
- [ ] BRPOPLPUSH
|
- [ ] BRPOPLPUSH
|
||||||
|
@ -240,6 +221,26 @@ API 2.0
|
||||||
- [ ] PFCOUNT
|
- [ ] PFCOUNT
|
||||||
- [ ] PFMERGE
|
- [ ] PFMERGE
|
||||||
|
|
||||||
|
In addition, we want to support efficient expiry (TTL) and cache eviction algorithms.
|
||||||
|
We should implement basic memory management support. For Master/Slave replication we should design
|
||||||
|
a distributed log format.
|
||||||
|
|
||||||
|
### Memchache API
|
||||||
|
- [X] set
|
||||||
|
- [X] get
|
||||||
|
- [X] replace
|
||||||
|
- [X] add
|
||||||
|
- [X] stats (partial)
|
||||||
|
- [x] append
|
||||||
|
- [x] prepend
|
||||||
|
- [x] delete
|
||||||
|
- [x] flush_all
|
||||||
|
- [x] incr
|
||||||
|
- [x] decr
|
||||||
|
- [x] version
|
||||||
|
- [x] quit
|
||||||
|
|
||||||
|
|
||||||
Commands that I prefer avoid implementing before launch:
|
Commands that I prefer avoid implementing before launch:
|
||||||
- PUNSUBSCRIBE
|
- PUNSUBSCRIBE
|
||||||
- PSUBSCRIBE
|
- PSUBSCRIBE
|
||||||
|
@ -252,22 +253,19 @@ Commands that I prefer avoid implementing before launch:
|
||||||
Also, I would omit keyspace notifications. For that I would like to deep dive and learn
|
Also, I would omit keyspace notifications. For that I would like to deep dive and learn
|
||||||
exact use-cases for this API.
|
exact use-cases for this API.
|
||||||
|
|
||||||
### Random commands we implemented along the way
|
### Random commands we implemented as decorators along the way
|
||||||
|
|
||||||
- [X] ROLE (2.8) decorator for for master withour replicas
|
- [X] ROLE (2.8) decorator for for master withour replicas
|
||||||
- [X] UNLINK (4.0) decorator for DEL command
|
- [X] UNLINK (4.0) decorator for DEL command
|
||||||
- [X] BGSAVE
|
- [X] BGSAVE
|
||||||
- [X] FUNCTION FLUSH
|
- [X] FUNCTION FLUSH
|
||||||
## Milestone Nymph
|
|
||||||
API 2,3,4 without cluster support, without modules, without memory inspection commands.
|
|
||||||
Without support for keyspace notifications.
|
|
||||||
|
|
||||||
Design config support. ~140 commands overall...
|
|
||||||
## Milestone Molt
|
|
||||||
API 5,6 - without cluster and modules. Streams support. ~80 commands overall.
|
|
||||||
## Milestone Adult
|
|
||||||
TBD.
|
|
||||||
|
|
||||||
|
## Milestone Stability
|
||||||
|
APIs 3,4,5 without cluster support, without modules, without memory introspection commands.
|
||||||
|
Without geo commands and without support for keyspace notifications, without streams.
|
||||||
|
Design config support. ~10-20 commands overall...
|
||||||
|
Probably implement cluster-API decorators to allow cluster-configured clients to connect to a single
|
||||||
|
instance.
|
||||||
|
|
||||||
## Design decisions along the way
|
## Design decisions along the way
|
||||||
### Expiration deadlines with relative accuracy
|
### Expiration deadlines with relative accuracy
|
||||||
|
@ -280,3 +278,5 @@ expiries of `PEXPIRE key 10010` will expire exactly after 10 seconds and 10ms. H
|
||||||
|
|
||||||
Such rounding has at most 0.002% error which I hope is acceptable for large ranges.
|
Such rounding has at most 0.002% error which I hope is acceptable for large ranges.
|
||||||
If it breaks your use-cases - talk to me or open an issue and explain your case.
|
If it breaks your use-cases - talk to me or open an issue and explain your case.
|
||||||
|
|
||||||
|
For more detailed differences between this and Redis implementations [see here](doc/differences.md).
|
|
@ -10,7 +10,7 @@ endif()
|
||||||
|
|
||||||
add_library(redis_lib crc64.c crcspeed.c debug.c dict.c endianconv.c intset.c
|
add_library(redis_lib crc64.c crcspeed.c debug.c dict.c endianconv.c intset.c
|
||||||
listpack.c mt19937-64.c object.c lzf_c.c lzf_d.c sds.c sha256.c
|
listpack.c mt19937-64.c object.c lzf_c.c lzf_d.c sds.c sha256.c
|
||||||
quicklist.c redis_aux.c siphash.c t_hash.c t_zset.c util.c ${ZMALLOC_SRC})
|
quicklist.c redis_aux.c siphash.c t_hash.c t_zset.c util.c ziplist.c ${ZMALLOC_SRC})
|
||||||
|
|
||||||
cxx_link(redis_lib ${ZMALLOC_DEPS})
|
cxx_link(redis_lib ${ZMALLOC_DEPS})
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ZIPLIST_H
|
||||||
|
#define _ZIPLIST_H
|
||||||
|
|
||||||
|
#define ZIPLIST_HEAD 0
|
||||||
|
#define ZIPLIST_TAIL 1
|
||||||
|
|
||||||
|
/* Each entry in the ziplist is either a string or an integer. */
|
||||||
|
typedef struct {
|
||||||
|
/* When string is used, it is provided with the length (slen). */
|
||||||
|
unsigned char *sval;
|
||||||
|
unsigned int slen;
|
||||||
|
/* When integer is used, 'sval' is NULL, and lval holds the value. */
|
||||||
|
long long lval;
|
||||||
|
} ziplistEntry;
|
||||||
|
|
||||||
|
unsigned char *ziplistNew(void);
|
||||||
|
unsigned char *ziplistMerge(unsigned char **first, unsigned char **second);
|
||||||
|
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);
|
||||||
|
unsigned char *ziplistIndex(unsigned char *zl, int index);
|
||||||
|
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p);
|
||||||
|
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p);
|
||||||
|
unsigned int ziplistGet(unsigned char *p, unsigned char **sval, unsigned int *slen, long long *lval);
|
||||||
|
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
|
||||||
|
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);
|
||||||
|
unsigned char *ziplistDeleteRange(unsigned char *zl, int index, unsigned int num);
|
||||||
|
unsigned char *ziplistReplace(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
|
||||||
|
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
|
||||||
|
unsigned char *ziplistFind(unsigned char *zl, unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);
|
||||||
|
unsigned int ziplistLen(unsigned char *zl);
|
||||||
|
size_t ziplistBlobLen(unsigned char *zl);
|
||||||
|
void ziplistRepr(unsigned char *zl);
|
||||||
|
typedef int (*ziplistValidateEntryCB)(unsigned char* p, void* userdata);
|
||||||
|
int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,
|
||||||
|
ziplistValidateEntryCB entry_cb, void *cb_userdata);
|
||||||
|
void ziplistRandomPair(unsigned char *zl, unsigned long total_count, ziplistEntry *key, ziplistEntry *val);
|
||||||
|
void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
|
||||||
|
unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
|
||||||
|
int ziplistSafeToAdd(unsigned char* zl, size_t add);
|
||||||
|
|
||||||
|
#ifdef REDIS_TEST
|
||||||
|
int ziplistTest(int argc, char *argv[], int accurate);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _ZIPLIST_H */
|
|
@ -10,8 +10,11 @@
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "redis/intset.h"
|
#include "redis/intset.h"
|
||||||
|
#include "redis/listpack.h"
|
||||||
|
#include "redis/ziplist.h"
|
||||||
#include "redis/rdb.h"
|
#include "redis/rdb.h"
|
||||||
#include "redis/util.h"
|
#include "redis/util.h"
|
||||||
|
#include "redis/zmalloc.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
|
@ -132,7 +135,7 @@ uint8_t RdbObjectType(const robj* o) {
|
||||||
return RDB_TYPE_ZSET_2;
|
return RDB_TYPE_ZSET_2;
|
||||||
break;
|
break;
|
||||||
case OBJ_HASH:
|
case OBJ_HASH:
|
||||||
if (o->encoding == OBJ_ENCODING_ZIPLIST)
|
if (o->encoding == OBJ_ENCODING_LISTPACK)
|
||||||
return RDB_TYPE_HASH_ZIPLIST;
|
return RDB_TYPE_HASH_ZIPLIST;
|
||||||
else if (o->encoding == OBJ_ENCODING_HT)
|
else if (o->encoding == OBJ_ENCODING_HT)
|
||||||
return RDB_TYPE_HASH;
|
return RDB_TYPE_HASH;
|
||||||
|
@ -206,6 +209,10 @@ error_code RdbSerializer::SaveObject(const robj* o) {
|
||||||
return SaveSetObject(o);
|
return SaveSetObject(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (o->type == OBJ_HASH) {
|
||||||
|
return SaveHSetObject(o);
|
||||||
|
}
|
||||||
|
|
||||||
LOG(FATAL) << "Not implemented " << o->type;
|
LOG(FATAL) << "Not implemented " << o->type;
|
||||||
return error_code{};
|
return error_code{};
|
||||||
}
|
}
|
||||||
|
@ -252,7 +259,7 @@ error_code RdbSerializer::SaveSetObject(const robj* obj) {
|
||||||
|
|
||||||
dictIterator* di = dictGetIterator(set);
|
dictIterator* di = dictGetIterator(set);
|
||||||
dictEntry* de;
|
dictEntry* de;
|
||||||
auto key_cleanup = absl::MakeCleanup([di] { dictReleaseIterator(di); });
|
auto cleanup = absl::MakeCleanup([di] { dictReleaseIterator(di); });
|
||||||
|
|
||||||
while ((de = dictNext(di)) != NULL) {
|
while ((de = dictNext(di)) != NULL) {
|
||||||
sds ele = (sds)de->key;
|
sds ele = (sds)de->key;
|
||||||
|
@ -270,6 +277,52 @@ error_code RdbSerializer::SaveSetObject(const robj* obj) {
|
||||||
return error_code{};
|
return error_code{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error_code RdbSerializer::SaveHSetObject(const robj* obj) {
|
||||||
|
DCHECK_EQ(OBJ_HASH, obj->type);
|
||||||
|
if (obj->encoding == OBJ_ENCODING_HT) {
|
||||||
|
dict* set = (dict*)obj->ptr;
|
||||||
|
|
||||||
|
RETURN_ON_ERR(SaveLen(dictSize(set)));
|
||||||
|
|
||||||
|
dictIterator* di = dictGetIterator(set);
|
||||||
|
dictEntry* de;
|
||||||
|
auto cleanup = absl::MakeCleanup([di] { dictReleaseIterator(di); });
|
||||||
|
|
||||||
|
while ((de = dictNext(di)) != NULL) {
|
||||||
|
sds key = (sds)de->key;
|
||||||
|
sds value = (sds)de->v.val;
|
||||||
|
|
||||||
|
RETURN_ON_ERR(SaveString(string_view{key, sdslen(key)}));
|
||||||
|
RETURN_ON_ERR(SaveString(string_view{value, sdslen(value)}));
|
||||||
|
}
|
||||||
|
} else if (obj->encoding == OBJ_ENCODING_LISTPACK) {
|
||||||
|
// convert to ziplist first.
|
||||||
|
uint8_t* lp = (uint8_t*)obj->ptr;
|
||||||
|
|
||||||
|
size_t lplen = lpLength(lp);
|
||||||
|
CHECK(lplen > 0 && lplen % 2 == 0); // has (key,value) pairs.
|
||||||
|
|
||||||
|
uint8_t* lpfield = lpFirst(lp);
|
||||||
|
uint8_t* zl = ziplistNew();
|
||||||
|
int64_t entry_len;
|
||||||
|
uint8_t* entry;
|
||||||
|
uint8_t buf[32];
|
||||||
|
|
||||||
|
while (lpfield) {
|
||||||
|
entry = lpGet(lpfield, &entry_len, buf);
|
||||||
|
zl = ziplistPush(zl, entry, entry_len, ZIPLIST_TAIL);
|
||||||
|
lpfield = lpNext(lp, lpfield);
|
||||||
|
}
|
||||||
|
size_t ziplen = ziplistBlobLen(zl);
|
||||||
|
auto cleanup = absl::MakeCleanup([zl] { zfree(zl); });
|
||||||
|
RETURN_ON_ERR(SaveString(string_view{reinterpret_cast<char*>(zl), ziplen}));
|
||||||
|
} else {
|
||||||
|
LOG(FATAL) << "Unknown jset encoding " << obj->encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error_code{};
|
||||||
|
}
|
||||||
|
|
||||||
/* Save a long long value as either an encoded string or a string. */
|
/* Save a long long value as either an encoded string or a string. */
|
||||||
error_code RdbSerializer::SaveLongLongAsString(int64_t value) {
|
error_code RdbSerializer::SaveLongLongAsString(int64_t value) {
|
||||||
uint8_t buf[32];
|
uint8_t buf[32];
|
||||||
|
|
|
@ -49,6 +49,7 @@ class RdbSerializer {
|
||||||
std::error_code SaveStringObject(const robj* obj);
|
std::error_code SaveStringObject(const robj* obj);
|
||||||
std::error_code SaveListObject(const robj* obj);
|
std::error_code SaveListObject(const robj* obj);
|
||||||
std::error_code SaveSetObject(const robj* obj);
|
std::error_code SaveSetObject(const robj* obj);
|
||||||
|
std::error_code SaveHSetObject(const robj* obj);
|
||||||
std::error_code SaveLongLongAsString(int64_t value);
|
std::error_code SaveLongLongAsString(int64_t value);
|
||||||
|
|
||||||
::io::Sink* sink_ = nullptr;
|
::io::Sink* sink_ = nullptr;
|
||||||
|
|
|
@ -82,9 +82,12 @@ TEST_F(RdbTest, LoadSmall) {
|
||||||
|
|
||||||
TEST_F(RdbTest, Save) {
|
TEST_F(RdbTest, Save) {
|
||||||
Run({"set", "string_key", "val"});
|
Run({"set", "string_key", "val"});
|
||||||
Run({"sadd", "set_key1", "val"});
|
Run({"sadd", "set_key1", "val1", "val2"});
|
||||||
Run({"sadd", "set_key2", "1", "2", "3"});
|
Run({"sadd", "set_key2", "1", "2", "3"});
|
||||||
Run({"rpush", "list_key", "val"});
|
|
||||||
|
// Run({"rpush", "list_key", "val"}); // TODO: invalid encoding when reading by redis 6.
|
||||||
|
// Run({"rpush", "list_key", "val"});
|
||||||
|
Run({"hset", "hset_key", "field1", "val1", "field2", "val2"});
|
||||||
Run({"save"});
|
Run({"save"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue