Second implementation of nested hashes
Former-commit-id: ba950a3d1f5708ed986f9b348eafcace6a7c22b9
This commit is contained in:
parent
7db922f44b
commit
e085772d01
@ -276,7 +276,7 @@ endif
|
|||||||
|
|
||||||
REDIS_SERVER_NAME=keydb-server
|
REDIS_SERVER_NAME=keydb-server
|
||||||
REDIS_SENTINEL_NAME=keydb-sentinel
|
REDIS_SENTINEL_NAME=keydb-sentinel
|
||||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(ASM_OBJ)
|
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_nhash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(ASM_OBJ)
|
||||||
REDIS_CLI_NAME=keydb-cli
|
REDIS_CLI_NAME=keydb-cli
|
||||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
|
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
|
||||||
REDIS_BENCHMARK_NAME=keydb-benchmark
|
REDIS_BENCHMARK_NAME=keydb-benchmark
|
||||||
|
@ -424,6 +424,10 @@ void addReplyProto(client *c, const char *s, size_t len) {
|
|||||||
_addReplyProtoToList(c,s,len);
|
_addReplyProtoToList(c,s,len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addReplyProtoCString(client *c, const char *s) {
|
||||||
|
addReplyProto(c, s, strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
std::string escapeString(sds str)
|
std::string escapeString(sds str)
|
||||||
{
|
{
|
||||||
std::string newstr;
|
std::string newstr;
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "cron.h"
|
#include "cron.h"
|
||||||
|
#include "t_nhash.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -395,6 +396,7 @@ void decrRefCount(robj_roptr o) {
|
|||||||
case OBJ_MODULE: freeModuleObject(o); break;
|
case OBJ_MODULE: freeModuleObject(o); break;
|
||||||
case OBJ_STREAM: freeStreamObject(o); break;
|
case OBJ_STREAM: freeStreamObject(o); break;
|
||||||
case OBJ_CRON: freeCronObject(o); break;
|
case OBJ_CRON: freeCronObject(o); break;
|
||||||
|
case OBJ_NESTEDHASH: freeNestedHashObject(o); break;
|
||||||
default: serverPanic("Unknown object type"); break;
|
default: serverPanic("Unknown object type"); break;
|
||||||
}
|
}
|
||||||
if (g_pserver->fActiveReplica) {
|
if (g_pserver->fActiveReplica) {
|
||||||
|
10
src/sds.h
10
src/sds.h
@ -390,6 +390,10 @@ public:
|
|||||||
: sdsview(sdsdup(other.m_str))
|
: sdsview(sdsdup(other.m_str))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
sdsstring(const char *rgch, size_t cch)
|
||||||
|
: sdsview(sdsnewlen(rgch, cch))
|
||||||
|
{}
|
||||||
|
|
||||||
sdsstring(sdsstring &&other)
|
sdsstring(sdsstring &&other)
|
||||||
: sdsview(other.m_str)
|
: sdsview(other.m_str)
|
||||||
{
|
{
|
||||||
@ -410,6 +414,12 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sds release() {
|
||||||
|
sds sdsT = m_str;
|
||||||
|
m_str = nullptr;
|
||||||
|
return sdsT;
|
||||||
|
}
|
||||||
|
|
||||||
~sdsstring()
|
~sdsstring()
|
||||||
{
|
{
|
||||||
sdsfree(m_str);
|
sdsfree(m_str);
|
||||||
|
@ -63,6 +63,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include "aelocker.h"
|
#include "aelocker.h"
|
||||||
#include "motd.h"
|
#include "motd.h"
|
||||||
|
#include "t_nhash.h"
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#endif
|
#endif
|
||||||
@ -1060,7 +1061,15 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
|
|
||||||
{"stralgo",stralgoCommand,-2,
|
{"stralgo",stralgoCommand,-2,
|
||||||
"read-only @string",
|
"read-only @string",
|
||||||
0,lcsGetKeys,0,0,0,0,0,0}
|
0,lcsGetKeys,0,0,0,0,0,0},
|
||||||
|
|
||||||
|
{"keydb.nhget",nhgetCommand,-2,
|
||||||
|
"read-only fast @hash",
|
||||||
|
0,NULL,1,1,1,0,0,0},
|
||||||
|
|
||||||
|
{"keydb.nhset",nhsetCommand,-3,
|
||||||
|
"read-only fast @hash",
|
||||||
|
0,NULL,1,1,1,0,0,0},
|
||||||
};
|
};
|
||||||
|
|
||||||
/*============================ Utility functions ============================ */
|
/*============================ Utility functions ============================ */
|
||||||
@ -3558,7 +3567,15 @@ void call(client *c, int flags) {
|
|||||||
updateCachedTime(0);
|
updateCachedTime(0);
|
||||||
incrementMvccTstamp();
|
incrementMvccTstamp();
|
||||||
start = g_pserver->ustime;
|
start = g_pserver->ustime;
|
||||||
|
try {
|
||||||
c->cmd->proc(c);
|
c->cmd->proc(c);
|
||||||
|
} catch (robj_roptr o) {
|
||||||
|
addReply(c, o);
|
||||||
|
} catch (robj *o) {
|
||||||
|
addReply(c, o);
|
||||||
|
} catch (const char *sz) {
|
||||||
|
addReplyError(c, sz);
|
||||||
|
}
|
||||||
serverTL->commandsExecuted++;
|
serverTL->commandsExecuted++;
|
||||||
duration = ustime()-start;
|
duration = ustime()-start;
|
||||||
dirty = g_pserver->dirty-dirty;
|
dirty = g_pserver->dirty-dirty;
|
||||||
|
@ -674,7 +674,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
|||||||
#define OBJ_MODULE 5 /* Module object. */
|
#define OBJ_MODULE 5 /* Module object. */
|
||||||
#define OBJ_STREAM 6 /* Stream object. */
|
#define OBJ_STREAM 6 /* Stream object. */
|
||||||
#define OBJ_CRON 7 /* CRON job */
|
#define OBJ_CRON 7 /* CRON job */
|
||||||
|
#define OBJ_NESTEDHASH 8 /* Nested Hash Object */
|
||||||
|
|
||||||
/* Extract encver / signature from a module type ID. */
|
/* Extract encver / signature from a module type ID. */
|
||||||
#define REDISMODULE_TYPE_ENCVER_BITS 10
|
#define REDISMODULE_TYPE_ENCVER_BITS 10
|
||||||
@ -2009,6 +2009,7 @@ void addReplyNullArray(client *c);
|
|||||||
void addReplyBool(client *c, int b);
|
void addReplyBool(client *c, int b);
|
||||||
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
|
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
|
||||||
void addReplyProto(client *c, const char *s, size_t len);
|
void addReplyProto(client *c, const char *s, size_t len);
|
||||||
|
void addReplyProtoCString(client *c, const char *s);
|
||||||
void addReplyBulk(client *c, robj_roptr obj);
|
void addReplyBulk(client *c, robj_roptr obj);
|
||||||
void AddReplyFromClient(client *c, client *src);
|
void AddReplyFromClient(client *c, client *src);
|
||||||
void addReplyBulkCString(client *c, const char *s);
|
void addReplyBulkCString(client *c, const char *s);
|
||||||
|
353
src/t_nhash.cpp
Normal file
353
src/t_nhash.cpp
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, EQ Alpha Technology Ltd. <john at eqalpha 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "server.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
void dictObjectDestructor(void *privdata, void *val);
|
||||||
|
dictType nestedHashDictType {
|
||||||
|
dictSdsHash, /* hash function */
|
||||||
|
NULL, /* key dup */
|
||||||
|
NULL, /* val dup */
|
||||||
|
dictSdsKeyCompare, /* key compare */
|
||||||
|
dictSdsDestructor, /* key destructor */
|
||||||
|
dictObjectDestructor, /* val destructor */
|
||||||
|
};
|
||||||
|
|
||||||
|
robj *createNestHashBucket() {
|
||||||
|
dict *d = dictCreate(&nestedHashDictType, nullptr);
|
||||||
|
return createObject(OBJ_NESTEDHASH, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeNestedHashObject(robj_roptr o) {
|
||||||
|
dictRelease((dict*)ptrFromObj(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
robj *fetchFromKey(redisDb *db, robj_roptr key) {
|
||||||
|
const char *pchCur = szFromObj(key);
|
||||||
|
const char *pchStart = pchCur;
|
||||||
|
const char *pchMax = pchCur + sdslen(pchCur);
|
||||||
|
robj *o = nullptr;
|
||||||
|
|
||||||
|
while (pchCur <= pchMax) {
|
||||||
|
if (pchCur == pchMax || *pchCur == '.') {
|
||||||
|
// WARNING: Don't deref pchCur as it may be pchMax
|
||||||
|
|
||||||
|
// New word
|
||||||
|
if ((pchCur - pchStart) < 1) {
|
||||||
|
throw shared.syntaxerr; // malformed
|
||||||
|
}
|
||||||
|
|
||||||
|
dict *d = nullptr;
|
||||||
|
if (o == nullptr)
|
||||||
|
d = db->pdict;
|
||||||
|
else
|
||||||
|
d = (dict*)ptrFromObj(o);
|
||||||
|
|
||||||
|
sdsstring str(pchStart, pchCur - pchStart);
|
||||||
|
dictEntry *de = dictFind(d, str.get());
|
||||||
|
o = (de != nullptr) ? (robj*)dictGetVal(de) : nullptr;
|
||||||
|
|
||||||
|
if (o == nullptr) throw shared.nokeyerr; // Not Found
|
||||||
|
serverAssert(o->type == OBJ_NESTEDHASH || o->type == OBJ_STRING || o->type == OBJ_LIST);
|
||||||
|
if (o->type == OBJ_STRING && pchCur != pchMax)
|
||||||
|
throw shared.nokeyerr; // Past the end
|
||||||
|
|
||||||
|
pchStart = pchCur + 1;
|
||||||
|
}
|
||||||
|
++pchCur;
|
||||||
|
}
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns one if we overwrote a value
|
||||||
|
bool setWithKey(redisDb *db, robj_roptr key, robj *val, bool fCreateBuckets) {
|
||||||
|
const char *pchCur = szFromObj(key);
|
||||||
|
const char *pchStart = pchCur;
|
||||||
|
const char *pchMax = pchCur + sdslen(pchCur);
|
||||||
|
robj *o = nullptr;
|
||||||
|
|
||||||
|
while (pchCur <= pchMax) {
|
||||||
|
if (pchCur == pchMax || *pchCur == '.') {
|
||||||
|
// WARNING: Don't deref pchCur as it may be pchMax
|
||||||
|
|
||||||
|
// New word
|
||||||
|
if ((pchCur - pchStart) < 1) {
|
||||||
|
throw shared.syntaxerr; // malformed
|
||||||
|
}
|
||||||
|
|
||||||
|
dict *d = nullptr;
|
||||||
|
if (o == nullptr)
|
||||||
|
d = db->pdict;
|
||||||
|
else
|
||||||
|
d = (dict*)ptrFromObj(o);
|
||||||
|
|
||||||
|
sdsstring str(pchStart, pchCur - pchStart);
|
||||||
|
dictEntry *de = dictFind(d, str.get());
|
||||||
|
|
||||||
|
if (pchCur == pchMax) {
|
||||||
|
val->addref();
|
||||||
|
if (de != nullptr) {
|
||||||
|
decrRefCount((robj*)dictGetVal(de));
|
||||||
|
dictSetVal(d, de, val);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
dictAdd(d, str.release(), val);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o = (de != nullptr) ? (robj*)dictGetVal(de) : nullptr;
|
||||||
|
|
||||||
|
if (o == nullptr) {
|
||||||
|
if (!fCreateBuckets)
|
||||||
|
throw shared.nokeyerr; // Not Found
|
||||||
|
o = createNestHashBucket();
|
||||||
|
serverAssert(dictAdd(d, str.release(), o) == DICT_OK);
|
||||||
|
} else if (o->type != OBJ_NESTEDHASH) {
|
||||||
|
decrRefCount(o);
|
||||||
|
o = createNestHashBucket();
|
||||||
|
de->v.val = o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pchStart = pchCur + 1;
|
||||||
|
}
|
||||||
|
++pchCur;
|
||||||
|
}
|
||||||
|
throw "Internal Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeNestedHashToClient(client *c, robj_roptr o) {
|
||||||
|
if (o == nullptr) {
|
||||||
|
addReply(c, shared.null[c->resp]);
|
||||||
|
} else if (o->type == OBJ_STRING) {
|
||||||
|
addReplyBulk(c, o);
|
||||||
|
} else if (o->type == OBJ_LIST) {
|
||||||
|
unsigned char *zl = (unsigned char*)ptrFromObj(o);
|
||||||
|
addReplyArrayLen(c, ziplistLen(zl));
|
||||||
|
unsigned char *p = ziplistIndex(zl, ZIPLIST_HEAD);
|
||||||
|
while (p != nullptr) {
|
||||||
|
unsigned char *str;
|
||||||
|
unsigned int len;
|
||||||
|
long long lval;
|
||||||
|
if (ziplistGet(p, &str, &len, &lval)) {
|
||||||
|
char rgT[128];
|
||||||
|
if (str == nullptr) {
|
||||||
|
len = ll2string(rgT, 128, lval);
|
||||||
|
str = (unsigned char*)rgT;
|
||||||
|
}
|
||||||
|
addReplyBulkCBuffer(c, (const char*)str, len);
|
||||||
|
}
|
||||||
|
p = ziplistNext(zl, p);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serverAssert(o->type == OBJ_NESTEDHASH );
|
||||||
|
dict *d = (dict*)ptrFromObj(o);
|
||||||
|
|
||||||
|
if (dictSize(d) > 1)
|
||||||
|
addReplyArrayLen(c, dictSize(d));
|
||||||
|
|
||||||
|
dictIterator *di = dictGetIterator(d);
|
||||||
|
dictEntry *de;
|
||||||
|
while ((de = dictNext(di))) {
|
||||||
|
robj_roptr oT = (robj*)dictGetVal(de);
|
||||||
|
addReplyArrayLen(c, 2);
|
||||||
|
addReplyBulkCBuffer(c, (sds)dictGetKey(de), sdslen((sds)dictGetKey(de)));
|
||||||
|
if (oT->type == OBJ_STRING) {
|
||||||
|
addReplyBulk(c, oT);
|
||||||
|
} else {
|
||||||
|
writeNestedHashToClient(c, oT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool FSimpleJsonEscapeCh(char ch) {
|
||||||
|
return (ch == '"' || ch == '\\');
|
||||||
|
}
|
||||||
|
inline bool FExtendedJsonEscapeCh(char ch) {
|
||||||
|
return ch <= 0x1F;
|
||||||
|
}
|
||||||
|
|
||||||
|
sds writeJsonValue(sds output, const char *valIn, size_t cchIn) {
|
||||||
|
const char *val = valIn;
|
||||||
|
size_t cch = cchIn;
|
||||||
|
int cchEscapeExtra = 0;
|
||||||
|
|
||||||
|
// First scan for escaped chars
|
||||||
|
for (size_t ich = 0; ich < cchIn; ++ich) {
|
||||||
|
if (FSimpleJsonEscapeCh(valIn[ich])) {
|
||||||
|
++cchEscapeExtra;
|
||||||
|
} else if (FExtendedJsonEscapeCh(valIn[ich])) {
|
||||||
|
cchEscapeExtra += 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cchEscapeExtra > 0) {
|
||||||
|
size_t ichDst = 0;
|
||||||
|
sds dst = sdsnewlen(SDS_NOINIT, cchIn+cchEscapeExtra);
|
||||||
|
for (size_t ich = 0; ich < cchIn; ++ich) {
|
||||||
|
switch (valIn[ich]) {
|
||||||
|
case '"':
|
||||||
|
dst[ichDst++] = '\\'; dst[ichDst++] = '"';
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
dst[ichDst++] = '\\'; dst[ichDst++] = '\\';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
serverAssert(!FSimpleJsonEscapeCh(valIn[ich]));
|
||||||
|
if (FExtendedJsonEscapeCh(valIn[ich])) {
|
||||||
|
dst[ichDst++] = '\\'; dst[ichDst++] = 'u';
|
||||||
|
sprintf(dst + ichDst, "%4x", valIn[ich]);
|
||||||
|
ichDst += 4;
|
||||||
|
} else {
|
||||||
|
dst[ichDst++] = valIn[ich];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val = (const char*)dst;
|
||||||
|
serverAssert(ichDst == (cchIn+cchEscapeExtra));
|
||||||
|
cch = ichDst;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = sdscat(output, "\"");
|
||||||
|
output = sdscatlen(output, val, cch);
|
||||||
|
output = sdscat(output, "\"");
|
||||||
|
|
||||||
|
if (val != valIn)
|
||||||
|
sdsfree(val);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
sds writeJsonValue(sds output, sds val) {
|
||||||
|
return writeJsonValue(output, (const char*)val, sdslen(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
sds writeNestedHashAsJson(sds output, robj_roptr o) {
|
||||||
|
if (o->type == OBJ_STRING) {
|
||||||
|
output = writeJsonValue(output, (sds)szFromObj(o));
|
||||||
|
} else if (o->type == OBJ_LIST) {
|
||||||
|
unsigned char *zl = (unsigned char*)ptrFromObj(o);
|
||||||
|
output = sdscat(output, "[");
|
||||||
|
unsigned char *p = ziplistIndex(zl, ZIPLIST_HEAD);
|
||||||
|
bool fFirst = true;
|
||||||
|
while (p != nullptr) {
|
||||||
|
unsigned char *str;
|
||||||
|
unsigned int len;
|
||||||
|
long long lval;
|
||||||
|
if (ziplistGet(p, &str, &len, &lval)) {
|
||||||
|
char rgT[128];
|
||||||
|
if (str == nullptr) {
|
||||||
|
len = ll2string(rgT, 128, lval);
|
||||||
|
str = (unsigned char*)rgT;
|
||||||
|
}
|
||||||
|
if (!fFirst)
|
||||||
|
output = sdscat(output, ",");
|
||||||
|
fFirst = false;
|
||||||
|
output = writeJsonValue(output, (const char*)str, len);
|
||||||
|
}
|
||||||
|
p = ziplistNext(zl, p);
|
||||||
|
}
|
||||||
|
output = sdscat(output, "]");
|
||||||
|
} else {
|
||||||
|
output = sdscat(output, "{");
|
||||||
|
dictIterator *di = dictGetIterator((dict*)ptrFromObj(o));
|
||||||
|
dictEntry *de;
|
||||||
|
bool fFirst = true;
|
||||||
|
while ((de = dictNext(di))) {
|
||||||
|
robj_roptr oT = (robj*)dictGetVal(de);
|
||||||
|
if (!fFirst)
|
||||||
|
output = sdscat(output, ",");
|
||||||
|
fFirst = false;
|
||||||
|
output = writeJsonValue(output, (sds)dictGetKey(de));
|
||||||
|
output = sdscat(output, " : ");
|
||||||
|
output = writeNestedHashAsJson(output, oT);
|
||||||
|
}
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
output = sdscat(output, "}");
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nhsetCommand(client *c) {
|
||||||
|
if (c->argc < 3)
|
||||||
|
throw shared.syntaxerr;
|
||||||
|
|
||||||
|
robj *val = c->argv[2];
|
||||||
|
if (c->argc > 3) {
|
||||||
|
// Its a list, we'll store as a ziplist
|
||||||
|
val = createZiplistObject();
|
||||||
|
for (int iarg = 2; iarg < c->argc; ++iarg) {
|
||||||
|
sds arg = (sds)szFromObj(c->argv[iarg]);
|
||||||
|
val->m_ptr = ziplistPush((unsigned char*)ptrFromObj(val), (unsigned char*)arg, sdslen(arg), ZIPLIST_TAIL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (setWithKey(c->db, c->argv[1], val, true)) {
|
||||||
|
addReplyLongLong(c, 1); // we replaced a value
|
||||||
|
} else {
|
||||||
|
addReplyLongLong(c, 0); // we added a new value
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
if (val != c->argv[2])
|
||||||
|
decrRefCount(val);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
if (val != c->argv[2])
|
||||||
|
decrRefCount(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nhgetCommand(client *c) {
|
||||||
|
if (c->argc != 2 && c->argc != 3)
|
||||||
|
throw shared.syntaxerr;
|
||||||
|
|
||||||
|
bool fJson = false;
|
||||||
|
int argOffset = 0;
|
||||||
|
if (c->argc == 3) {
|
||||||
|
argOffset++;
|
||||||
|
if (strcasecmp(szFromObj(c->argv[1]), "json") == 0) {
|
||||||
|
fJson = true;
|
||||||
|
} else if (strcasecmp(szFromObj(c->argv[1]), "resp") != 0) {
|
||||||
|
throw shared.syntaxerr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
robj *o = fetchFromKey(c->db, c->argv[argOffset + 1]);
|
||||||
|
if (fJson) {
|
||||||
|
sds val = writeNestedHashAsJson(sdsnew(nullptr), o);
|
||||||
|
addReplyBulkSds(c, val);
|
||||||
|
} else {
|
||||||
|
writeNestedHashToClient(c, o);
|
||||||
|
}
|
||||||
|
}
|
6
src/t_nhash.h
Normal file
6
src/t_nhash.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void freeNestedHashObject(robj_roptr o);
|
||||||
|
|
||||||
|
void nhsetCommand(client *c);
|
||||||
|
void nhgetCommand(client *c);
|
@ -28,6 +28,7 @@ set ::all_tests {
|
|||||||
unit/type/hash
|
unit/type/hash
|
||||||
unit/type/stream
|
unit/type/stream
|
||||||
unit/type/stream-cgroups
|
unit/type/stream-cgroups
|
||||||
|
unit/type/nestedhash
|
||||||
unit/sort
|
unit/sort
|
||||||
unit/expire
|
unit/expire
|
||||||
unit/other
|
unit/other
|
||||||
|
Loading…
x
Reference in New Issue
Block a user