| 1 | #include "redis.h" |
| 2 | |
| 3 | #include <signal.h> |
| 4 | #include <ctype.h> |
| 5 | |
| 6 | void SlotToKeyAdd(robj *key); |
| 7 | void SlotToKeyDel(robj *key); |
| 8 | |
| 9 | /*----------------------------------------------------------------------------- |
| 10 | * C-level DB API |
| 11 | *----------------------------------------------------------------------------*/ |
| 12 | |
| 13 | robj *lookupKey(redisDb *db, robj *key) { |
| 14 | dictEntry *de = dictFind(db->dict,key->ptr); |
| 15 | if (de) { |
| 16 | robj *val = dictGetVal(de); |
| 17 | |
| 18 | /* Update the access time for the aging algorithm. |
| 19 | * Don't do it if we have a saving child, as this will trigger |
| 20 | * a copy on write madness. */ |
| 21 | if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) |
| 22 | val->lru = server.lruclock; |
| 23 | return val; |
| 24 | } else { |
| 25 | return NULL; |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | robj *lookupKeyRead(redisDb *db, robj *key) { |
| 30 | robj *val; |
| 31 | |
| 32 | expireIfNeeded(db,key); |
| 33 | val = lookupKey(db,key); |
| 34 | if (val == NULL) |
| 35 | server.stat_keyspace_misses++; |
| 36 | else |
| 37 | server.stat_keyspace_hits++; |
| 38 | return val; |
| 39 | } |
| 40 | |
| 41 | robj *lookupKeyWrite(redisDb *db, robj *key) { |
| 42 | expireIfNeeded(db,key); |
| 43 | return lookupKey(db,key); |
| 44 | } |
| 45 | |
| 46 | robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) { |
| 47 | robj *o = lookupKeyRead(c->db, key); |
| 48 | if (!o) addReply(c,reply); |
| 49 | return o; |
| 50 | } |
| 51 | |
| 52 | robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) { |
| 53 | robj *o = lookupKeyWrite(c->db, key); |
| 54 | if (!o) addReply(c,reply); |
| 55 | return o; |
| 56 | } |
| 57 | |
| 58 | /* Add the key to the DB. It's up to the caller to increment the reference |
| 59 | * counte of the value if needed. |
| 60 | * |
| 61 | * The program is aborted if the key already exists. */ |
| 62 | void dbAdd(redisDb *db, robj *key, robj *val) { |
| 63 | sds copy = sdsdup(key->ptr); |
| 64 | int retval = dictAdd(db->dict, copy, val); |
| 65 | |
| 66 | redisAssertWithInfo(NULL,key,retval == REDIS_OK); |
| 67 | if (server.cluster_enabled) SlotToKeyAdd(key); |
| 68 | } |
| 69 | |
| 70 | /* Overwrite an existing key with a new value. Incrementing the reference |
| 71 | * count of the new value is up to the caller. |
| 72 | * This function does not modify the expire time of the existing key. |
| 73 | * |
| 74 | * The program is aborted if the key was not already present. */ |
| 75 | void dbOverwrite(redisDb *db, robj *key, robj *val) { |
| 76 | struct dictEntry *de = dictFind(db->dict,key->ptr); |
| 77 | |
| 78 | redisAssertWithInfo(NULL,key,de != NULL); |
| 79 | dictReplace(db->dict, key->ptr, val); |
| 80 | } |
| 81 | |
| 82 | /* High level Set operation. This function can be used in order to set |
| 83 | * a key, whatever it was existing or not, to a new object. |
| 84 | * |
| 85 | * 1) The ref count of the value object is incremented. |
| 86 | * 2) clients WATCHing for the destination key notified. |
| 87 | * 3) The expire time of the key is reset (the key is made persistent). */ |
| 88 | void setKey(redisDb *db, robj *key, robj *val) { |
| 89 | if (lookupKeyWrite(db,key) == NULL) { |
| 90 | dbAdd(db,key,val); |
| 91 | } else { |
| 92 | dbOverwrite(db,key,val); |
| 93 | } |
| 94 | incrRefCount(val); |
| 95 | removeExpire(db,key); |
| 96 | signalModifiedKey(db,key); |
| 97 | } |
| 98 | |
| 99 | int dbExists(redisDb *db, robj *key) { |
| 100 | return dictFind(db->dict,key->ptr) != NULL; |
| 101 | } |
| 102 | |
| 103 | /* Return a random key, in form of a Redis object. |
| 104 | * If there are no keys, NULL is returned. |
| 105 | * |
| 106 | * The function makes sure to return keys not already expired. */ |
| 107 | robj *dbRandomKey(redisDb *db) { |
| 108 | struct dictEntry *de; |
| 109 | |
| 110 | while(1) { |
| 111 | sds key; |
| 112 | robj *keyobj; |
| 113 | |
| 114 | de = dictGetRandomKey(db->dict); |
| 115 | if (de == NULL) return NULL; |
| 116 | |
| 117 | key = dictGetKey(de); |
| 118 | keyobj = createStringObject(key,sdslen(key)); |
| 119 | if (dictFind(db->expires,key)) { |
| 120 | if (expireIfNeeded(db,keyobj)) { |
| 121 | decrRefCount(keyobj); |
| 122 | continue; /* search for another key. This expired. */ |
| 123 | } |
| 124 | } |
| 125 | return keyobj; |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | /* Delete a key, value, and associated expiration entry if any, from the DB */ |
| 130 | int dbDelete(redisDb *db, robj *key) { |
| 131 | /* Deleting an entry from the expires dict will not free the sds of |
| 132 | * the key, because it is shared with the main dictionary. */ |
| 133 | if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr); |
| 134 | if (dictDelete(db->dict,key->ptr) == DICT_OK) { |
| 135 | if (server.cluster_enabled) SlotToKeyDel(key); |
| 136 | return 1; |
| 137 | } else { |
| 138 | return 0; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | long long emptyDb() { |
| 143 | int j; |
| 144 | long long removed = 0; |
| 145 | |
| 146 | for (j = 0; j < server.dbnum; j++) { |
| 147 | removed += dictSize(server.db[j].dict); |
| 148 | dictEmpty(server.db[j].dict); |
| 149 | dictEmpty(server.db[j].expires); |
| 150 | } |
| 151 | return removed; |
| 152 | } |
| 153 | |
| 154 | int selectDb(redisClient *c, int id) { |
| 155 | if (id < 0 || id >= server.dbnum) |
| 156 | return REDIS_ERR; |
| 157 | c->db = &server.db[id]; |
| 158 | return REDIS_OK; |
| 159 | } |
| 160 | |
| 161 | /*----------------------------------------------------------------------------- |
| 162 | * Hooks for key space changes. |
| 163 | * |
| 164 | * Every time a key in the database is modified the function |
| 165 | * signalModifiedKey() is called. |
| 166 | * |
| 167 | * Every time a DB is flushed the function signalFlushDb() is called. |
| 168 | *----------------------------------------------------------------------------*/ |
| 169 | |
| 170 | void signalModifiedKey(redisDb *db, robj *key) { |
| 171 | touchWatchedKey(db,key); |
| 172 | } |
| 173 | |
| 174 | void signalFlushedDb(int dbid) { |
| 175 | touchWatchedKeysOnFlush(dbid); |
| 176 | } |
| 177 | |
| 178 | /*----------------------------------------------------------------------------- |
| 179 | * Type agnostic commands operating on the key space |
| 180 | *----------------------------------------------------------------------------*/ |
| 181 | |
| 182 | void flushdbCommand(redisClient *c) { |
| 183 | server.dirty += dictSize(c->db->dict); |
| 184 | signalFlushedDb(c->db->id); |
| 185 | dictEmpty(c->db->dict); |
| 186 | dictEmpty(c->db->expires); |
| 187 | addReply(c,shared.ok); |
| 188 | } |
| 189 | |
| 190 | void flushallCommand(redisClient *c) { |
| 191 | signalFlushedDb(-1); |
| 192 | server.dirty += emptyDb(); |
| 193 | addReply(c,shared.ok); |
| 194 | if (server.rdb_child_pid != -1) { |
| 195 | kill(server.rdb_child_pid,SIGKILL); |
| 196 | rdbRemoveTempFile(server.rdb_child_pid); |
| 197 | } |
| 198 | if (server.saveparamslen > 0) { |
| 199 | /* Normally rdbSave() will reset dirty, but we don't want this here |
| 200 | * as otherwise FLUSHALL will not be replicated nor put into the AOF. */ |
| 201 | int saved_dirty = server.dirty; |
| 202 | rdbSave(server.rdb_filename); |
| 203 | server.dirty = saved_dirty; |
| 204 | } |
| 205 | server.dirty++; |
| 206 | } |
| 207 | |
| 208 | void delCommand(redisClient *c) { |
| 209 | int deleted = 0, j; |
| 210 | |
| 211 | for (j = 1; j < c->argc; j++) { |
| 212 | if (dbDelete(c->db,c->argv[j])) { |
| 213 | signalModifiedKey(c->db,c->argv[j]); |
| 214 | server.dirty++; |
| 215 | deleted++; |
| 216 | } |
| 217 | } |
| 218 | addReplyLongLong(c,deleted); |
| 219 | } |
| 220 | |
| 221 | void existsCommand(redisClient *c) { |
| 222 | expireIfNeeded(c->db,c->argv[1]); |
| 223 | if (dbExists(c->db,c->argv[1])) { |
| 224 | addReply(c, shared.cone); |
| 225 | } else { |
| 226 | addReply(c, shared.czero); |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | void selectCommand(redisClient *c) { |
| 231 | int id = atoi(c->argv[1]->ptr); |
| 232 | |
| 233 | if (server.cluster_enabled && id != 0) { |
| 234 | addReplyError(c,"SELECT is not allowed in cluster mode"); |
| 235 | return; |
| 236 | } |
| 237 | if (selectDb(c,id) == REDIS_ERR) { |
| 238 | addReplyError(c,"invalid DB index"); |
| 239 | } else { |
| 240 | addReply(c,shared.ok); |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | void randomkeyCommand(redisClient *c) { |
| 245 | robj *key; |
| 246 | |
| 247 | if ((key = dbRandomKey(c->db)) == NULL) { |
| 248 | addReply(c,shared.nullbulk); |
| 249 | return; |
| 250 | } |
| 251 | |
| 252 | addReplyBulk(c,key); |
| 253 | decrRefCount(key); |
| 254 | } |
| 255 | |
| 256 | void keysCommand(redisClient *c) { |
| 257 | dictIterator *di; |
| 258 | dictEntry *de; |
| 259 | sds pattern = c->argv[1]->ptr; |
| 260 | int plen = sdslen(pattern), allkeys; |
| 261 | unsigned long numkeys = 0; |
| 262 | void *replylen = addDeferredMultiBulkLength(c); |
| 263 | |
| 264 | di = dictGetIterator(c->db->dict); |
| 265 | allkeys = (pattern[0] == '*' && pattern[1] == '\0'); |
| 266 | while((de = dictNext(di)) != NULL) { |
| 267 | sds key = dictGetKey(de); |
| 268 | robj *keyobj; |
| 269 | |
| 270 | if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { |
| 271 | keyobj = createStringObject(key,sdslen(key)); |
| 272 | if (expireIfNeeded(c->db,keyobj) == 0) { |
| 273 | addReplyBulk(c,keyobj); |
| 274 | numkeys++; |
| 275 | } |
| 276 | decrRefCount(keyobj); |
| 277 | } |
| 278 | } |
| 279 | dictReleaseIterator(di); |
| 280 | setDeferredMultiBulkLength(c,replylen,numkeys); |
| 281 | } |
| 282 | |
| 283 | void dbsizeCommand(redisClient *c) { |
| 284 | addReplyLongLong(c,dictSize(c->db->dict)); |
| 285 | } |
| 286 | |
| 287 | void lastsaveCommand(redisClient *c) { |
| 288 | addReplyLongLong(c,server.lastsave); |
| 289 | } |
| 290 | |
| 291 | void typeCommand(redisClient *c) { |
| 292 | robj *o; |
| 293 | char *type; |
| 294 | |
| 295 | o = lookupKeyRead(c->db,c->argv[1]); |
| 296 | if (o == NULL) { |
| 297 | type = "none"; |
| 298 | } else { |
| 299 | switch(o->type) { |
| 300 | case REDIS_STRING: type = "string"; break; |
| 301 | case REDIS_LIST: type = "list"; break; |
| 302 | case REDIS_SET: type = "set"; break; |
| 303 | case REDIS_ZSET: type = "zset"; break; |
| 304 | case REDIS_HASH: type = "hash"; break; |
| 305 | default: type = "unknown"; break; |
| 306 | } |
| 307 | } |
| 308 | addReplyStatus(c,type); |
| 309 | } |
| 310 | |
| 311 | void shutdownCommand(redisClient *c) { |
| 312 | int flags = 0; |
| 313 | |
| 314 | if (c->argc > 2) { |
| 315 | addReply(c,shared.syntaxerr); |
| 316 | return; |
| 317 | } else if (c->argc == 2) { |
| 318 | if (!strcasecmp(c->argv[1]->ptr,"nosave")) { |
| 319 | flags |= REDIS_SHUTDOWN_NOSAVE; |
| 320 | } else if (!strcasecmp(c->argv[1]->ptr,"save")) { |
| 321 | flags |= REDIS_SHUTDOWN_SAVE; |
| 322 | } else { |
| 323 | addReply(c,shared.syntaxerr); |
| 324 | return; |
| 325 | } |
| 326 | } |
| 327 | if (prepareForShutdown(flags) == REDIS_OK) exit(0); |
| 328 | addReplyError(c,"Errors trying to SHUTDOWN. Check logs."); |
| 329 | } |
| 330 | |
| 331 | void renameGenericCommand(redisClient *c, int nx) { |
| 332 | robj *o; |
| 333 | long long expire; |
| 334 | |
| 335 | /* To use the same key as src and dst is probably an error */ |
| 336 | if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) { |
| 337 | addReply(c,shared.sameobjecterr); |
| 338 | return; |
| 339 | } |
| 340 | |
| 341 | if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) |
| 342 | return; |
| 343 | |
| 344 | incrRefCount(o); |
| 345 | expire = getExpire(c->db,c->argv[1]); |
| 346 | if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { |
| 347 | if (nx) { |
| 348 | decrRefCount(o); |
| 349 | addReply(c,shared.czero); |
| 350 | return; |
| 351 | } |
| 352 | /* Overwrite: delete the old key before creating the new one with the same name. */ |
| 353 | dbDelete(c->db,c->argv[2]); |
| 354 | } |
| 355 | dbAdd(c->db,c->argv[2],o); |
| 356 | if (expire != -1) setExpire(c->db,c->argv[2],expire); |
| 357 | dbDelete(c->db,c->argv[1]); |
| 358 | signalModifiedKey(c->db,c->argv[1]); |
| 359 | signalModifiedKey(c->db,c->argv[2]); |
| 360 | server.dirty++; |
| 361 | addReply(c,nx ? shared.cone : shared.ok); |
| 362 | } |
| 363 | |
| 364 | void renameCommand(redisClient *c) { |
| 365 | renameGenericCommand(c,0); |
| 366 | } |
| 367 | |
| 368 | void renamenxCommand(redisClient *c) { |
| 369 | renameGenericCommand(c,1); |
| 370 | } |
| 371 | |
| 372 | void moveCommand(redisClient *c) { |
| 373 | robj *o; |
| 374 | redisDb *src, *dst; |
| 375 | int srcid; |
| 376 | |
| 377 | if (server.cluster_enabled) { |
| 378 | addReplyError(c,"MOVE is not allowed in cluster mode"); |
| 379 | return; |
| 380 | } |
| 381 | |
| 382 | /* Obtain source and target DB pointers */ |
| 383 | src = c->db; |
| 384 | srcid = c->db->id; |
| 385 | if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { |
| 386 | addReply(c,shared.outofrangeerr); |
| 387 | return; |
| 388 | } |
| 389 | dst = c->db; |
| 390 | selectDb(c,srcid); /* Back to the source DB */ |
| 391 | |
| 392 | /* If the user is moving using as target the same |
| 393 | * DB as the source DB it is probably an error. */ |
| 394 | if (src == dst) { |
| 395 | addReply(c,shared.sameobjecterr); |
| 396 | return; |
| 397 | } |
| 398 | |
| 399 | /* Check if the element exists and get a reference */ |
| 400 | o = lookupKeyWrite(c->db,c->argv[1]); |
| 401 | if (!o) { |
| 402 | addReply(c,shared.czero); |
| 403 | return; |
| 404 | } |
| 405 | |
| 406 | /* Return zero if the key already exists in the target DB */ |
| 407 | if (lookupKeyWrite(dst,c->argv[1]) != NULL) { |
| 408 | addReply(c,shared.czero); |
| 409 | return; |
| 410 | } |
| 411 | dbAdd(dst,c->argv[1],o); |
| 412 | incrRefCount(o); |
| 413 | |
| 414 | /* OK! key moved, free the entry in the source DB */ |
| 415 | dbDelete(src,c->argv[1]); |
| 416 | server.dirty++; |
| 417 | addReply(c,shared.cone); |
| 418 | } |
| 419 | |
| 420 | /*----------------------------------------------------------------------------- |
| 421 | * Expires API |
| 422 | *----------------------------------------------------------------------------*/ |
| 423 | |
| 424 | int removeExpire(redisDb *db, robj *key) { |
| 425 | /* An expire may only be removed if there is a corresponding entry in the |
| 426 | * main dict. Otherwise, the key will never be freed. */ |
| 427 | redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); |
| 428 | return dictDelete(db->expires,key->ptr) == DICT_OK; |
| 429 | } |
| 430 | |
| 431 | void setExpire(redisDb *db, robj *key, long long when) { |
| 432 | dictEntry *kde, *de; |
| 433 | |
| 434 | /* Reuse the sds from the main dict in the expire dict */ |
| 435 | kde = dictFind(db->dict,key->ptr); |
| 436 | redisAssertWithInfo(NULL,key,kde != NULL); |
| 437 | de = dictReplaceRaw(db->expires,dictGetKey(kde)); |
| 438 | dictSetSignedIntegerVal(de,when); |
| 439 | } |
| 440 | |
| 441 | /* Return the expire time of the specified key, or -1 if no expire |
| 442 | * is associated with this key (i.e. the key is non volatile) */ |
| 443 | long long getExpire(redisDb *db, robj *key) { |
| 444 | dictEntry *de; |
| 445 | |
| 446 | /* No expire? return ASAP */ |
| 447 | if (dictSize(db->expires) == 0 || |
| 448 | (de = dictFind(db->expires,key->ptr)) == NULL) return -1; |
| 449 | |
| 450 | /* The entry was found in the expire dict, this means it should also |
| 451 | * be present in the main dict (safety check). */ |
| 452 | redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); |
| 453 | return dictGetSignedIntegerVal(de); |
| 454 | } |
| 455 | |
| 456 | /* Propagate expires into slaves and the AOF file. |
| 457 | * When a key expires in the master, a DEL operation for this key is sent |
| 458 | * to all the slaves and the AOF file if enabled. |
| 459 | * |
| 460 | * This way the key expiry is centralized in one place, and since both |
| 461 | * AOF and the master->slave link guarantee operation ordering, everything |
| 462 | * will be consistent even if we allow write operations against expiring |
| 463 | * keys. */ |
| 464 | void propagateExpire(redisDb *db, robj *key) { |
| 465 | robj *argv[2]; |
| 466 | |
| 467 | argv[0] = shared.del; |
| 468 | argv[1] = key; |
| 469 | incrRefCount(argv[0]); |
| 470 | incrRefCount(argv[1]); |
| 471 | |
| 472 | if (server.aof_state != REDIS_AOF_OFF) |
| 473 | feedAppendOnlyFile(server.delCommand,db->id,argv,2); |
| 474 | if (listLength(server.slaves)) |
| 475 | replicationFeedSlaves(server.slaves,db->id,argv,2); |
| 476 | |
| 477 | decrRefCount(argv[0]); |
| 478 | decrRefCount(argv[1]); |
| 479 | } |
| 480 | |
| 481 | int expireIfNeeded(redisDb *db, robj *key) { |
| 482 | long long when = getExpire(db,key); |
| 483 | |
| 484 | if (when < 0) return 0; /* No expire for this key */ |
| 485 | |
| 486 | /* Don't expire anything while loading. It will be done later. */ |
| 487 | if (server.loading) return 0; |
| 488 | |
| 489 | /* If we are running in the context of a slave, return ASAP: |
| 490 | * the slave key expiration is controlled by the master that will |
| 491 | * send us synthesized DEL operations for expired keys. |
| 492 | * |
| 493 | * Still we try to return the right information to the caller, |
| 494 | * that is, 0 if we think the key should be still valid, 1 if |
| 495 | * we think the key is expired at this time. */ |
| 496 | if (server.masterhost != NULL) { |
| 497 | return mstime() > when; |
| 498 | } |
| 499 | |
| 500 | /* Return when this key has not expired */ |
| 501 | if (mstime() <= when) return 0; |
| 502 | |
| 503 | /* Delete the key */ |
| 504 | server.stat_expiredkeys++; |
| 505 | propagateExpire(db,key); |
| 506 | return dbDelete(db,key); |
| 507 | } |
| 508 | |
| 509 | /*----------------------------------------------------------------------------- |
| 510 | * Expires Commands |
| 511 | *----------------------------------------------------------------------------*/ |
| 512 | |
| 513 | /* Given an string object return true if it contains exactly the "ms" |
| 514 | * or "MS" string. This is used in order to check if the last argument |
| 515 | * of EXPIRE, EXPIREAT or TTL is "ms" to switch into millisecond input/output */ |
| 516 | int stringObjectEqualsMs(robj *a) { |
| 517 | char *arg = a->ptr; |
| 518 | return tolower(arg[0]) == 'm' && tolower(arg[1]) == 's' && arg[2] == '\0'; |
| 519 | } |
| 520 | |
| 521 | void expireGenericCommand(redisClient *c, long long offset, int unit) { |
| 522 | dictEntry *de; |
| 523 | robj *key = c->argv[1], *param = c->argv[2]; |
| 524 | long long milliseconds; |
| 525 | |
| 526 | if (getLongLongFromObjectOrReply(c, param, &milliseconds, NULL) != REDIS_OK) |
| 527 | return; |
| 528 | |
| 529 | if (unit == UNIT_SECONDS) milliseconds *= 1000; |
| 530 | milliseconds -= offset; |
| 531 | |
| 532 | de = dictFind(c->db->dict,key->ptr); |
| 533 | if (de == NULL) { |
| 534 | addReply(c,shared.czero); |
| 535 | return; |
| 536 | } |
| 537 | /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past |
| 538 | * should never be executed as a DEL when load the AOF or in the context |
| 539 | * of a slave instance. |
| 540 | * |
| 541 | * Instead we take the other branch of the IF statement setting an expire |
| 542 | * (possibly in the past) and wait for an explicit DEL from the master. */ |
| 543 | if (milliseconds <= 0 && !server.loading && !server.masterhost) { |
| 544 | robj *aux; |
| 545 | |
| 546 | redisAssertWithInfo(c,key,dbDelete(c->db,key)); |
| 547 | server.dirty++; |
| 548 | |
| 549 | /* Replicate/AOF this as an explicit DEL. */ |
| 550 | aux = createStringObject("DEL",3); |
| 551 | rewriteClientCommandVector(c,2,aux,key); |
| 552 | decrRefCount(aux); |
| 553 | signalModifiedKey(c->db,key); |
| 554 | addReply(c, shared.cone); |
| 555 | return; |
| 556 | } else { |
| 557 | long long when = mstime()+milliseconds; |
| 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,0,UNIT_SECONDS); |
| 568 | } |
| 569 | |
| 570 | void expireatCommand(redisClient *c) { |
| 571 | expireGenericCommand(c,mstime(),UNIT_SECONDS); |
| 572 | } |
| 573 | |
| 574 | void pexpireCommand(redisClient *c) { |
| 575 | expireGenericCommand(c,0,UNIT_MILLISECONDS); |
| 576 | } |
| 577 | |
| 578 | void pexpireatCommand(redisClient *c) { |
| 579 | expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); |
| 580 | } |
| 581 | |
| 582 | void ttlGenericCommand(redisClient *c, int output_ms) { |
| 583 | long long expire, ttl = -1; |
| 584 | |
| 585 | expire = getExpire(c->db,c->argv[1]); |
| 586 | if (expire != -1) { |
| 587 | ttl = expire-mstime(); |
| 588 | if (ttl < 0) ttl = -1; |
| 589 | } |
| 590 | if (ttl == -1) { |
| 591 | addReplyLongLong(c,-1); |
| 592 | } else { |
| 593 | addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000)); |
| 594 | } |
| 595 | } |
| 596 | |
| 597 | void ttlCommand(redisClient *c) { |
| 598 | ttlGenericCommand(c, 0); |
| 599 | } |
| 600 | |
| 601 | void pttlCommand(redisClient *c) { |
| 602 | ttlGenericCommand(c, 1); |
| 603 | } |
| 604 | |
| 605 | void persistCommand(redisClient *c) { |
| 606 | dictEntry *de; |
| 607 | |
| 608 | de = dictFind(c->db->dict,c->argv[1]->ptr); |
| 609 | if (de == NULL) { |
| 610 | addReply(c,shared.czero); |
| 611 | } else { |
| 612 | if (removeExpire(c->db,c->argv[1])) { |
| 613 | addReply(c,shared.cone); |
| 614 | server.dirty++; |
| 615 | } else { |
| 616 | addReply(c,shared.czero); |
| 617 | } |
| 618 | } |
| 619 | } |
| 620 | |
| 621 | /* ----------------------------------------------------------------------------- |
| 622 | * API to get key arguments from commands |
| 623 | * ---------------------------------------------------------------------------*/ |
| 624 | |
| 625 | int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) { |
| 626 | int j, i = 0, last, *keys; |
| 627 | REDIS_NOTUSED(argv); |
| 628 | |
| 629 | if (cmd->firstkey == 0) { |
| 630 | *numkeys = 0; |
| 631 | return NULL; |
| 632 | } |
| 633 | last = cmd->lastkey; |
| 634 | if (last < 0) last = argc+last; |
| 635 | keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1)); |
| 636 | for (j = cmd->firstkey; j <= last; j += cmd->keystep) { |
| 637 | redisAssert(j < argc); |
| 638 | keys[i++] = j; |
| 639 | } |
| 640 | *numkeys = i; |
| 641 | return keys; |
| 642 | } |
| 643 | |
| 644 | int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { |
| 645 | if (cmd->getkeys_proc) { |
| 646 | return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags); |
| 647 | } else { |
| 648 | return getKeysUsingCommandTable(cmd,argv,argc,numkeys); |
| 649 | } |
| 650 | } |
| 651 | |
| 652 | void getKeysFreeResult(int *result) { |
| 653 | zfree(result); |
| 654 | } |
| 655 | |
| 656 | int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { |
| 657 | if (flags & REDIS_GETKEYS_PRELOAD) { |
| 658 | *numkeys = 0; |
| 659 | return NULL; |
| 660 | } else { |
| 661 | return getKeysUsingCommandTable(cmd,argv,argc,numkeys); |
| 662 | } |
| 663 | } |
| 664 | |
| 665 | int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { |
| 666 | if (flags & REDIS_GETKEYS_PRELOAD) { |
| 667 | int *keys = zmalloc(sizeof(int)); |
| 668 | *numkeys = 1; |
| 669 | keys[0] = 1; |
| 670 | return keys; |
| 671 | } else { |
| 672 | return getKeysUsingCommandTable(cmd,argv,argc,numkeys); |
| 673 | } |
| 674 | } |
| 675 | |
| 676 | int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { |
| 677 | int i, num, *keys; |
| 678 | REDIS_NOTUSED(cmd); |
| 679 | REDIS_NOTUSED(flags); |
| 680 | |
| 681 | num = atoi(argv[2]->ptr); |
| 682 | /* Sanity check. Don't return any key if the command is going to |
| 683 | * reply with syntax error. */ |
| 684 | if (num > (argc-3)) { |
| 685 | *numkeys = 0; |
| 686 | return NULL; |
| 687 | } |
| 688 | keys = zmalloc(sizeof(int)*num); |
| 689 | for (i = 0; i < num; i++) keys[i] = 3+i; |
| 690 | *numkeys = num; |
| 691 | return keys; |
| 692 | } |
| 693 | |
| 694 | /* Slot to Key API. This is used by Redis Cluster in order to obtain in |
| 695 | * a fast way a key that belongs to a specified hash slot. This is useful |
| 696 | * while rehashing the cluster. */ |
| 697 | void SlotToKeyAdd(robj *key) { |
| 698 | unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); |
| 699 | |
| 700 | zslInsert(server.cluster.slots_to_keys,hashslot,key); |
| 701 | incrRefCount(key); |
| 702 | } |
| 703 | |
| 704 | void SlotToKeyDel(robj *key) { |
| 705 | unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); |
| 706 | |
| 707 | zslDelete(server.cluster.slots_to_keys,hashslot,key); |
| 708 | } |
| 709 | |
| 710 | unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) { |
| 711 | zskiplistNode *n; |
| 712 | zrangespec range; |
| 713 | int j = 0; |
| 714 | |
| 715 | range.min = range.max = hashslot; |
| 716 | range.minex = range.maxex = 0; |
| 717 | |
| 718 | n = zslFirstInRange(server.cluster.slots_to_keys, range); |
| 719 | while(n && n->score == hashslot && count--) { |
| 720 | keys[j++] = n->obj; |
| 721 | n = n->level[0].forward; |
| 722 | } |
| 723 | return j; |
| 724 | } |