Figure out what slot to use based on args. Raises a RedisClusterException if there's a missing key and we can't determine what slots to map the command to; or, if the keys don't all map to the same key slot.
(self, *args)
| 1386 | return self.commands_parser.get_keys(redis_conn, *args) |
| 1387 | |
| 1388 | def determine_slot(self, *args) -> Optional[int]: |
| 1389 | """ |
| 1390 | Figure out what slot to use based on args. |
| 1391 | |
| 1392 | Raises a RedisClusterException if there's a missing key and we can't |
| 1393 | determine what slots to map the command to; or, if the keys don't |
| 1394 | all map to the same key slot. |
| 1395 | """ |
| 1396 | command = args[0] |
| 1397 | if self.command_flags.get(command) == SLOT_ID: |
| 1398 | # The command contains the slot ID |
| 1399 | return args[1] |
| 1400 | |
| 1401 | # Get the keys in the command |
| 1402 | |
| 1403 | # CLIENT TRACKING is a special case. |
| 1404 | # It doesn't have any keys, it needs to be sent to the provided nodes |
| 1405 | # By default it will be sent to all nodes. |
| 1406 | if command.upper() == "CLIENT TRACKING": |
| 1407 | return None |
| 1408 | |
| 1409 | # EVAL and EVALSHA are common enough that it's wasteful to go to the |
| 1410 | # redis server to parse the keys. Besides, there is a bug in redis<7.0 |
| 1411 | # where `self._get_command_keys()` fails anyway. So, we special case |
| 1412 | # EVAL/EVALSHA. |
| 1413 | if command.upper() in ("EVAL", "EVALSHA"): |
| 1414 | # command syntax: EVAL "script body" num_keys ... |
| 1415 | if len(args) <= 2: |
| 1416 | raise RedisClusterException(f"Invalid args in command: {args}") |
| 1417 | num_actual_keys = int(args[2]) |
| 1418 | eval_keys = args[3 : 3 + num_actual_keys] |
| 1419 | # if there are 0 keys, that means the script can be run on any node |
| 1420 | # so we can just return a random slot |
| 1421 | if len(eval_keys) == 0: |
| 1422 | return random.randrange(0, REDIS_CLUSTER_HASH_SLOTS) |
| 1423 | keys = eval_keys |
| 1424 | else: |
| 1425 | keys = self._get_command_keys(*args) |
| 1426 | if keys is None or len(keys) == 0: |
| 1427 | # FCALL can call a function with 0 keys, that means the function |
| 1428 | # can be run on any node so we can just return a random slot |
| 1429 | if command.upper() in ("FCALL", "FCALL_RO"): |
| 1430 | return random.randrange(0, REDIS_CLUSTER_HASH_SLOTS) |
| 1431 | raise RedisClusterException( |
| 1432 | "No way to dispatch this command to Redis Cluster. " |
| 1433 | "Missing key.\nYou can execute the command by specifying " |
| 1434 | f"target nodes.\nCommand: {args}" |
| 1435 | ) |
| 1436 | |
| 1437 | # single key command |
| 1438 | if len(keys) == 1: |
| 1439 | return self.keyslot(keys[0]) |
| 1440 | |
| 1441 | # multi-key command; we need to make sure all keys are mapped to |
| 1442 | # the same slot |
| 1443 | slots = {self.keyslot(key) for key in keys} |
| 1444 | if len(slots) != 1: |
| 1445 | raise RedisClusterException( |
no test coverage detected