Skip to content

Commit 9fdc57c

Browse files
committed
HMSETCK: set values in hash if another checks out
hmsetck key checkfield checkvalue field1 value1 ... Semantics are basically (pseudocode): if (hash->{checkfield} equals checkvalue) { foreach field in fields list { hash->{fieldX} = valueX } within a hash-value. This command is a very powerful primitive for Redis use cases that require versioning of data. It's also much easier to use (since it's a single command) to avoid races in such use cases than Redis transactions. If you have strong latency and throughput requirements: Both Redis transactions and Lua scripts are much more heavy-handed than this single, fast, atomic command. The particular use case I have is fast, race-free, versioned session handling in a cross-datacenter setup, where each session is a hash with am embedded version. If session updates only required one round-trip (like with this command) and didn't require running a Lua script, that'd be a improvement for us.
1 parent fdf50e1 commit 9fdc57c

5 files changed

Lines changed: 102 additions & 0 deletions

File tree

src/help.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@ struct commandHelp {
269269
"Set the value of a hash field, only if the field does not exist",
270270
5,
271271
"2.0.0" },
272+
{ "HMSETCK",
273+
"key field-to-check value-to-check-for field value [field value ...]",
274+
"Set values of a hash, only if another field has a particuler value",
275+
5,
276+
"2.9.11" },
272277
{ "HVALS",
273278
"key",
274279
"Get all the values in a hash",

src/redis.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ struct redisCommand redisCommandTable[] = {
185185
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
186186
{"hset",hsetCommand,4,"wm",0,NULL,1,1,1,0,0},
187187
{"hsetnx",hsetnxCommand,4,"wm",0,NULL,1,1,1,0,0},
188+
{"hmsetck",hmsetckCommand,-6,"wm",0,NULL,1,1,1,0,0},
188189
{"hget",hgetCommand,3,"r",0,NULL,1,1,1,0,0},
189190
{"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0},
190191
{"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0},

src/redis.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,7 @@ void zrankCommand(redisClient *c);
13681368
void zrevrankCommand(redisClient *c);
13691369
void hsetCommand(redisClient *c);
13701370
void hsetnxCommand(redisClient *c);
1371+
void hmsetckCommand(redisClient *c);
13711372
void hgetCommand(redisClient *c);
13721373
void hmsetCommand(redisClient *c);
13731374
void hmgetCommand(redisClient *c);

src/t_hash.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,37 @@ void hsetnxCommand(redisClient *c) {
492492
}
493493
}
494494

495+
/* HSETCK myhash checkkey checkval setkey setval */
496+
void hmsetckCommand(redisClient *c) {
497+
robj *o;
498+
robj *testobj;
499+
int i;
500+
501+
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
502+
503+
if (!hashTypeExists(o, c->argv[2])) {
504+
addReply(c, shared.czero);
505+
return;
506+
}
507+
508+
testobj = hashTypeGetObject(o,c->argv[2]);
509+
if (!equalStringObjects(testobj, c->argv[3])) {
510+
addReply(c, shared.czero);
511+
decrRefCount(testobj);
512+
return;
513+
}
514+
515+
hashTypeTryConversion(o,c->argv,4,c->argc-1);
516+
for (i = 4; i < c->argc; i += 2) {
517+
hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
518+
hashTypeSet(o,c->argv[i],c->argv[i+1]);
519+
}
520+
addReply(c, shared.ok);
521+
signalModifiedKey(c->db,c->argv[1]);
522+
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
523+
server.dirty++;
524+
}
525+
495526
void hmsetCommand(redisClient *c) {
496527
int i;
497528
robj *o;

tests/unit/type/hash.tcl

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,70 @@ start_server {tags {"hash"}} {
107107
set _ $result
108108
} {foo}
109109

110+
test {HMSETCK check-key missing - small hash} {
111+
r hmsetck newhash __123123123__ version1 foo bar
112+
set result [r hget newhash foo]
113+
set _ $result
114+
} {}
115+
116+
test {HMSETCK check-key exists but no match - small hash} {
117+
r hset newhash __123123123__ version2
118+
r hmsetck newhash __123123123__ version1 foo bar
119+
set result [r hget newhash foo]
120+
set _ $result
121+
} {}
122+
123+
test {HMSETCK check-key exists and matches - small hash} {
124+
r hset newhash __123123123__ version1
125+
r hmsetck newhash __123123123__ version1 foo bar
126+
set result [r hget newhash foo]
127+
r hdel newhash foo
128+
set _ $result
129+
} {bar}
130+
131+
test {HMSETCK check-key exists and matches, multi - small hash} {
132+
r hset newhash __123123123__ version1
133+
r hmsetck newhash __123123123__ version1 __123123123__ version2 foo bar
134+
set rv {}
135+
lappend rv [r hget newhash __123123123__]
136+
lappend rv [r hget newhash foo]
137+
r hdel newhash foo
138+
set _ $rv
139+
} {version2 bar}
140+
141+
test {HMSETCK check-key missing - big hash} {
142+
for {set i 0} {$i < 1050} {incr i} {
143+
set key [randstring 0 8 alpha]
144+
set val [randstring 0 8 alpha]
145+
r hset hmsetckbighash $key $val
146+
}
147+
r hmsetck hmsetckbighash __123123123__ version1 foo bar
148+
r hget hmsetckbighash foo
149+
} {}
150+
151+
test {HMSETCK check-key exists but no match - big hash} {
152+
r hset hmsetckbighash __123123123__ version2
153+
r hmsetck hmsetckbighash __123123123__ version1 foo bar
154+
r hget hmsetckbighash foo
155+
} {}
156+
157+
test {HMSETCK check-key exists and matches - big hash} {
158+
r hset hmsetckbighash __123123123__ version1
159+
r hmsetck hmsetckbighash __123123123__ version1 foo bar
160+
set result [r hget hmsetckbighash foo]
161+
set _ $result
162+
} {bar}
163+
164+
test {HMSETCK check-key exists and matches, multi - big hash} {
165+
r hset hmsetckbighash __123123123__ version1
166+
r hdel hmsetckbighash foo
167+
r hmsetck hmsetckbighash __123123123__ version1 __123123123__ version2 foo bar
168+
set rv {}
169+
lappend rv [r hget hmsetckbighash __123123123__]
170+
lappend rv [r hget hmsetckbighash foo]
171+
set _ $rv
172+
} {version2 bar}
173+
110174
test {HMSET wrong number of args} {
111175
catch {r hmset smallhash key1 val1 key2} err
112176
format $err

0 commit comments

Comments
 (0)