Skip to content

Prepare for Redis 7.4 RC2 #3303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ permissions:

env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
REDIS_IMAGE: redis/redis-stack-server:7.4.0-rc1
REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc1
REDIS_IMAGE: redis:7.4-rc2
REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc2

jobs:
dependency-audit:
Expand Down
45 changes: 18 additions & 27 deletions redis/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5126,9 +5126,8 @@ def hexpire(
lt: Set expiry only when the new expiry is less than the current one.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
Expand Down Expand Up @@ -5187,9 +5186,8 @@ def hpexpire(
lt: Set expiry only when the new expiry is less than the current one.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
Expand Down Expand Up @@ -5248,9 +5246,8 @@ def hexpireat(
lt: Set expiry only when the new expiry is less than the current one.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
Expand Down Expand Up @@ -5315,9 +5312,8 @@ def hpexpireat(
lt: Set expiry only when the new expiry is less than the current one.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
Expand Down Expand Up @@ -5362,9 +5358,8 @@ def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
expiration time.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expiration time.
- `1` if the expiration time was successfully removed from the field.
"""
Expand All @@ -5382,9 +5377,8 @@ def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
time.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the expiration Unix timestamp in
seconds, if the field has an associated expiration time.
Expand All @@ -5405,9 +5399,8 @@ def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
time.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the expiration Unix timestamp in
milliseconds, if the field has an associated expiration time.
Expand All @@ -5428,9 +5421,8 @@ def httl(self, key: KeyT, *fields: str) -> ResponseT:
fields: A list of fields within the hash for which to get the TTL.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the TTL in seconds if the field has
an associated expiration time.
Expand All @@ -5451,9 +5443,8 @@ def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
fields: A list of fields within the hash for which to get the TTL.

Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the TTL in milliseconds if the field
has an associated expiration time.
Expand Down
8 changes: 4 additions & 4 deletions tests/test_asyncio/test_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async def test_hexpire_conditions(r):
@skip_if_server_version_lt("7.3.240")
async def test_hexpire_nonexistent_key_or_field(r):
await r.delete("test:hash")
assert await r.hexpire("test:hash", 1, "field1") == []
assert await r.hexpire("test:hash", 1, "field1") == [-2]
await r.hset("test:hash", "field1", "value1")
assert await r.hexpire("test:hash", 1, "nonexistent_field") == [-2]

Expand Down Expand Up @@ -105,7 +105,7 @@ async def test_hpexpire_conditions(r):
@skip_if_server_version_lt("7.3.240")
async def test_hpexpire_nonexistent_key_or_field(r):
await r.delete("test:hash")
assert await r.hpexpire("test:hash", 500, "field1") == []
assert await r.hpexpire("test:hash", 500, "field1") == [-2]
await r.hset("test:hash", "field1", "value1")
assert await r.hpexpire("test:hash", 500, "nonexistent_field") == [-2]

Expand Down Expand Up @@ -163,7 +163,7 @@ async def test_hexpireat_conditions(r):
async def test_hexpireat_nonexistent_key_or_field(r):
await r.delete("test:hash")
future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
assert await r.hexpireat("test:hash", future_exp_time, "field1") == []
assert await r.hexpireat("test:hash", future_exp_time, "field1") == [-2]
await r.hset("test:hash", "field1", "value1")
assert await r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]

Expand Down Expand Up @@ -228,7 +228,7 @@ async def test_hpexpireat_nonexistent_key_or_field(r):
future_exp_time = int(
(datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
)
assert await r.hpexpireat("test:hash", future_exp_time, "field1") == []
assert await r.hpexpireat("test:hash", future_exp_time, "field1") == [-2]
await r.hset("test:hash", "field1", "value1")
assert await r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]

Expand Down
2 changes: 1 addition & 1 deletion tests/test_asyncio/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ async def test_mset(decoded_r: redis.Redis):
async def test_clear(decoded_r: redis.Redis):
await decoded_r.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
assert 1 == await decoded_r.json().clear("arr", Path.root_path())
assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], [[[]]])
assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], [])


@pytest.mark.redismod
Expand Down
18 changes: 9 additions & 9 deletions tests/test_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_hexpire_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hexpire_nonexistent_key_or_field(r):
r.delete("test:hash")
assert r.hexpire("test:hash", 1, "field1") == []
assert r.hexpire("test:hash", 1, "field1") == [-2]
r.hset("test:hash", "field1", "value1")
assert r.hexpire("test:hash", 1, "nonexistent_field") == [-2]

Expand Down Expand Up @@ -115,7 +115,7 @@ def test_hpexpire_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hpexpire_nonexistent_key_or_field(r):
r.delete("test:hash")
assert r.hpexpire("test:hash", 500, "field1") == []
assert r.hpexpire("test:hash", 500, "field1") == [-2]
r.hset("test:hash", "field1", "value1")
assert r.hpexpire("test:hash", 500, "nonexistent_field") == [-2]

Expand Down Expand Up @@ -182,7 +182,7 @@ def test_hexpireat_conditions(r):
def test_hexpireat_nonexistent_key_or_field(r):
r.delete("test:hash")
future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
assert r.hexpireat("test:hash", future_exp_time, "field1") == []
assert r.hexpireat("test:hash", future_exp_time, "field1") == [-2]
r.hset("test:hash", "field1", "value1")
assert r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]

Expand Down Expand Up @@ -257,7 +257,7 @@ def test_hpexpireat_nonexistent_key_or_field(r):
future_exp_time = int(
(datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
)
assert r.hpexpireat("test:hash", future_exp_time, "field1") == []
assert r.hpexpireat("test:hash", future_exp_time, "field1") == [-2]
r.hset("test:hash", "field1", "value1")
assert r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]

Expand Down Expand Up @@ -298,7 +298,7 @@ def test_hpersist_multiple_fields(r):
@skip_if_server_version_lt("7.3.240")
def test_hpersist_nonexistent_key(r):
r.delete("test:hash")
assert r.hpersist("test:hash", "field1", "field2", "field3") == []
assert r.hpersist("test:hash", "field1", "field2", "field3") == [-2, -2, -2]


@skip_if_server_version_lt("7.3.240")
Expand All @@ -315,7 +315,7 @@ def test_hexpiretime_multiple_fields_mixed_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hexpiretime_nonexistent_key(r):
r.delete("test:hash")
assert r.hexpiretime("test:hash", "field1", "field2", "field3") == []
assert r.hexpiretime("test:hash", "field1", "field2", "field3") == [-2, -2, -2]


@skip_if_server_version_lt("7.3.240")
Expand All @@ -332,7 +332,7 @@ def test_hpexpiretime_multiple_fields_mixed_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hpexpiretime_nonexistent_key(r):
r.delete("test:hash")
assert r.hpexpiretime("test:hash", "field1", "field2", "field3") == []
assert r.hpexpiretime("test:hash", "field1", "field2", "field3") == [-2, -2, -2]


@skip_if_server_version_lt("7.3.240")
Expand All @@ -349,7 +349,7 @@ def test_httl_multiple_fields_mixed_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_httl_nonexistent_key(r):
r.delete("test:hash")
assert r.httl("test:hash", "field1", "field2", "field3") == []
assert r.httl("test:hash", "field1", "field2", "field3") == [-2, -2, -2]


@skip_if_server_version_lt("7.3.240")
Expand All @@ -366,4 +366,4 @@ def test_hpttl_multiple_fields_mixed_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hpttl_nonexistent_key(r):
r.delete("test:hash")
assert r.hpttl("test:hash", "field1", "field2", "field3") == []
assert r.hpttl("test:hash", "field1", "field2", "field3") == [-2, -2, -2]
2 changes: 1 addition & 1 deletion tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def test_mset(client):
def test_clear(client):
client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
assert 1 == client.json().clear("arr", Path.root_path())
assert_resp_response(client, client.json().get("arr"), [], [[[]]])
assert_resp_response(client, client.json().get("arr"), [], [])


@pytest.mark.redismod
Expand Down
33 changes: 17 additions & 16 deletions tests/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -2369,27 +2369,27 @@ def test_search_missing_fields(client):

with pytest.raises(redis.exceptions.ResponseError) as e:
client.ft().search(
Query("ismissing(@title)").dialect(5).return_field("id").no_content()
Query("ismissing(@title)").dialect(2).return_field("id").no_content()
)
assert "to be defined with 'INDEXMISSING'" in e.value.args[0]

res = client.ft().search(
Query("ismissing(@features)").dialect(5).return_field("id").no_content()
Query("ismissing(@features)").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:2"])

res = client.ft().search(
Query("-ismissing(@features)").dialect(5).return_field("id").no_content()
Query("-ismissing(@features)").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:1", "property:3"])

res = client.ft().search(
Query("ismissing(@description)").dialect(5).return_field("id").no_content()
Query("ismissing(@description)").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:3"])

res = client.ft().search(
Query("-ismissing(@description)").dialect(5).return_field("id").no_content()
Query("-ismissing(@description)").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:1", "property:2"])

Expand Down Expand Up @@ -2438,27 +2438,29 @@ def test_search_empty_fields(client):

with pytest.raises(redis.exceptions.ResponseError) as e:
client.ft().search(
Query("@title:''").dialect(5).return_field("id").no_content()
Query("@title:''").dialect(2).return_field("id").no_content()
)
assert "to be defined with `INDEXEMPTY`" in e.value.args[0]
assert "Use `INDEXEMPTY` in field creation" in e.value.args[0]

res = client.ft().search(
Query("@features:{ }").dialect(5).return_field("id").no_content()
Query("@features:{$empty}").dialect(2).return_field("id").no_content(),
query_params={"empty": ""},
)
_assert_search_result(client, res, ["property:2"])

res = client.ft().search(
Query("-@features:{ }").dialect(5).return_field("id").no_content()
Query("-@features:{$empty}").dialect(2).return_field("id").no_content(),
query_params={"empty": ""},
)
_assert_search_result(client, res, ["property:1", "property:3"])

res = client.ft().search(
Query("@description:''").dialect(5).return_field("id").no_content()
Query("@description:''").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:3"])

res = client.ft().search(
Query("-@description:''").dialect(5).return_field("id").no_content()
Query("-@description:''").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:1", "property:2"])

Expand Down Expand Up @@ -2503,22 +2505,21 @@ def test_special_characters_in_fields(client):
)
_assert_search_result(client, res, ["resource:1"])

# with dialect 5 no need to escape the - even without params
# with double quotes exact match no need to escape the - even without params
res = client.ft().search(
Query("@uuid:{123e4567-e89b-12d3-a456-426614174000}").dialect(5)
Query('@uuid:{"123e4567-e89b-12d3-a456-426614174000"}').dialect(2)
)
_assert_search_result(client, res, ["resource:1"])

# also no need to escape ' with dialect 5
res = client.ft().search(Query("@tags:{new-year's-resolutions}").dialect(5))
res = client.ft().search(Query('@tags:{"new-year\'s-resolutions"}').dialect(2))
_assert_search_result(client, res, ["resource:2"])

# possible to search numeric fields by single value
res = client.ft().search(Query("@rating:[4]").dialect(2))
_assert_search_result(client, res, ["resource:2"])

# some chars still need escaping
res = client.ft().search(Query(r"@tags:{\$btc}").dialect(5))
res = client.ft().search(Query(r"@tags:{\$btc}").dialect(2))
_assert_search_result(client, res, ["resource:1"])


Expand Down
Loading