Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HAPPEND and HAPPENDX commands #2

Open
wants to merge 4 commits into
base: unstable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/help.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ struct commandHelp {
"Set the string value of a key and return its old value",
1,
"1.0.0" },
{ "HAPPEND",
"key field value",
"Append a value to a hash field",
5,
"2.8.0" },
{ "HAPPENDX",
"key field value",
"Append a value to a hash field, only if the key exists",
5,
"2.8.0" },
{ "HDEL",
"key field [field ...]",
"Delete one or more hash fields",
Expand Down
2 changes: 2 additions & 0 deletions src/redis.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ struct redisCommand redisCommandTable[] = {
{"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
{"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},
{"hexists",hexistsCommand,3,"r",0,NULL,1,1,1,0,0},
{"happend",happendCommand,4,"wm",0,NULL,1,1,1,0,0},
{"happendx",happendxCommand,4,"wm",0,NULL,1,1,1,0,0},
{"incrby",incrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
{"decrby",decrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
{"incrbyfloat",incrbyfloatCommand,3,"wm",0,NULL,1,1,1,0,0},
Expand Down
2 changes: 2 additions & 0 deletions src/redis.h
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,8 @@ void zinterstoreCommand(redisClient *c);
void hkeysCommand(redisClient *c);
void hvalsCommand(redisClient *c);
void hgetallCommand(redisClient *c);
void happendCommand(redisClient *c);
void happendxCommand(redisClient *c);
void hexistsCommand(redisClient *c);
void configCommand(redisClient *c);
void hincrbyCommand(redisClient *c);
Expand Down
110 changes: 110 additions & 0 deletions src/t_hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,88 @@ int hashTypeExists(robj *o, robj *field) {
return 0;
}

/* Append to an element.
* Return total length of appended string.
* This function will take care of incrementing the reference count of the
* retained fields and value objects. */
size_t hashTypeAppend(robj *o, robj *field, robj *append) {
int update = 0;
long long totlen = 0;
robj *value;

if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl, *fptr, *vptr;
unsigned char *vstr = NULL;
unsigned int vlen = UINT_MAX;
long long vll = LLONG_MAX;
int ret;

field = getDecodedObject(field);
append = getDecodedObject(append);

zl = o->ptr;
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
if (fptr != NULL) {
fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
if (fptr != NULL) {
/* Grab pointer to the value (fptr points to the field) */
vptr = ziplistNext(zl, fptr);
redisAssert(vptr != NULL);
update = 1;

/* Append to current string */
ret = ziplistGet(vptr, &vstr, &vlen, &vll);
redisAssert(ret);
value = createStringObject((char *)vstr, (size_t)vlen);
value->ptr = sdscatlen(value->ptr, append->ptr, sdslen(append->ptr));

/* Delete value */
zl = ziplistDelete(zl, &vptr);

/* Insert new value */
zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
totlen = sdslen(value->ptr);
decrRefCount(value);
}
}

if (!update) {
/* Push new field/value pair onto the tail of the ziplist */
value = append;
zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
totlen = sdslen(value->ptr);
}

o->ptr = zl;
decrRefCount(field);
decrRefCount(append);

/* Check if the ziplist needs to be converted to a hash table */
if (hashTypeLength(o) > server.hash_max_ziplist_entries ||
totlen > server.hash_max_ziplist_value)
hashTypeConvert(o, REDIS_ENCODING_HT);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to be checking the hash-max-ziplist-value here.

} else if (o->encoding == REDIS_ENCODING_HT) {
/* Append to current string */
if ((value = hashTypeGetObject(o,field)) != NULL) {
value->ptr = sdscatlen(value->ptr,append->ptr,sdslen(append->ptr));
} else {
value = append;
incrRefCount(value);
}

if (dictReplace(o->ptr, field, value)) { /* Insert */
incrRefCount(field);
} else { /* Update */
update = 1;
}
totlen = sdslen(value->ptr);
} else {
redisPanic("Unknown hash encoding");
}
return totlen;
}

/* Add an element, discard the old if the key already exists.
* Return 0 on insert and 1 on update.
* This function will take care of incrementing the reference count of the
Expand Down Expand Up @@ -752,6 +834,34 @@ void hgetallCommand(redisClient *c) {
genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
}

void genericHappendCommand(redisClient *c, int nx) {
size_t totlen;
robj *o;

if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
hashTypeTryConversion(o,c->argv,2,3);

if (!nx && !hashTypeExists(o, c->argv[2])) {
addReply(c, shared.czero);
return;
} else {
hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
totlen = hashTypeAppend(o,c->argv[2],c->argv[3]);
addReplyLongLong(c, totlen);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"happend",c->argv[1],c->db->id);
server.dirty++;
}
}

void happendxCommand(redisClient *c) {
genericHappendCommand(c,0);
}

void happendCommand(redisClient *c) {
genericHappendCommand(c,1);
}

void hexistsCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
Expand Down
72 changes: 72 additions & 0 deletions tests/unit/type/hash.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,78 @@ start_server {tags {"hash"}} {
set _ $result
} {foo}

test {HAPPEND target key missing - small hash} {
set rv {}
lappend rv [r happend smallhash __123123123__ foo]
lappend rv [r hget smallhash __123123123__]
r hdel smallhash __123123123__
set _ $rv
} {3 foo}

test {HAPPEND target key exists - small hash} {
set rv {}
r hset smallhash __123123123__ foo
lappend rv [r happend smallhash __123123123__ bar]
lappend rv [r hget smallhash __123123123__]
r hdel smallhash __123123123__
set _ $rv
} {6 foobar}

test {HAPPEND target key missing - big hash} {
set rv {}
lappend rv [r happend bighash __123123123__ foo]
lappend rv [r hget bighash __123123123__]
set _ $rv
} {3 foo}

test {HAPPEND target key exists - big hash} {
set rv {}
r hset bighash __123123123__ foo
lappend rv [r happend bighash __123123123__ bar]
lappend rv [r hget bighash __123123123__]
r hdel bighash __123123123__
set _ $rv
} {6 foobar}

test {HAPPEND Is a ziplist encoded Hash promoted on big payload?} {
r hset myhash foo bar
assert_encoding ziplist myhash
r happend myhash foo [string repeat a 1024]
r debug object myhash
} {*hashtable*}

test {HAPPENDX target key missing - small hash} {
set rv {}
lappend rv [r happendx smallhash __123123123__ foo]
lappend rv [r hget smallhash __123123123__]
set _ $rv
} {0 {}}

test {HAPPENDX target key exists - small hash} {
set rv {}
r hset smallhash __123123123__ foo
lappend rv [r happendx smallhash __123123123__ bar]
lappend rv [r hget smallhash __123123123__]
r hdel smallhash __123123123__
set _ $rv
} {6 foobar}

test {HAPPENDX target key missing - big hash} {
set rv {}
lappend rv [r happendx bighash __123123123__ foo]
lappend rv [r hget bighash __123123123__]
set _ $rv
} {0 {}}

test {HAPPENDX target key exists - big hash} {
set rv {}
r hset bighash __123123123__ foo
lappend rv [r happendx bighash __123123123__ bar]
lappend rv [r hget bighash __123123123__]
r hdel bighash __123123123__
set _ $rv
} {6 foobar}

test {HMSET wrong number of args} {
catch {r hmset smallhash key1 val1 key2} err
format $err
Expand Down