@@ -289,6 +289,9 @@ class RedisCluster(RedisClusterCommands):
289289 [
290290 "FLUSHALL" ,
291291 "FLUSHDB" ,
292+ "SCRIPT EXISTS" ,
293+ "SCRIPT FLUSH" ,
294+ "SCRIPT LOAD" ,
292295 ],
293296 PRIMARIES ,
294297 ),
@@ -379,6 +382,24 @@ class RedisCluster(RedisClusterCommands):
379382 ],
380383 parse_scan_result ,
381384 ),
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+ ),
382403 )
383404
384405 ERRORS_ALLOW_RETRY = (
@@ -778,40 +799,70 @@ def _get_command_keys(self, *args):
778799 """
779800 Get the keys in the command. If the command has no keys in in, None is
780801 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.
781809 """
782810 redis_conn = self .get_default_node ().redis_connection
783811 return self .commands_parser .get_keys (redis_conn , * args )
784812
785813 def determine_slot (self , * args ):
786814 """
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.
788820 """
789- if self .command_flags .get (args [0 ]) == SLOT_ID :
821+ command = args [0 ]
822+ if self .command_flags .get (command ) == SLOT_ID :
790823 # The command contains the slot ID
791824 return args [1 ]
792825
793826 # 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- )
801827
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 :
807846 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 } "
809850 )
810- return slots . pop ()
811- else :
812- # single key command
851+
852+ # single key command
853+ if len ( keys ) == 1 :
813854 return self .keyslot (keys [0 ])
814855
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+
815866 def reinitialize_caches (self ):
816867 self .nodes_manager .initialize ()
817868
0 commit comments