xref: /redis-3.2.3/src/db.c (revision 4942145d)
1 #include "redis.h"
2 
3 #include <signal.h>
4 
5 /*-----------------------------------------------------------------------------
6  * C-level DB API
7  *----------------------------------------------------------------------------*/
8 
9 robj *lookupKey(redisDb *db, robj *key) {
10     dictEntry *de = dictFind(db->dict,key->ptr);
11     if (de) {
12         robj *val = dictGetEntryVal(de);
13 
14         /* Update the access time for the aging algorithm.
15          * Don't do it if we have a saving child, as this will trigger
16          * a copy on write madness. */
17         if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1)
18             val->lru = server.lruclock;
19 
20         if (server.ds_enabled &&
21             cacheScheduleIOGetFlags(db,key) & REDIS_IO_SAVEINPROG)
22         {
23             /* There is a save in progress for this object!
24              * Wait for it to get out. */
25             waitEmptyIOJobsQueue();
26             processAllPendingIOJobs();
27             redisAssert(!(cacheScheduleIOGetFlags(db,key) & REDIS_IO_SAVEINPROG));
28         }
29         server.stat_keyspace_hits++;
30         return val;
31     } else {
32         time_t expire;
33         robj *val;
34 
35         /* Key not found in the in memory hash table, but if disk store is
36          * enabled we may have this key on disk. If so load it in memory
37          * in a blocking way. */
38         if (server.ds_enabled && cacheKeyMayExist(db,key)) {
39             if (cacheScheduleIOGetFlags(db,key) & REDIS_IO_SAVEINPROG) {
40                 /* There is a save in progress for this object!
41                  * Wait for it to get out. */
42                 waitEmptyIOJobsQueue();
43                 processAllPendingIOJobs();
44                 redisAssert((cacheScheduleIOGetFlags(db,key) & REDIS_IO_SAVEINPROG) == 0);
45             }
46 
47             redisLog(REDIS_DEBUG,"Force loading key %s via lookup",
48                 key->ptr);
49             val = dsGet(db,key,&expire);
50             if (val) {
51                 int retval = dbAdd(db,key,val);
52                 redisAssert(retval == REDIS_OK);
53                 if (expire != -1) setExpire(db,key,expire);
54                 server.stat_keyspace_hits++;
55                 return val;
56             }
57         }
58         server.stat_keyspace_misses++;
59         return NULL;
60     }
61 }
62 
63 robj *lookupKeyRead(redisDb *db, robj *key) {
64     expireIfNeeded(db,key);
65     return lookupKey(db,key);
66 }
67 
68 robj *lookupKeyWrite(redisDb *db, robj *key) {
69     expireIfNeeded(db,key);
70     return lookupKey(db,key);
71 }
72 
73 robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) {
74     robj *o = lookupKeyRead(c->db, key);
75     if (!o) addReply(c,reply);
76     return o;
77 }
78 
79 robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) {
80     robj *o = lookupKeyWrite(c->db, key);
81     if (!o) addReply(c,reply);
82     return o;
83 }
84 
85 /* Add the key to the DB. If the key already exists REDIS_ERR is returned,
86  * otherwise REDIS_OK is returned, and the caller should increment the
87  * refcount of 'val'. */
88 int dbAdd(redisDb *db, robj *key, robj *val) {
89     /* Perform a lookup before adding the key, as we need to copy the
90      * key value. */
91     if (dictFind(db->dict, key->ptr) != NULL) {
92         return REDIS_ERR;
93     } else {
94         sds copy = sdsdup(key->ptr);
95         dictAdd(db->dict, copy, val);
96         if (server.ds_enabled) {
97             /* FIXME: remove entry from negative cache */
98         }
99         return REDIS_OK;
100     }
101 }
102 
103 /* If the key does not exist, this is just like dbAdd(). Otherwise
104  * the value associated to the key is replaced with the new one.
105  *
106  * On update (key already existed) 0 is returned. Otherwise 1. */
107 int dbReplace(redisDb *db, robj *key, robj *val) {
108     robj *oldval;
109 
110     if ((oldval = dictFetchValue(db->dict,key->ptr)) == NULL) {
111         sds copy = sdsdup(key->ptr);
112         dictAdd(db->dict, copy, val);
113         return 1;
114     } else {
115         dictReplace(db->dict, key->ptr, val);
116         return 0;
117     }
118 }
119 
120 int dbExists(redisDb *db, robj *key) {
121     return dictFind(db->dict,key->ptr) != NULL;
122 }
123 
124 /* Return a random key, in form of a Redis object.
125  * If there are no keys, NULL is returned.
126  *
127  * The function makes sure to return keys not already expired. */
128 robj *dbRandomKey(redisDb *db) {
129     struct dictEntry *de;
130 
131     while(1) {
132         sds key;
133         robj *keyobj;
134 
135         de = dictGetRandomKey(db->dict);
136         if (de == NULL) return NULL;
137 
138         key = dictGetEntryKey(de);
139         keyobj = createStringObject(key,sdslen(key));
140         if (dictFind(db->expires,key)) {
141             if (expireIfNeeded(db,keyobj)) {
142                 decrRefCount(keyobj);
143                 continue; /* search for another key. This expired. */
144             }
145         }
146         return keyobj;
147     }
148 }
149 
150 /* Delete a key, value, and associated expiration entry if any, from the DB */
151 int dbDelete(redisDb *db, robj *key) {
152     /* If diskstore is enabled make sure to awake waiting clients for this key
153      * as it is not really useful to wait for a key already deleted to be
154      * loaded from disk. */
155     if (server.ds_enabled) handleClientsBlockedOnSwappedKey(db,key);
156 
157     /* FIXME: we should mark this key as non existing on disk in the negative
158      * cache. */
159 
160     /* Deleting an entry from the expires dict will not free the sds of
161      * the key, because it is shared with the main dictionary. */
162     if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
163     return dictDelete(db->dict,key->ptr) == DICT_OK;
164 }
165 
166 /* Empty the whole database */
167 long long emptyDb() {
168     int j;
169     long long removed = 0;
170 
171     for (j = 0; j < server.dbnum; j++) {
172         removed += dictSize(server.db[j].dict);
173         dictEmpty(server.db[j].dict);
174         dictEmpty(server.db[j].expires);
175     }
176     return removed;
177 }
178 
179 int selectDb(redisClient *c, int id) {
180     if (id < 0 || id >= server.dbnum)
181         return REDIS_ERR;
182     c->db = &server.db[id];
183     return REDIS_OK;
184 }
185 
186 /*-----------------------------------------------------------------------------
187  * Hooks for key space changes.
188  *
189  * Every time a key in the database is modified the function
190  * signalModifiedKey() is called.
191  *
192  * Every time a DB is flushed the function signalFlushDb() is called.
193  *----------------------------------------------------------------------------*/
194 
195 void signalModifiedKey(redisDb *db, robj *key) {
196     touchWatchedKey(db,key);
197     if (server.ds_enabled)
198         cacheScheduleIO(db,key,REDIS_IO_SAVE);
199 }
200 
201 void signalFlushedDb(int dbid) {
202     touchWatchedKeysOnFlush(dbid);
203     if (server.ds_enabled)
204         dsFlushDb(dbid);
205 }
206 
207 /*-----------------------------------------------------------------------------
208  * Type agnostic commands operating on the key space
209  *----------------------------------------------------------------------------*/
210 
211 void flushdbCommand(redisClient *c) {
212     server.dirty += dictSize(c->db->dict);
213     signalFlushedDb(c->db->id);
214     dictEmpty(c->db->dict);
215     dictEmpty(c->db->expires);
216     addReply(c,shared.ok);
217 }
218 
219 void flushallCommand(redisClient *c) {
220     signalFlushedDb(-1);
221     server.dirty += emptyDb();
222     addReply(c,shared.ok);
223     if (server.bgsavechildpid != -1) {
224         kill(server.bgsavechildpid,SIGKILL);
225         rdbRemoveTempFile(server.bgsavechildpid);
226     }
227     rdbSave(server.dbfilename);
228     server.dirty++;
229 }
230 
231 void delCommand(redisClient *c) {
232     int deleted = 0, j;
233 
234     for (j = 1; j < c->argc; j++) {
235         if (server.ds_enabled) {
236             lookupKeyRead(c->db,c->argv[j]);
237             /* FIXME: this can be optimized a lot, no real need to load
238              * a possibly huge value. */
239         }
240         if (dbDelete(c->db,c->argv[j])) {
241             signalModifiedKey(c->db,c->argv[j]);
242             server.dirty++;
243             deleted++;
244         } else if (server.ds_enabled) {
245             if (cacheKeyMayExist(c->db,c->argv[j]) &&
246                 dsExists(c->db,c->argv[j]))
247             {
248                 cacheScheduleIO(c->db,c->argv[j],REDIS_IO_SAVE);
249                 deleted = 1;
250             }
251         }
252     }
253     addReplyLongLong(c,deleted);
254 }
255 
256 void existsCommand(redisClient *c) {
257     expireIfNeeded(c->db,c->argv[1]);
258     if (dbExists(c->db,c->argv[1])) {
259         addReply(c, shared.cone);
260     } else {
261         addReply(c, shared.czero);
262     }
263 }
264 
265 void selectCommand(redisClient *c) {
266     int id = atoi(c->argv[1]->ptr);
267 
268     if (selectDb(c,id) == REDIS_ERR) {
269         addReplyError(c,"invalid DB index");
270     } else {
271         addReply(c,shared.ok);
272     }
273 }
274 
275 void randomkeyCommand(redisClient *c) {
276     robj *key;
277 
278     if ((key = dbRandomKey(c->db)) == NULL) {
279         addReply(c,shared.nullbulk);
280         return;
281     }
282 
283     addReplyBulk(c,key);
284     decrRefCount(key);
285 }
286 
287 void keysCommand(redisClient *c) {
288     dictIterator *di;
289     dictEntry *de;
290     sds pattern = c->argv[1]->ptr;
291     int plen = sdslen(pattern), allkeys;
292     unsigned long numkeys = 0;
293     void *replylen = addDeferredMultiBulkLength(c);
294 
295     di = dictGetIterator(c->db->dict);
296     allkeys = (pattern[0] == '*' && pattern[1] == '\0');
297     while((de = dictNext(di)) != NULL) {
298         sds key = dictGetEntryKey(de);
299         robj *keyobj;
300 
301         if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
302             keyobj = createStringObject(key,sdslen(key));
303             if (expireIfNeeded(c->db,keyobj) == 0) {
304                 addReplyBulk(c,keyobj);
305                 numkeys++;
306             }
307             decrRefCount(keyobj);
308         }
309     }
310     dictReleaseIterator(di);
311     setDeferredMultiBulkLength(c,replylen,numkeys);
312 }
313 
314 void dbsizeCommand(redisClient *c) {
315     addReplyLongLong(c,dictSize(c->db->dict));
316 }
317 
318 void lastsaveCommand(redisClient *c) {
319     addReplyLongLong(c,server.lastsave);
320 }
321 
322 void typeCommand(redisClient *c) {
323     robj *o;
324     char *type;
325 
326     o = lookupKeyRead(c->db,c->argv[1]);
327     if (o == NULL) {
328         type = "none";
329     } else {
330         switch(o->type) {
331         case REDIS_STRING: type = "string"; break;
332         case REDIS_LIST: type = "list"; break;
333         case REDIS_SET: type = "set"; break;
334         case REDIS_ZSET: type = "zset"; break;
335         case REDIS_HASH: type = "hash"; break;
336         default: type = "unknown"; break;
337         }
338     }
339     addReplyStatus(c,type);
340 }
341 
342 void saveCommand(redisClient *c) {
343     if (server.bgsavechildpid != -1) {
344         addReplyError(c,"Background save already in progress");
345         return;
346     }
347     if (rdbSave(server.dbfilename) == REDIS_OK) {
348         addReply(c,shared.ok);
349     } else {
350         addReply(c,shared.err);
351     }
352 }
353 
354 void bgsaveCommand(redisClient *c) {
355     if (server.bgsavechildpid != -1) {
356         addReplyError(c,"Background save already in progress");
357         return;
358     }
359     if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
360         addReplyStatus(c,"Background saving started");
361     } else {
362         addReply(c,shared.err);
363     }
364 }
365 
366 void shutdownCommand(redisClient *c) {
367     if (prepareForShutdown() == REDIS_OK)
368         exit(0);
369     addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
370 }
371 
372 void renameGenericCommand(redisClient *c, int nx) {
373     robj *o;
374 
375     /* To use the same key as src and dst is probably an error */
376     if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) {
377         addReply(c,shared.sameobjecterr);
378         return;
379     }
380 
381     if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL)
382         return;
383 
384     incrRefCount(o);
385     if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) {
386         if (nx) {
387             decrRefCount(o);
388             addReply(c,shared.czero);
389             return;
390         }
391         dbReplace(c->db,c->argv[2],o);
392     }
393     dbDelete(c->db,c->argv[1]);
394     signalModifiedKey(c->db,c->argv[1]);
395     signalModifiedKey(c->db,c->argv[2]);
396     server.dirty++;
397     addReply(c,nx ? shared.cone : shared.ok);
398 }
399 
400 void renameCommand(redisClient *c) {
401     renameGenericCommand(c,0);
402 }
403 
404 void renamenxCommand(redisClient *c) {
405     renameGenericCommand(c,1);
406 }
407 
408 void moveCommand(redisClient *c) {
409     robj *o;
410     redisDb *src, *dst;
411     int srcid;
412 
413     /* Obtain source and target DB pointers */
414     src = c->db;
415     srcid = c->db->id;
416     if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) {
417         addReply(c,shared.outofrangeerr);
418         return;
419     }
420     dst = c->db;
421     selectDb(c,srcid); /* Back to the source DB */
422 
423     /* If the user is moving using as target the same
424      * DB as the source DB it is probably an error. */
425     if (src == dst) {
426         addReply(c,shared.sameobjecterr);
427         return;
428     }
429 
430     /* Check if the element exists and get a reference */
431     o = lookupKeyWrite(c->db,c->argv[1]);
432     if (!o) {
433         addReply(c,shared.czero);
434         return;
435     }
436 
437     /* Try to add the element to the target DB */
438     if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) {
439         addReply(c,shared.czero);
440         return;
441     }
442     incrRefCount(o);
443 
444     /* OK! key moved, free the entry in the source DB */
445     dbDelete(src,c->argv[1]);
446     server.dirty++;
447     addReply(c,shared.cone);
448 }
449 
450 /*-----------------------------------------------------------------------------
451  * Expires API
452  *----------------------------------------------------------------------------*/
453 
454 int removeExpire(redisDb *db, robj *key) {
455     /* An expire may only be removed if there is a corresponding entry in the
456      * main dict. Otherwise, the key will never be freed. */
457     redisAssert(dictFind(db->dict,key->ptr) != NULL);
458     return dictDelete(db->expires,key->ptr) == DICT_OK;
459 }
460 
461 void setExpire(redisDb *db, robj *key, time_t when) {
462     dictEntry *de;
463 
464     /* Reuse the sds from the main dict in the expire dict */
465     de = dictFind(db->dict,key->ptr);
466     redisAssert(de != NULL);
467     dictReplace(db->expires,dictGetEntryKey(de),(void*)when);
468 }
469 
470 /* Return the expire time of the specified key, or -1 if no expire
471  * is associated with this key (i.e. the key is non volatile) */
472 time_t getExpire(redisDb *db, robj *key) {
473     dictEntry *de;
474 
475     /* No expire? return ASAP */
476     if (dictSize(db->expires) == 0 ||
477        (de = dictFind(db->expires,key->ptr)) == NULL) return -1;
478 
479     /* The entry was found in the expire dict, this means it should also
480      * be present in the main dict (safety check). */
481     redisAssert(dictFind(db->dict,key->ptr) != NULL);
482     return (time_t) dictGetEntryVal(de);
483 }
484 
485 /* Propagate expires into slaves and the AOF file.
486  * When a key expires in the master, a DEL operation for this key is sent
487  * to all the slaves and the AOF file if enabled.
488  *
489  * This way the key expiry is centralized in one place, and since both
490  * AOF and the master->slave link guarantee operation ordering, everything
491  * will be consistent even if we allow write operations against expiring
492  * keys. */
493 void propagateExpire(redisDb *db, robj *key) {
494     robj *argv[2];
495 
496     argv[0] = createStringObject("DEL",3);
497     argv[1] = key;
498     incrRefCount(key);
499 
500     if (server.appendonly)
501         feedAppendOnlyFile(server.delCommand,db->id,argv,2);
502     if (listLength(server.slaves))
503         replicationFeedSlaves(server.slaves,db->id,argv,2);
504 
505     decrRefCount(argv[0]);
506     decrRefCount(argv[1]);
507 }
508 
509 int expireIfNeeded(redisDb *db, robj *key) {
510     time_t when = getExpire(db,key);
511 
512     /* If we are running in the context of a slave, return ASAP:
513      * the slave key expiration is controlled by the master that will
514      * send us synthesized DEL operations for expired keys.
515      *
516      * Still we try to return the right information to the caller,
517      * that is, 0 if we think the key should be still valid, 1 if
518      * we think the key is expired at this time. */
519     if (server.masterhost != NULL) {
520         return time(NULL) > when;
521     }
522 
523     if (when < 0) return 0;
524 
525     /* Return when this key has not expired */
526     if (time(NULL) <= when) return 0;
527 
528     /* Delete the key */
529     server.stat_expiredkeys++;
530     propagateExpire(db,key);
531     return dbDelete(db,key);
532 }
533 
534 /*-----------------------------------------------------------------------------
535  * Expires Commands
536  *----------------------------------------------------------------------------*/
537 
538 void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
539     dictEntry *de;
540     long seconds;
541 
542     if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return;
543 
544     seconds -= offset;
545 
546     de = dictFind(c->db->dict,key->ptr);
547     if (de == NULL) {
548         addReply(c,shared.czero);
549         return;
550     }
551     if (seconds <= 0) {
552         if (dbDelete(c->db,key)) server.dirty++;
553         addReply(c, shared.cone);
554         signalModifiedKey(c->db,key);
555         return;
556     } else {
557         time_t when = time(NULL)+seconds;
558         setExpire(c->db,key,when);
559         addReply(c,shared.cone);
560         signalModifiedKey(c->db,key);
561         server.dirty++;
562         return;
563     }
564 }
565 
566 void expireCommand(redisClient *c) {
567     expireGenericCommand(c,c->argv[1],c->argv[2],0);
568 }
569 
570 void expireatCommand(redisClient *c) {
571     expireGenericCommand(c,c->argv[1],c->argv[2],time(NULL));
572 }
573 
574 void ttlCommand(redisClient *c) {
575     time_t expire, ttl = -1;
576 
577     expire = getExpire(c->db,c->argv[1]);
578     if (expire != -1) {
579         ttl = (expire-time(NULL));
580         if (ttl < 0) ttl = -1;
581     }
582     addReplyLongLong(c,(long long)ttl);
583 }
584 
585 void persistCommand(redisClient *c) {
586     dictEntry *de;
587 
588     de = dictFind(c->db->dict,c->argv[1]->ptr);
589     if (de == NULL) {
590         addReply(c,shared.czero);
591     } else {
592         if (removeExpire(c->db,c->argv[1])) {
593             addReply(c,shared.cone);
594             server.dirty++;
595         } else {
596             addReply(c,shared.czero);
597         }
598     }
599 }
600