diff --git a/src/t_hash.c b/src/t_hash.c index 1c5481bdb..8af25c830 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -2811,8 +2811,9 @@ static void httlGenericCommand(client *c, const char *cmd, long long basetime, i long numFields = 0, numFieldsAt = 3; /* Read the hash object */ - if ((hashObj = lookupKeyReadOrReply(c, c->argv[1], shared.emptyarray)) == NULL || - checkType(c, hashObj, OBJ_HASH)) return; + hashObj = lookupKeyRead(c->db, c->argv[1]); + if (checkType(c, hashObj, OBJ_HASH)) + return; if (strcasecmp(c->argv[numFieldsAt-1]->ptr, "FIELDS")) { addReplyError(c, "Mandatory argument FIELDS is missing or not at the right position"); @@ -2830,6 +2831,16 @@ static void httlGenericCommand(client *c, const char *cmd, long long basetime, i return; } + /* Non-existing keys and empty hashes are the same thing. It also means + * fields in the command don't exist in the hash key. */ + if (!hashObj) { + addReplyArrayLen(c, numFields); + for (int i = 0; i < numFields; i++) { + addReplyLongLong(c, HFE_GET_NO_FIELD); + } + return; + } + if (hashObj->encoding == OBJ_ENCODING_LISTPACK) { void *lp = hashObj->ptr; @@ -2934,8 +2945,9 @@ static void hexpireGenericCommand(client *c, const char *cmd, long long basetime robj *hashObj, *keyArg = c->argv[1], *expireArg = c->argv[2]; /* Read the hash object */ - if ((hashObj = lookupKeyWriteOrReply(c, keyArg, shared.emptyarray)) == NULL || - checkType(c, hashObj, OBJ_HASH)) return; + hashObj = lookupKeyWrite(c->db, keyArg); + if (checkType(c, hashObj, OBJ_HASH)) + return; /* Read the expiry time from command */ if (getLongLongFromObjectOrReply(c, expireArg, &expire, NULL) != C_OK) @@ -2988,6 +3000,16 @@ static void hexpireGenericCommand(client *c, const char *cmd, long long basetime return; } + /* Non-existing keys and empty hashes are the same thing. It also means + * fields in the command don't exist in the hash key. */ + if (!hashObj) { + addReplyArrayLen(c, numFields); + for (int i = 0; i < numFields; i++) { + addReplyLongLong(c, HSETEX_NO_FIELD); + } + return; + } + HashTypeSetEx exCtx; hashTypeSetExInit(keyArg, hashObj, c, c->db, cmd, expireSetCond, &exCtx); addReplyArrayLen(c, numFields); @@ -3062,8 +3084,9 @@ void hpersistCommand(client *c) { int changed = 0; /* Used to determine whether to send a notification. */ /* Read the hash object */ - if ((hashObj = lookupKeyWriteOrReply(c, c->argv[1], shared.emptyarray)) == NULL || - checkType(c, hashObj, OBJ_HASH)) return; + hashObj = lookupKeyWrite(c->db, c->argv[1]); + if (checkType(c, hashObj, OBJ_HASH)) + return; if (strcasecmp(c->argv[numFieldsAt-1]->ptr, "FIELDS")) { addReplyError(c, "Mandatory argument FIELDS is missing or not at the right position"); @@ -3081,6 +3104,16 @@ void hpersistCommand(client *c) { return; } + /* Non-existing keys and empty hashes are the same thing. It also means + * fields in the command don't exist in the hash key. */ + if (!hashObj) { + addReplyArrayLen(c, numFields); + for (int i = 0; i < numFields; i++) { + addReplyLongLong(c, HFE_PERSIST_NO_FIELD); + } + return; + } + if (hashObj->encoding == OBJ_ENCODING_LISTPACK) { addReplyArrayLen(c, numFields); for (int i = 0 ; i < numFields ; i++) { diff --git a/tests/unit/type/hash-field-expire.tcl b/tests/unit/type/hash-field-expire.tcl index 58ff4cfc9..f78f2b142 100644 --- a/tests/unit/type/hash-field-expire.tcl +++ b/tests/unit/type/hash-field-expire.tcl @@ -117,15 +117,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" { @@ -305,13 +302,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,7 +314,6 @@ 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" @@ -337,13 +330,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)" { @@ -711,12 +701,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,7 +714,6 @@ 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" }