mirror of https://github.com/redis/redis.git
				
				
				
			Reply with array of return codes if the key does not exist for HFE commands (#13343)
Currently, HFE commands reply with empty array if the key does not exist. Though, non-existing key and empty key is the same thing. It means fields given in the command do not exist in the empty key. So, replying with an array of 'no field' error codes (-2) suits better to Redis logic. e.g. Similarly, `hmget` returns array of nulls if the key does not exist. After this PR: ``` 127.0.0.1:6379> hpersist missingkey fields 2 a b 1) (integer) -2 2) (integer) -2 ```
This commit is contained in:
		
							parent
							
								
									871c985919
								
							
						
					
					
						commit
						4aa25d042c
					
				
							
								
								
									
										45
									
								
								src/t_hash.c
								
								
								
								
							
							
						
						
									
										45
									
								
								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++) { | ||||
|  |  | |||
|  | @ -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" | ||||
|         } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue