@@ -289,6 +289,9 @@ class RedisCluster(RedisClusterCommands):
289
289
[
290
290
"FLUSHALL" ,
291
291
"FLUSHDB" ,
292
+ "SCRIPT EXISTS" ,
293
+ "SCRIPT FLUSH" ,
294
+ "SCRIPT LOAD" ,
292
295
],
293
296
PRIMARIES ,
294
297
),
@@ -379,6 +382,24 @@ class RedisCluster(RedisClusterCommands):
379
382
],
380
383
parse_scan_result ,
381
384
),
385
+ list_keys_to_dict (
386
+ [
387
+ "SCRIPT LOAD" ,
388
+ ],
389
+ lambda command , res : list (res .values ()).pop (),
390
+ ),
391
+ list_keys_to_dict (
392
+ [
393
+ "SCRIPT EXISTS" ,
394
+ ],
395
+ lambda command , res : [all (k ) for k in zip (* res .values ())],
396
+ ),
397
+ list_keys_to_dict (
398
+ [
399
+ "SCRIPT FLUSH" ,
400
+ ],
401
+ lambda command , res : all (res .values ()),
402
+ ),
382
403
)
383
404
384
405
ERRORS_ALLOW_RETRY = (
@@ -778,40 +799,70 @@ def _get_command_keys(self, *args):
778
799
"""
779
800
Get the keys in the command. If the command has no keys in in, None is
780
801
returned.
802
+
803
+ NOTE: Due to a bug in redis<7.0, this function does not work properly
804
+ for EVAL or EVALSHA when the `numkeys` arg is 0.
805
+ - issue: https://github.com/redis/redis/issues/9493
806
+ - fix: https://github.com/redis/redis/pull/9733
807
+
808
+ So, don't use this function with EVAL or EVALSHA.
781
809
"""
782
810
redis_conn = self .get_default_node ().redis_connection
783
811
return self .commands_parser .get_keys (redis_conn , * args )
784
812
785
813
def determine_slot (self , * args ):
786
814
"""
787
- Figure out what slot based on command and args
815
+ Figure out what slot to use based on args.
816
+
817
+ Raises a RedisClusterException if there's a missing key and we can't
818
+ determine what slots to map the command to; or, if the keys don't
819
+ all map to the same key slot.
788
820
"""
789
- if self .command_flags .get (args [0 ]) == SLOT_ID :
821
+ command = args [0 ]
822
+ if self .command_flags .get (command ) == SLOT_ID :
790
823
# The command contains the slot ID
791
824
return args [1 ]
792
825
793
826
# Get the keys in the command
794
- keys = self ._get_command_keys (* args )
795
- if keys is None or len (keys ) == 0 :
796
- raise RedisClusterException (
797
- "No way to dispatch this command to Redis Cluster. "
798
- "Missing key.\n You can execute the command by specifying "
799
- f"target nodes.\n Command: { args } "
800
- )
801
827
802
- if len (keys ) > 1 :
803
- # multi-key command, we need to make sure all keys are mapped to
804
- # the same slot
805
- slots = {self .keyslot (key ) for key in keys }
806
- if len (slots ) != 1 :
828
+ # EVAL and EVALSHA are common enough that it's wasteful to go to the
829
+ # redis server to parse the keys. Besides, there is a bug in redis<7.0
830
+ # where `self._get_command_keys()` fails anyway. So, we special case
831
+ # EVAL/EVALSHA.
832
+ if command in ("EVAL" , "EVALSHA" ):
833
+ # command syntax: EVAL "script body" num_keys ...
834
+ if len (args ) <= 2 :
835
+ raise RedisClusterException (f"Invalid args in command: { args } " )
836
+ num_actual_keys = args [2 ]
837
+ eval_keys = args [3 : 3 + num_actual_keys ]
838
+ # if there are 0 keys, that means the script can be run on any node
839
+ # so we can just return a random slot
840
+ if len (eval_keys ) == 0 :
841
+ return random .randrange (0 , REDIS_CLUSTER_HASH_SLOTS )
842
+ keys = eval_keys
843
+ else :
844
+ keys = self ._get_command_keys (* args )
845
+ if keys is None or len (keys ) == 0 :
807
846
raise RedisClusterException (
808
- f"{ args [0 ]} - all keys must map to the same key slot"
847
+ "No way to dispatch this command to Redis Cluster. "
848
+ "Missing key.\n You can execute the command by specifying "
849
+ f"target nodes.\n Command: { args } "
809
850
)
810
- return slots . pop ()
811
- else :
812
- # single key command
851
+
852
+ # single key command
853
+ if len ( keys ) == 1 :
813
854
return self .keyslot (keys [0 ])
814
855
856
+ # multi-key command; we need to make sure all keys are mapped to
857
+ # the same slot
858
+ slots = {self .keyslot (key ) for key in keys }
859
+ if len (slots ) != 1 :
860
+ raise RedisClusterException (
861
+ f"{ command } - all keys must map to the same key slot"
862
+ )
863
+
864
+ return slots .pop ()
865
+
815
866
def reinitialize_caches (self ):
816
867
self .nodes_manager .initialize ()
817
868
0 commit comments