]>
Commit | Line | Data |
---|---|---|
e2641e09 | 1 | #include "redis.h" |
2 | ||
3 | #include <math.h> | |
4 | ||
5 | /*----------------------------------------------------------------------------- | |
6 | * Hash type API | |
7 | *----------------------------------------------------------------------------*/ | |
8 | ||
9 | /* Check the length of a number of objects to see if we need to convert a | |
10 | * zipmap to a real hash. Note that we only check string encoded objects | |
11 | * as their string length can be queried in constant time. */ | |
12 | void hashTypeTryConversion(robj *subject, robj **argv, int start, int end) { | |
13 | int i; | |
14 | if (subject->encoding != REDIS_ENCODING_ZIPMAP) return; | |
15 | ||
16 | for (i = start; i <= end; i++) { | |
17 | if (argv[i]->encoding == REDIS_ENCODING_RAW && | |
18 | sdslen(argv[i]->ptr) > server.hash_max_zipmap_value) | |
19 | { | |
20 | convertToRealHash(subject); | |
21 | return; | |
22 | } | |
23 | } | |
24 | } | |
25 | ||
26 | /* Encode given objects in-place when the hash uses a dict. */ | |
27 | void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) { | |
28 | if (subject->encoding == REDIS_ENCODING_HT) { | |
29 | if (o1) *o1 = tryObjectEncoding(*o1); | |
30 | if (o2) *o2 = tryObjectEncoding(*o2); | |
31 | } | |
32 | } | |
33 | ||
34 | /* Get the value from a hash identified by key. Returns either a string | |
35 | * object or NULL if the value cannot be found. The refcount of the object | |
36 | * is always increased by 1 when the value was found. */ | |
37 | robj *hashTypeGet(robj *o, robj *key) { | |
38 | robj *value = NULL; | |
39 | if (o->encoding == REDIS_ENCODING_ZIPMAP) { | |
40 | unsigned char *v; | |
41 | unsigned int vlen; | |
42 | key = getDecodedObject(key); | |
43 | if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) { | |
44 | value = createStringObject((char*)v,vlen); | |
45 | } | |
46 | decrRefCount(key); | |
47 | } else { | |
48 | dictEntry *de = dictFind(o->ptr,key); | |
49 | if (de != NULL) { | |
50 | value = dictGetEntryVal(de); | |
51 | incrRefCount(value); | |
52 | } | |
53 | } | |
54 | return value; | |
55 | } | |
56 | ||
57 | /* Test if the key exists in the given hash. Returns 1 if the key | |
58 | * exists and 0 when it doesn't. */ | |
59 | int hashTypeExists(robj *o, robj *key) { | |
60 | if (o->encoding == REDIS_ENCODING_ZIPMAP) { | |
61 | key = getDecodedObject(key); | |
62 | if (zipmapExists(o->ptr,key->ptr,sdslen(key->ptr))) { | |
63 | decrRefCount(key); | |
64 | return 1; | |
65 | } | |
66 | decrRefCount(key); | |
67 | } else { | |
68 | if (dictFind(o->ptr,key) != NULL) { | |
69 | return 1; | |
70 | } | |
71 | } | |
72 | return 0; | |
73 | } | |
74 | ||
75 | /* Add an element, discard the old if the key already exists. | |
76 | * Return 0 on insert and 1 on update. */ | |
77 | int hashTypeSet(robj *o, robj *key, robj *value) { | |
78 | int update = 0; | |
79 | if (o->encoding == REDIS_ENCODING_ZIPMAP) { | |
80 | key = getDecodedObject(key); | |
81 | value = getDecodedObject(value); | |
82 | o->ptr = zipmapSet(o->ptr, | |
83 | key->ptr,sdslen(key->ptr), | |
84 | value->ptr,sdslen(value->ptr), &update); | |
85 | decrRefCount(key); | |
86 | decrRefCount(value); | |
87 | ||
88 | /* Check if the zipmap needs to be upgraded to a real hash table */ | |
89 | if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries) | |
90 | convertToRealHash(o); | |
91 | } else { | |
92 | if (dictReplace(o->ptr,key,value)) { | |
93 | /* Insert */ | |
94 | incrRefCount(key); | |
95 | } else { | |
96 | /* Update */ | |
97 | update = 1; | |
98 | } | |
99 | incrRefCount(value); | |
100 | } | |
101 | return update; | |
102 | } | |
103 | ||
104 | /* Delete an element from a hash. | |
105 | * Return 1 on deleted and 0 on not found. */ | |
106 | int hashTypeDelete(robj *o, robj *key) { | |
107 | int deleted = 0; | |
108 | if (o->encoding == REDIS_ENCODING_ZIPMAP) { | |
109 | key = getDecodedObject(key); | |
110 | o->ptr = zipmapDel(o->ptr,key->ptr,sdslen(key->ptr), &deleted); | |
111 | decrRefCount(key); | |
112 | } else { | |
113 | deleted = dictDelete((dict*)o->ptr,key) == DICT_OK; | |
114 | /* Always check if the dictionary needs a resize after a delete. */ | |
115 | if (deleted && htNeedsResize(o->ptr)) dictResize(o->ptr); | |
116 | } | |
117 | return deleted; | |
118 | } | |
119 | ||
120 | /* Return the number of elements in a hash. */ | |
121 | unsigned long hashTypeLength(robj *o) { | |
122 | return (o->encoding == REDIS_ENCODING_ZIPMAP) ? | |
123 | zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr); | |
124 | } | |
125 | ||
126 | hashTypeIterator *hashTypeInitIterator(robj *subject) { | |
127 | hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator)); | |
128 | hi->encoding = subject->encoding; | |
129 | if (hi->encoding == REDIS_ENCODING_ZIPMAP) { | |
130 | hi->zi = zipmapRewind(subject->ptr); | |
131 | } else if (hi->encoding == REDIS_ENCODING_HT) { | |
132 | hi->di = dictGetIterator(subject->ptr); | |
133 | } else { | |
134 | redisAssert(NULL); | |
135 | } | |
136 | return hi; | |
137 | } | |
138 | ||
139 | void hashTypeReleaseIterator(hashTypeIterator *hi) { | |
140 | if (hi->encoding == REDIS_ENCODING_HT) { | |
141 | dictReleaseIterator(hi->di); | |
142 | } | |
143 | zfree(hi); | |
144 | } | |
145 | ||
146 | /* Move to the next entry in the hash. Return REDIS_OK when the next entry | |
147 | * could be found and REDIS_ERR when the iterator reaches the end. */ | |
148 | int hashTypeNext(hashTypeIterator *hi) { | |
149 | if (hi->encoding == REDIS_ENCODING_ZIPMAP) { | |
150 | if ((hi->zi = zipmapNext(hi->zi, &hi->zk, &hi->zklen, | |
151 | &hi->zv, &hi->zvlen)) == NULL) return REDIS_ERR; | |
152 | } else { | |
153 | if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR; | |
154 | } | |
155 | return REDIS_OK; | |
156 | } | |
157 | ||
158 | /* Get key or value object at current iteration position. | |
159 | * This increases the refcount of the field object by 1. */ | |
160 | robj *hashTypeCurrent(hashTypeIterator *hi, int what) { | |
161 | robj *o; | |
162 | if (hi->encoding == REDIS_ENCODING_ZIPMAP) { | |
163 | if (what & REDIS_HASH_KEY) { | |
164 | o = createStringObject((char*)hi->zk,hi->zklen); | |
165 | } else { | |
166 | o = createStringObject((char*)hi->zv,hi->zvlen); | |
167 | } | |
168 | } else { | |
169 | if (what & REDIS_HASH_KEY) { | |
170 | o = dictGetEntryKey(hi->de); | |
171 | } else { | |
172 | o = dictGetEntryVal(hi->de); | |
173 | } | |
174 | incrRefCount(o); | |
175 | } | |
176 | return o; | |
177 | } | |
178 | ||
179 | robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) { | |
180 | robj *o = lookupKeyWrite(c->db,key); | |
181 | if (o == NULL) { | |
182 | o = createHashObject(); | |
183 | dbAdd(c->db,key,o); | |
184 | } else { | |
185 | if (o->type != REDIS_HASH) { | |
186 | addReply(c,shared.wrongtypeerr); | |
187 | return NULL; | |
188 | } | |
189 | } | |
190 | return o; | |
191 | } | |
192 | ||
193 | void convertToRealHash(robj *o) { | |
194 | unsigned char *key, *val, *p, *zm = o->ptr; | |
195 | unsigned int klen, vlen; | |
196 | dict *dict = dictCreate(&hashDictType,NULL); | |
197 | ||
198 | redisAssert(o->type == REDIS_HASH && o->encoding != REDIS_ENCODING_HT); | |
199 | p = zipmapRewind(zm); | |
200 | while((p = zipmapNext(p,&key,&klen,&val,&vlen)) != NULL) { | |
201 | robj *keyobj, *valobj; | |
202 | ||
203 | keyobj = createStringObject((char*)key,klen); | |
204 | valobj = createStringObject((char*)val,vlen); | |
205 | keyobj = tryObjectEncoding(keyobj); | |
206 | valobj = tryObjectEncoding(valobj); | |
207 | dictAdd(dict,keyobj,valobj); | |
208 | } | |
209 | o->encoding = REDIS_ENCODING_HT; | |
210 | o->ptr = dict; | |
211 | zfree(zm); | |
212 | } | |
213 | ||
214 | /*----------------------------------------------------------------------------- | |
215 | * Hash type commands | |
216 | *----------------------------------------------------------------------------*/ | |
217 | ||
218 | void hsetCommand(redisClient *c) { | |
219 | int update; | |
220 | robj *o; | |
221 | ||
222 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
223 | hashTypeTryConversion(o,c->argv,2,3); | |
224 | hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); | |
225 | update = hashTypeSet(o,c->argv[2],c->argv[3]); | |
226 | addReply(c, update ? shared.czero : shared.cone); | |
5b4bff9c | 227 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 228 | server.dirty++; |
229 | } | |
230 | ||
231 | void hsetnxCommand(redisClient *c) { | |
232 | robj *o; | |
233 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
234 | hashTypeTryConversion(o,c->argv,2,3); | |
235 | ||
236 | if (hashTypeExists(o, c->argv[2])) { | |
237 | addReply(c, shared.czero); | |
238 | } else { | |
239 | hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); | |
240 | hashTypeSet(o,c->argv[2],c->argv[3]); | |
241 | addReply(c, shared.cone); | |
5b4bff9c | 242 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 243 | server.dirty++; |
244 | } | |
245 | } | |
246 | ||
247 | void hmsetCommand(redisClient *c) { | |
248 | int i; | |
249 | robj *o; | |
250 | ||
251 | if ((c->argc % 2) == 1) { | |
252 | addReplySds(c,sdsnew("-ERR wrong number of arguments for HMSET\r\n")); | |
253 | return; | |
254 | } | |
255 | ||
256 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
257 | hashTypeTryConversion(o,c->argv,2,c->argc-1); | |
258 | for (i = 2; i < c->argc; i += 2) { | |
259 | hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); | |
260 | hashTypeSet(o,c->argv[i],c->argv[i+1]); | |
261 | } | |
262 | addReply(c, shared.ok); | |
5b4bff9c | 263 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 264 | server.dirty++; |
265 | } | |
266 | ||
267 | void hincrbyCommand(redisClient *c) { | |
268 | long long value, incr; | |
269 | robj *o, *current, *new; | |
270 | ||
271 | if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; | |
272 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
273 | if ((current = hashTypeGet(o,c->argv[2])) != NULL) { | |
274 | if (getLongLongFromObjectOrReply(c,current,&value, | |
275 | "hash value is not an integer") != REDIS_OK) { | |
276 | decrRefCount(current); | |
277 | return; | |
278 | } | |
279 | decrRefCount(current); | |
280 | } else { | |
281 | value = 0; | |
282 | } | |
283 | ||
284 | value += incr; | |
285 | new = createStringObjectFromLongLong(value); | |
286 | hashTypeTryObjectEncoding(o,&c->argv[2],NULL); | |
287 | hashTypeSet(o,c->argv[2],new); | |
288 | decrRefCount(new); | |
289 | addReplyLongLong(c,value); | |
5b4bff9c | 290 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 291 | server.dirty++; |
292 | } | |
293 | ||
294 | void hgetCommand(redisClient *c) { | |
295 | robj *o, *value; | |
296 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || | |
297 | checkType(c,o,REDIS_HASH)) return; | |
298 | ||
299 | if ((value = hashTypeGet(o,c->argv[2])) != NULL) { | |
300 | addReplyBulk(c,value); | |
301 | decrRefCount(value); | |
302 | } else { | |
303 | addReply(c,shared.nullbulk); | |
304 | } | |
305 | } | |
306 | ||
307 | void hmgetCommand(redisClient *c) { | |
308 | int i; | |
309 | robj *o, *value; | |
310 | o = lookupKeyRead(c->db,c->argv[1]); | |
311 | if (o != NULL && o->type != REDIS_HASH) { | |
312 | addReply(c,shared.wrongtypeerr); | |
313 | } | |
314 | ||
315 | /* Note the check for o != NULL happens inside the loop. This is | |
316 | * done because objects that cannot be found are considered to be | |
317 | * an empty hash. The reply should then be a series of NULLs. */ | |
318 | addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-2)); | |
319 | for (i = 2; i < c->argc; i++) { | |
320 | if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) { | |
321 | addReplyBulk(c,value); | |
322 | decrRefCount(value); | |
323 | } else { | |
324 | addReply(c,shared.nullbulk); | |
325 | } | |
326 | } | |
327 | } | |
328 | ||
329 | void hdelCommand(redisClient *c) { | |
330 | robj *o; | |
331 | if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || | |
332 | checkType(c,o,REDIS_HASH)) return; | |
333 | ||
334 | if (hashTypeDelete(o,c->argv[2])) { | |
335 | if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); | |
336 | addReply(c,shared.cone); | |
5b4bff9c | 337 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 338 | server.dirty++; |
339 | } else { | |
340 | addReply(c,shared.czero); | |
341 | } | |
342 | } | |
343 | ||
344 | void hlenCommand(redisClient *c) { | |
345 | robj *o; | |
346 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || | |
347 | checkType(c,o,REDIS_HASH)) return; | |
348 | ||
349 | addReplyUlong(c,hashTypeLength(o)); | |
350 | } | |
351 | ||
352 | void genericHgetallCommand(redisClient *c, int flags) { | |
353 | robj *o, *lenobj, *obj; | |
354 | unsigned long count = 0; | |
355 | hashTypeIterator *hi; | |
356 | ||
357 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL | |
358 | || checkType(c,o,REDIS_HASH)) return; | |
359 | ||
360 | lenobj = createObject(REDIS_STRING,NULL); | |
361 | addReply(c,lenobj); | |
362 | decrRefCount(lenobj); | |
363 | ||
364 | hi = hashTypeInitIterator(o); | |
365 | while (hashTypeNext(hi) != REDIS_ERR) { | |
366 | if (flags & REDIS_HASH_KEY) { | |
367 | obj = hashTypeCurrent(hi,REDIS_HASH_KEY); | |
368 | addReplyBulk(c,obj); | |
369 | decrRefCount(obj); | |
370 | count++; | |
371 | } | |
372 | if (flags & REDIS_HASH_VALUE) { | |
373 | obj = hashTypeCurrent(hi,REDIS_HASH_VALUE); | |
374 | addReplyBulk(c,obj); | |
375 | decrRefCount(obj); | |
376 | count++; | |
377 | } | |
378 | } | |
379 | hashTypeReleaseIterator(hi); | |
380 | ||
381 | lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",count); | |
382 | } | |
383 | ||
384 | void hkeysCommand(redisClient *c) { | |
385 | genericHgetallCommand(c,REDIS_HASH_KEY); | |
386 | } | |
387 | ||
388 | void hvalsCommand(redisClient *c) { | |
389 | genericHgetallCommand(c,REDIS_HASH_VALUE); | |
390 | } | |
391 | ||
392 | void hgetallCommand(redisClient *c) { | |
393 | genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE); | |
394 | } | |
395 | ||
396 | void hexistsCommand(redisClient *c) { | |
397 | robj *o; | |
398 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || | |
399 | checkType(c,o,REDIS_HASH)) return; | |
400 | ||
401 | addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); | |
402 | } |