Skip to content
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

Python: add JSON.DEBUG.FIELDS and JSON.DEBUG.MEMORY commands #2481

Open
wants to merge 2 commits into
base: release-1.2
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Python: Add JSON.TYPE command ([#2409](https://github.com/valkey-io/valkey-glide/pull/2409))
* Python: Add JSON.NUMINCRBY command ([#2448](https://github.com/valkey-io/valkey-glide/pull/2448))
* Python: Add JSON.NUMMULTBY command ([#2458](https://github.com/valkey-io/valkey-glide/pull/2458))
* Python: Add JSON.DEBUG_MEMORY and JSON_DEBUG_FIELDS commands ([#2481](https://github.com/valkey-io/valkey-glide/pull/2481))
* Java: Added `FT.CREATE` ([#2414](https://github.com/valkey-io/valkey-glide/pull/2414))
* Java: Added `FT.INFO` ([#2405](https://github.com/valkey-io/valkey-glide/pull/2441))
* Java: Added `FT.DROPINDEX` ([#2440](https://github.com/valkey-io/valkey-glide/pull/2440))
Expand Down
91 changes: 91 additions & 0 deletions python/python/glide/async_commands/server_modules/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,97 @@ async def clear(
return cast(int, await client.custom_command(args))


async def debug_fields(
client: TGlideClient,
key: TEncodable,
path: Optional[TEncodable] = None,
) -> Optional[Union[int, List[int]]]:
"""
Reports the number of fields of the JSON value at the specified `path` within the JSON document stored at `key`.

Args:
client (TGlideClient): The client to execute the command.
key (TEncodable): The key of the JSON document.
path (Optional[TEncodable]): The path within the JSON document. Defaults to root if not provided.

Returns:
Optional[Union[int, List[int]]]:
For JSONPath (`path` starts with `$`):
Returns an array of integers. indicating the number of fields for each matched `path`.
If `path` doesn't exist, an empty array will be returned.
For legacy path (`path` doesn't start with `$`):
Returns an integer, indicating the number of fields for each matched `path`.
If multiple paths match, number of fields of the first JSON value match is returned.
If `path` doesn't exist, an error is raised.
If `path` is not provided, it reports the total number of fields in the entire JSON document.
If `key` doesn't exist, None is returned.

Examples:
>>> from glide import json
>>> await json.set(client, "k1", "$", '[1, 2.3, "foo", true, null, {}, [], {"a":1, "b":2}, [1,2,3]]')
'OK'
>>> await json.debug_fields(client, "k1", "$[*]")
[1,1,1,1,1,0,0,2,3]

>>> await json.set(client, "k1", "$", '{"firstName":"John","lastName":"Smith","age":27,"weight":135.25,"isAlive":true,"address":{"street":"21 2nd Street","city":"New York","state":"NY","zipcode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"}],"children":[],"spouse":null}')
'OK'
>>> await json.debug_fields(client, "k1")
19
>>> await json.debug_fields(client, "k1", ".address")
4
"""
args = ["JSON.DEBUG", "FIELDS", key]
if path:
args.append(path)

return cast(Optional[Union[int, List[int]]], await client.custom_command(args))


async def debug_memory(
client: TGlideClient,
key: TEncodable,
path: Optional[TEncodable] = None,
) -> Optional[Union[int, List[int]]]:
"""
Reports memory usage in bytes of a JSON value. at the specified `path` within the JSON document stored at `key`.

Args:
client (TGlideClient): The client to execute the command.
key (TEncodable): The key of the JSON document.
path (Optional[TEncodable]): The path within the JSON document. Defaults to root if not provided.

Returns:
Optional[Union[int, List[int]]]:
For JSONPath (`path` starts with `$`):
Returns an array of integers. indicating the memory usage in bytes of a JSON value for each matched `path`.
If `path` doesn't exist, an empty array will be returned.
For legacy path (`path` doesn't start with `$`):
Returns an integer, indicating the number of fields for each matched `path`.
If multiple paths match, number of fields of the first JSON value match is returned.
If `path` doesn't exist, an error is raised.
If `path` is not provided, it reports the total memory usage in bytes in the entire JSON document.
If `key` doesn't exist, None is returned.
Examples:
>>> from glide import json
>>> await json.set(client, "k1", "$", '[1, 2.3, "foo", true, null, {}, [], {"a":1, "b":2}, [1,2,3]]')
'OK'
>>> await json.debug_memory(client, "k1", "$[*]")
[16,16,19,16,16,16,16,66,64]

>>> await json.set(client, "k1", "$", '{"firstName":"John","lastName":"Smith","age":27,"weight":135.25,"isAlive":true,"address":{"street":"21 2nd Street","city":"New York","state":"NY","zipcode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"}],"children":[],"spouse":null}')
'OK'
>>> await json.debug(client, "k1")
472
>>> await json.debug(client, "k1", ".phoneNumbers")
164
"""
args = ["JSON.DEBUG", "MEMORY", key]
if path:
args.append(path)

return cast(Optional[Union[int, List[int]]], await client.custom_command(args))


async def delete(
client: TGlideClient,
key: TEncodable,
Expand Down
215 changes: 215 additions & 0 deletions python/python/tests/tests_server_modules/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,218 @@ async def test_json_nummultby(self, glide_client: TGlideClient):
# Check for Overflow in legacy
with pytest.raises(RequestError):
await json.nummultby(glide_client, key, ".key9", 1.7976931348623157e308)

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_json_debug_fields(self, glide_client: TGlideClient):
key = get_random_string(10)

json_value = {
"key1": 1,
"key2": 3.5,
"key3": {"nested_key": {"key1": [4, 5]}},
"key4": [1, 2, 3],
"key5": 0,
"key6": "hello",
"key7": None,
"key8": {"nested_key": {"key1": 3.5953862697246314e307}},
"key9": 3.5953862697246314e307,
"key10": True,
}

assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK

# Test JSONPath - Fields Subcommand
# Test integer
result = await json.debug_fields(glide_client, key, "$.key1")
assert result == [1]

# Test float
result = await json.debug_fields(glide_client, key, "$.key2")
assert result == [1]

# Test Nested Value
result = await json.debug_fields(glide_client, key, "$.key3.nested_key.key1")
assert result == [2]

# Test Array
result = await json.debug_fields(glide_client, key, "$.key4[2]")
assert result == [1]

# Test String
result = await json.debug_fields(glide_client, key, "$.key6")
assert result == [1]

# Test Null
result = await json.debug_fields(glide_client, key, "$.key7")
assert result == [1]

# Test Bool
result = await json.debug_fields(glide_client, key, "$.key10")
assert result == [1]

# Test all keys
result = await json.debug_fields(glide_client, key, "$[*]")
assert result == [1, 1, 4, 3, 1, 1, 1, 2, 1, 1]

# Test multiple paths
result = await json.debug_fields(glide_client, key, "$..key1")
assert result == [1, 2, 1]

# Test for non-existent path
result = await json.debug_fields(glide_client, key, "$.key11")
assert result == []

# Test for non-existent key
result = await json.debug_fields(glide_client, "non_existent_key", "$.key10")
assert result == None

# Test no provided path
# Total Fields (19) - breakdown:
# Top-Level Fields: 10
# Fields within key3: 4 ($.key3, $.key3.nested_key, $.key3.nested_key.key1, $.key3.nested_key.key1)
# Fields within key4: 3 ($.key4[0], $.key4[1], $.key4[2])
# Fields within key8: 2 ($.key8, $.key8.nested_key)
result = await json.debug_fields(glide_client, key)
assert result == 19

# Test legacy path - Fields Subcommand
# Test integer
result = await json.debug_fields(glide_client, key, ".key1")
assert result == 1

# Test float
result = await json.debug_fields(glide_client, key, ".key2")
assert result == 1

# Test Nested Value
result = await json.debug_fields(glide_client, key, ".key3.nested_key.key1")
assert result == 2

# Test Array
result = await json.debug_fields(glide_client, key, ".key4[2]")
assert result == 1

# Test String
result = await json.debug_fields(glide_client, key, ".key6")
assert result == 1

# Test Null
result = await json.debug_fields(glide_client, key, ".key7")
assert result == 1

# Test Bool
result = await json.debug_fields(glide_client, key, ".key10")
assert result == 1

# Test multiple paths
result = await json.debug_fields(glide_client, key, "..key1")
assert result == 1 # Returns number of fields of the first JSON value

# Test for non-existent path
with pytest.raises(RequestError):
await json.debug_fields(glide_client, key, ".key11")

# Test for non-existent key
result = await json.debug_fields(glide_client, "non_existent_key", ".key10")
assert result == None

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_json_debug_memory(self, glide_client: TGlideClient):
key = get_random_string(10)

json_value = {
"key1": 1,
"key2": 3.5,
"key3": {"nested_key": {"key1": [4, 5]}},
"key4": [1, 2, 3],
"key5": 0,
"key6": "hello",
"key7": None,
"key8": {"nested_key": {"key1": 3.5953862697246314e307}},
"key9": 3.5953862697246314e307,
"key10": True,
}

assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK
# Test JSONPath - Memory Subcommand
# Test integer
result = await json.debug_memory(glide_client, key, "$.key1")
assert result == [16]
# Test float
result = await json.debug_memory(glide_client, key, "$.key2")
assert result == [16]
# Test Nested Value
result = await json.debug_memory(glide_client, key, "$.key3.nested_key.key1[0]")
assert result == [16]
# Test Array
result = await json.debug_memory(glide_client, key, "$.key4[2]")
assert result == [16]
# Test String
result = await json.debug_memory(glide_client, key, "$.key6")
assert result == [16]
# Test Null
result = await json.debug_memory(glide_client, key, "$.key7")
assert result == [16]
# Test Bool
result = await json.debug_memory(glide_client, key, "$.key10")
assert result == [16]
# Test all keys
result = await json.debug_memory(glide_client, key, "$[*]")
assert result == [16, 16, 110, 64, 16, 16, 16, 101, 39, 16]
# Test multiple paths
result = await json.debug_memory(glide_client, key, "$..key1")
assert result == [16, 48, 39]
# Test for non-existent path
result = await json.debug_memory(glide_client, key, "$.key11")
assert result == []
# Test for non-existent key
result = await json.debug_memory(glide_client, "non_existent_key", "$.key10")
assert result == None
# Test no provided path
# Total Memory (504 bytes) - visual breakdown:
# ├── Root Object Overhead (129 bytes)
# └── JSON Elements (374 bytes)
# ├── key1: 16 bytes
# ├── key2: 16 bytes
# ├── key3: 110 bytes
# ├── key4: 64 bytes
# ├── key5: 16 bytes
# ├── key6: 16 bytes
# ├── key7: 16 bytes
# ├── key8: 101 bytes
# └── key9: 39 bytes
result = await json.debug_memory(glide_client, key)
assert result == 504
# Test Legacy Path - Memory Subcommand
# Test integer
result = await json.debug_memory(glide_client, key, ".key1")
assert result == 16
# Test float
result = await json.debug_memory(glide_client, key, ".key2")
assert result == 16
# Test Nested Value
result = await json.debug_memory(glide_client, key, ".key3.nested_key.key1[0]")
assert result == 16
# Test Array
result = await json.debug_memory(glide_client, key, ".key4[2]")
assert result == 16
# Test String
result = await json.debug_memory(glide_client, key, ".key6")
assert result == 16
# Test Null
result = await json.debug_memory(glide_client, key, ".key7")
assert result == 16
# Test Bool
result = await json.debug_memory(glide_client, key, ".key10")
assert result == 16
# Test multiple paths
result = await json.debug_memory(glide_client, key, "..key1")
assert result == 16 # Returns the memory usage of the first JSON value
# Test for non-existent path
with pytest.raises(RequestError):
await json.debug_memory(glide_client, key, ".key11")
# Test for non-existent key
result = await json.debug_memory(glide_client, "non_existent_key", ".key10")
assert result == None
Loading