Skip to content

Commit

Permalink
Python: added SINTERCARD command (#1511)
Browse files Browse the repository at this point in the history
* Python: added SINTERCARD command
  • Loading branch information
GilboaAWS authored Jun 4, 2024
1 parent abd1f62 commit 259ad57
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Python: Added LASTSAVE command ([#1509](https://github.com/aws/glide-for-redis/pull/1509))
* Python: Added GETDEL command ([#1514](https://github.com/aws/glide-for-redis/pull/1514))
* Python: Added ZINTER, ZUNION commands ([#1478](https://github.com/aws/glide-for-redis/pull/1478))
* Python: Added SINTERCARD command ([#1511](https://github.com/aws/glide-for-redis/pull/1511))

## 0.4.1 (2024-02-06)

Expand Down
1 change: 1 addition & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ enum RequestType {
BLMove = 169;
GetDel = 170;
SRandMember = 171;
SInterCard = 175;
}

message Command {
Expand Down
3 changes: 3 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ pub enum RequestType {
BLMove = 169,
GetDel = 170,
SRandMember = 171,
SInterCard = 175,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -347,6 +348,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::BLMove => RequestType::BLMove,
ProtobufRequestType::GetDel => RequestType::GetDel,
ProtobufRequestType::SRandMember => RequestType::SRandMember,
ProtobufRequestType::SInterCard => RequestType::SInterCard,
}
}
}
Expand Down Expand Up @@ -518,6 +520,7 @@ impl RequestType {
RequestType::BLMove => Some(cmd("BLMOVE")),
RequestType::GetDel => Some(cmd("GETDEL")),
RequestType::SRandMember => Some(cmd("SRANDMEMBER")),
RequestType::SInterCard => Some(cmd("SINTERCARD")),
}
}
}
35 changes: 35 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,41 @@ async def sinterstore(self, destination: str, keys: List[str]) -> int:
await self._execute_command(RequestType.SInterStore, [destination] + keys),
)

async def sintercard(self, keys: List[str], limit: Optional[int] = None) -> int:
"""
Gets the cardinality of the intersection of all the given sets.
Optionally, a `limit` can be specified to stop the computation early if the intersection cardinality reaches the specified limit.
When in cluster mode, all keys in `keys` must map to the same hash slot.
See https://valkey.io/commands/sintercard for more details.
Args:
keys (List[str]): A list of keys representing the sets to intersect.
limit (Optional[int]): An optional limit to the maximum number of intersecting elements to count.
If specified, the computation stops as soon as the cardinality reaches this limit.
Returns:
int: The number of elements in the resulting set of the intersection.
Examples:
>>> await client.sadd("set1", {"a", "b", "c"})
>>> await client.sadd("set2", {"b", "c", "d"})
>>> await client.sintercard(["set1", "set2"])
2 # The intersection of "set1" and "set2" contains 2 elements: "b" and "c".
>>> await client.sintercard(["set1", "set2"], limit=1)
1 # The computation stops early as the intersection cardinality reaches the limit of 1.
"""
args = [str(len(keys))]
args += keys
if limit is not None:
args += ["LIMIT", str(limit)]
return cast(
int,
await self._execute_command(RequestType.SInterCard, args),
)

async def sdiff(self, keys: List[str]) -> Set[str]:
"""
Computes the difference between the first set and all the successive sets in `keys`.
Expand Down
23 changes: 23 additions & 0 deletions python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,29 @@ def sinterstore(
"""
return self.append_command(RequestType.SInterStore, [destination] + keys)

def sintercard(
self: TTransaction, keys: List[str], limit: Optional[int] = None
) -> TTransaction:
"""
Gets the cardinality of the intersection of all the given sets.
Optionally, a `limit` can be specified to stop the computation early if the intersection cardinality reaches the specified limit.
See https://valkey.io/commands/sintercard for more details.
Args:
keys (List[str]): A list of keys representing the sets to intersect.
limit (Optional[int]): An optional limit to the maximum number of intersecting elements to count.
If specified, the computation stops as soon as the cardinality reaches this limit.
Command response:
int: The number of elements in the resulting set of the intersection.
"""
args = [str(len(keys))]
args += keys
if limit is not None:
args += ["LIMIT", str(limit)]
return self.append_command(RequestType.SInterCard, args)

def sdiff(self: TTransaction, keys: List[str]) -> TTransaction:
"""
Computes the difference between the first set and all the successive sets in `keys`.
Expand Down
48 changes: 48 additions & 0 deletions python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,53 @@ async def test_sinterstore(self, redis_client: TRedisClient):
assert await redis_client.sinterstore(string_key, [key2]) == 1
assert await redis_client.smembers(string_key) == {"c"}

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_sintercard(self, redis_client: TRedisClient):
min_version = "7.0.0"
if await check_if_server_version_lt(redis_client, min_version):
return pytest.mark.skip(reason=f"Redis version required >= {min_version}")

key1 = f"{{testKey}}:{get_random_string(10)}"
key2 = f"{{testKey}}:{get_random_string(10)}"
key3 = f"{{testKey}}:{get_random_string(10)}"
string_key = f"{{testKey}}:{get_random_string(10)}"
non_existing_key = f"{{testKey}}:non_existing_key"
member1_list = ["a", "b", "c"]
member2_list = ["b", "c", "d", "e"]
member3_list = ["b", "c", "f", "g"]

assert await redis_client.sadd(key1, member1_list) == 3
assert await redis_client.sadd(key2, member2_list) == 4
assert await redis_client.sadd(key3, member3_list) == 4

# Basic intersection
assert (
await redis_client.sintercard([key1, key2]) == 2
) # Intersection of key1 and key2 is {"b", "c"}

# Intersection with non-existing key
assert (
await redis_client.sintercard([key1, non_existing_key]) == 0
) # No common elements

# Intersection with a single key
assert await redis_client.sintercard([key1]) == 3 # All elements in key1

# Intersection with limit
assert (
await redis_client.sintercard([key1, key2, key3], limit=1) == 1
) # Stops early at limit

# Invalid argument - key list must not be empty
with pytest.raises(RequestError):
await redis_client.sintercard([])

# Non-set key
assert await redis_client.set(string_key, "value") == "OK"
with pytest.raises(RequestError):
await redis_client.sintercard([string_key])

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_sdiff(self, redis_client: TRedisClient):
Expand Down Expand Up @@ -3752,6 +3799,7 @@ async def test_multi_key_command_returns_cross_slot_error(
redis_client.bzmpop(["abc", "zxy", "lkn"], ScoreFilter.MAX, 0.1),
redis_client.zintercard(["abc", "def"]),
redis_client.zmpop(["abc", "zxy", "lkn"], ScoreFilter.MAX),
redis_client.sintercard(["def", "ghi"]),
]
)

Expand Down
5 changes: 5 additions & 0 deletions python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ async def transaction_test(
args.append({"foo", "bar"})
transaction.sinterstore(key7, [key7, key7])
args.append(2)
if not await check_if_server_version_lt(redis_client, "7.0.0"):
transaction.sintercard([key7, key7])
args.append(2)
transaction.sintercard([key7, key7], 1)
args.append(1)
transaction.sdiff([key7, key7])
args.append(set())
transaction.spop_count(key7, 4)
Expand Down

0 comments on commit 259ad57

Please sign in to comment.