Redis 7.4 RC2 (#13371)
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos7-jemalloc (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled
Upgrade urgency LOW: This is the second Release Candidate for Redis 7.4. Performance and resource utilization improvements ================================================= * #13296 Optimize CPU cache efficiency Changes to new 7.4 new features (compared to 7.4 RC1) ===================================================== * #13343 Hash - expiration of individual fields: when key does not exist - reply with an array (nonexisting code for each field) * #13329 Hash - expiration of individual fields: new keyspace event: `hexpired` Modules API - Potentially breaking changes to new 7.4 features (compared to 7.4 RC1) ==================================================================================== * #13326 Hash - expiration of individual fields: avoid lazy expire when called from a Modules API function
This commit is contained in:
+21
-1
@@ -12,6 +12,26 @@ SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4 RC2 Released Thu 27 Jun 2024 10:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the second Release Candidate for Redis 7.4.
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
* #13296 Optimize CPU cache efficiency
|
||||
|
||||
Changes to new 7.4 new features (compared to 7.4 RC1)
|
||||
=====================================================
|
||||
* #13343 Hash - expiration of individual fields: when key does not exist - reply with an array (nonexisting code for each field)
|
||||
* #13329 Hash - expiration of individual fields: new keyspace event: `hexpired`
|
||||
|
||||
Modules API - Potentially breaking changes to new 7.4 features (compared to 7.4 RC1)
|
||||
====================================================================================
|
||||
* #13326 Hash - expiration of individual fields: avoid lazy expire when called from a Modules API function
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4 RC1 Released Thu 6 Jun 2024 10:00:00 IST
|
||||
================================================================================
|
||||
@@ -75,7 +95,7 @@ Other general improvements
|
||||
* #13020 Allow adjusting defrag configurations while active defragmentation is running
|
||||
* #12949 Increase the accuracy of avg_ttl (the average keyspace keys TTL)
|
||||
* #12977 Allow running `WAITAOF` in scripts
|
||||
* #12782 Implement TCP Keep-Alives across most Unix-like systems
|
||||
* #12782 Implement TCP keep-alive across most Unix-like systems
|
||||
* #12707 Improved error codes when rejecting scripts in cluster mode
|
||||
* #12596 Support `XREAD ... BLOCK` in scripts; rejected only if it ends up blocking
|
||||
|
||||
|
||||
@@ -1968,7 +1968,7 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
|
||||
hashTypeIterator *hi;
|
||||
long long count = 0, items = hashTypeLength(o, 0);
|
||||
|
||||
int isHFE = hashTypeGetMinExpire(o) != EB_EXPIRE_TIME_INVALID;
|
||||
int isHFE = hashTypeGetMinExpire(o, 0) != EB_EXPIRE_TIME_INVALID;
|
||||
hi = hashTypeInitIterator(o);
|
||||
|
||||
if (!isHFE) {
|
||||
|
||||
+6
-4
@@ -176,7 +176,6 @@ void dumpCommand(client *c) {
|
||||
|
||||
/* RESTORE key ttl serialized-value [REPLACE] [ABSTTL] [IDLETIME seconds] [FREQ frequency] */
|
||||
void restoreCommand(client *c) {
|
||||
uint64_t minExpiredField = EB_EXPIRE_TIME_INVALID;
|
||||
long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock = -1;
|
||||
rio payload;
|
||||
int j, type, replace = 0, absttl = 0;
|
||||
@@ -240,7 +239,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,key->ptr,c->db,NULL, &minExpiredField)) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,key->ptr,c->db->id,NULL)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@@ -270,8 +269,11 @@ void restoreCommand(client *c) {
|
||||
|
||||
/* If minExpiredField was set, then the object is hash with expiration
|
||||
* on fields and need to register it in global HFE DS */
|
||||
if (minExpiredField != EB_EXPIRE_TIME_INVALID)
|
||||
hashTypeAddToExpires(c->db, dictGetKey(de), obj, minExpiredField);
|
||||
if (obj->type == OBJ_HASH) {
|
||||
uint64_t minExpiredField = hashTypeGetMinExpire(obj, 1);
|
||||
if (minExpiredField != EB_EXPIRE_TIME_INVALID)
|
||||
hashTypeAddToExpires(c->db, dictGetKey(de), obj, minExpiredField);
|
||||
}
|
||||
|
||||
if (ttl) {
|
||||
setExpire(c,c->db,key,ttl);
|
||||
|
||||
+72
-36
@@ -3330,14 +3330,18 @@ struct COMMAND_ARG HEXPIRE_condition_Subargs[] = {
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIRE fields argument table */
|
||||
struct COMMAND_ARG HEXPIRE_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIRE argument table */
|
||||
struct COMMAND_ARG HEXPIRE_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HEXPIRE_condition_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIRE_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HEXPIREAT ********************/
|
||||
@@ -3367,14 +3371,18 @@ struct COMMAND_ARG HEXPIREAT_condition_Subargs[] = {
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIREAT fields argument table */
|
||||
struct COMMAND_ARG HEXPIREAT_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIREAT argument table */
|
||||
struct COMMAND_ARG HEXPIREAT_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HEXPIREAT_condition_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIREAT_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HEXPIRETIME ********************/
|
||||
@@ -3396,12 +3404,16 @@ keySpec HEXPIRETIME_Keyspecs[1] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HEXPIRETIME fields argument table */
|
||||
struct COMMAND_ARG HEXPIRETIME_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HEXPIRETIME argument table */
|
||||
struct COMMAND_ARG HEXPIRETIME_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIRETIME_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HGET ********************/
|
||||
@@ -3632,12 +3644,16 @@ keySpec HPERSIST_Keyspecs[1] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HPERSIST fields argument table */
|
||||
struct COMMAND_ARG HPERSIST_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPERSIST argument table */
|
||||
struct COMMAND_ARG HPERSIST_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPERSIST_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HPEXPIRE ********************/
|
||||
@@ -3667,14 +3683,18 @@ struct COMMAND_ARG HPEXPIRE_condition_Subargs[] = {
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIRE fields argument table */
|
||||
struct COMMAND_ARG HPEXPIRE_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIRE argument table */
|
||||
struct COMMAND_ARG HPEXPIRE_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HPEXPIRE_condition_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIRE_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HPEXPIREAT ********************/
|
||||
@@ -3704,14 +3724,18 @@ struct COMMAND_ARG HPEXPIREAT_condition_Subargs[] = {
|
||||
{MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIREAT fields argument table */
|
||||
struct COMMAND_ARG HPEXPIREAT_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIREAT argument table */
|
||||
struct COMMAND_ARG HPEXPIREAT_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HPEXPIREAT_condition_Subargs},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIREAT_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HPEXPIRETIME ********************/
|
||||
@@ -3733,12 +3757,16 @@ keySpec HPEXPIRETIME_Keyspecs[1] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HPEXPIRETIME fields argument table */
|
||||
struct COMMAND_ARG HPEXPIRETIME_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPEXPIRETIME argument table */
|
||||
struct COMMAND_ARG HPEXPIRETIME_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIRETIME_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HPTTL ********************/
|
||||
@@ -3760,12 +3788,16 @@ keySpec HPTTL_Keyspecs[1] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HPTTL fields argument table */
|
||||
struct COMMAND_ARG HPTTL_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HPTTL argument table */
|
||||
struct COMMAND_ARG HPTTL_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPTTL_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HRANDFIELD ********************/
|
||||
@@ -3934,12 +3966,16 @@ keySpec HTTL_Keyspecs[1] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
/* HTTL fields argument table */
|
||||
struct COMMAND_ARG HTTL_fields_Subargs[] = {
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* HTTL argument table */
|
||||
struct COMMAND_ARG HTTL_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
{MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HTTL_fields_Subargs},
|
||||
};
|
||||
|
||||
/********** HVALS ********************/
|
||||
@@ -10993,9 +11029,9 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
||||
/* hash */
|
||||
{MAKE_CMD("hdel","Deletes one or more fields and their values from a hash. Deletes the hash if no fields remain.","O(N) where N is the number of fields to be removed.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HDEL_History,1,HDEL_Tips,0,hdelCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HDEL_Keyspecs,1,NULL,2),.args=HDEL_Args},
|
||||
{MAKE_CMD("hexists","Determines whether a field exists in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXISTS_History,0,HEXISTS_Tips,0,hexistsCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HEXISTS_Keyspecs,1,NULL,2),.args=HEXISTS_Args},
|
||||
{MAKE_CMD("hexpire","Set expiry for hash field using relative time to expire (seconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRE_History,0,HEXPIRE_Tips,0,hexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HEXPIRE_Keyspecs,1,NULL,6),.args=HEXPIRE_Args},
|
||||
{MAKE_CMD("hexpireat","Set expiry for hash field using an absolute Unix timestamp (seconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIREAT_History,0,HEXPIREAT_Tips,0,hexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HEXPIREAT_Keyspecs,1,NULL,6),.args=HEXPIREAT_Args},
|
||||
{MAKE_CMD("hexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in seconds.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRETIME_History,0,HEXPIRETIME_Tips,0,hexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HEXPIRETIME_Keyspecs,1,NULL,4),.args=HEXPIRETIME_Args},
|
||||
{MAKE_CMD("hexpire","Set expiry for hash field using relative time to expire (seconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRE_History,0,HEXPIRE_Tips,0,hexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HEXPIRE_Keyspecs,1,NULL,4),.args=HEXPIRE_Args},
|
||||
{MAKE_CMD("hexpireat","Set expiry for hash field using an absolute Unix timestamp (seconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIREAT_History,0,HEXPIREAT_Tips,0,hexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HEXPIREAT_Keyspecs,1,NULL,4),.args=HEXPIREAT_Args},
|
||||
{MAKE_CMD("hexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in seconds.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRETIME_History,0,HEXPIRETIME_Tips,0,hexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HEXPIRETIME_Keyspecs,1,NULL,2),.args=HEXPIRETIME_Args},
|
||||
{MAKE_CMD("hget","Returns the value of a field in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGET_History,0,HGET_Tips,0,hgetCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HGET_Keyspecs,1,NULL,2),.args=HGET_Args},
|
||||
{MAKE_CMD("hgetall","Returns all fields and values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETALL_History,0,HGETALL_Tips,1,hgetallCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HGETALL_Keyspecs,1,NULL,1),.args=HGETALL_Args},
|
||||
{MAKE_CMD("hincrby","Increments the integer value of a field in a hash by a number. Uses 0 as initial value if the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HINCRBY_History,0,HINCRBY_Tips,0,hincrbyCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HINCRBY_Keyspecs,1,NULL,3),.args=HINCRBY_Args},
|
||||
@@ -11004,17 +11040,17 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
||||
{MAKE_CMD("hlen","Returns the number of fields in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HLEN_History,0,HLEN_Tips,0,hlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HLEN_Keyspecs,1,NULL,1),.args=HLEN_Args},
|
||||
{MAKE_CMD("hmget","Returns the values of all fields in a hash.","O(N) where N is the number of fields being requested.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HMGET_History,0,HMGET_Tips,0,hmgetCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HMGET_Keyspecs,1,NULL,2),.args=HMGET_Args},
|
||||
{MAKE_CMD("hmset","Sets the values of multiple fields.","O(N) where N is the number of fields being set.","2.0.0",CMD_DOC_DEPRECATED,"`HSET` with multiple field-value pairs","4.0.0","hash",COMMAND_GROUP_HASH,HMSET_History,0,HMSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HMSET_Keyspecs,1,NULL,2),.args=HMSET_Args},
|
||||
{MAKE_CMD("hpersist","Removes the expiration time for each specified field","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPERSIST_History,0,HPERSIST_Tips,0,hpersistCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HPERSIST_Keyspecs,1,NULL,4),.args=HPERSIST_Args},
|
||||
{MAKE_CMD("hpexpire","Set expiry for hash field using relative time to expire (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRE_Keyspecs,1,NULL,6),.args=HPEXPIRE_Args},
|
||||
{MAKE_CMD("hpexpireat","Set expiry for hash field using an absolute Unix timestamp (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIREAT_Keyspecs,1,NULL,6),.args=HPEXPIREAT_Args},
|
||||
{MAKE_CMD("hpexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in msec.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRETIME_Keyspecs,1,NULL,4),.args=HPEXPIRETIME_Args},
|
||||
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,0,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,4),.args=HPTTL_Args},
|
||||
{MAKE_CMD("hpersist","Removes the expiration time for each specified field","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPERSIST_History,0,HPERSIST_Tips,0,hpersistCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HPERSIST_Keyspecs,1,NULL,2),.args=HPERSIST_Args},
|
||||
{MAKE_CMD("hpexpire","Set expiry for hash field using relative time to expire (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRE_Keyspecs,1,NULL,4),.args=HPEXPIRE_Args},
|
||||
{MAKE_CMD("hpexpireat","Set expiry for hash field using an absolute Unix timestamp (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIREAT_Keyspecs,1,NULL,4),.args=HPEXPIREAT_Args},
|
||||
{MAKE_CMD("hpexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in msec.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRETIME_Keyspecs,1,NULL,2),.args=HPEXPIRETIME_Args},
|
||||
{MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,0,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args},
|
||||
{MAKE_CMD("hrandfield","Returns one or more random fields from a hash.","O(N) where N is the number of fields returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HRANDFIELD_History,0,HRANDFIELD_Tips,1,hrandfieldCommand,-2,CMD_READONLY,ACL_CATEGORY_HASH,HRANDFIELD_Keyspecs,1,NULL,2),.args=HRANDFIELD_Args},
|
||||
{MAKE_CMD("hscan","Iterates over fields and values of a hash.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSCAN_History,0,HSCAN_Tips,1,hscanCommand,-3,CMD_READONLY,ACL_CATEGORY_HASH,HSCAN_Keyspecs,1,NULL,5),.args=HSCAN_Args},
|
||||
{MAKE_CMD("hset","Creates or modifies the value of a field in a hash.","O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSET_History,1,HSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSET_Keyspecs,1,NULL,2),.args=HSET_Args},
|
||||
{MAKE_CMD("hsetnx","Sets the value of a field in a hash only when the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETNX_History,0,HSETNX_Tips,0,hsetnxCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETNX_Keyspecs,1,NULL,3),.args=HSETNX_Args},
|
||||
{MAKE_CMD("hstrlen","Returns the length of the value of a field.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSTRLEN_History,0,HSTRLEN_Tips,0,hstrlenCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HSTRLEN_Keyspecs,1,NULL,2),.args=HSTRLEN_Args},
|
||||
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,0,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,4),.args=HTTL_Args},
|
||||
{MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,0,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args},
|
||||
{MAKE_CMD("hvals","Returns all values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HVALS_History,0,HVALS_Tips,1,hvalsCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HVALS_Keyspecs,1,NULL,1),.args=HVALS_Args},
|
||||
/* hyperloglog */
|
||||
{MAKE_CMD("pfadd","Adds elements to a HyperLogLog key. Creates the key if it doesn't exist.","O(1) to add every element.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFADD_History,0,PFADD_Tips,0,pfaddCommand,-2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HYPERLOGLOG,PFADD_Keyspecs,1,NULL,2),.args=PFADD_Args},
|
||||
|
||||
+14
-11
@@ -99,17 +99,20 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+14
-11
@@ -99,17 +99,20 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -64,17 +64,20 @@
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+14
-11
@@ -63,17 +63,20 @@
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+14
-11
@@ -99,17 +99,20 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -99,17 +99,20 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -64,17 +64,20 @@
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+14
-11
@@ -64,17 +64,20 @@
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+14
-11
@@ -64,17 +64,20 @@
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "FIELDS",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
"name": "fields",
|
||||
"token": "FIELDS",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "numfields",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+24
@@ -15,6 +15,7 @@
|
||||
#include "fpconv_dtoa.h"
|
||||
#include "cluster.h"
|
||||
#include "threads_mngr.h"
|
||||
#include "script.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <signal.h>
|
||||
@@ -1013,6 +1014,29 @@ NULL
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "dict-resizing") && c->argc == 3) {
|
||||
server.dict_resizing = atoi(c->argv[2]->ptr);
|
||||
addReply(c, shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"script") && c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[2]->ptr,"list")) {
|
||||
dictIterator *di = dictGetIterator(getLuaScripts());
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
luaScript *script = dictGetVal(de);
|
||||
sds *sha = dictGetKey(de);
|
||||
serverLog(LL_WARNING, "SCRIPT SHA: %s\n%s", (char*)sha, (char*)script->body->ptr);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else if (sdslen(c->argv[2]->ptr) == 40) {
|
||||
dictEntry *de;
|
||||
if ((de = dictFind(getLuaScripts(), c->argv[2]->ptr)) == NULL) {
|
||||
addReplyErrorObject(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
luaScript *script = dictGetVal(de);
|
||||
serverLog(LL_WARNING, "SCRIPT SHA: %s\n%s", (char*)c->argv[2]->ptr, (char*)script->body->ptr);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
} else if(!handleDebugClusterCommand(c)) {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
|
||||
+1
-1
@@ -751,7 +751,7 @@ void defragKey(defragCtx *ctx, dictEntry *de) {
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
if (unlikely(ob->type == OBJ_HASH && hashTypeGetMinExpire(ob) != EB_EXPIRE_TIME_INVALID)) {
|
||||
if (unlikely(ob->type == OBJ_HASH && hashTypeGetMinExpire(ob, 0) != EB_EXPIRE_TIME_INVALID)) {
|
||||
/* Update its reference in the ebucket while defragging it. */
|
||||
newob = ebDefragItem(&db->hexpires, &hashExpireBucketsType, ob,
|
||||
(ebDefragFunction *)activeDefragStringOb);
|
||||
|
||||
@@ -1737,3 +1737,7 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
||||
rctx->start_time = getMonotonicUs();
|
||||
}
|
||||
}
|
||||
|
||||
dict *getLuaScripts(void) {
|
||||
return lctx.lua_scripts;
|
||||
}
|
||||
|
||||
+59
-36
@@ -3524,9 +3524,7 @@ int RM_ReplyWithLongDouble(RedisModuleCtx *ctx, long double ld) {
|
||||
*
|
||||
* The replicated commands are always wrapped into the MULTI/EXEC that
|
||||
* contains all the commands replicated in a given module command
|
||||
* execution. However the commands replicated with RedisModule_Call()
|
||||
* are the first items, the ones replicated with RedisModule_Replicate()
|
||||
* will all follow before the EXEC.
|
||||
* execution, in the order they were executed.
|
||||
*
|
||||
* Modules should try to use one interface or the other.
|
||||
*
|
||||
@@ -3548,9 +3546,8 @@ int RM_ReplyWithLongDouble(RedisModuleCtx *ctx, long double ld) {
|
||||
* the callback, and will propagate all the commands wrapped in a MULTI/EXEC
|
||||
* transaction. However when calling this function from a threaded safe context
|
||||
* that can live an undefined amount of time, and can be locked/unlocked in
|
||||
* at will, the behavior is different: MULTI/EXEC wrapper is not emitted
|
||||
* and the command specified is inserted in the AOF and replication stream
|
||||
* immediately.
|
||||
* at will, it is important to note that this API is not thread-safe and
|
||||
* must be executed while holding the GIL.
|
||||
*
|
||||
* #### Return value
|
||||
*
|
||||
@@ -3588,15 +3585,18 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
|
||||
}
|
||||
|
||||
/* This function will replicate the command exactly as it was invoked
|
||||
* by the client. Note that this function will not wrap the command into
|
||||
* a MULTI/EXEC stanza, so it should not be mixed with other replication
|
||||
* commands.
|
||||
* by the client. Note that the replicated commands are always wrapped
|
||||
* into the MULTI/EXEC that contains all the commands replicated in a
|
||||
* given module command execution, in the order they were executed.
|
||||
*
|
||||
* Basically this form of replication is useful when you want to propagate
|
||||
* the command to the slaves and AOF file exactly as it was called, since
|
||||
* the command can just be re-executed to deterministically re-create the
|
||||
* new state starting from the old one.
|
||||
*
|
||||
* It is important to note that this API is not thread-safe and
|
||||
* must be executed while holding the GIL.
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplicateVerbatim(RedisModuleCtx *ctx) {
|
||||
alsoPropagate(ctx->client->db->id,
|
||||
@@ -5271,10 +5271,21 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
|
||||
|
||||
/* Handle XX and NX */
|
||||
if (flags & (REDISMODULE_HASH_XX|REDISMODULE_HASH_NX)) {
|
||||
int isHashDeleted;
|
||||
int exists = hashTypeExists(key->db, key->value, field->ptr, &isHashDeleted);
|
||||
/* hash-field-expiration is not exposed to modules */
|
||||
serverAssert(isHashDeleted == 0);
|
||||
int hfeFlags = HFE_LAZY_AVOID_HASH_DEL; /* Avoid invalidate the key */
|
||||
|
||||
/*
|
||||
* The hash might contain expired fields. If we lazily delete expired
|
||||
* field and the command was sent with XX flag, the operation could
|
||||
* fail and leave the hash empty, which the caller might not expect.
|
||||
* To prevent unexpected behavior, we avoid lazy deletion in this case
|
||||
* yet let the operation fail. Note that moduleDelKeyIfEmpty()
|
||||
* below won't delete the hash if it left with single expired key
|
||||
* because hash counts blindly expired fields as well.
|
||||
*/
|
||||
if (flags & REDISMODULE_HASH_XX)
|
||||
hfeFlags |= HFE_LAZY_AVOID_FIELD_DEL;
|
||||
|
||||
int exists = hashTypeExists(key->db, key->value, field->ptr, hfeFlags, NULL);
|
||||
if (((flags & REDISMODULE_HASH_XX) && !exists) ||
|
||||
((flags & REDISMODULE_HASH_NX) && exists))
|
||||
{
|
||||
@@ -5357,6 +5368,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
|
||||
* RedisModule_FreeString(), or by enabling automatic memory management.
|
||||
*/
|
||||
int RM_HashGet(RedisModuleKey *key, int flags, ...) {
|
||||
int hfeFlags = HFE_LAZY_AVOID_FIELD_DEL | HFE_LAZY_AVOID_HASH_DEL;
|
||||
va_list ap;
|
||||
if (key->value && key->value->type != OBJ_HASH) return REDISMODULE_ERR;
|
||||
|
||||
@@ -5378,21 +5390,16 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) {
|
||||
if (flags & REDISMODULE_HASH_EXISTS) {
|
||||
existsptr = va_arg(ap,int*);
|
||||
if (key->value) {
|
||||
int isHashDeleted;
|
||||
*existsptr = hashTypeExists(key->db, key->value, field->ptr, &isHashDeleted);
|
||||
/* hash-field-expiration is not exposed to modules */
|
||||
serverAssert(isHashDeleted == 0);
|
||||
*existsptr = hashTypeExists(key->db, key->value, field->ptr, hfeFlags, NULL);
|
||||
} else {
|
||||
*existsptr = 0;
|
||||
}
|
||||
} else {
|
||||
int isHashDeleted;
|
||||
valueptr = va_arg(ap,RedisModuleString**);
|
||||
if (key->value) {
|
||||
*valueptr = hashTypeGetValueObject(key->db,key->value,field->ptr, &isHashDeleted);
|
||||
*valueptr = hashTypeGetValueObject(key->db, key->value, field->ptr,
|
||||
hfeFlags, NULL);
|
||||
|
||||
/* Currently hash-field-expiration is not exposed to modules */
|
||||
serverAssert(isHashDeleted == 0);
|
||||
if (*valueptr) {
|
||||
robj *decoded = getDecodedObject(*valueptr);
|
||||
decrRefCount(*valueptr);
|
||||
@@ -11080,6 +11087,11 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) {
|
||||
value = NULL;
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
sds val = dictGetVal(de);
|
||||
|
||||
/* If field is expired, then ignore */
|
||||
if (hfieldIsExpired(key))
|
||||
return;
|
||||
|
||||
field = createStringObject(key, hfieldlen(key));
|
||||
value = createStringObject(val, sdslen(val));
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
@@ -11189,9 +11201,8 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
|
||||
ret = 0;
|
||||
} else if (o->type == OBJ_ZSET || o->type == OBJ_HASH) {
|
||||
unsigned char *lp, *p;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vll;
|
||||
/* is hash with expiry on fields, then lp tuples are [field][value][expire] */
|
||||
int hfe = o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_LISTPACK_EX;
|
||||
|
||||
if (o->type == OBJ_HASH)
|
||||
lp = hashTypeListpackGetLp(o);
|
||||
@@ -11200,19 +11211,32 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
|
||||
|
||||
p = lpSeek(lp,0);
|
||||
while(p) {
|
||||
vstr = lpGetValue(p,&vlen,&vll);
|
||||
robj *field = (vstr != NULL) ?
|
||||
createStringObject((char*)vstr,vlen) :
|
||||
createStringObjectFromLongLongWithSds(vll);
|
||||
long long vllField, vllValue, vllExpire;
|
||||
unsigned int lenField, lenValue;
|
||||
unsigned char *pField, *pValue;
|
||||
|
||||
pField = lpGetValue(p,&lenField,&vllField);
|
||||
p = lpNext(lp,p);
|
||||
vstr = lpGetValue(p,&vlen,&vll);
|
||||
robj *value = (vstr != NULL) ?
|
||||
createStringObject((char*)vstr,vlen) :
|
||||
createStringObjectFromLongLongWithSds(vll);
|
||||
pValue = lpGetValue(p,&lenValue,&vllValue);
|
||||
p = lpNext(lp,p);
|
||||
|
||||
if (hfe) {
|
||||
serverAssert(lpGetIntegerValue(p, &vllExpire));
|
||||
p = lpNext(lp, p);
|
||||
|
||||
/* Skip expired fields */
|
||||
if (hashTypeIsExpired(o, vllExpire))
|
||||
continue;
|
||||
}
|
||||
|
||||
robj *value = (pValue != NULL) ?
|
||||
createStringObject((char*)pValue,lenValue) :
|
||||
createStringObjectFromLongLongWithSds(vllValue);
|
||||
|
||||
robj *field = (pField != NULL) ?
|
||||
createStringObject((char*)pField,lenField) :
|
||||
createStringObjectFromLongLongWithSds(vllField);
|
||||
fn(key, field, value, privdata);
|
||||
p = lpNext(lp,p);
|
||||
if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_LISTPACK_EX)
|
||||
p = lpNext(lp, p); /* Skip expire time */
|
||||
|
||||
decrRefCount(field);
|
||||
decrRefCount(value);
|
||||
@@ -11225,7 +11249,6 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* ## Module fork API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
@@ -1733,6 +1733,9 @@ void freeClientAsync(client *c) {
|
||||
* idle. */
|
||||
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_SCRIPT) return;
|
||||
c->flags |= CLIENT_CLOSE_ASAP;
|
||||
/* Replicas that was marked as CLIENT_CLOSE_ASAP should not keep the
|
||||
* replication backlog from been trimmed. */
|
||||
if (c->flags & CLIENT_SLAVE) freeReplicaReferencedReplBuffer(c);
|
||||
if (server.io_threads_num == 1) {
|
||||
/* no need to bother with locking if there's just one thread (the main thread) */
|
||||
listAddNodeTail(server.clients_to_close,c);
|
||||
|
||||
@@ -699,7 +699,7 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
|
||||
else if (o->encoding == OBJ_ENCODING_LISTPACK_EX)
|
||||
return rdbSaveType(rdb,RDB_TYPE_HASH_LISTPACK_EX);
|
||||
else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
if (hashTypeGetMinExpire(o) == EB_EXPIRE_TIME_INVALID)
|
||||
if (hashTypeGetMinExpire(o, 0) == EB_EXPIRE_TIME_INVALID)
|
||||
return rdbSaveType(rdb,RDB_TYPE_HASH);
|
||||
else
|
||||
return rdbSaveType(rdb,RDB_TYPE_HASH_METADATA);
|
||||
@@ -960,7 +960,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
|
||||
* RDB_TYPE_HASH_METADATA layout, including tuples of [ttl][field][value].
|
||||
* Otherwise, use the standard RDB_TYPE_HASH layout containing only
|
||||
* the tuples [field][value]. */
|
||||
int with_ttl = (hashTypeGetMinExpire(o) != EB_EXPIRE_TIME_INVALID);
|
||||
int with_ttl = (hashTypeGetMinExpire(o, 0) != EB_EXPIRE_TIME_INVALID);
|
||||
|
||||
/* save number of fields in hash */
|
||||
if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) {
|
||||
@@ -1897,10 +1897,8 @@ int lpValidateIntegrityAndDups(unsigned char *lp, size_t size, int deep, int tup
|
||||
* no fields with expiration or it is not a hash, then it will set be to
|
||||
* EB_EXPIRE_TIME_INVALID.
|
||||
*/
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb* db, int *error,
|
||||
uint64_t *minExpiredField)
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
|
||||
{
|
||||
uint64_t minExpField = EB_EXPIRE_TIME_INVALID;
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@@ -2241,8 +2239,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb* db, int *error,
|
||||
/* All pairs should be read by now */
|
||||
serverAssert(len == 0);
|
||||
} else if (rdbtype == RDB_TYPE_HASH_METADATA) {
|
||||
size_t fieldLen;
|
||||
sds value, field;
|
||||
sds value;
|
||||
hfield field;
|
||||
uint64_t expireAt;
|
||||
dict *dupSearchDict = NULL;
|
||||
|
||||
@@ -2285,9 +2283,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb* db, int *error,
|
||||
|
||||
/* if needed create field with TTL metadata */
|
||||
if (expireAt !=0)
|
||||
field = rdbGenericLoadStringObject(rdb, RDB_LOAD_HFLD_TTL, &fieldLen);
|
||||
field = rdbGenericLoadStringObject(rdb, RDB_LOAD_HFLD_TTL, NULL);
|
||||
else
|
||||
field = rdbGenericLoadStringObject(rdb, RDB_LOAD_HFLD, &fieldLen);
|
||||
field = rdbGenericLoadStringObject(rdb, RDB_LOAD_HFLD, NULL);
|
||||
|
||||
if (field == NULL) {
|
||||
serverLog(LL_WARNING, "failed reading hash field");
|
||||
@@ -2305,9 +2303,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb* db, int *error,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* keep the nearest expiration to connect listpack object to db expiry */
|
||||
if ((expireAt != 0) && (expireAt < minExpField)) minExpField = expireAt;
|
||||
|
||||
/* store the values read - either to listpack or dict */
|
||||
if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
|
||||
/* integrity - check for key duplication (if required) */
|
||||
@@ -2378,11 +2373,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb* db, int *error,
|
||||
|
||||
if (dupSearchDict != NULL) dictRelease(dupSearchDict);
|
||||
|
||||
/* check for empty key (if all fields were expired) */
|
||||
if (hashTypeLength(o, 0) == 0) {
|
||||
decrRefCount(o);
|
||||
goto expiredHash;
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_LIST_QUICKLIST || rdbtype == RDB_TYPE_LIST_QUICKLIST_2) {
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
@@ -2706,9 +2696,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb* db, int *error,
|
||||
goto emptykey;
|
||||
}
|
||||
|
||||
/* for TTL listpack, find the minimum expiry */
|
||||
minExpField = hashTypeGetNextTimeToExpire(o);
|
||||
|
||||
/* Convert listpack to hash table without registering in global HFE DS,
|
||||
* if has HFEs, since the listpack is not connected yet to the DB */
|
||||
if (hashTypeLength(o, 0) > server.hash_max_listpack_entries)
|
||||
@@ -3053,13 +3040,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb* db, int *error,
|
||||
RedisModuleIO io;
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
/* shouldn't happen since db is NULL only in RDB check mode, and
|
||||
* in this mode the module load code returns few lines above after
|
||||
* checking module name, few lines above. So this check is only
|
||||
* for safety.
|
||||
*/
|
||||
if (db == NULL) return NULL;
|
||||
moduleInitIOContext(io,mt,rdb,&keyobj,db->id);
|
||||
moduleInitIOContext(io,mt,rdb,&keyobj,dbid);
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
void *ptr = mt->rdb_load(&io,moduleid&1023);
|
||||
@@ -3099,17 +3080,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb* db, int *error,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (minExpiredField) *minExpiredField = minExpField;
|
||||
|
||||
if (error) *error = 0;
|
||||
return o;
|
||||
|
||||
emptykey:
|
||||
if (error) *error = RDB_LOAD_ERR_EMPTY_KEY;
|
||||
return NULL;
|
||||
expiredHash:
|
||||
if (error) *error = RDB_LOAD_ERR_EXPIRED_HASH;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark that we are loading in the global state and setup the fields
|
||||
@@ -3279,7 +3255,6 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* currently it only allow to set db object and functionLibCtx to which the data
|
||||
* will be loaded (in the future it might contains more such objects). */
|
||||
int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadingCtx *rdb_loading_ctx) {
|
||||
uint64_t minExpiredField = EB_EXPIRE_TIME_INVALID;
|
||||
uint64_t dbid = 0;
|
||||
int type, rdbver;
|
||||
uint64_t db_size = 0, expires_size = 0;
|
||||
@@ -3521,7 +3496,7 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
|
||||
if ((key = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL)
|
||||
goto eoferr;
|
||||
/* Read value */
|
||||
val = rdbLoadObject(type,rdb,key,db,&error, &minExpiredField);
|
||||
val = rdbLoadObject(type,rdb,key,db->id,&error);
|
||||
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
@@ -3540,9 +3515,6 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
|
||||
if(empty_keys_skipped++ < 10)
|
||||
serverLog(LL_NOTICE, "rdbLoadObject skipping empty key: %s", key);
|
||||
sdsfree(key);
|
||||
} else if (error == RDB_LOAD_ERR_EXPIRED_HASH) {
|
||||
/* Valid flow. Continue. */
|
||||
sdsfree(key);
|
||||
} else {
|
||||
sdsfree(key);
|
||||
goto eoferr;
|
||||
@@ -3589,8 +3561,11 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
|
||||
|
||||
/* If minExpiredField was set, then the object is hash with expiration
|
||||
* on fields and need to register it in global HFE DS */
|
||||
if (minExpiredField != EB_EXPIRE_TIME_INVALID)
|
||||
hashTypeAddToExpires(db, key, val, minExpiredField);
|
||||
if (val->type == OBJ_HASH) {
|
||||
uint64_t minExpiredField = hashTypeGetMinExpire(val, 1);
|
||||
if (minExpiredField != EB_EXPIRE_TIME_INVALID)
|
||||
hashTypeAddToExpires(db, key, val, minExpiredField);
|
||||
}
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) {
|
||||
|
||||
@@ -121,8 +121,7 @@
|
||||
/* When rdbLoadObject() returns NULL, the err flag is
|
||||
* set to hold the type of error that occurred */
|
||||
#define RDB_LOAD_ERR_EMPTY_KEY 1 /* Error of empty key */
|
||||
#define RDB_LOAD_ERR_EXPIRED_HASH 2 /* Expired hash since all its fields are expired */
|
||||
#define RDB_LOAD_ERR_OTHER 3 /* Any other errors */
|
||||
#define RDB_LOAD_ERR_OTHER 2 /* Any other errors */
|
||||
|
||||
ssize_t rdbWriteRaw(rio *rdb, void *p, size_t len);
|
||||
int rdbSaveType(rio *rdb, unsigned char type);
|
||||
@@ -143,7 +142,7 @@ int rdbSaveToFile(const char *filename);
|
||||
int rdbSave(int req, char *filename, rdbSaveInfo *rsi, int rdbflags);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key, int dbid);
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, redisDb *db, int *error, uint64_t *minExpiredField);
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime,int dbid);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
|
||||
@@ -175,6 +175,7 @@ void rdbCheckSetupSignals(void) {
|
||||
* otherwise the already open file 'fp' is checked. */
|
||||
int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
uint64_t dbid;
|
||||
int selected_dbid = -1;
|
||||
int type, rdbver;
|
||||
char buf[1024];
|
||||
long long expiretime, now = mstime();
|
||||
@@ -246,6 +247,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
|
||||
goto eoferr;
|
||||
rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid);
|
||||
selected_dbid = dbid;
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_RESIZEDB) {
|
||||
/* RESIZEDB: Hint about the size of the keys in the currently
|
||||
@@ -331,7 +333,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr,NULL,NULL,NULL)) == NULL)
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr,selected_dbid,NULL)) == NULL)
|
||||
goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
|
||||
@@ -189,6 +189,9 @@ int canFeedReplicaReplBuffer(client *replica) {
|
||||
/* Don't feed replicas that are still waiting for BGSAVE to start. */
|
||||
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) return 0;
|
||||
|
||||
/* Don't feed replicas that are going to be closed ASAP. */
|
||||
if (replica->flags & CLIENT_CLOSE_ASAP) return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ extern scriptFlag scripts_flags_def[];
|
||||
|
||||
void luaEnvInit(void);
|
||||
lua_State *createLuaState(void);
|
||||
dict *getLuaScripts(void);
|
||||
uint64_t scriptFlagsToCmdFlags(uint64_t cmd_flags, uint64_t script_flags);
|
||||
int scriptPrepareForRun(scriptRunCtx *r_ctx, client *engine_client, client *caller, const char *funcname, uint64_t script_flags, int ro);
|
||||
void scriptResetRun(scriptRunCtx *r_ctx);
|
||||
|
||||
@@ -334,6 +334,10 @@ uint64_t dictObjHash(const void *key) {
|
||||
return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr));
|
||||
}
|
||||
|
||||
uint64_t dictPtrHash(const void *key) {
|
||||
return dictGenHashFunction((unsigned char*)&key,sizeof(key));
|
||||
}
|
||||
|
||||
uint64_t dictSdsHash(const void *key) {
|
||||
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
|
||||
}
|
||||
|
||||
+22
-12
@@ -2071,7 +2071,8 @@ struct redisServer {
|
||||
char *locale_collate;
|
||||
};
|
||||
|
||||
#define MAX_KEYS_BUFFER 256
|
||||
/* we use 6 so that all getKeyResult fits a cacheline */
|
||||
#define MAX_KEYS_BUFFER 6
|
||||
|
||||
typedef struct {
|
||||
int pos; /* The position of the key within the client array */
|
||||
@@ -2084,12 +2085,12 @@ typedef struct {
|
||||
* for returning channel information.
|
||||
*/
|
||||
typedef struct {
|
||||
int numkeys; /* Number of key indices return */
|
||||
int size; /* Available array size */
|
||||
keyReference keysbuf[MAX_KEYS_BUFFER]; /* Pre-allocated buffer, to save heap allocations */
|
||||
keyReference *keys; /* Key indices array, points to keysbuf or heap */
|
||||
int numkeys; /* Number of key indices return */
|
||||
int size; /* Available array size */
|
||||
} getKeysResult;
|
||||
#define GETKEYS_RESULT_INIT { {{0}}, NULL, 0, MAX_KEYS_BUFFER }
|
||||
#define GETKEYS_RESULT_INIT { 0, MAX_KEYS_BUFFER, {{0}}, NULL }
|
||||
|
||||
/* Key specs definitions.
|
||||
*
|
||||
@@ -3163,7 +3164,9 @@ robj *setTypeDup(robj *o);
|
||||
typedef struct listpackEx {
|
||||
ExpireMeta meta; /* To be used in order to register the hash in the
|
||||
global ebuckets (i.e. db->hexpires) with next,
|
||||
minimum, hash-field to expire. */
|
||||
minimum, hash-field to expire. TTL value might be
|
||||
inaccurate up-to few seconds due to optimization
|
||||
consideration. */
|
||||
sds key; /* reference to the key, same one that stored in
|
||||
db->dict. Will be used from active-expiration flow
|
||||
for notification and deletion of the object, if
|
||||
@@ -3178,7 +3181,9 @@ typedef struct dictExpireMetadata {
|
||||
ExpireMeta expireMeta; /* embedded ExpireMeta in dict.
|
||||
To be used in order to register the hash in the
|
||||
global ebuckets (i.e db->hexpires) with next,
|
||||
minimum, hash-field to expire */
|
||||
minimum, hash-field to expire. TTL value might be
|
||||
inaccurate up-to few seconds due to optimization
|
||||
consideration. */
|
||||
ebuckets hfe; /* DS of Hash Fields Expiration, associated to each hash */
|
||||
sds key; /* reference to the key, same one that stored in
|
||||
db->dict. Will be used from active-expiration flow
|
||||
@@ -3191,9 +3196,16 @@ typedef struct dictExpireMetadata {
|
||||
#define HASH_SET_TAKE_VALUE (1<<1)
|
||||
#define HASH_SET_COPY 0
|
||||
|
||||
/* Hash field lazy expiration flags. Used by core hashTypeGetValue() and its callers */
|
||||
#define HFE_LAZY_EXPIRE (0) /* Delete expired field, and if last field also the hash */
|
||||
#define HFE_LAZY_AVOID_FIELD_DEL (1<<0) /* Avoid deleting expired field */
|
||||
#define HFE_LAZY_AVOID_HASH_DEL (1<<1) /* Avoid deleting hash if the field is the last one */
|
||||
#define HFE_LAZY_NO_NOTIFICATION (1<<2) /* Do not send notification, used when multiple fields
|
||||
* may expire and only one notification is desired. */
|
||||
|
||||
void hashTypeConvert(robj *o, int enc, ebuckets *hexpires);
|
||||
void hashTypeTryConversion(redisDb *db, robj *subject, robj **argv, int start, int end);
|
||||
int hashTypeExists(redisDb *db, robj *o, sds key, int *isHashDeleted);
|
||||
int hashTypeExists(redisDb *db, robj *o, sds key, int hfeFlags, int *isHashDeleted);
|
||||
int hashTypeDelete(robj *o, void *key, int isSdsField);
|
||||
unsigned long hashTypeLength(const robj *o, int subtractExpiredFields);
|
||||
hashTypeIterator *hashTypeInitIterator(robj *subject);
|
||||
@@ -3210,20 +3222,17 @@ void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr,
|
||||
unsigned int *vlen, long long *vll, uint64_t *expireTime);
|
||||
sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what);
|
||||
hfield hashTypeCurrentObjectNewHfield(hashTypeIterator *hi);
|
||||
robj *hashTypeGetValueObject(redisDb *db, robj *o, sds field, int *isHashDeleted);
|
||||
robj *hashTypeGetValueObject(redisDb *db, robj *o, sds field, int hfeFlags, int *isHashDeleted);
|
||||
int hashTypeSet(redisDb *db, robj *o, sds field, sds value, int flags);
|
||||
robj *hashTypeDup(robj *o, sds newkey, uint64_t *minHashExpire);
|
||||
uint64_t hashTypeRemoveFromExpires(ebuckets *hexpires, robj *o);
|
||||
void hashTypeAddToExpires(redisDb *db, sds key, robj *hashObj, uint64_t expireTime);
|
||||
void hashTypeFree(robj *o);
|
||||
int hashTypeIsExpired(const robj *o, uint64_t expireAt);
|
||||
uint64_t hashTypeGetMinExpire(robj *o);
|
||||
unsigned char *hashTypeListpackGetLp(robj *o);
|
||||
uint64_t hashTypeGetMinExpire(robj *o);
|
||||
uint64_t hashTypeGetMinExpire(robj *o, int accurate);
|
||||
void hashTypeUpdateKeyRef(robj *o, sds newkey);
|
||||
ebuckets *hashTypeGetDictMetaHFE(dict *d);
|
||||
uint64_t hashTypeGetMinExpire(robj *keyObj);
|
||||
uint64_t hashTypeGetNextTimeToExpire(robj *o);
|
||||
void initDictExpireMetadata(sds key, robj *o);
|
||||
struct listpackEx *listpackExCreate(void);
|
||||
void listpackExAddNew(robj *o, char *field, size_t flen,
|
||||
@@ -3531,6 +3540,7 @@ void startEvictionTimeProc(void);
|
||||
|
||||
/* Keys hashing / comparison functions for dict.c hash tables. */
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
uint64_t dictPtrHash(const void *key);
|
||||
uint64_t dictSdsCaseHash(const void *key);
|
||||
int dictSdsKeyCompare(dict *d, const void *key1, const void *key2);
|
||||
int dictSdsMstrKeyCompare(dict *d, const void *sdsLookup, const void *mstrStored);
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
|
||||
/* Retrieve value from hash by the field name. The returned object
|
||||
* is a new object with refcount already incremented. */
|
||||
int isHashDeleted;
|
||||
o = hashTypeGetValueObject(db, o, fieldobj->ptr, &isHashDeleted);
|
||||
o = hashTypeGetValueObject(db, o, fieldobj->ptr, HFE_LAZY_EXPIRE, &isHashDeleted);
|
||||
|
||||
if (isHashDeleted)
|
||||
goto noobj;
|
||||
|
||||
+675
-534
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,2 +1,2 @@
|
||||
#define REDIS_VERSION "7.3.240"
|
||||
#define REDIS_VERSION_NUM 0x000703f0
|
||||
#define REDIS_VERSION "7.3.241"
|
||||
#define REDIS_VERSION_NUM 0x000703f1
|
||||
|
||||
@@ -429,8 +429,8 @@ start_server [list overrides [list "dir" $server_path]] {
|
||||
r HMSET key a 1 b 2 c 3 d 4 e 5
|
||||
# expected to be expired long after restart
|
||||
r HEXPIREAT key 2524600800 FIELDS 1 a
|
||||
# expected long TTL value (6 bytes) is saved and loaded correctly
|
||||
r HPEXPIREAT key 188900976391764 FIELDS 1 b
|
||||
# expected long TTL value (46 bits) is saved and loaded correctly
|
||||
r HPEXPIREAT key 65755674080852 FIELDS 1 b
|
||||
# expected to be already expired after restart
|
||||
r HPEXPIRE key 80 FIELDS 1 d
|
||||
# expected to be expired soon after restart
|
||||
@@ -443,7 +443,7 @@ start_server [list overrides [list "dir" $server_path]] {
|
||||
wait_done_loading r
|
||||
|
||||
assert_equal [lsort [r hgetall key]] "1 2 3 a b c"
|
||||
assert_equal [r hpexpiretime key FIELDS 3 a b c] {2524600800000 188900976391764 -1}
|
||||
assert_equal [r hpexpiretime key FIELDS 3 a b c] {2524600800000 65755674080852 -1}
|
||||
assert_equal [s rdb_last_load_keys_loaded] 1
|
||||
|
||||
# wait until expired_hash_fields equals 2
|
||||
|
||||
@@ -302,6 +302,21 @@ int propagateTestIncr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int propagateTestVerbatim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc < 2){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
long long replicate_num;
|
||||
RedisModule_StringToLongLong(argv[1], &replicate_num);
|
||||
/* Replicate the command verbatim for the specified number of times. */
|
||||
for (long long i = 0; i < replicate_num; i++)
|
||||
RedisModule_ReplicateVerbatim(ctx);
|
||||
RedisModule_ReplyWithSimpleString(ctx,"OK");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
@@ -368,6 +383,11 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
propagateTestIncr,
|
||||
"write",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"propagate-test.verbatim",
|
||||
propagateTestVerbatim,
|
||||
"write",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,45 @@ start_server {tags {"modules"}} {
|
||||
r hgetall k
|
||||
} {squirrel ofcourse banana no what nothing something nice}
|
||||
|
||||
test {Module hash - set (override) NX expired field successfully} {
|
||||
r debug set-active-expire 0
|
||||
r del H1 H2
|
||||
r hash.set H1 "n" f1 v1
|
||||
r hpexpire H1 1 FIELDS 1 f1
|
||||
r hash.set H2 "n" f1 v1 f2 v2
|
||||
r hpexpire H2 1 FIELDS 1 f1
|
||||
after 5
|
||||
assert_equal 0 [r hash.set H1 "n" f1 xx]
|
||||
assert_equal "f1 xx" [r hgetall H1]
|
||||
assert_equal 0 [r hash.set H2 "n" f1 yy]
|
||||
assert_equal "f1 f2 v2 yy" [lsort [r hgetall H2]]
|
||||
r debug set-active-expire 1
|
||||
} {OK} {needs:debug}
|
||||
|
||||
test {Module hash - set XX of expired field gets failed as expected} {
|
||||
r debug set-active-expire 0
|
||||
r del H1 H2
|
||||
r hash.set H1 "n" f1 v1
|
||||
r hpexpire H1 1 FIELDS 1 f1
|
||||
r hash.set H2 "n" f1 v1 f2 v2
|
||||
r hpexpire H2 1 FIELDS 1 f1
|
||||
after 5
|
||||
|
||||
# expected to fail on condition XX. hgetall should return empty list
|
||||
r hash.set H1 "x" f1 xx
|
||||
assert_equal "" [lsort [r hgetall H1]]
|
||||
# But expired field was not lazy deleted
|
||||
assert_equal 1 [r hlen H1]
|
||||
|
||||
# expected to fail on condition XX. hgetall should return list without expired f1
|
||||
r hash.set H2 "x" f1 yy
|
||||
assert_equal "f2 v2" [lsort [r hgetall H2]]
|
||||
# But expired field was not lazy deleted
|
||||
assert_equal 2 [r hlen H2]
|
||||
|
||||
r debug set-active-expire 1
|
||||
} {OK} {needs:debug}
|
||||
|
||||
test "Unload the module - hash" {
|
||||
assert_equal {OK} [r module unload hash]
|
||||
}
|
||||
|
||||
@@ -761,3 +761,41 @@ tags "modules aof" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# This test does not really test module functionality, but rather uses a module
|
||||
# command to test Redis replication mechanisms.
|
||||
test {Replicas that was marked as CLIENT_CLOSE_ASAP should not keep the replication backlog from been trimmed} {
|
||||
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||
set replica [srv 0 client]
|
||||
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
$master config set client-output-buffer-limit "replica 10mb 5mb 0"
|
||||
|
||||
# Start the replication process...
|
||||
$replica replicaof $master_host $master_port
|
||||
wait_for_sync $replica
|
||||
|
||||
test {module propagates from timer} {
|
||||
# Replicate large commands to make the replica disconnected.
|
||||
$master write [format_command propagate-test.verbatim 100000 [string repeat "a" 1000]] ;# almost 100mb
|
||||
# Execute this command together with module commands within the same
|
||||
# event loop to prevent periodic cleanup of replication backlog.
|
||||
$master write [format_command info memory]
|
||||
$master flush
|
||||
$master read ;# propagate-test.verbatim
|
||||
set res [$master read] ;# info memory
|
||||
|
||||
# Wait for the replica to be disconnected.
|
||||
wait_for_log_messages 0 {"*flags=S*scheduled to be closed ASAP for overcoming of output buffer limits*"} 0 1500 10
|
||||
# Due to the replica reaching the soft limit (5MB), memory peaks should not significantly
|
||||
# exceed the replica soft limit. Furthermore, as the replica release its reference to
|
||||
# replication backlog, it should be properly trimmed, the memory usage of replication
|
||||
# backlog should not significantly exceed repl-backlog-size (default 1MB). */
|
||||
assert_lessthan [getInfoProperty $res used_memory_peak] 10000000;# less than 10mb
|
||||
assert_lessthan [getInfoProperty $res mem_replication_backlog] 2000000;# less than 2mb
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,16 @@ start_server {tags {"modules"}} {
|
||||
} {{f1 1}}
|
||||
|
||||
test {Module scan hash listpack with hexpire} {
|
||||
r hmset hh f1 v1 f2 v2
|
||||
r debug set-active-expire 0
|
||||
r hmset hh f1 v1 f2 v2 f3 v3
|
||||
r hexpire hh 100000 fields 1 f1
|
||||
r hpexpire hh 1 fields 1 f3
|
||||
after 10
|
||||
assert_range [r httl hh fields 1 f1] 10000 100000
|
||||
assert_encoding listpackex hh
|
||||
r debug set-active-expire 1
|
||||
lsort [r scan.scan_key hh]
|
||||
} {{f1 v1} {f2 v2}}
|
||||
} {{f1 v1} {f2 v2}} {needs:debug}
|
||||
|
||||
test {Module scan hash dict} {
|
||||
r config set hash-max-ziplist-entries 2
|
||||
@@ -44,10 +48,22 @@ start_server {tags {"modules"}} {
|
||||
r del hh
|
||||
r hmset hh f1 v1 f2 v2 f3 v3
|
||||
r hexpire hh 100000 fields 1 f2
|
||||
r hpexpire hh 5 fields 1 f3
|
||||
assert_range [r httl hh fields 1 f2] 10000 100000
|
||||
assert_encoding hashtable hh
|
||||
after 10
|
||||
lsort [r scan.scan_key hh]
|
||||
} {{f1 v1} {f2 v2} {f3 v3}}
|
||||
} {{f1 v1} {f2 v2}}
|
||||
|
||||
test {Module scan hash with hexpire can return no items} {
|
||||
r del hh
|
||||
r debug set-active-expire 0
|
||||
r hmset hh f1 v1 f2 v2 f3 v3
|
||||
r hpexpire hh 1 fields 3 f1 f2 f3
|
||||
after 10
|
||||
r debug set-active-expire 1
|
||||
lsort [r scan.scan_key hh]
|
||||
} {} {needs:debug}
|
||||
|
||||
test {Module scan zset listpack} {
|
||||
r zadd zz 1 f1 2 f2
|
||||
|
||||
+34
-3
@@ -356,16 +356,17 @@ start_server {tags {"pubsub network"}} {
|
||||
foreach {type max_lp_entries} {listpackex 512 hashtable 0} {
|
||||
test "Keyspace notifications: hash events test ($type)" {
|
||||
r config set hash-max-listpack-entries $max_lp_entries
|
||||
r config set notify-keyspace-events Kh
|
||||
r config set notify-keyspace-events Khg
|
||||
r del myhash
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r hmset myhash yes 1 no 0
|
||||
r hmset myhash yes 1 no 0 f1 1 f2 2 f3_hdel 3
|
||||
r hincrby myhash yes 10
|
||||
r hexpire myhash 999999 FIELDS 1 yes
|
||||
r hexpireat myhash [expr {[clock seconds] + 999999}] NX FIELDS 1 no
|
||||
r hpexpire myhash 999999 FIELDS 1 yes
|
||||
r hpersist myhash FIELDS 1 yes
|
||||
r hpexpire myhash 0 FIELDS 1 yes
|
||||
assert_encoding $type myhash
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hincrby" [$rd1 read]
|
||||
@@ -373,8 +374,38 @@ start_server {tags {"pubsub network"}} {
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hpersist" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
|
||||
|
||||
# Test that we will get `hexpired` notification when
|
||||
# a hash field is removed by active expire.
|
||||
r hpexpire myhash 10 FIELDS 1 no
|
||||
after 100 ;# Wait for active expire
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
|
||||
|
||||
# Test that when a field with TTL is deleted by commands like hdel without
|
||||
# updating the global DS, active expire will not send a notification.
|
||||
r hpexpire myhash 100 FIELDS 1 f3_hdel
|
||||
r hdel myhash f3_hdel
|
||||
after 200 ;# Wait for active expire
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read]
|
||||
|
||||
# Test that we will get `hexpired` notification when
|
||||
# a hash field is removed by lazy expire.
|
||||
r debug set-active-expire 0
|
||||
r hpexpire myhash 10 FIELDS 2 f1 f2
|
||||
after 20
|
||||
r hmget myhash f1 f2 ;# Trigger lazy expire
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
# We should get only one `hexpired` notification even two fields was expired.
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
|
||||
# We should get a `del` notification after all fields were expired.
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read]
|
||||
r debug set-active-expire 1
|
||||
|
||||
$rd1 close
|
||||
}
|
||||
} {0} {needs:debug}
|
||||
} ;# foreach
|
||||
|
||||
test "Keyspace notifications: stream events test" {
|
||||
|
||||
@@ -32,13 +32,6 @@ proc get_hashes_with_expiry_fields {r} {
|
||||
return 0
|
||||
}
|
||||
|
||||
proc create_hash {key entries} {
|
||||
r del $key
|
||||
foreach entry $entries {
|
||||
r hset $key [lindex $entry 0] [lindex $entry 1]
|
||||
}
|
||||
}
|
||||
|
||||
proc get_keys {l} {
|
||||
set res {}
|
||||
foreach entry $l {
|
||||
@@ -48,22 +41,6 @@ proc get_keys {l} {
|
||||
return $res
|
||||
}
|
||||
|
||||
proc cmp_hrandfield_result {hash_name expected_result} {
|
||||
# Accumulate hrandfield results
|
||||
unset -nocomplain myhash
|
||||
array set myhash {}
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
set key [r hrandfield $hash_name]
|
||||
set myhash($key) 1
|
||||
}
|
||||
set res [lsort [array names myhash]]
|
||||
if {$res eq $expected_result} {
|
||||
return 1
|
||||
} else {
|
||||
return $res
|
||||
}
|
||||
}
|
||||
|
||||
proc dumpAllHashes {client} {
|
||||
set keyAndFields(0,0) 0
|
||||
unset keyAndFields
|
||||
@@ -77,36 +54,6 @@ proc dumpAllHashes {client} {
|
||||
return [array get keyAndFields]
|
||||
}
|
||||
|
||||
proc hrandfieldTest {activeExpireConfig} {
|
||||
r debug set-active-expire $activeExpireConfig
|
||||
r del myhash
|
||||
set contents {{field1 1} {field2 2} }
|
||||
create_hash myhash $contents
|
||||
|
||||
set factorValgrind [expr {$::valgrind ? 2 : 1}]
|
||||
|
||||
# Set expiration time for field1 and field2 such that field1 expires first
|
||||
r hpexpire myhash 1 NX FIELDS 1 field1
|
||||
r hpexpire myhash 100 NX FIELDS 1 field2
|
||||
|
||||
# On call hrandfield command lazy expire deletes field1 first
|
||||
wait_for_condition 8 10 {
|
||||
[cmp_hrandfield_result myhash "field2"] == 1
|
||||
} else {
|
||||
fail "Expected field2 to be returned by HRANDFIELD."
|
||||
}
|
||||
|
||||
# On call hrandfield command lazy expire deletes field2 as well
|
||||
wait_for_condition 8 20 {
|
||||
[cmp_hrandfield_result myhash "{}"] == 1
|
||||
} else {
|
||||
fail "Expected {} to be returned by HRANDFIELD."
|
||||
}
|
||||
|
||||
# restore the default value
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
############################### TESTS #########################################
|
||||
|
||||
start_server {tags {"external:skip needs:debug"}} {
|
||||
@@ -117,15 +64,12 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r config set hash-max-listpack-entries 512
|
||||
}
|
||||
|
||||
test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Returns empty array if key does not exist" {
|
||||
test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Returns array if the key does not exist" {
|
||||
r del myhash
|
||||
# Make sure we can distinguish between an empty array and a null response
|
||||
r readraw 1
|
||||
assert_equal {*0} [r HEXPIRE myhash 1000 FIELDS 1 a]
|
||||
assert_equal {*0} [r HEXPIREAT myhash 1000 FIELDS 1 a]
|
||||
assert_equal {*0} [r HPEXPIRE myhash 1000 FIELDS 1 a]
|
||||
assert_equal {*0} [r HPEXPIREAT myhash 1000 FIELDS 1 a]
|
||||
r readraw 0
|
||||
assert_equal [r HEXPIRE myhash 1000 FIELDS 1 a] [list $E_NO_FIELD]
|
||||
assert_equal [r HEXPIREAT myhash 1000 FIELDS 1 a] [list $E_NO_FIELD]
|
||||
assert_equal [r HPEXPIRE myhash 1000 FIELDS 2 a b] [list $E_NO_FIELD $E_NO_FIELD]
|
||||
assert_equal [r HPEXPIREAT myhash 1000 FIELDS 2 a b] [list $E_NO_FIELD $E_NO_FIELD]
|
||||
}
|
||||
|
||||
test "HEXPIRE/HEXPIREAT/HPEXPIRE/HPEXPIREAT - Verify that the expire time does not overflow" {
|
||||
@@ -205,15 +149,17 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r del myhash
|
||||
r hset myhash f1 v1
|
||||
assert_error {*Parameter `numFields` should be greater than 0} {r hpexpire myhash 1000 NX FIELDS 0 f1 f2 f3}
|
||||
assert_error {*Parameter `numFields` is more than number of arguments} {r hpexpire myhash 1000 NX FIELDS 4 f1 f2 f3}
|
||||
# <count> not match with actual number of fields
|
||||
assert_error {*parameter must match the number*} {r hpexpire myhash 1000 NX FIELDS 4 f1 f2 f3}
|
||||
assert_error {*parameter must match the number*} {r hpexpire myhash 1000 NX FIELDS 2 f1 f2 f3}
|
||||
}
|
||||
|
||||
test "HPEXPIRE - parameter expire-time near limit of 2^48 ($type)" {
|
||||
test "HPEXPIRE - parameter expire-time near limit of 2^46 ($type)" {
|
||||
r del myhash
|
||||
r hset myhash f1 v1
|
||||
# below & above
|
||||
assert_equal [r hpexpire myhash [expr (1<<48) - [clock milliseconds] - 1000 ] FIELDS 1 f1] [list $E_OK]
|
||||
assert_error {*invalid expire time*} {r hpexpire myhash [expr (1<<48) - [clock milliseconds] + 100 ] FIELDS 1 f1}
|
||||
assert_equal [r hpexpire myhash [expr (1<<46) - [clock milliseconds] - 1000 ] FIELDS 1 f1] [list $E_OK]
|
||||
assert_error {*invalid expire time*} {r hpexpire myhash [expr (1<<46) - [clock milliseconds] + 100 ] FIELDS 1 f1}
|
||||
}
|
||||
|
||||
test "Lazy Expire - fields are lazy deleted ($type)" {
|
||||
@@ -305,13 +251,10 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r flushall async
|
||||
}
|
||||
|
||||
test "HTTL/HPTTL - Returns empty array if key does not exist" {
|
||||
test "HTTL/HPTTL - Returns array if the key does not exist" {
|
||||
r del myhash
|
||||
# Make sure we can distinguish between an empty array and a null response
|
||||
r readraw 1
|
||||
assert_equal {*0} [r HTTL myhash FIELDS 1 a]
|
||||
assert_equal {*0} [r HPTTL myhash FIELDS 1 a]
|
||||
r readraw 0
|
||||
assert_equal [r HTTL myhash FIELDS 1 a] [list $T_NO_FIELD]
|
||||
assert_equal [r HPTTL myhash FIELDS 2 a b] [list $T_NO_FIELD $T_NO_FIELD]
|
||||
}
|
||||
|
||||
test "HTTL/HPTTL - Input validation gets failed on nonexists field or field without expire ($type)" {
|
||||
@@ -320,10 +263,10 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r HPEXPIRE myhash 1000 NX FIELDS 1 field1
|
||||
|
||||
foreach cmd {HTTL HPTTL} {
|
||||
assert_equal [r $cmd non_exists_key FIELDS 1 f] {}
|
||||
assert_equal [r $cmd myhash FIELDS 2 field2 non_exists_field] "$T_NO_EXPIRY $T_NO_FIELD"
|
||||
# Set numFields less than actual number of fields. Fine.
|
||||
assert_equal [r $cmd myhash FIELDS 1 non_exists_field1 non_exists_field2] "$T_NO_FIELD"
|
||||
# <count> not match with actual number of fields
|
||||
assert_error {*parameter must match the number*} {r $cmd myhash FIELDS 1 non_exists_field1 non_exists_field2}
|
||||
assert_error {*parameter must match the number*} {r $cmd myhash FIELDS 3 non_exists_field1 non_exists_field2}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,13 +280,10 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
assert_range $ttl 1000 2000
|
||||
}
|
||||
|
||||
test "HEXPIRETIME/HPEXPIRETIME - Returns empty array if key does not exist" {
|
||||
test "HEXPIRETIME/HPEXPIRETIME - Returns array if the key does not exist" {
|
||||
r del myhash
|
||||
# Make sure we can distinguish between an empty array and a null response
|
||||
r readraw 1
|
||||
assert_equal {*0} [r HEXPIRETIME myhash FIELDS 1 a]
|
||||
assert_equal {*0} [r HPEXPIRETIME myhash FIELDS 1 a]
|
||||
r readraw 0
|
||||
assert_equal [r HEXPIRETIME myhash FIELDS 1 a] [list $T_NO_FIELD]
|
||||
assert_equal [r HPEXPIRETIME myhash FIELDS 2 a b] [list $T_NO_FIELD $T_NO_FIELD]
|
||||
}
|
||||
|
||||
test "HEXPIRETIME - returns TTL in Unix timestamp ($type)" {
|
||||
@@ -406,22 +346,33 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
# OPEN: To decide if to delete expired fields at start of HRANDFIELD.
|
||||
# test "Test HRANDFIELD does not return expired fields ($type)" {
|
||||
# hrandfieldTest 0
|
||||
# hrandfieldTest 1
|
||||
# }
|
||||
|
||||
test "Test HRANDFIELD can return expired fields ($type)" {
|
||||
test "Test HRANDFIELD deletes all expired fields ($type)" {
|
||||
r debug set-active-expire 0
|
||||
r del myhash
|
||||
r flushall
|
||||
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
|
||||
r hpexpire myhash 1 NX FIELDS 4 f1 f2 f3 f4
|
||||
r hpexpire myhash 1 FIELDS 2 f1 f2
|
||||
after 5
|
||||
set res [cmp_hrandfield_result myhash "f1 f2 f3 f4 f5"]
|
||||
assert {$res == 1}
|
||||
r debug set-active-expire 1
|
||||
assert_equal [lsort [r hrandfield myhash 5]] "f3 f4 f5"
|
||||
r hpexpire myhash 1 FIELDS 3 f3 f4 f5
|
||||
after 5
|
||||
assert_equal [lsort [r hrandfield myhash 5]] ""
|
||||
assert_equal [r keys *] ""
|
||||
|
||||
r del myhash
|
||||
r hset myhash f1 v1 f2 v2 f3 v3
|
||||
r hpexpire myhash 1 FIELDS 1 f1
|
||||
after 5
|
||||
set res [r hrandfield myhash]
|
||||
assert {$res == "f2" || $res == "f3"}
|
||||
r hpexpire myhash 1 FIELDS 1 f2
|
||||
after 5
|
||||
assert_equal [lsort [r hrandfield myhash 5]] "f3"
|
||||
r hpexpire myhash 1 FIELDS 1 f3
|
||||
after 5
|
||||
assert_equal [r hrandfield myhash] ""
|
||||
assert_equal [r keys *] ""
|
||||
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
test "Lazy Expire - HLEN does count expired fields ($type)" {
|
||||
@@ -711,12 +662,10 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
test "HPERSIST - Returns empty array if key does not exist ($type)" {
|
||||
test "HPERSIST - Returns array if the key does not exist ($type)" {
|
||||
r del myhash
|
||||
# Make sure we can distinguish between an empty array and a null response
|
||||
r readraw 1
|
||||
assert_equal {*0} [r HPERSIST myhash FIELDS 1 a]
|
||||
r readraw 0
|
||||
assert_equal [r HPERSIST myhash FIELDS 1 a] [list $P_NO_FIELD]
|
||||
assert_equal [r HPERSIST myhash FIELDS 2 a b] [list $P_NO_FIELD $P_NO_FIELD]
|
||||
}
|
||||
|
||||
test "HPERSIST - input validation ($type)" {
|
||||
@@ -726,9 +675,11 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r hexpire myhash 1000 NX FIELDS 1 f1
|
||||
assert_error {*wrong number of arguments*} {r hpersist myhash}
|
||||
assert_error {*wrong number of arguments*} {r hpersist myhash FIELDS 1}
|
||||
assert_equal [r hpersist not-exists-key FIELDS 1 f1] {}
|
||||
assert_equal [r hpersist myhash FIELDS 2 f1 not-exists-field] "$P_OK $P_NO_FIELD"
|
||||
assert_equal [r hpersist myhash FIELDS 1 f2] "$P_NO_EXPIRY"
|
||||
# <count> not match with actual number of fields
|
||||
assert_error {*parameter must match the number*} {r hpersist myhash FIELDS 2 f1 f2 f3}
|
||||
assert_error {*parameter must match the number*} {r hpersist myhash FIELDS 4 f1 f2 f3}
|
||||
}
|
||||
|
||||
test "HPERSIST - verify fields with TTL are persisted ($type)" {
|
||||
@@ -969,14 +920,29 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r config set hash-max-listpack-entries 512
|
||||
}
|
||||
|
||||
test "Command rewrite and expired hash fields are propagated to replica ($type)" {
|
||||
test "Test Command propagated to replica as expected ($type)" {
|
||||
start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} {
|
||||
|
||||
set aof [get_last_incr_aof_path r]
|
||||
|
||||
# Time is in the past so it should propagate HDELs to replica
|
||||
# and delete the fields
|
||||
r hset h0 x1 y1 x2 y2
|
||||
r hexpireat h0 1 fields 3 x1 x2 non_exists_field
|
||||
|
||||
r hset h1 f1 v1 f2 v2
|
||||
|
||||
# Next command won't be propagated to replica
|
||||
# because XX condition not met or field not exists
|
||||
r hexpire h1 10 XX FIELDS 3 f1 f2 non_exists_field
|
||||
|
||||
r hpexpire h1 20 FIELDS 1 f1
|
||||
r hpexpire h1 30 FIELDS 1 f2
|
||||
|
||||
# Next command will be propagate with only field 'f2'
|
||||
# because NX condition not met for field 'f1'
|
||||
r hpexpire h1 30 NX FIELDS 2 f1 f2
|
||||
|
||||
# Non exists field should be ignored
|
||||
r hpexpire h1 30 FIELDS 1 non_exists_field
|
||||
r hset h2 f1 v1 f2 v2 f3 v3 f4 v4
|
||||
r hpexpire h2 40 FIELDS 2 f1 non_exists_field
|
||||
@@ -993,11 +959,16 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
# Assert that each TTL-related command are persisted with absolute timestamps in AOF
|
||||
assert_aof_content $aof {
|
||||
{select *}
|
||||
{hset h0 x1 y1 x2 y2}
|
||||
{multi}
|
||||
{hdel h0 x1}
|
||||
{hdel h0 x2}
|
||||
{exec}
|
||||
{hset h1 f1 v1 f2 v2}
|
||||
{hpexpireat h1 * FIELDS 1 f1}
|
||||
{hpexpireat h1 * FIELDS 1 f2}
|
||||
{hset h2 f1 v1 f2 v2 f3 v3 f4 v4}
|
||||
{hpexpireat h2 * FIELDS 2 f1 non_exists_field}
|
||||
{hpexpireat h2 * FIELDS 1 f1}
|
||||
{hpexpireat h2 * FIELDS 1 f2}
|
||||
{hpexpireat h2 * FIELDS 1 f3}
|
||||
{hpexpireat h2 * FIELDS 1 f4}
|
||||
@@ -1067,7 +1038,13 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r hpexpireat h1 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2
|
||||
r hpexpire h1 100000 NX FIELDS 3 f3 f4 f5
|
||||
r hexpire h1 100000 FIELDS 1 f6
|
||||
r hset h5 f1 v1
|
||||
|
||||
# Verify HRANDFIELD deletes expired fields and propagates it
|
||||
r hset h2 f1 v1 f2 v2
|
||||
r hpexpire h2 1 FIELDS 2 f1 f2
|
||||
after 5
|
||||
assert_equal [r hrandfield h4 2] ""
|
||||
after 200
|
||||
|
||||
assert_aof_content $aof {
|
||||
{select *}
|
||||
@@ -1076,7 +1053,10 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
{hpexpireat h1 * FIELDS 1 f2}
|
||||
{hpexpireat h1 * NX FIELDS 3 f3 f4 f5}
|
||||
{hpexpireat h1 * FIELDS 1 f6}
|
||||
{hset h5 f1 v1}
|
||||
{hset h2 f1 v1 f2 v2}
|
||||
{hpexpireat h2 * FIELDS 2 f1 f2}
|
||||
{hdel h2 *}
|
||||
{hdel h2 *}
|
||||
}
|
||||
|
||||
array set keyAndFields1 [dumpAllHashes r]
|
||||
@@ -1099,12 +1079,16 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
r flushall ; # Clean up keyspace to avoid interference by keys from other tests
|
||||
set repl [attach_to_replication_stream]
|
||||
|
||||
# HEXPIRE/HPEXPIRE should be translated into HPEXPIREAT
|
||||
r hset h1 f1 v1
|
||||
r hexpireat h1 [expr [clock seconds]+100] NX FIELDS 1 f1
|
||||
r hset h2 f2 v2
|
||||
r hpexpireat h2 [expr [clock seconds]*1000+100000] NX FIELDS 1 f2
|
||||
r hset h3 f3 v3 f4 v4
|
||||
r hset h3 f3 v3 f4 v4 f5 v5
|
||||
# hpersist does nothing here. Verify it is not propagated.
|
||||
r hpersist h3 FIELDS 1 f5
|
||||
r hexpire h3 100 FIELDS 3 f3 f4 non_exists_field
|
||||
r hpersist h3 FIELDS 1 f3
|
||||
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
@@ -1112,12 +1096,55 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
{hpexpireat h1 * NX FIELDS 1 f1}
|
||||
{hset h2 f2 v2}
|
||||
{hpexpireat h2 * NX FIELDS 1 f2}
|
||||
{hset h3 f3 v3 f4 v4}
|
||||
{hpexpireat h3 * FIELDS 3 f3 f4 non_exists_field}
|
||||
{hset h3 f3 v3 f4 v4 f5 v5}
|
||||
{hpexpireat h3 * FIELDS 2 f3 f4}
|
||||
{hpersist h3 FIELDS 1 f3}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
} {} {needs:repl}
|
||||
|
||||
test {HRANDFIELD delete expired fields and propagate DELs to replica} {
|
||||
r debug set-active-expire 0
|
||||
r flushall
|
||||
set repl [attach_to_replication_stream]
|
||||
|
||||
# HRANDFIELD delete expired fields and propagate MULTI-EXEC DELs. Reply none.
|
||||
r hset h1 f1 v1 f2 v2
|
||||
r hpexpire h1 1 FIELDS 2 f1 f2
|
||||
after 5
|
||||
assert_equal [r hrandfield h1 2] ""
|
||||
|
||||
# HRANDFIELD delete expired field and propagate DEL. Reply non-expired field.
|
||||
r hset h2 f1 v1 f2 v2
|
||||
r hpexpire h2 1 FIELDS 1 f1
|
||||
after 5
|
||||
assert_equal [r hrandfield h2 2] "f2"
|
||||
|
||||
# HRANDFIELD delete expired field and propagate DEL. Reply none.
|
||||
r hset h3 f1 v1
|
||||
r hpexpire h3 1 FIELDS 1 f1
|
||||
after 5
|
||||
assert_equal [r hrandfield h3 2] ""
|
||||
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{hset h1 f1 v1 f2 v2}
|
||||
{hpexpireat h1 * FIELDS 2 f1 f2}
|
||||
{multi}
|
||||
{hdel h1 *}
|
||||
{hdel h1 *}
|
||||
{exec}
|
||||
{hset h2 f1 v1 f2 v2}
|
||||
{hpexpireat h2 * FIELDS 1 f1}
|
||||
{hdel h2 f1}
|
||||
{hset h3 f1 v1}
|
||||
{hpexpireat h3 * FIELDS 1 f1}
|
||||
{hdel h3 f1}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
r debug set-active-expire 1
|
||||
} {OK} {needs:repl}
|
||||
|
||||
# Start another server to test replication of TTLs
|
||||
start_server {tags {needs:repl external:skip}} {
|
||||
# Set the outer layer server as primary
|
||||
@@ -1161,4 +1188,3 @@ start_server {tags {"external:skip needs:debug"}} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user