from utils_json import DEFAULT_MAX_PATH_LIMIT, DEFAULT_MAX_DOCUMENT_SIZE, \ DEFAULT_WIKIPEDIA_COMPACT_PATH, DEFAULT_WIKIPEDIA_PATH, \ JSON_INFO_METRICS_SECTION, JSON_INFO_NAMES from valkey.exceptions import ResponseError, NoPermissionError from valkeytests.conftest import resource_port_tracker import pytest import glob import logging import os import random import struct import json from math import isclose, isnan, isinf, frexp from json_test_case import JsonTestCase logging.basicConfig(level=logging.DEBUG) DATA_ORGANISM = ''' { "heavy_animal" : 200, "plants" : [ { "name" : "Cactus", "height" : 120, "weight" : 90 }, { "name" : "Ghost Plant", "height" : "Unknown", "weight" : "Unknown" }, { "name" : "Redwood", "height" : 4200, "weight" : 50000 } ], "animals" : [ { "name" : "Platypus", "length" : 24, "weight" : 5 }, { "fish" : [ { "name" : "Bass", "length" : 34, "weight" : 5 }, { "name" : "Swordfish", "length" : 177, "weight" : 200 } ] }, { "mammals" : [ { "name" : "Platypus", "length" : 24, "weight" : 5 }, { "name" : "Horse", "height" : 68, "weight" : 660 }, { "primates" : [ { "name" : "Monkey", "height" : 18, "weight" : 30 }, { "name" : "Baboon", "height" : 26, "weight" : 50 }, { "apes" : [ { "name" : "Chimpanzee", "height" : 66, "weight" : 130 }, { "name" : "Gorilla", "height" : 66, "weight" : 400 }, { "name" : "Orangutan", "height" : 70, "weight" : 300 } ] } ] } ] } ] } ''' # valkey keys wikipedia = 'wikipedia' wikipedia2 = 'wikipedia2' wikipedia3 = 'wikipedia3' wikipedia4 = 'wikipedia4' store = 'store' store2 = 'store2' organism = 'organism' organism2 = 'organism2' str_key = 'str_key' k1 = 'k1' k2 = 'k2' k3 = 'k3' k4 = 'k4' k5 = 'k5' k6 = 'k6' k7 = 'k7' k8 = 'k8' k9 = 'k9' k10 = 'k10' k11 = 'k11' k12 = 'k12' k = 'k' nonexistentkey = 'nonexistentkey' nonexistentpath = 'nonexistentpath' input = 'input' input2 = 'input2' arr = 'arr' foo = 'foo' baz = 'baz' # Base Test class containing all core json module tests class TestJsonBasic(JsonTestCase): def setup_data(self): client = self.server.get_new_client() client.config_set( 'json.max-path-limit', DEFAULT_MAX_PATH_LIMIT) client.config_set( 'json.max-document-size', DEFAULT_MAX_DOCUMENT_SIZE) # Need the following line when executing the test against a running Valkey. # Otherwise, data from previous test cases will interfere current test case. client.execute_command("FLUSHDB") # Load wikipedia sample JSONs. We use wikipedia.json as input to create a document key. Then, use # wikipedia_compact.json, which does not have indent/space/newline, to verify correctness of serialization. with open(DEFAULT_WIKIPEDIA_PATH, 'r') as file: self.data_wikipedia = file.read() with open(DEFAULT_WIKIPEDIA_COMPACT_PATH, 'r') as file: self.data_wikipedia_compact = file.read() assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.', self.data_wikipedia) # Create a string key to be used for verifying that JSON.GET should not operate on a non-document key. client.execute_command( 'SET', str_key, '{"firstName":"John","lastName":"Smith"}') def setup(self): super(TestJsonBasic, self).setup() self.setup_data() def test_sanity(self): ''' Test simple SET/GET/MGET/DEL, both legacy and JSONPath syntax. ''' client = self.server.get_new_client() assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', '{"a":"1", "b":"2", "c":"3"}') assert b'OK' == client.execute_command( 'JSON.SET', k2, '.', '[1,2,3,4,5]') assert [b'{"a":"1","b":"2","c":"3"}', b'[1,2,3,4,5]'] == client.execute_command( 'JSON.MGET', k1, k2, '.') assert b'{"a":"1","b":"2","c":"3"}' == client.execute_command( 'JSON.GET', k1) for (key, path, exp) in [ (k1, '.', '{"a":"1","b":"2","c":"3"}'), (k1, '.a', '"1"'), (k2, '.', '[1,2,3,4,5]'), (k2, '[-1]', '5') ]: assert exp == client.execute_command( 'JSON.GET', key, path).decode() # pretty print for (key, fmt, path, exp) in [ (k1, 'SPACE', '.', '{"a": "1","b": "2","c": "3"}'), (k1, 'INDENT', '.', '{ "a":"1", "b":"2", "c":"3"}'), (k2, 'INDENT', '.', '[ 1, 2, 3, 4, 5]'), (k2, 'INDENT', '[-2]', '4') ]: assert exp == client.execute_command( 'JSON.GET', key, fmt, ' ', path).decode() assert [b'[{"a":"1","b":"2","c":"3"}]', b'[[1,2,3,4,5]]'] == client.execute_command( 'JSON.MGET', k1, k2, '$') for (key, path, exp) in [ (k1, '$', '[{"a":"1","b":"2","c":"3"}]'), (k1, '$.*', '["1","2","3"]'), (k2, '$', '[[1,2,3,4,5]]'), (k2, '$.[*]', '[1,2,3,4,5]'), (k2, '$.[-1]', '[5]'), (k2, '$.[0:3]', '[1,2,3]') ]: assert exp == client.execute_command( 'JSON.GET', key, path).decode() # pretty print for (key, fmt, path, exp) in [ (k1, 'SPACE', '$', '[{"a": "1","b": "2","c": "3"}]'), (k1, 'INDENT', '$', '[ { "a":"1", "b":"2", "c":"3" }]'), (k1, 'INDENT', '$.*', '[ "1", "2", "3"]'), (k2, 'INDENT', '$', '[ [ 1, 2, 3, 4, 5 ]]'), (k2, 'INDENT', '$.[0:3]', '[ 1, 2, 3]') ]: assert exp == client.execute_command( 'JSON.GET', key, fmt, ' ', path).decode() assert 1 == client.execute_command('JSON.DEL', k1) assert 1 == client.execute_command('JSON.DEL', k2) assert b'OK' == client.execute_command( 'JSON.SET', k1, '$', '{"a":"1", "b":"2", "c":"3"}') assert b'OK' == client.execute_command( 'JSON.SET', k2, '$', '[1,2,3,4,5]') for (key, val_before, del_path, del_ret, val_after) in [ (k1, '[{"a":"1","b":"2","c":"3"}]', '$.*', 3, '[{}]'), (k2, '[[1,2,3,4,5]]', '$.[*]', 5, '[[]]') ]: assert val_before == client.execute_command( 'JSON.GET', key, '$').decode() assert del_ret == client.execute_command( 'JSON.DEL', key, del_path) assert val_after == client.execute_command( 'JSON.GET', key, '$').decode() def test_parse_invalid_json_string(self): client = self.server.get_new_client() for input_str in ['foo', '{"a"}', '[a]']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, '.firstName', input_str) assert self.error_class.is_syntax_error(str(e.value)) def test_json_set_command_supports_all_datatypes(self): client = self.server.get_new_client() for (path, value) in [('.address.city', '"Boston"'), # string # number ('.age', '30'), ('.foo', '[1,2,3]'), # array # array element access ('.phoneNumbers[0].number', '"1234567"'), # object ('.foo', '{"a":"b"}'), ('.lastName', 'null'), # null ('.isAlive', 'false')]: # boolean assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, path, value) assert value.encode() == client.execute_command( 'JSON.GET', wikipedia, path) def test_json_set_command_root_document(self): client = self.server.get_new_client() # path to the root document is represented as '.' for (key, value) in [(k1, '"Boston"'), # string (k2, '123'), # number (k3, '["Seattle","Boston"]'), # array (k3, '[1,2,3]'), # array (k4, '{"a":"b"}'), # object (k4, '{}'), # empty object (k5, 'null'), # null (k6, 'false')]: # boolean assert b'OK' == client.execute_command( 'JSON.SET', key, '.', value) assert value.encode() == client.execute_command('JSON.GET', key) def test_json_set_command_nx_xx_options(self): client = self.server.get_new_client() for (path, value, cond, exp_set_return, exp_get_return) in [ ('.address.city', '"Boston"', 'NX', None, b'"New York"'), ('.address.city', '"Boston"', 'XX', b'OK', b'"Boston"'), ('.foo', '"bar"', 'NX', b'OK', b'"bar"'), ('.firstName', '"bar"', 'NX', None, b'"John"')]: assert exp_set_return == client.execute_command( 'JSON.SET', wikipedia, path, value, cond) assert exp_get_return == client.execute_command( 'JSON.GET', wikipedia, path) with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', k1, '.' '"value"', 'badword') assert self.error_class.is_syntax_error(str(e.value)) def test_json_set_command_with_error_conditions(self): client = self.server.get_new_client() # A new Valkey key's path must be root with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.SET', foo, '.bar', '"bar"') assert self.error_class.is_syntax_error(str(e.value)) # Option XX means setting the value only if the JSON path exists, a.k.a, updating the value. # According to API, if the value does not exist, the command should return null instead of error. assert None == client.execute_command( 'JSON.SET', k1, '.', '"some value"', 'XX') assert None == client.execute_command( 'JSON.SET', wikipedia, '.foo', '"bar"', 'XX') with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.GET', wikipedia, '.foo') assert self.error_class.is_nonexistent_error(str(e.value)) # Option NX means setting the value only if the JSON path does not exists, a.k.a, inserting the value. # According to API, if the value exists, the command should return null instead of error. assert None == client.execute_command( 'JSON.SET', wikipedia, '.', '"some new value"', 'NX') assert None == client.execute_command( 'JSON.SET', wikipedia, '.firstName', '"Tom"', 'NX') assert b'"John"' == client.execute_command( 'JSON.GET', wikipedia, '.firstName') # syntax error: wrong option with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, '.', '"some new value"', 'NXXX') assert self.error_class.is_syntax_error(str(e.value)) with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, '.', '"some new value"', 'XXXX') assert self.error_class.is_syntax_error(str(e.value)) with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.SET', wikipedia, '.', '"bar"', 'a', 'b') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_set_ancestor_keys_should_not_be_overridden(self): client = self.server.get_new_client() with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, '.firstName.a', '"some new value"') assert self.error_class.is_write_error(str(e.value)) with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, '.age.a', '1') assert self.error_class.is_write_error(str(e.value)) with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, '.address.a.b', '"some new value"') assert self.error_class.is_write_error(str(e.value)) with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, '.address[0]', '"some new value"') assert self.error_class.is_write_error(str(e.value)) def test_json_set_reject_out_of_boundary_array_index(self): client = self.server.get_new_client() with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, 'phoneNumbers[9]', '"123"') assert self.error_class.is_outofboundaries_error(str(e.value)) def test_json_set_insert_value(self): client = self.server.get_new_client() # insert is allowed if and only if the parent node is the last child in the path. for (path, new_val) in [ ('["address"]["z"]', '"z"'), ('.address.z2', '"z2"') ]: client.execute_command( 'JSON.SET', wikipedia, path, new_val) assert new_val.encode() == client.execute_command( 'JSON.GET', wikipedia, path) # if the parent node is not the last child in the path, insertion is not allowed. for (path, new_val) in [ ('["address"]["foo"]["z"]', '"z"'), ('.address.foo.z', '"z"') ]: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', wikipedia, path, new_val) assert self.error_class.is_nonexistent_error(str(e.value)) def test_json_set_negative_array_index(self): client = self.server.get_new_client() new_val = '"1-2-3"' client.execute_command( 'JSON.SET', wikipedia, '.phoneNumbers[-1].number', new_val) assert b'"1-2-3"' == client.execute_command( 'JSON.GET', wikipedia, '.phoneNumbers[-1].number') assert b'["212 555-1234","1-2-3"]' == client.execute_command( 'JSON.GET', wikipedia, '$.phoneNumbers[*].number') def test_json_set_legacy_and_v2path_wildcard(self): client = self.server.get_new_client() data = ''' {"firstName":"John","lastName":"Smith","age":27,"weight":135.17,"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,"groups":{}} ''' client.execute_command( 'JSON.SET', wikipedia2, '.', data) client.execute_command( 'JSON.SET', wikipedia3, '.', data) client.execute_command( 'JSON.SET', wikipedia4, '.', data) for (key, path, new_val, exp, path2, exp2) in [ (wikipedia, '$.address.*', '"1"', b'["1","1","1","1"]', '$.address.*', b'["1","1","1","1"]'), (wikipedia2, '.address.*', '"1"', b'"1"', '$.address.*', b'["1","1","1","1"]'), (wikipedia3, '$.phoneNumbers[*].number', '"1"', b'["1","1"]', '$.phoneNumbers[*].number', b'["1","1"]'), (wikipedia4, '.phoneNumbers[*].number', '"1"', b'"1"', '$.phoneNumbers[*].number', b'["1","1"]') ]: client.execute_command( 'JSON.SET', key, path, new_val) assert exp == client.execute_command( 'JSON.GET', key, path) # verify all values assert exp2 == client.execute_command( 'JSON.GET', key, path2) client.execute_command( 'JSON.SET', k1, '.', '{"a":[], "b":[1], "c":[1,2,3]}') client.execute_command( 'JSON.SET', k2, '.', '{"a":{}, "b":{"a":1}, "c":{"a":1, "b":2, "c":3}}') # NOTE: The expected results below account for the outcome of previous commands. test_cases = [ (k1, '$.a[*]', '1', b'[]', b'{"a":[],"b":[1],"c":[1,2,3]}'), (k1, '$.b[*]', '2', b'[2]', b'{"a":[],"b":[2],"c":[1,2,3]}'), (k1, '$.c[*]', '4', b'[4,4,4]', b'{"a":[],"b":[2],"c":[4,4,4]}'), (k1, '.a[*]', '1', None, None), (k1, '.b[*]', '3', b'3', b'{"a":[],"b":[3],"c":[4,4,4]}'), (k1, '.c[*]', '5', b'5', b'{"a":[],"b":[3],"c":[5,5,5]}'), (k2, '$.a.*', '1', b'[]', b'{"a":{},"b":{"a":1},"c":{"a":1,"b":2,"c":3}}'), (k2, '$.b.*', '2', b'[2]', b'{"a":{},"b":{"a":2},"c":{"a":1,"b":2,"c":3}}'), (k2, '$.c.*', '4', b'[4,4,4]', b'{"a":{},"b":{"a":2},"c":{"a":4,"b":4,"c":4}}'), (k2, '.a.*', '1', None, None), (k2, '.b.*', '3', b'3', b'{"a":{},"b":{"a":3},"c":{"a":4,"b":4,"c":4}}'), (k2, '.c.*', '5', b'5', b'{"a":{},"b":{"a":3},"c":{"a":5,"b":5,"c":5}}') ] for (key, path, new_val, exp, exp2) in test_cases: if exp == None: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', key, path, new_val) client.execute_command( 'JSON.GET', key, path) assert self.error_class.is_nonexistent_error(str(e.value)) else: client.execute_command( 'JSON.SET', key, path, new_val) assert exp == client.execute_command( 'JSON.GET', key, path) # verify entire key assert exp2 == client.execute_command( 'JSON.GET', key, '.') def test_json_get_command_supports_all_datatypes(self): client = self.server.get_new_client() for (path, value) in [('.firstName', '"John"'), # string ('.address.city', '"New York"'), # string ('.spouse', 'null'), # null ('.children', '[]'), # empy array ('.groups', '{}'), # empy object ('.isAlive', 'true'), # boolean ('.age', '27')]: # float number assert value.encode() == client.execute_command( 'JSON.GET', wikipedia, path) for (path, value) in [('["weight"]', '135.17')]: # float number assert value == client.execute_command( 'JSON.GET', wikipedia, path).decode() def test_json_path_syntax_objectkeys(self): client = self.server.get_new_client() for (path, value) in [('["firstName"]', '"John"'), ('address[\'city\']', '"New York"'), ('[\'address\'][\'city\']', '"New York"'), ('["address"]["city"]', '"New York"'), ('["address"][\'city\']', '"New York"'), ('["isAlive"]', 'true'), ('[\'age\']', '27')]: assert value.encode() == client.execute_command( 'JSON.GET', wikipedia, path) for (path, value) in [('["weight"]', '135.17')]: assert value == client.execute_command( 'JSON.GET', wikipedia, path).decode() test_cases = [ '[firstName"]', 'address["city\'', '["address\'][[[["city"]', '[[["address"]]]["city"]', '"["address"][\'city\']', '"[\'address"]["city"]', '[""""address]["city"]', '[address""""]', '[\'address\']]][\'city\']', '["address"]\'[\'"city"]', ] # invalid json path for path in test_cases: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.GET', wikipedia, path) assert self.error_class.is_syntax_error(str(e.value)) def test_json_get_command_floating_point(self): ''' Test special cases of floating point values. ''' client = self.server.get_new_client() for value in ['0', '0.1', '0.3', '1.23456789', '-0.1', '-0.3', '-1.23456789']: assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', value) assert value == client.execute_command( 'JSON.GET', wikipedia, '.foo').decode() # max double and min double: floating points will be returned exactly as is assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', '1.7976e+308') assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.bar', '-1.7976e+308') assert '1.7976e+308' == client.execute_command( 'JSON.GET', wikipedia, '.foo').decode() assert '-1.7976e+308' == client.execute_command( 'JSON.GET', wikipedia, '.bar').decode() # 1.234567890123456789 exceeds the precision of a double but will persist regardless. assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', '1.234567890123456789') assert '1.234567890123456789' == client.execute_command( 'JSON.GET', wikipedia, '.foo').decode() # trailing zeros will no longer be removed. assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', '0.3000000') assert '0.3000000' == client.execute_command( 'JSON.GET', wikipedia, '.foo').decode() def test_json_get_command_with_multiple_paths(self): client = self.server.get_new_client() assert b'{".firstName":"John",".lastName":"Smith"}' == client.execute_command( 'JSON.GET', wikipedia, '.firstName', '.lastName') with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.GET', wikipedia, '.firstName', '.lastName', '.foo', '.bar') assert self.error_class.is_nonexistent_error(str(e.value)) def test_json_get_command_returns_json_with_default_format(self): client = self.server.get_new_client() exp = self.data_wikipedia_compact # default format is compact JSON string - no indent, no space and no newline assert exp == client.execute_command( 'JSON.GET', wikipedia).decode('utf-8') # test NOESCAPE: verify that NOESCAPE is ignored. See the API doc. assert exp == client.execute_command( 'JSON.GET', wikipedia, 'NOESCAPE').decode('utf-8') def test_json_get_command_returns_json_with_custom_format(self): client = self.server.get_new_client() exp = self.data_wikipedia # get the root document with custom indent/space/newline ret = client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', 'SPACE', ' ', 'NEWLINE', '\n').decode('utf-8') assert exp == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', 'SPACE', ' ', 'NEWLINE', '\n').decode('utf-8') # test NOESCAPE: verify that NOESCAPE is ignored. See the API doc. assert exp == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', 'SPACE', ' ', 'NEWLINE', '\n', 'NOESCAPE').decode('utf-8') assert exp == client.execute_command( 'JSON.GET', wikipedia, 'NOESCAPE', 'INDENT', ' ', 'SPACE', ' ', 'NEWLINE', '\n').decode('utf-8') assert exp == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', 'NOESCAPE', 'SPACE', ' ', 'NEWLINE', '\n').decode('utf-8') # get a sub-document with custom indent/space/newline exp_json = '{\n\t"street": "21 2nd Street",\n\t"city": "New York",\n\t"state": "NY",\n\t"zipcode": "10021-3100"\n}' assert exp_json == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', '\t', 'SPACE', ' ', 'NEWLINE', '\n', '.address').decode('utf-8') # INDENT: =*=*, SPACE: --, NEWLINE: \r\n exp_json = '{\r\n=*=*"street":--"21 2nd Street",\r\n=*=*"city":--"New York",\r\n=*=*"state":--"NY",\r\n=*=*"zipcode":--"10021-3100"\r\n}' assert exp_json == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', '=*=*', 'SPACE', '--', 'NEWLINE', '\r\n', '.address').decode('utf-8') # verify that path args do not need to be positioned at the end assert exp == client.execute_command( 'JSON.GET', wikipedia, '.', 'INDENT', ' ', 'SPACE', ' ', 'NEWLINE', '\n').decode('utf-8') exp_json = '"John"' assert exp_json == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', '.firstName', 'SPACE', ' ', 'NEWLINE', '\n').decode('utf-8') exp_json = '{\n ".firstName": "John",\n ".lastName": "Smith"\n}' assert exp_json == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', 'SPACE', ' ', '.firstName', '.lastName', 'NEWLINE', '\n').decode('utf-8') exp_json = '{\n\t"street": "21 2nd Street",\n\t"city": "New York",\n\t"state": "NY",\n\t"zipcode": "10021-3100"\n}' assert exp_json == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', '\t', '.address', 'SPACE', ' ', 'NEWLINE', '\n').decode('utf-8') exp_json = '[\n\t{\n\t\t"street": "21 2nd Street",\n\t\t"city": "New York",\n\t\t"state": "NY",\n\t\t"zipcode": "10021-3100"\n\t}\n]' assert exp_json == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', '\t', '$.address', 'SPACE', ' ', 'NEWLINE', '\n').decode('utf-8') # check that path args can have formatting in between them exp_json = '{\n ".firstName": "John",\n ".lastName": "Smith"\n}' assert exp_json == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', '.firstName', 'SPACE', ' ', '.lastName', 'NEWLINE', '\n').decode('utf-8') assert exp_json == client.execute_command( 'JSON.GET', wikipedia, '.firstName', 'INDENT', ' ', 'SPACE', ' ', 'NEWLINE', '\n', '.lastName').decode('utf-8') def test_json_get_command_returns_json_with_custom_format_error_conditions(self): client = self.server.get_new_client() # NEWLINE is the last arg with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', 'SPACE', ' ', 'NEWLINE') assert self.error_class.is_syntax_error(str(e.value)) # SPACE is the last arg with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.GET', wikipedia, 'INDENT', ' ', 'SPACE') assert self.error_class.is_syntax_error(str(e.value)) # INDENT is the last arg with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.GET', wikipedia, 'NEWLINE', '\n', 'SPACE', ' ', 'INDENT') assert self.error_class.is_syntax_error(str(e.value)) def test_json_get_command_with_error_conditions(self): client = self.server.get_new_client() # if the document key does not exist, the command should return null without throwing an error. assert None == client.execute_command( 'JSON.GET', foo, '.firstName') # if the key is not a document key, the command should throw an error. with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.GET', str_key) assert self.error_class.is_wrongtype_error(str(e.value)) # if the JSON path does not exist, the command should throw an error. with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.GET', wikipedia, '.foo') assert self.error_class.is_nonexistent_error(str(e.value)) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command('JSON.GET') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_number_as_member_name(self): ''' Given a JSON that has numbers as member names, test GET and SET. ''' client = self.server.get_new_client() client.execute_command( 'JSON.SET', k1, '.', '{"1":1, "2":{"3":4}}') client.execute_command('COPY', k1, k2) client.execute_command('COPY', k1, k3) client.execute_command('COPY', k1, k4) client.execute_command('COPY', k1, k5) # test GET for (path, exp) in [ ('["2"]["3"]', '4'), ('[\'2\'][\'3\']', '4'), ('.1', '1'), ('.2.3', '4'), ('$.2.3', '[4]') ]: assert exp == client.execute_command( 'JSON.GET', k1, path).decode() # test SET for (key, path, val, exp_new_val) in [ (k1, '["2"]["3"]', '5', '{"1":1,"2":{"3":5}}'), (k2, '[\'2\'][\'3\']', '5', '{"1":1,"2":{"3":5}}'), (k3, '.2.3', '5', '{"1":1,"2":{"3":5}}'), (k4, '.1', '2', '{"1":2,"2":{"3":4}}'), (k5, '$.2.*', '5', '{"1":1,"2":{"3":5}}') ]: assert b'OK' == client.execute_command( 'JSON.SET', key, path, val) assert exp_new_val == client.execute_command( 'JSON.GET', key, '.').decode() def test_json_get_legacy_and_v2path_wildcard(self): ''' Test two versions of path syntax - V2 JSONPath and the legacy path. A V2 JSONPath must starts with the dollar sign that represents the root element. If a path does not start with the dollar sign, it's a legacy path. For queries, the legacy path always returns a single value, which is the first value if multiple values are selected. If no value is selected, the legacy path returns NONEXISTENT error. The JSONPath always returns an array of values, which could contain zero or one or more values. ''' client = self.server.get_new_client() test_cases = [ ('$.address.*', b'["21 2nd Street","New York","NY","10021-3100"]'), ('$.[\'address\'].*', b'["21 2nd Street","New York","NY","10021-3100"]'), ('.address.*', b'"21 2nd Street"'), ('.["address"].*', b'"21 2nd Street"'), ('$.phoneNumbers.*.type', b'["home","office"]'), ('$.phoneNumbers[*].type', b'["home","office"]'), ('$.["phoneNumbers"][*].["type"]', b'["home","office"]'), ('.phoneNumbers.*.type', b'"home"'), ('.phoneNumbers[*].type', b'"home"'), ('.["phoneNumbers"][*].["type"]', b'"home"'), ('$.[ \'address\' ].*', b'["21 2nd Street","New York","NY","10021-3100"]'), ('.[ "address" ].*', b'"21 2nd Street"'), ('$.[ "phoneNumbers" ][ * ].[ "type" ]', b'["home","office"]'), ] for (path, exp) in test_cases: assert exp == client.execute_command( 'JSON.GET', wikipedia, path) client.execute_command( 'JSON.SET', k1, '.', '{"a":[], "b":[1], "c":[1,2]}') client.execute_command( 'JSON.SET', k2, '.', '{"a":{}, "b": {"a": 1}, "c": {"a": 1, "b": 2}}') client.execute_command( 'JSON.SET', k3, '.', '{"a":[[[1,2],[3,4],[5,6]],[[7,8],[9,10],[11,12]]]}') client.execute_command( 'JSON.SET', k4, '.', '{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h:":1}}}}}}}}') # JSONPath always returns an array of values # Test multiple wildcards for (key, path, exp) in [ (k1, '$.a[*]', b'[]'), (k1, '$.b[*]', b'[1]'), (k1, '$.c[*]', b'[1,2]'), (k2, '$.a.*', b'[]'), (k2, '$.b.*', b'[1]'), (k2, '$.c.*', b'[1,2]'), (k3, '$.a[*]', b'[[[1,2],[3,4],[5,6]],[[7,8],[9,10],[11,12]]]'), (k3, '$.a[*][*]', b'[[1,2],[3,4],[5,6],[7,8],[9,10],[11,12]]'), (k3, '$.a[*][*][1]', b'[2,4,6,8,10,12]'), (k4, '$.a.*.*', b'[{"d":{"e":{"f":{"g":{"h:":1}}}}}]'), (k4, '$.a.*.*.*', b'[{"e":{"f":{"g":{"h:":1}}}}]'), (k4, '$.a.*.c.*', b'[{"e":{"f":{"g":{"h:":1}}}}]'), (k4, '$.a.*.c.*.e.*', b'[{"g":{"h:":1}}]'), (k4, '$.a.*.c.*.e.*.g.*', b'[1]') ]: assert exp == client.execute_command( 'JSON.GET', key, path) # Legacy path always returns a single value, which is the first value. # Test multiple wildcards for (key, path, exp) in [ (k1, '.b[*]', b'1'), (k1, '.c[*]', b'1'), (k2, '.b.*', b'1'), (k2, '.c.*', b'1'), (k3, '.a[*]', b'[[1,2],[3,4],[5,6]]'), (k3, '.a[*][*]', b'[1,2]'), (k3, '.a[*][*][1]', b'2'), (k4, '.a.*.*', b'{"d":{"e":{"f":{"g":{"h:":1}}}}}'), (k4, '.a.*.*.*', b'{"e":{"f":{"g":{"h:":1}}}}'), (k4, '.a.*.c.*', b'{"e":{"f":{"g":{"h:":1}}}}'), (k4, '.a.*.c.*.e.*', b'{"g":{"h:":1}}'), (k4, '.a.*.c.*.e.*.g.*', b'1') ]: assert exp == client.execute_command( 'JSON.GET', key, path) # Legacy path returns non-existent error if no value is selected. for (key, path, exp) in [ (k1, '.a[*]', None), (k2, '.a.*', None) ]: with pytest.raises(ResponseError) as e: assert exp == client.execute_command( 'JSON.GET', key, path) assert self.error_class.is_nonexistent_error(str(e.value)) def test_json_get_negative_array_index_legacypath(self): ''' Test negative array index with the legacy path syntax. ''' client = self.server.get_new_client() # Negative indices do not throw error in Valkey test_cases = [ ('.phoneNumbers[-2].type', '"home"'), ('.phoneNumbers[-1].type', '"office"'), ('.phoneNumbers[ -1 ].type', '"office"'), ('.["phoneNumbers"][-2]["type"]', '"home"'), ('.["phoneNumbers"][-1]["type"]', '"office"'), ('.["phoneNumbers"][ -1]["type" ]', '"office"'), ('.["phoneNumbers"][-1 ][ "type"]', '"office"'), ('.["phoneNumbers"][ -1 ][ "type" ]', '"office"') ] # Out of boundary test cases oob_test_cases = [ '.phoneNumbers[2].type', '.phoneNumbers[10].type', '.phoneNumbers[-3].type', ] # Legacy path always returns a single value for (path, exp) in test_cases: assert exp.encode() == client.execute_command( 'JSON.GET', wikipedia, path) # index out of bounds for path in oob_test_cases: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.GET', wikipedia, path) assert self.error_class.is_outofboundaries_error(str(e.value)) # test using negative index on a non-array value for path in [ '.firstName[-1]', '.age[-1]', '.weight[-1]', '.address[-1]', '.phoneNumbers[0][-1]' ]: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.GET', wikipedia, path) assert self.error_class.is_wrongtype_error(str(e.value)) def test_json_get_negative_array_index_v2path(self): ''' Test negative array index with the V2 JSONPath syntax. ''' client = self.server.get_new_client() test_cases = [ ('$.phoneNumbers[-2].type', '["home"]'), ('$.phoneNumbers[ -2 ].type', '["home"]'), ('$.phoneNumbers[-1].type', '["office"]'), ('$.phoneNumbers[ -1].type', '["office"]'), ('$.phoneNumbers[-1 ].type', '["office"]'), ('$.["phoneNumbers"][-2]["type"]', '["home"]'), ('$.["phoneNumbers"][-1]["type"]', '["office"]'), # index out of bounds ('$.phoneNumbers[2].type', '[]'), ('$.phoneNumbers[10].type', '[]'), # using negative index on a non-array value ('$.firstName[-1]', '[]'), ('$.age[-1]', '[]'), ('$.weight[-1]', '[]'), ('$.weight[ -1 ]', '[]'), ('$.phoneNumbers[0][-1]', '[]'), ('$.phoneNumbers[ 0][ -1 ]', '[]'), ('$.phoneNumbers[ 0 ][ -1 ]', '[]'), ('$.[ "phoneNumbers" ][ -1 ][ "type" ]', '["office"]'), ('$.phoneNumbers[-3].type', '[]'), ('$.phoneNumbers[ -3 ].type', '[]'), ] # JSONPath always returns an array of values for (path, exp) in test_cases: assert exp.encode() == client.execute_command( 'JSON.GET', wikipedia, path) def test_json_get_v2path_array_slice(self): ''' Test negative array slice with the V2 JSONPath syntax. ''' client = self.server.get_new_client() test_cases = [ ('$[0:3]', '[0,1,2]'), ('$[ 0 : 3 ]', '[0,1,2]'), ('$[0:+3]', '[0,1,2]'), ('$[ 0 : +3 ]', '[0,1,2]'), ('$[0:-1]', '[0,1,2,3,4,5,6,7,8]'), ('$[ 0 : -1 ]', '[0,1,2,3,4,5,6,7,8]'), ('$[2:-2]', '[2,3,4,5,6,7]'), ('$[ 2 : -2 ]', '[2,3,4,5,6,7]'), ('$[+2:-2]', '[2,3,4,5,6,7]'), ('$[1:1]', '[]'), ('$[1:2]', '[1]'), ('$[+1:+2]', '[1]'), ('$[1:3]', '[1,2]'), ('$[1:0]', '[]'), ('$[5:]', '[5,6,7,8,9]'), ('$[ 5 : ]', '[5,6,7,8,9]'), ('$[:3]', '[0,1,2]'), ('$[ : 3]', '[0,1,2]'), ('$[:+3]', '[0,1,2]'), ('$[: +3]', '[0,1,2]'), ('$[:6:2]', '[0,2,4]'), ('$[ : 6 : 2]', '[0,2,4]'), ('$[:]', '[0,1,2,3,4,5,6,7,8,9]'), ('$[ : ]', '[0,1,2,3,4,5,6,7,8,9]'), ('$[::]', '[0,1,2,3,4,5,6,7,8,9]'), ('$[ : : ]', '[0,1,2,3,4,5,6,7,8,9]'), ('$[::2]', '[0,2,4,6,8]'), ('$[ : : 2 ]', '[0,2,4,6,8]'), ('$[3::2]', '[3,5,7,9]'), ('$[3 :: 2]', '[3,5,7,9]'), ('$[0::1]', '[0,1,2,3,4,5,6,7,8,9]'), ('$[0:8:2]', '[0,2,4,6]'), ('$[ 0 : 8 : 2 ]', '[0,2,4,6]'), ('$[0:+8:+2]', '[0,2,4,6]'), ('$[0 : +8 : +2]', '[0,2,4,6]'), ('$[6:0:-1]', '[6,5,4,3,2,1]'), ('$[6::-1]', '[]'), ('$[6:0:-2]', '[6,4,2]'), ('$[6::-2]', '[]'), ('$[8:0:-2]', '[8,6,4,2]') ] client.execute_command( 'JSON.SET', k1, '.', '[0,1,2,3,4,5,6,7,8,9]') for (path, exp) in test_cases: assert exp.encode() == client.execute_command('JSON.GET', k1, path) def test_json_get_v2path_array_union(self): ''' Test array union with the V2 JSONPath syntax. ''' client = self.server.get_new_client() test_cases = [ ('$[0,1,2]', '[0,1,2]'), ('$[0, 1, 2]', '[0,1,2]'), ('$[0, 1, 2 ]', '[0,1,2]'), ('$[ 0, 1, 2 ]', '[0,1,2]'), ('$[ 0,1, 2 ]', '[0,1,2]'), ('$[0,1]', '[0,1]'), ('$[-1,-2]', '[9,8]'), ('$[-10,-5,-6]', '[0,5,4]'), ('$[0,1,5,0,1,2]', '[0,1,5,0,1,2]'), ('$[0, 1,5,0, 1,2]', '[0,1,5,0,1,2]'), ('$[ 0, 1, 5, 0, 1, 2 ]', '[0,1,5,0,1,2]'), ('$[ -10 , -5 , -6 ]', '[0,5,4]'), ('$[-10,-9,-8,0,1,2,-1000,1000]', '[0,1,2,0,1,2]'), ] client.execute_command( 'JSON.SET', k1, '.', '[0,1,2,3,4,5,6,7,8,9]') for (path, exp) in test_cases: assert exp.encode() == client.execute_command('JSON.GET', k1, path) client.execute_command( 'JSON.SET', k2, '.', '[{"name":"name0","id":0},{"name":"name1","id":1},{"name":"name2","id":2}]') for (path, exp) in [ ('$[0,2].name', '["name0","name2"]'), ]: assert exp.encode() == client.execute_command('JSON.GET', k2, path) # we do not support mixing of unions and slices, nor do we support extraneous commas for path in [ '$[0,1,2:4]', '$[0:2,3,4]', '$[0,,4]', '$[0,,,4]', '$[,4]', '$[4,]', '$[,4,]', '$[,,4,,]', '$[,]', '$[,0,4]', '$[,0,4,]', '$[0,4,]', ]: with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.GET', k1, path) assert self.error_class.is_syntax_error(str(e.value)) def test_json_get_multipaths_legacy_and_v2path_wildcard(self): ''' Test JSON.GET with multiple paths, legacy path or v2 JSONPath or mixed. ''' client = self.server.get_new_client() client.execute_command( 'JSON.SET', k1, '.', '{"a":[], "b":[1], "c":[1,2]}') client.execute_command( 'JSON.SET', k2, '.', '{"a":{}, "b": {"a": 1}, "c": {"a": 1, "b": 2}}') test_cases = [ (k1, '.b[*]', '.c[*]', '{".b[*]":1,".c[*]":1}'), (k2, '.b.*', '.c.*', '{".b.*":1,".c.*":1}'), ] # if all paths are legacy path, the result conforms to the legacy path version for (key, path1, path2, exp) in test_cases: # 1st path returns 1 value. 2nd path returns the first one of 2 values. assert exp.encode() == client.execute_command( 'JSON.GET', key, path1, path2) # all paths are legacy path, the result conforms to the legacy path version. # If one path returns 0 value, the command should fail with NONEXISTENT error. for (key, path1, path2, exp) in [ (k1, '.a[*]', '.b[*]', None), (k2, '.a.*', '.b.*', None), (k2, '.foo.*', '.c.*', None) ]: with pytest.raises(ResponseError) as e: assert exp == client.execute_command( 'JSON.GET', key, path1, path2) assert self.error_class.is_nonexistent_error(str(e.value)) # if at least one path is JSONPath, the result conforms to the JSONPath version for (key, path1, path2, path3, exp) in [ (k1, '$.a[*]', '$.b[*]', '$.c[*]', '{"$.a[*]":[],"$.b[*]":[1],"$.c[*]":[1,2]}'), (k1, '$.a[*]', '.b[*]', '.c[*]', '{"$.a[*]":[],".b[*]":[1],".c[*]":[1,2]}'), (k1, '.a[*]', '$.b[*]', '.c[*]', '{".a[*]":[],"$.b[*]":[1],".c[*]":[1,2]}'), (k2, '.a.*', '.b.*', '$.c.*', '{".a.*":[],".b.*":[1],"$.c.*":[1,2]}'), (k2, '$.a.*', '$.b.*', '.c.*', '{"$.a.*":[],"$.b.*":[1],".c.*":[1,2]}'), (k2, '.a.*', '$.b.*', '$.c.*', '{".a.*":[],"$.b.*":[1],"$.c.*":[1,2]}'), # 1st path returns 0 value. 2nd path returns 1 value. 3rd path returns 2 values. (k2, '.foo.*', '$.b.*', '$.c.*', '{".foo.*":[],"$.b.*":[1],"$.c.*":[1,2]}') ]: assert exp.encode() == client.execute_command( 'JSON.GET', key, path1, path2, path3) def test_json_mget_command(self): client = self.server.get_new_client() assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', '{"foo":"bar1"}') assert b'OK' == client.execute_command( 'JSON.SET', k2, '.', '{"foo":"bar2"}') assert b'OK' == client.execute_command( 'JSON.SET', k3, '.', '{"foo":"bar3"}') assert [b'"bar1"', b'"bar2"', b'"bar3"'] == client.execute_command( 'JSON.MGET', k1, k2, k3, '.foo') # test the condition of JSON path does not exist assert [None, None, None] == client.execute_command( 'JSON.MGET', k1, k2, k3, '.bar') # test the condition of key does not exist assert [None, None] == client.execute_command( 'JSON.MGET', baz, foo, '.') assert [None, b'"bar2"'] == client.execute_command( 'JSON.MGET', baz, k2, '.foo') # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command('JSON.MGET') assert str(e.value).find('wrong number of arguments') >= 0 assert b'OK' == client.execute_command( 'json.set', k4, '.', '[2,5,{"level0":[null,true,{"level0_1":[3,false]}],"level1":{"level1_0":33}}]') assert b'OK' == client.execute_command( 'json.set', k5, '.', '[4,5,{"level0":[null,false,{"level0_1":[null,false]}],"level1":{"level1_0":[12,13]}}]') assert b'OK' == client.execute_command( 'json.set', k6, '.', '[2,5,{"level0":[true,20,{"level0_1":[3,true]}],"level1":{"level1_0":33}}]') for (path, exp) in [ ('$..level0_1', [b"[[3,false]]", b"[[null,false]]", b"[[3,true]]"]), ('$.[2].level1', [b'[{"level1_0":33}]', b'[{"level1_0":[12,13]}]', b'[{"level1_0":33}]']), ('$.[2].level1.level1_0[2]', [b"[]", b"[]", b"[]"]), ('$.[2].level1.level1_0[1]', [b"[]", b"[13]", b"[]"]) ]: assert [exp[0], exp[1], exp[2]] == client.execute_command( 'JSON.MGET', k4, k5, k6, path) def test_json_key_declaration(self): client = self.server.get_new_client() cmd_need_val = set( 'SET NUMMULTBY NUMINCRBY ARRAPPEND ARRINDEX STRAPPEND RESP'.split()) # These commands should only get the single key for cmd in ('DEL', 'GET', 'SET', 'TYPE', 'NUMINCRBY', 'NUMMULTBY', 'TOGGLE', 'STRAPPEND', 'STRLEN', 'ARRAPPEND', 'ARRINDEX', 'ARRLEN', 'ARRPOP', 'CLEAR', 'OBJKEYS', 'OBJLEN', 'FORGET', 'RESP'): if cmd not in cmd_need_val: assert [k1] == client.execute_command( 'COMMAND GETKEYS', f'JSON.{cmd}', k1) else: # Dummy value in command assert [k1] == client.execute_command( 'COMMAND GETKEYS', f'JSON.{cmd}', k1, '.', '5') # ARRINSERT requires index assert ['k1'] == client.execute_command( 'COMMAND GETKEYS', 'JSON.ARRINSERT', 'k1', '.', 0, '5') # ARRINSERT requires start end assert ['k1'] == client.execute_command( 'COMMAND GETKEYS', 'JSON.ARRTRIM', 'k1', '.', 0, 5) debug_subcmd = set('MEMORY DEPTH'.split()) for cmd in debug_subcmd: assert [k1] == client.execute_command( 'COMMAND GETKEYS', 'JSON.DEBUG', cmd, k1) # JSON.MGET is the only multi-key command, so make sure it returns the right set of keys assert [k1, k2, k3] == client.execute_command( 'COMMAND GETKEYS', 'JSON.MGET', k1, k2, k3, '.') def __json_del_or_forget__(self, cmd): client = self.server.get_new_client() # delete an element for path in ['.spouse', '.phoneNumbers']: assert 1 == client.execute_command( cmd, wikipedia, path) with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.GET', wikipedia, path) assert self.error_class.is_nonexistent_error(str(e.value)) # delete a doc: path not provided assert 1 == client.execute_command(cmd, wikipedia) assert 0 == client.execute_command('EXISTS', wikipedia) # delete a doc: path arg provided client.execute_command('json.set', k1, '.', '1') assert 1 == client.execute_command(cmd, k1, '.') assert 0 == client.execute_command('EXISTS', k1) # return should be 0 if the document key does not exist assert 0 == client.execute_command( cmd, foo, '.firstName') # return should be 0 if the path does not exist assert 0 == client.execute_command( cmd, wikipedia, '.foo') # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( cmd, wikipedia, '.children', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_del_command(self): self.__json_del_or_forget__('JSON.DEL') def test_json_forget_command(self): self.__json_del_or_forget__('JSON.FORGET') def test_json_del_command_v2path_wildcard(self): client = self.server.get_new_client() client.execute_command( 'json.set', k1, '.', '{"x": {}, "y": {"a":"a"}, "z": {"a":"", "b":"b"}}') client.execute_command( 'json.set', k2, '.', '[0,1,2,3,4,5,6,7,8,9]') client.execute_command( 'json.set', k3, '.', '[0,1,2,3,4,5,6,7,8,9]') client.execute_command( 'json.set', k4, '.', '[0,1,2,3,4,5,6,7,8,9]') client.execute_command( 'json.set', k5, '.', '[0,1,2,3,4,5,6,7,8,9]') # NOTE: The expected values below account for the outcome of previous commands. for (key, path, exp_ret, exp_val) in [ (k1, '$.z.*', 2, '{"x":{},"y":{"a":"a"},"z":{}}'), (k1, '$.*', 3, '{}'), (k2, '$.[3:6]', 3, '[0,1,2,6,7,8,9]'), (k2, '$.*', 7, '[]'), ]: assert exp_ret == client.execute_command( 'JSON.DEL', key, path) assert exp_val == client.execute_command( 'JSON.GET', key).decode() # delete whole doc for key in [k1, k2]: assert 1 == client.execute_command( 'JSON.DEL', key, '$') assert 0 == client.execute_command('EXISTS', key) # delete with wildcard, slice, and union assert 10 == client.execute_command( 'JSON.DEL', k3, '$[*]') assert b'[]' == client.execute_command('JSON.GET', k3) assert 4 == client.execute_command( 'JSON.DEL', k4, '$[3:7]') assert b'[0,1,2,7,8,9]' == client.execute_command( 'JSON.GET', k4) assert 4 == client.execute_command( 'JSON.DEL', k5, '$[1,5,7,8]') assert b'[0,2,3,4,6,9]' == client.execute_command( 'JSON.GET', k5) assert b'OK' == client.execute_command( 'json.set', k1, '.', '[2,5,{"level0":[null,true,{"level0_1":[3,false]}],"level1":{"level1_0":33}}]') for (key, path, exp_ret, exp_val) in [ (k1, '$[2].level0..level0_1[1]', 1, '[2,5,{"level0":[null,true,{"level0_1":[3]}],"level1":{"level1_0":33}}]'), (k1, '$[2].level0..level0_1', 1, '[2,5,{"level0":[null,true,{}],"level1":{"level1_0":33}}]'), (k1, '$..level1.level1_0', 1, '[2,5,{"level0":[null,true,{}],"level1":{}}]'), (k1, '$..level1', 1, '[2,5,{"level0":[null,true,{}]}]'), (k1, '.*', 3, '[]'), ]: assert exp_ret == client.execute_command( 'JSON.DEL', key, path) assert exp_val == client.execute_command( 'JSON.GET', key).decode() if path != '.*': assert '[]' == client.execute_command( 'JSON.GET', key, path).decode() else: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.GET', key, path).decode() assert self.error_class.is_nonexistent_error(str(e.value)) def test_json_unicode_is_supported(self): client = self.server.get_new_client() for unicode_str in [ '"Eat, drink, 愛"', '"hyvää-élève"' ]: utf8 = unicode_str.encode('utf-8') assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', unicode_str) assert utf8 == client.execute_command( 'JSON.GET', k1, 'NOESCAPE', '.') assert 1 == client.execute_command('JSON.DEL', k1) assert 0 == client.execute_command('EXISTS', k1) def test_nonASCII_in_jsonpath(self): client = self.server.get_new_client() client.execute_command( 'JSON.SET', k1, '.', '{"a": {"愛": "love", "b": "b"}}') client.execute_command( 'JSON.SET', k2, '.', '{"愛": [1,2,3]}') for (key, path1, path2, exp) in [ (k1, '.a.愛', None, b'"love"'), (k1, '.a.愛', '.a.b', b'{".a.\xe6\x84\x9b":"love",".a.b":"b"}'), (k2, '.愛[1]', None, b'2'), (k2, '$.愛[*]', None, b'[1,2,3]') ]: if path2 is None: assert exp == client.execute_command( 'JSON.GET', key, path1) else: # Valkey behavior is weird and returns dictionaries in a non-deterministic order assert exp == client.execute_command( 'JSON.GET', key, path1, path2) def test_json_number_scanner(self): '''Test that numeric conversion gets the right types around various edge cases''' client = self.server.get_new_client() maxpos = (1 << 63) - 1 for v in [ (maxpos, b'integer'), (-maxpos, b'integer'), (-maxpos-1, b'integer')]: client.execute_command( 'JSON.SET', k1, '.', str(v[0])) assert v[1] == client.execute_command( 'JSON.TYPE', k1, '.'), "Value is " + str(v[0]) for v in [ (maxpos+1, b'number'), (-maxpos-2, b'number') ]: client.execute_command( 'JSON.SET', k1, '.', str(v[0])) assert v[1] == client.execute_command( 'JSON.TYPE', k1, '.'), "Value is " + str(v[0]) def test_json_toggle(self): client = self.server.get_new_client() # Toggle back and forth assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foobool', 'false') assert b'false' == client.execute_command( 'JSON.GET', wikipedia, '.foobool') assert b'true' == client.execute_command( 'JSON.TOGGLE', wikipedia, '.foobool') assert b'true' == client.execute_command( 'JSON.GET', wikipedia, '.foobool') assert b'false' == client.execute_command( 'JSON.TOGGLE', wikipedia, '.foobool') assert b'false' == client.execute_command( 'JSON.GET', wikipedia, '.foobool') # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.TOGGLE', wikipedia, '.foobool', 'extra') assert self.error_class.is_wrong_number_of_arguments_error( str(e.value)) # Wrong types assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foonum', '55') with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.TOGGLE', wikipedia, '.foonum') assert self.error_class.is_wrongtype_error(str(e.value)) assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foostr', '"ok"') with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.TOGGLE', wikipedia, '.foostr') assert self.error_class.is_wrongtype_error(str(e.value)) def test_json_toggle_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '{"a":true, "b":false, "c":1, "d":null, "e":"foo", "f":[], "g":{}}') assert b'OK' == client.execute_command('JSON.SET', k2, '.', '[true, false, 1, null, "foo", [], {}]') for (key, path, exp, exp_new_val) in [ (k1, '$.*', [0, 1, None, None, None, None, None], '{"a":false,"b":true,"c":1,"d":null,"e":"foo","f":[],"g":{}}'), (k1, '$.*', [1, 0, None, None, None, None, None], '{"a":true,"b":false,"c":1,"d":null,"e":"foo","f":[],"g":{}}'), (k2, '$[*]', [0, 1, None, None, None, None, None], '[false,true,1,null,"foo",[],{}]'), (k2, '$[*]', [1, 0, None, None, None, None, None], '[true,false,1,null,"foo",[],{}]') ]: assert exp == client.execute_command( 'JSON.TOGGLE', key, path) assert exp_new_val == client.execute_command( 'JSON.GET', key, '.').decode() def test_json_numincrby(self): client = self.server.get_new_client() assert b'28' == client.execute_command( 'JSON.NUMINCRBY', wikipedia, '.age', '1') assert b'38' == client.execute_command( 'JSON.NUMINCRBY', wikipedia, '.age', '10') assert b'33' == client.execute_command( 'JSON.NUMINCRBY', wikipedia, '.age', '-5') assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', '1') assert b'1.5' == client.execute_command( 'JSON.NUMINCRBY', wikipedia, '.foo', '0.5') assert b'2' == client.execute_command( 'JSON.NUMINCRBY', wikipedia, '.foo', '0.5') # error condition: document key does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.NUMINCRBY', foo, '.age', '2') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.NUMINCRBY', wikipedia, '.bar', '2') assert self.error_class.is_nonexistent_error(str(e.value)) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.NUMINCRBY', wikipedia, '.age', '1', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_nummultby(self): client = self.server.get_new_client() assert b'270' == client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.age', '10') assert b'2700' == client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.age', '10') assert b'27' == client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.age', '0.01') assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', '1') assert b'0.5' == client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.foo', '0.5') assert b'0.25' == client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.foo', '0.5') assert b'1' == client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.foo', '4') # error condition: document key does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.NUMMULTBY', foo, '.age', '2') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.bar', '2') assert self.error_class.is_nonexistent_error(str(e.value)) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.age', '2', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_simple_operations_remove_double_styling(self): # Multiplying by one or adding zero will now remove styling # and really change output for negative exponents due to decimals client = self.server.get_new_client() data = [ ('2.50000', '2.5'), ('2e30', '2e+30'), ('2E+30', '2e+30'), ('2E30', '2e+30'), ('2E-30', '2.0000000000000002e-30'), ('2e5', '200000'), ('-2.50000', '-2.5'), ('-2e30', '-2e+30'), ('-2E+30', '-2e+30'), ('-2E30', '-2e+30'), ('-2E-30', '-2.0000000000000002e-30'), ('-2e5', '-200000'), ] for (initial, unstyled) in data: assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', initial) assert initial == client.execute_command( 'JSON.GET', wikipedia, '.foo').decode() client.execute_command( 'JSON.NUMMULTBY', wikipedia, '.foo', '1') assert unstyled == client.execute_command( 'JSON.GET', wikipedia, '.foo').decode() for (initial, unstyled) in data: assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', initial) assert initial == client.execute_command( 'JSON.GET', wikipedia, '.foo').decode() client.execute_command( 'JSON.NUMINCRBY', wikipedia, '.foo', '0') assert unstyled == client.execute_command( 'JSON.GET', wikipedia, '.foo').decode() def test_json_double_operations_wrongtype(self): client = self.server.get_new_client() # None of these will these will succeed because the doubles are not valid or the field is not a double for (cmd, key, field, arg) in [ ('JSON.NUMMULTBY', wikipedia, '.age', '"2.0"'), ('JSON.NUMMULTBY', wikipedia, '.age', '2.'), ('JSON.NUMINCRBY', wikipedia, '.age', '2.0.'), ('JSON.NUMMULTBY', wikipedia, '.age', '-2.'), ('JSON.NUMINCRBY', wikipedia, '.age', '-2.0.'), ('JSON.NUMMULTBY', wikipedia, '.age', '+2.'), ('JSON.NUMINCRBY', wikipedia, '.age', '+2.0.'), ('JSON.NUMINCRBY', wikipedia, '.age', '.2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', 'a2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', '-a2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', '+a2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', 'e2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', '-e2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', '+e2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', 'e+2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', '-e-2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', '+E+2.0'), ('JSON.NUMINCRBY', wikipedia, '.age', '2.0e'), ('JSON.NUMINCRBY', wikipedia, '.age', '2.0eq'), ('JSON.NUMINCRBY', wikipedia, '.age', '2.0e3q'), ('JSON.NUMINCRBY', wikipedia, '.age', '2.0e+'), ('JSON.NUMINCRBY', wikipedia, '.age', '2.0e+a'), ('JSON.NUMINCRBY', wikipedia, '.age', '2.0e+41a'), ('JSON.NUMINCRBY', wikipedia, '.firstName', '2'), ]: with pytest.raises(ResponseError) as e: assert None == client.execute_command( cmd, key, field, arg) assert self.error_class.is_wrongtype_error(str(e.value)) def test_json_double_consistency(self): ''' Test that double values remain consistent when going through JSON Engine. This tests a tolerance of 2^-50 for a decent number of iterations, but is not enough to guarantee that level of presicion to our customers. Also verify that regular and pretty print double values have the same output. ''' client = self.server.get_new_client() # arbitrarily generated hex to double to string, to send to json random.seed(1234) data = [] for i in range(24000): potential_double = struct.unpack( '= 0 def test_json_strappend_command(self): client = self.server.get_new_client() for (val, new_len, new_val) in [ ('"son"', 7, '"Johnson"'), ('" Junior"', 14, '"Johnson Junior"'), ('" is"', 17, '"Johnson Junior is"'), ('" my"', 20, '"Johnson Junior is my"'), ('" friend."', 28, '"Johnson Junior is my friend."'), ('""', 28, '"Johnson Junior is my friend."') ]: assert new_len == client.execute_command( 'JSON.STRAPPEND', wikipedia, '.firstName', val) assert new_val == client.execute_command( 'JSON.GET', wikipedia, '.firstName').decode('utf-8') # edge case: appending to an empty string assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foo', '""') client.execute_command( 'JSON.STRAPPEND', wikipedia, '.foo', '"abc"') assert b'"abc"' == client.execute_command( 'JSON.GET', wikipedia, '.foo') # error condition: document key does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.STRAPPEND', foo, '.firstName', '"abc"') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.STRAPPEND', wikipedia, '.bar', '"abc"') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: attempt to append a non-string value for val in ['123', 'true', 'false', 'null', '{}', '[]']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.STRAPPEND', wikipedia, '.firstName', val) assert self.error_class.is_wrongtype_error(str(e.value)) # error condition: attempt to append to a non-string element for path in ['.address', '.groups', '.age', '.isAlive', '.spouse']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.STRAPPEND', wikipedia, path, '"12"') assert self.error_class.is_wrongtype_error(str(e.value)) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.STRAPPEND', wikipedia, '.firstName', '"abc"', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_strlen_command_legacy_and_jsonpath_wildcard(self): client = self.server.get_new_client() client.execute_command('JSON.SET', k1, '.', '{"a":{"a":"a"}, "b":{"a":""}, "c":{"a":"a", "b":"bb"}, "d":{"a":1, "b":"b", "c":3}}') for (key, path, exp) in [ (k1, '$.a.a', [1]), (k1, '$.a.*', [1]), (k1, '$.b.a', [0]), (k1, '$.b.*', [0]), (k1, '$.c.*', [1, 2]), (k1, '$.c.b', [2]), (k1, '$.d.*', [None, 1, None]), (k1, '.a.a', 1), (k1, '.a.*', 1), (k1, '.b.a', 0), (k1, '.b.*', 0), (k1, '.c.*', 1), (k1, '.c.b', 2), ]: assert exp == client.execute_command( 'JSON.STRLEN', key, path) assert 1 == client.execute_command( 'JSON.STRLEN', k1, '.d.*') def test_json_strappend_command_legacy_and_jsonpath_wildcard(self): client = self.server.get_new_client() client.execute_command('JSON.SET', k1, '.', '{"a":{"a":"a"}, "b":{"a":""}, "c":{"a":"a", "b":"bb"}, "d":{"a":1, "b":"b", "c":3}}') # NOTE: The expected result below accounts for the outcome of previous commands. for (key, path, append, exp_ret, exp_new_str, exp_whole_json) in [ (k1, '$.a.a', '"x"', [ 2], '["ax"]', '{"a":{"a":"ax"},"b":{"a":""},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '$.a.a', '""', [ 2], '["ax"]', '{"a":{"a":"ax"},"b":{"a":""},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '$.a.*', '"yz"', [4], '["axyz"]', '{"a":{"a":"axyz"},"b":{"a":""},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '.a.a', '"a"', 5, '"axyza"', '{"a":{"a":"axyza"},"b":{"a":""},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '.a.*', '"a"', 6, '"axyzaa"', '{"a":{"a":"axyzaa"},"b":{"a":""},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '$.b.a', '"a"', [ 1], '["a"]', '{"a":{"a":"axyzaa"},"b":{"a":"a"},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '$.b.*', '""', [1], '["a"]', '{"a":{"a":"axyzaa"},"b":{"a":"a"},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '$.b.*', '"a"', [2], '["aa"]', '{"a":{"a":"axyzaa"},"b":{"a":"aa"},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '.b.a', '"a"', 3, '"aaa"', '{"a":{"a":"axyzaa"},"b":{"a":"aaa"},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '.b.*', '"a"', 4, '"aaaa"', '{"a":{"a":"axyzaa"},"b":{"a":"aaaa"},"c":{"a":"a","b":"bb"},"d":{"a":1,"b":"b","c":3}}'), (k1, '$.c.*', '"a"', [2, 3], '["aa","bba"]', '{"a":{"a":"axyzaa"},"b":{"a":"aaaa"},"c":{"a":"aa","b":"bba"},"d":{"a":1,"b":"b","c":3}}'), (k1, '$.c.b', '"a"', [ 4], '["bbaa"]', '{"a":{"a":"axyzaa"},"b":{"a":"aaaa"},"c":{"a":"aa","b":"bbaa"},"d":{"a":1,"b":"b","c":3}}'), # The following strappend changes value at $.c value to {"a":"aaa", "b":"bbaaa"}. # strappend returns length of the last updated value, which is 5, # while 'json.get .c.*' returns the first selected element, which is "aaa". (k1, '.c.*', '"a"', 5, '"aaa"', '{"a":{"a":"axyzaa"},"b":{"a":"aaaa"},"c":{"a":"aaa","b":"bbaaa"},"d":{"a":1,"b":"b","c":3}}'), (k1, '.c.b', '"a"', 6, '"bbaaaa"', '{"a":{"a":"axyzaa"},"b":{"a":"aaaa"},"c":{"a":"aaa","b":"bbaaaa"},"d":{"a":1,"b":"b","c":3}}'), (k1, '$.d.*', '"a"', [None, 2, None], '[1,"ba",3]', '{"a":{"a":"axyzaa"},"b":{"a":"aaaa"},"c":{"a":"aaa","b":"bbaaaa"},"d":{"a":1,"b":"ba","c":3}}'), # strappend returns length of the last updated value, which is 3 ("baa"), # while 'json.get .d.*' returns the first selected element, which is 1. (k1, '.d.*', '"a"', 3, '1', '{"a":{"a":"axyzaa"},"b":{"a":"aaaa"},"c":{"a":"aaa","b":"bbaaaa"},"d":{"a":1,"b":"baa","c":3}}') ]: assert exp_ret == client.execute_command( 'JSON.STRAPPEND', key, path, append) assert exp_new_str == client.execute_command( 'JSON.GET', key, path).decode() def test_json_objectlen_command(self): client = self.server.get_new_client() assert 4 == client.execute_command( 'JSON.OBJLEN', wikipedia, '.address') # edge case: empty object assert 0 == client.execute_command( 'JSON.OBJLEN', wikipedia, '.groups') # return should be null if document key does not exist assert None == client.execute_command( 'JSON.OBJLEN', foo, '.address') # return error if path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.OBJLEN', wikipedia, '.foo') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: the element is not an object for path in ['.children', '.phoneNumbers', '.age', '.weight', '.isAlive', '.spouse', '.firstName']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.OBJLEN', wikipedia, path) assert self.error_class.is_wrongtype_error(str(e.value)) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.OBJLEN', wikipedia, '.', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_objlen_command_jsonpath_wildcard(self): client = self.server.get_new_client() client.execute_command('JSON.SET', k1, '.', '{"a":{}, "c":{"a":"a", "b":"bb"}, "d":{"a":1, "b":"b", "c":{"a":3,"b":4}}, "e":1}') test_cases = [ (k1, '$.a', [0]), (k1, '$.a.*', []), (k1, '.a', 0), (k1, '$.c', [2]), (k1, '$.c.*', [None, None]), (k1, '.c', 2), (k1, '$.d', [3]), (k1, '$.d.*', [None, None, 2]), (k1, '.d', 3), (k1, '$.*', [0, 2, 3, None]), (k1, '.*', 0), (k1, '.d.*', 2), ] for (key, path, exp) in test_cases: assert exp == client.execute_command( 'JSON.OBJLEN', key, path) with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.OBJLEN', k1, '.a.*') assert self.error_class.is_nonexistent_error(str(e.value)) for (key, path) in [ (k1, '.c.*'), (k1, '.e') ]: with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.OBJLEN', key, path) assert self.error_class.is_wrongtype_error(str(e.value)) def test_json_objectkeys_command(self): client = self.server.get_new_client() obj_keys = [b'street', b'city', b'state', b'zipcode'] assert obj_keys == client.execute_command( 'JSON.OBJKEYS', wikipedia, '.address') # edge case: empty object assert [] == client.execute_command( 'JSON.OBJKEYS', wikipedia, '.groups') # return should be null if document key does not exist assert None == client.execute_command( 'JSON.OBJKEYS', foo, '.address') # return should be null if path does not exist assert None == client.execute_command( 'JSON.OBJKEYS', wikipedia, '.foo') # error condition: the element is not an object for path in ['.children', '.phoneNumbers', '.age', '.weight', '.isAlive', '.spouse', '.firstName']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.OBJKEYS', wikipedia, path) assert self.error_class.is_wrongtype_error(str(e.value)) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.OBJKEYS', wikipedia, '.', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_objkeys_command_jsonpath_wildcard(self): client = self.server.get_new_client() client.execute_command('JSON.SET', k1, '.', '{"a":{}, "c":{"a":"a", "b":"bb"}, "d":{"a":1, "b":"b", "c":{"a":3,"b":4}}, "e":1}') test_cases = [ (k1, '$.a', [[]]), (k1, '$.a.*', []), (k1, '.a', []), (k1, '$.c', [[b"a", b"b"]]), (k1, '.c', [b"a", b"b"]), (k1, '$.d', [[b"a", b"b", b"c"]]), (k1, '$.d.*', [[], [], [b"a", b"b"]]), (k1, '.d', [b"a", b"b", b"c"]), (k1, '.d.*', [b"a", b"b"]), (k1, '$.*', [[], [b"a", b"b"], [b"a", b"b", b"c"], []]), (k1, '.*', [b"a", b"b"]), (k1, '$.c.*', [[], []]), (k1, '$.c.*', [None, None]) ] for (key, path, exp) in [ ]: assert exp == client.execute_command( 'JSON.OBJKEYS', key, path) for (key, path) in [ (k1, '.c.*'), (k1, '.e') ]: with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.OBJLEN', key, path) assert self.error_class.is_wrongtype_error(str(e.value)) def test_json_arrlen_command(self): client = self.server.get_new_client() assert 2 == client.execute_command( 'JSON.ARRLEN', wikipedia, '.phoneNumbers') # edge case: empty array assert 0 == client.execute_command( 'JSON.ARRLEN', wikipedia, '.children') # return should be null if document key does not exist assert None == client.execute_command( 'JSON.ARRLEN', foo, '.phoneNumbers') # return error if path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRLEN', wikipedia, '.foo') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: the element is not an array for path in ['.address', '.groups', '.age', '.weight', '.isAlive', '.spouse', '.firstName']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRLEN', wikipedia, path) assert self.error_class.is_wrongtype_error(str(e.value)) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.ARRLEN', wikipedia, '.phoneNumbers', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_arrlen_command_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '[[], [\"a\"], [\"a\", \"b\"], [\"a\", \"b\", \"c\"]]') assert b'OK' == client.execute_command('JSON.SET', k2, '.', '[[], \"a\", [\"a\", \"b\"], [\"a\", \"b\", \"c\"], 4]') for (key, path, exp) in [ (k1, '$.[*]', [0, 1, 2, 3]), (k2, '$.[*]', [0, None, 2, 3, None]) ]: assert exp == client.execute_command( 'JSON.ARRLEN', key, path) def test_json_arrappend_command(self): client = self.server.get_new_client() # edge case: append to an empty array assert 1 == client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.children', '"John"') assert b'["John"]' == client.execute_command( 'JSON.GET', wikipedia, '.children') # append to non-empty array assert 3 == client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.children', '"Mary"', '"Tom"') assert b'["John","Mary","Tom"]' == client.execute_command( 'JSON.GET', wikipedia, '.children') assert 3 == client.execute_command( 'JSON.ARRLEN', wikipedia, '.children') # return error if document key does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRAPPEND', foo, '.children', '"Mary"') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.foo', '"abc"') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: the element is not an array for path in ['.address', '.groups', '.age', '.weight', '.isAlive', '.spouse', '.firstName']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRAPPEND', wikipedia, path, '123') assert self.error_class.is_wrongtype_error(str(e.value)) def test_json_arrappend_command_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '[[], [\"a\"], [\"a\", \"b\"], [\"a\", \"b\", \"c\"]]') assert b'OK' == client.execute_command('JSON.SET', k2, '.', '[[], [\"a\"], [\"a\", \"b\"], [\"a\", \"b\", \"c\"]]') for (key, path, val, exp, new_val) in [ (k1, '$.[*]', '"a"', [1, 2, 3, 4], '[[\"a\"],[\"a\",\"a\"],[\"a\",\"b\",\"a\"],[\"a\",\"b\",\"c\",\"a\"]]'), (k2, '$.[*]', '""', [1, 2, 3, 4], '[[\"\"],[\"a\",\"\"],[\"a\",\"b\",\"\"],[\"a\",\"b\",\"c\",\"\"]]') ]: assert exp == client.execute_command( 'JSON.ARRAPPEND', key, path, val) assert new_val == client.execute_command( 'JSON.GET', key, path).decode() def test_json_arrpop_command(self): client = self.server.get_new_client() # edge case: pop an empty array assert None == client.execute_command( 'JSON.ARRPOP', wikipedia, '.children') # populate the array assert 3 == client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.children', '"John"', '"Mary"', '"Tom"') for (idx, popped_out, new_len, new_val) in [ (1, '"Mary"', 2, '["John","Tom"]'), (-1, '"Tom"', 1, '["John"]'), (0, '"John"', 0, '[]') ]: assert popped_out == client.execute_command( 'JSON.ARRPOP', wikipedia, '.children', idx).decode('utf-8') assert new_len == client.execute_command( 'JSON.ARRLEN', wikipedia, '.children') assert new_val == client.execute_command( 'JSON.GET', wikipedia, '.children').decode('utf-8') # edge case: pop an empty array assert None == client.execute_command( 'JSON.ARRPOP', wikipedia, '.children') # return error if document key does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRPOP', foo, '.children') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRPOP', wikipedia, '.foo') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: the element is not an array for path in ['.address', '.groups', '.age', '.weight', '.isAlive', '.spouse', '.firstName']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRPOP', wikipedia, path) assert self.error_class.is_wrongtype_error(str(e.value)) # test large index: larger than int32 client.execute_command( 'JSON.ARRPOP', wikipedia, ".children", 3000000000) # Wrong number of arguments res = b'{"type":"home","number":"212 555-1234"}' with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.ARRPOP', wikipedia, '.phoneNumbers', 0, 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_arrpop_command_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '[[], ["a"], ["a", "b"], ["a", "b", "c"]]') client.execute_command('COPY', k1, k2) client.execute_command('COPY', k1, k3) for (key, path, index, exp, exp_new_val) in [ (k1, '$.[*]', 0, [None, b'"a"', b'"a"', b'"a"'], '[[],[],["b"],["b","c"]]'), (k2, '$.[*]', 1, [None, b'"a"', b'"b"', b'"b"'], '[[],[],["a"],["a","c"]]'), (k3, '$.[*]', -1, [None, b'"a"', b'"b"', b'"c"'], '[[],[],["a"],["a","b"]]') ]: assert exp == client.execute_command( 'JSON.ARRPOP', key, path, index) assert exp_new_val == client.execute_command( 'JSON.GET', key, '.').decode() def test_json_arrinsert_command(self): client = self.server.get_new_client() # edge case: insert into an empty array assert 1 == client.execute_command( 'JSON.ARRINSERT', wikipedia, '.children', 0, '"foo"') assert b'["foo"]' == client.execute_command( 'JSON.GET', wikipedia, '.children') assert b'"foo"' == client.execute_command( 'JSON.ARRPOP', wikipedia, '.children') # populate the array assert 3 == client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.children', '"John"', '"Mary"', '"Tom"') assert b'["John","Mary","Tom"]' == client.execute_command( 'JSON.GET', wikipedia, '.children') for (idx, val, new_len, new_val) in [ (0, '"Kathy"', 4, '["Kathy","John","Mary","Tom"]'), (2, '"Rose"', 5, '["Kathy","John","Rose","Mary","Tom"]'), (3, '"Bob"', 6, '["Kathy","John","Rose","Bob","Mary","Tom"]'), (-1, '"Peter"', 7, '["Kathy","John","Rose","Bob","Mary","Peter","Tom"]'), (-1, '"Jane"', 8, '["Kathy","John","Rose","Bob","Mary","Peter","Jane","Tom"]'), (8, '"Grace"', 9, '["Kathy","John","Rose","Bob","Mary","Peter","Jane","Tom","Grace"]') ]: assert new_len == client.execute_command( 'JSON.ARRINSERT', wikipedia, '.children', idx, val) assert new_val == client.execute_command( 'JSON.GET', wikipedia, '.children').decode('utf-8') # return error if document key does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINSERT', foo, '.children', 0, '"abc"') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINSERT', wikipedia, '.foo', 0, '123') assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: the element is not an array for path in ['.address', '.groups', '.age', '.weight', '.isAlive', '.spouse', '.firstName']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINSERT', wikipedia, path, 0, '1') assert self.error_class.is_wrongtype_error(str(e.value)) # error condition: index arg is out of array boundaries with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINSERT', wikipedia, ".children", 3000000000, '"a"') assert self.error_class.is_outofboundaries_error(str(e.value)) # error condition: index arg is out of array boundaries with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINSERT', wikipedia, ".children", 31, '"a"') assert self.error_class.is_outofboundaries_error(str(e.value)) def test_json_arrinsert_command_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '[[], [0], [0, 1], [0, 1, 2]]') # COPY NOT SUPPORTED by REJSON client.execute_command('JSON.SET', k2, '.', '[[], [0], [0, 1], [0, 1, 2]]') client.execute_command('JSON.SET', k3, '.', '[[], [0], [0, 1], [0, 1, 2]]') test_cases = [ (k1, '$.[*]', 0, '3', [1, 2, 3, 4], '[[3],[3,0],[3,0,1],[3,0,1,2]]'), (k3, '$.[*]', -1, '3', [1, 2, 3, 4], '[[3],[3,0],[0,3,1],[0,1,3,2]]') ] # Negative paths beyond array length work strangely in ReJSON for (key, path, index, val, exp, exp_new_val) in test_cases: assert exp == client.execute_command( 'JSON.ARRINSERT', key, path, index, val) assert exp_new_val == client.execute_command( 'JSON.GET', key, '.').decode() # test index out of bounds error with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINSERT', k2, "$.[*]", 1, '3') assert self.error_class.is_outofboundaries_error(str(e.value)) def test_json_clear_command(self): client = self.server.get_new_client() # populate the array to be cleared assert 3 == client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.children', '"John"', '"Mary"', '"Tom"') assert b'["John","Mary","Tom"]' == client.execute_command( 'JSON.GET', wikipedia, '.children') # clears and counts 1, 0 remain assert 1 == client.execute_command( 'JSON.CLEAR', wikipedia, '.children') assert 0 == client.execute_command( 'JSON.ARRLEN', wikipedia, '.children') # clears empty array assert 0 == client.execute_command( 'JSON.CLEAR', wikipedia, '.children') assert 0 == client.execute_command( 'JSON.ARRLEN', wikipedia, '.children') # if path does not exist, the command should return 0 assert 0 == client.execute_command( 'JSON.CLEAR', wikipedia, '.foo') # if the value at the path is not a container, the command should return 0 assert b'OK' == client.execute_command( 'JSON.SET', wikipedia, '.foobool', 'false') assert 0 == client.execute_command( 'JSON.CLEAR', wikipedia, '.foobool') # clear the wikipedia object entirely wikipedia_objlen = client.execute_command( 'JSON.OBJLEN', wikipedia) assert 0 != wikipedia_objlen assert 1 == client.execute_command( 'JSON.CLEAR', wikipedia) assert 0 == client.execute_command( 'JSON.OBJLEN', wikipedia) def test_json_clear_command_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '{"a":{}, "b":{"a": 1, "b": null, "c": true}, "c":1, "d":true, "e":null, "f":"d"}') assert b'OK' == client.execute_command('JSON.SET', k2, '.', '[[], [0], [0,1], [0,1,2], 1, true, null, "d"]') test_cases = [ (k1, '$.*', 4, '{"a":{},"b":{},"c":0,"d":false,"e":null,"f":""}'), (k2, '$[*]', 6, '[[],[],[],[],0,false,null,""]') ] for (key, path, exp, exp_new_val) in test_cases: assert exp == client.execute_command( 'JSON.CLEAR', key, path) assert exp_new_val == client.execute_command( 'JSON.GET', key, '.').decode() def test_json_arrtrim_command(self): client = self.server.get_new_client() # edge case: empty array assert 0 == client.execute_command( 'JSON.ARRTRIM', wikipedia, '.children', 0, 1) # populate the array assert 3 == client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.children', '"John"', '"Mary"', '"Tom"') assert b'["John","Mary","Tom"]' == client.execute_command( 'JSON.GET', wikipedia, '.children') for (path, start, end, new_len, new_val) in [ ('.children', 1, 2, 2, '["Mary","Tom"]'), ('.children', 0, 0, 1, '["Mary"]'), ('.children', -1, 5, 1, '["Mary"]'), ('.phoneNumbers', 2, 0, 0, '[]') ]: assert new_len == client.execute_command( 'JSON.ARRTRIM', wikipedia, path, start, end) assert new_val == client.execute_command( 'JSON.GET', wikipedia, path).decode('utf-8') # return error if document key does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRTRIM', foo, '.children', 0, 1) assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRTRIM', wikipedia, '.foo', 0, 3) assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: the element is not an array for path in ['.address', '.groups', '.age', '.weight', '.isAlive', '.spouse', '.firstName']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRTRIM', wikipedia, path, 0, 1) assert self.error_class.is_wrongtype_error(str(e.value)) # test large index: larger than int32 client.execute_command( 'JSON.ARRTRIM', wikipedia, ".phoneNumbers", 3000000000, 3000000001) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.ARRTRIM', wikipedia, '.phoneNumbers', 0, 1, 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_arrtrim_command_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '[[], ["a"], ["a", "b"], ["a", "b", "c"]]') assert b'OK' == client.execute_command('JSON.SET', k2, '.', '[[], ["a"], ["a", "b"], ["a", "b", "c"]]') assert b'OK' == client.execute_command('JSON.SET', k3, '.', '[[], [0], [0,1], [0,1,2], [0,1,2,3]]') for (key, path, start, stop, exp, exp_new_val) in [ (k1, '$.[*]', 0, 1, [0, 1, 2, 2], '[[],["a"],["a","b"],["a","b"]]'), (k2, '$.[*]', 1, 1, [0, 0, 1, 1], '[[],[],["b"],["b"]]'), (k3, '$.[*]', 1, 2, [0, 0, 1, 2, 2], '[[],[],[1],[1,2],[1,2]]') ]: assert exp == client.execute_command( 'JSON.ARRTRIM', key, path, start, stop) assert exp_new_val == client.execute_command( 'JSON.GET', key, '.').decode() def test_json_arrindex_command(self): # edge case: empty array client = self.server.get_new_client() assert -1 == client.execute_command( 'JSON.ARRINDEX', wikipedia, '.children', '"tom"') # populate the array assert 5 == client.execute_command('JSON.ARRAPPEND', wikipedia, '.children', '"John"', '"Mary"', '"Tom"', '"Paul"', '"Peter"') for (val, idx) in [ ('"John"', 0), ('"Tom"', 2), ('"Peter"', 4), ('"Peter2"', -1) ]: assert idx == client.execute_command( 'JSON.ARRINDEX', wikipedia, '.children', val) for (val, start, stop, idx) in [ ('"Tom"', 5, 0, -1), ('"Paul"', 0, 4, 3), ('"Paul"', 0, 0, 3) ]: assert idx == client.execute_command( 'JSON.ARRINDEX', wikipedia, '.children', val, start, stop) assert b'OK' == client.execute_command( 'JSON.SET', arr, '.', '[0, 1, 2, 3, 4]') assert 1 == client.execute_command( 'JSON.ARRINDEX', arr, '.', '1') assert - \ 1 == client.execute_command( 'JSON.ARRINDEX', arr, '.', '1', '2') # return error if document key does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINDEX', foo, '.children', 0, 5) assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINDEX', wikipedia, '.foo', 0, 3) assert self.error_class.is_nonexistent_error(str(e.value)) # error condition: the element is not an array for path in ['.address', '.groups', '.age', '.weight', '.isAlive', '.spouse', '.firstName']: with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.ARRINDEX', wikipedia, path, '1') assert self.error_class.is_wrongtype_error(str(e.value)) # test large index: larger than int32 client.execute_command( 'JSON.ARRINDEX', wikipedia, ".phoneNumbers", '1', 3000000000, 0) def test_json_arrindex_command_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '[[], [\"a\"], [\"a\", \"b\"], [\"a\", \"b\", \"c\"]]') assert b'OK' == client.execute_command('JSON.SET', k2, '.', '[[], [0], [0,1], [0,1,2]]') assert b'OK' == client.execute_command('JSON.SET', k3, '.', '[[], [0,true], [0,1,false], [0,1,2,null,true]]') for (key, path, val, exp) in [ (k1, '$.[*]', '"a"', [-1, 0, 0, 0]), (k1, '$.[*]', '"b"', [-1, -1, 1, 1]), (k1, '$.[*]', '"c"', [-1, -1, -1, 2]), (k2, '$.[*]', '1', [-1, -1, 1, 1]), (k2, '$.[*]', '2', [-1, -1, -1, 2]), (k2, '$.[*]', 'true', [-1, -1, -1, -1]), (k3, '$.[*]', 'true', [-1, 1, -1, 4]), (k3, '$.[*]', 'null', [-1, -1, -1, 3]) ]: assert exp == client.execute_command( 'JSON.ARRINDEX', key, path, val) def test_json_arrindex_should_not_limit_to_scalar_value(self): client = self.server.get_new_client() client.execute_command( 'JSON.SET', k1, '.', '[5, 6, {"a":"b"}, [99,100]]') assert 2 == client.execute_command( 'JSON.ARRINDEX', k1, '.', '{"a":"b"}', 0, 0) assert 3 == client.execute_command( 'JSON.ARRINDEX', k1, '.', '[99,100]', 0, 0) assert 0 == client.execute_command( 'JSON.ARRINDEX', k1, '.', '5', 0, 0) assert 1 == client.execute_command( 'JSON.ARRINDEX', k1, '.', '6', 0, 0) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.ARRINDEX', wikipedia, '.phoneNumbers', '1', 0, 3, 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_arrindex_complex_v2_path(self): client = self.server.get_new_client() json_string = '{"level0":{"level1_0":{"level2":[1,2,3, [25, [4,5,{"c":"d"}]]]},"level1_1":{"level2":[[{"a":[2,5]},true,null]]}}}' assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', json_string) for (path, val, exp) in [ ("$..level0.level1_0..", b'[4,5,{"c":"d"}]', [ None, -1, 1, -1, None]), ("$..level0.level1_0..", b'[25,[4,5,{"c":"d"}]]', [ None, 3, -1, -1, None]), ("$..level0.level1_0..", b'{"c":"d"}', [None, -1, -1, 2, None]), ("$..level0.level1_1..", b'[{"a":[2,5]},true,null]', [ None, 0, -1, None, -1]), ("$..level0.level1_1..", b'[null,true,{"a":[2,5]}]', [ None, -1, -1, None, -1]), ("$..level0.level1_1..", b'[{"a":[2,5]},true]', [ None, -1, -1, None, -1]), ("$..level0.level1_0..", b'[4,{"c":"d"}]', [ None, -1, -1, -1, None]) ]: assert exp == client.execute_command( 'JSON.ARRINDEX', k1, path, val) def test_json_type_command(self): client = self.server.get_new_client() for (path, type) in [ ('.', 'object'), ('.groups', 'object'), ('.phoneNumbers', 'array'), ('.children', 'array'), ('.isAlive', 'boolean'), ('.spouse', 'null'), ('.address.city', 'string'), ('.age', 'integer'), ('.weight', 'number') ]: assert type == client.execute_command( 'JSON.TYPE', wikipedia, path).decode('utf-8') # return should be null if document key does not exist assert None == client.execute_command('JSON.TYPE', foo) # return should be null if path does not exist assert None == client.execute_command( 'JSON.TYPE', wikipedia, '.foo') # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.TYPE', wikipedia, '.phoneNumbers', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_type_command_jsonpath(self): client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '{"a":1, "b":2.3, "c":"foo", "d":true, "e":null, "f":{}, "g":[]}') assert b'OK' == client.execute_command('JSON.SET', k2, '.', '[1, 2.3, "foo", true, null, {}, []]') for (key, path, exp) in [ (k1, '$.*', [b"integer", b"number", b"string", b"boolean", b"null", b"object", b"array"]), (k2, '$[*]', [b"integer", b"number", b"string", b"boolean", b"null", b"object", b"array"]) ]: assert exp == client.execute_command( 'JSON.TYPE', key, path) def test_json_resp_command(self): client = self.server.get_new_client() for (path, res) in [ ('.firstName', b'John'), ('.isAlive', b'true'), ('.age', 27), ('.spouse', None) ]: assert res == client.execute_command( 'JSON.RESP', wikipedia, path) for (path, res) in [('.weight', '135.17')]: assert res == client.execute_command( 'JSON.RESP', wikipedia, path).decode() arr = client.execute_command( 'JSON.RESP', wikipedia, '.children') assert 1 == len(arr) assert arr == [b'['] arr = client.execute_command( 'JSON.RESP', wikipedia, '.address') assert 5 == len(arr) assert arr[0:4] == [b'{', [b'street', b'21 2nd Street'], [ b'city', b'New York'], [b'state', b'NY']] assert b'10021-3100' == arr[4][1] arr = client.execute_command( 'JSON.RESP', wikipedia, '.phoneNumbers') assert 3 == len(arr) assert b'[' == arr[0] assert 3 == len(arr[1]) assert arr[1] == [b'{', [b'type', b'home'], [b'number', b'212 555-1234']] assert 3 == len(arr[2]) assert arr[2] == [b'{', [b'type', b'office'], [b'number', b'646 555-4567']] # return should be null if document key does not exist assert None == client.execute_command('JSON.RESP', foo) # error condition: path does not exist with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.RESP', wikipedia, '.foo') assert self.error_class.is_nonexistent_error(str(e.value)) # Wrong number of arguments with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.RESP', wikipedia, '.phoneNumbers', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 def test_json_resp_command_jsonpath(self): client = self.server.get_new_client() for (path, res) in [ ('$.firstName', [b'John']), ('$.isAlive', [b'true']), ('$.age', [27]), ('$.spouse', [None]), ('$.foo', []) ]: assert res == client.execute_command( 'JSON.RESP', wikipedia, path) for (path, res) in [('$.weight', '135.17')]: assert res == client.execute_command( 'JSON.RESP', wikipedia, path)[0].decode() arr = client.execute_command( 'JSON.RESP', wikipedia, '$.children') assert 1 == len(arr) assert arr == [[b'[']] arr = client.execute_command( 'JSON.RESP', wikipedia, '$.address.*') assert 4 == len(arr) assert arr == [b'21 2nd Street', b'New York', b'NY', b'10021-3100'] arr = client.execute_command( 'JSON.RESP', wikipedia, '$.phoneNumbers.*') assert 2 == len(arr) assert arr[0] == [b'{', [b'type', b'home'], [b'number', b'212 555-1234']] assert arr[1] == [b'{', [b'type', b'office'], [b'number', b'646 555-4567']] def test_json_debug_memory(self): # non-existent key client = self.server.get_new_client() assert None == client.execute_command( 'JSON.DEBUG MEMORY', nonexistentkey) # non-existent path with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.DEBUG MEMORY', wikipedia, nonexistentpath) assert self.error_class.is_nonexistent_error(str(e.value)) # syntax error: key not provided with pytest.raises(ResponseError) as e: client.execute_command('JSON.DEBUG MEMORY') assert str(e.value).startswith('wrong number of arguments') # syntax error: wrong subcommand with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.DEBUG MEMORY123', wikipedia) assert self.error_class.is_syntax_error(str(e.value)) with pytest.raises(ResponseError) as e: client.execute_command('JSON.DEBUG M', wikipedia) assert self.error_class.is_syntax_error(str(e.value)) with pytest.raises(ResponseError) as e: assert None == client.execute_command( 'JSON.DEBUG MEMORY', wikipedia, '.', 'extra') assert str(e.value).find('wrong number of arguments') >= 0 # Test shared path no_shared_mem = client.execute_command( 'JSON.DEBUG', 'MEMORY', wikipedia) with_shared_mem = client.execute_command( 'JSON.DEBUG', 'MEMORY', wikipedia, '.') assert with_shared_mem > no_shared_mem def test_json_duplicate_keys(self): client = self.server.get_new_client() '''Test handling of object with duplicate keys''' client.execute_command( 'JSON.SET', k1, '.', '{"a":0, "a":1}') assert b'{"a":1}' == client.execute_command( 'JSON.GET', k1, '.') assert [b"a"] == client.execute_command( 'JSON.OBJKEYS', k1) assert b'1' == client.execute_command( 'JSON.GET', k1, 'a') assert 1 == client.execute_command('JSON.OBJLEN', k1) client.execute_command('JSON.SET', k1, 'a', '2') assert b'{"a":2}' == client.execute_command( 'JSON.GET', k1, '.') client.execute_command('JSON.NUMINCRBY', k1, 'a', '2') assert b'{"a":4}' == client.execute_command( 'JSON.GET', k1, '.') client.execute_command('JSON.NUMMULTBY', k1, 'a', '2') assert b'{"a":8}' == client.execute_command( 'JSON.GET', k1, '.') client.execute_command('JSON.DEL', k1, 'a') assert b'{}' == client.execute_command( 'JSON.GET', k1, '.') def test_json_set_command_max_depth(self): client = self.server.get_new_client() def json_with_depth(depth): return '{"a":'*depth + '{}' + '}'*depth depth_limit = 128 client.execute_command( 'config set json.max-path-limit ' + str(depth_limit)) # json not too deep: ok assert b'OK' == client.execute_command( 'JSON.SET', k, '.', json_with_depth(127)) # error condition: json is too deep with pytest.raises(ResponseError) as e: json_deep = json_with_depth(200000) client.execute_command( 'JSON.SET', k, '.', json_deep) assert self.error_class.is_limit_exceeded_error(str(e.value)) def test_json_set_command_max_size(self): client = self.server.get_new_client() def json_with_size(size): return '"' + 'a'*(size-2) + '"' MB = 2**20 assert b'OK' == client.execute_command( 'JSON.SET', k, '.', json_with_size(33*MB)) with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', k, '.', json_with_size(64*MB)) assert self.error_class.is_limit_exceeded_error(str(e.value)) def test_multi_exec(self): client = self.server.get_new_client() client.execute_command('MULTI') client.execute_command( 'JSON.DEL', wikipedia, '.address.street') client.execute_command( 'JSON.DEL', wikipedia, '.address.zipcode') client.execute_command( 'JSON.SET', wikipedia, '.address.region', '"US East"') client.execute_command('EXEC') v = client.execute_command( 'JSON.GET', wikipedia, '.address').decode() assert v == '{"city":"New York","state":"NY","region":"US East"}' or \ v == '{"state":"NY","city":"New York","region":"US East"}' client.execute_command('MULTI') client.execute_command( 'JSON.ARRPOP', wikipedia, '.phoneNumbers') client.execute_command( 'JSON.ARRPOP', wikipedia, '.phoneNumbers') client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.phoneNumbers', '123') client.execute_command( 'JSON.ARRAPPEND', wikipedia, '.phoneNumbers', '456') client.execute_command('EXEC') v = client.execute_command( 'JSON.GET', wikipedia, '.phoneNumbers').decode() assert v == '[123,456]' def test_escaped_member_names(self): ''' Test accessing member names that contain escaped characters. ''' client = self.server.get_new_client() assert b'OK' == client.execute_command('JSON.SET', k1, '.', '{"a\\\\a":1, "b\\tb":2, "c\\nc":3, "d\\rd":4, "e\\be":5, "f\\"f":6, "":7, "\'":8}') assert b'OK' == client.execute_command('JSON.SET', k2, '.', '{"key\\u0000":"value\\u0000", "key\\u001F":"value\\u001F"}') for (key, path, exp) in [ (k1, '$["a\\\\a"]', '[1]'), (k1, "$['a\\a']", '[1]'), (k1, '$["b\\tb"]', '[2]'), (k1, "$['b\\tb']", '[2]'), (k1, '$["c\\nc"]', '[3]'), (k1, "$['c\\nc']", '[3]'), (k1, '$["d\\rd"]', '[4]'), (k1, "$['d\\rd']", '[4]'), (k1, '$["e\\be"]', '[5]'), (k1, "$['e\\be']", '[5]'), (k1, '$["f\\"f"]', '[6]'), (k1, "$['f\"f']", '[6]'), (k1, '$[""]', '[7]'), (k1, "$['']", '[7]'), (k1, '$["\'"]', '[8]'), (k1, "$['\\\'']", '[8]'), (k2, '$["key\\u0000"]', '["value\\u0000"]'), (k2, '$["key\\u001F"]', '["value\\u001F"]') ]: assert exp == client.execute_command( 'JSON.GET', key, path).decode() def test_serializing_escaped_quotes_in_member_name(self): client = self.server.get_new_client() assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', '{"\\"a":1, "\\"b":2}') for (path, space, exp) in [ ('.', None, '{"\\"a":1,"\\"b":2}'), ('.', ' ', '{"\\"a": 1,"\\"b": 2}') ]: if space is None: assert exp == client.execute_command( 'JSON.GET', k1, path).decode() else: assert exp == client.execute_command( 'JSON.GET', k1, 'space', space, path).decode() def test_json_numincrby_jsonpath_and_wildcard(self): client = self.server.get_new_client() client.execute_command( 'JSON.SET', k1, '.', '{"a":[], "b":[1], "c":[1,2], "d":[1,2,3]}') client.execute_command( 'JSON.SET', k2, '.', '{"a":{}, "b":{"a":1}, "c":{"a":1, "b":2}, "d":{"a":1, "b":2, "c":3}}') client.execute_command( 'JSON.SET', k3, '.', '{"a":{"a":"a"}, "b":{"a":"a", "b":1}, "c":{"a":"a", "b":"b"}, "d":{"a":1, "b":"b", "c":3}}') # JSONPath: return an array of values. # If a value is not a number, its corresponding returned element is JSON null. # NOTE: The expected value has accounted for the outcome of previous commands on the same key. for (cmd, key, path, incr_num, exp) in [ ('JSON.NUMINCRBY', k1, '$.a.*', '1', '[]'), ('JSON.GET', k1, '$.a.*', None, '[]'), ('JSON.NUMINCRBY', k1, '$.b.*', '1', '[2]'), ('JSON.GET', k1, '$.b.*', None, '[2]'), ('JSON.NUMINCRBY', k1, '$.b[*]', '1', '[3]'), ('JSON.GET', k1, '$.b[*]', None, '[3]'), ('JSON.NUMINCRBY', k1, '$.d.*', '1', '[2,3,4]'), ('JSON.GET', k1, '$.d.*', None, '[2,3,4]'), ('JSON.NUMINCRBY', k1, '$.d[*]', '1', '[3,4,5]'), ('JSON.GET', k1, '$.d[*]', None, '[3,4,5]'), ('JSON.NUMINCRBY', k2, '$.a.*', '1', '[]'), ('JSON.GET', k2, '$.a.*', None, '[]'), ('JSON.NUMINCRBY', k2, '$.b.*', '1', '[2]'), ('JSON.GET', k2, '$.b.*', None, '[2]'), ('JSON.NUMINCRBY', k2, '$.d.*', '1', '[2,3,4]'), ('JSON.GET', k2, '$.d.*', None, '[2,3,4]'), ('JSON.NUMINCRBY', k3, '$.a.*', '1', '[null]'), ('JSON.GET', k3, '$.a.*', None, '["a"]'), ('JSON.NUMINCRBY', k3, '$.b.*', '1', '[null,2]'), ('JSON.GET', k3, '$.b.*', None, '["a",2]'), ('JSON.NUMINCRBY', k3, '$.c.*', '1', '[null,null]'), ('JSON.GET', k3, '$.c.*', None, '["a","b"]'), ('JSON.NUMINCRBY', k3, '$.d.*', '1', '[2,null,4]'), ('JSON.GET', k3, '$.d.*', None, '[2,"b",4]') ]: if incr_num is not None: assert exp.encode() == client.execute_command(cmd, key, path, incr_num) else: assert exp.encode() == client.execute_command(cmd, key, path) def test_json_numincrby_legacy_path_and_wildcard(self): client = self.server.get_new_client() client.execute_command( 'JSON.SET', k1, '.', '{"a":[], "b":[1], "c":[1,2], "d":[1,2,3]}') client.execute_command( 'JSON.SET', k2, '.', '{"a":{}, "b":{"a":1}, "c":{"a":1, "b":2}, "d":{"a":1, "b":2, "c":3}}') client.execute_command( 'JSON.SET', k3, '.', '{"a":{"a":"a"}, "b":{"a":"a", "b":1}, "c":{"a":"a", "b":"b"}, "d":{"a":1, "b":"b", "c":3}}') # Legacy path: return NONEXISTENT error if no value is selected for (cmd, key, path, incr_num, exp) in [ ('JSON.NUMINCRBY', k1, '.a.*', '1', None), ('JSON.NUMINCRBY', k2, '.a.*', '1', None) ]: with pytest.raises(ResponseError) as e: assert exp == client.execute_command( cmd, key, path, incr_num) assert self.error_class.is_nonexistent_error(str(e.value)) with pytest.raises(ResponseError) as e: assert exp == client.execute_command( 'JSON.GET', key, path) assert self.error_class.is_nonexistent_error(str(e.value)) # Legacy path: return WRONGTYPE error if no number value is selected for (cmd, key, path, incr_num, exp) in [ ('JSON.NUMINCRBY', k3, '.a.*', '1', None), ('JSON.NUMINCRBY', k3, '.c.*', '1', None) ]: with pytest.raises(ResponseError) as e: assert exp == client.execute_command( cmd, key, path, incr_num) assert self.error_class.is_wrongtype_error(str(e.value)) # Legacy path: return a single value, which is the last updated value. # NOTE: The expected value has accounted for the outcome of previous commands on the same key. for (cmd, key, path, incr_num, exp) in [ ('JSON.NUMINCRBY', k1, '.b.*', '1', '2'), ('JSON.GET', k1, '.b.*', None, '2'), ('JSON.NUMINCRBY', k1, '.b[*]', '1', '3'), ('JSON.GET', k1, '.b[*]', None, '3'), ('JSON.NUMINCRBY', k1, '.d.*', '1', '4'), ('JSON.GET', k1, '.d.*', None, '2'), ('JSON.NUMINCRBY', k1, '.d[*]', '1', '5'), ('JSON.GET', k1, '.d[*]', None, '3'), ('JSON.NUMINCRBY', k2, '.b.*', '1', '2'), ('JSON.GET', k2, '.b.*', None, '2'), ('JSON.NUMINCRBY', k2, '.d.*', '1', '4'), ('JSON.GET', k2, '.d.*', None, '2'), ('JSON.NUMINCRBY', k3, '.b.*', '1', '2'), ('JSON.GET', k3, '.b.*', None, '"a"'), ('JSON.NUMINCRBY', k3, '.d.*', '1', '4'), ('JSON.GET', k3, '.d.*', None, '2') ]: if incr_num is not None: assert exp.encode() == client.execute_command(cmd, key, path, incr_num) else: assert exp.encode() == client.execute_command(cmd, key, path) def test_json_nummultby_jsonpath_and_wildcard(self): client = self.server.get_new_client() client.execute_command( 'JSON.SET', k1, '.', '{"a":[], "b":[1], "c":[1,2], "d":[1,2,3]}') client.execute_command( 'JSON.SET', k2, '.', '{"a":{}, "b":{"a":1}, "c":{"a":1, "b":2}, "d":{"a":1, "b":2, "c":3}}') client.execute_command( 'JSON.SET', k3, '.', '{"a":{"a":"a"}, "b":{"a":"a", "b":1}, "c":{"a":"a", "b":"b"}, "d":{"a":1, "b":"b", "c":3}}') # JSONPath: return an array of values. # If a value is not a number, its corresponding returned element is JSON null. # NOTE: The expected value has accounted for the outcome of previous commands on the same key. for (cmd, key, path, incr_num, exp) in [ ('JSON.NUMMULTBY', k1, '$.a.*', '2', '[]'), ('JSON.GET', k1, '$.a.*', None, '[]'), ('JSON.NUMMULTBY', k1, '$.b.*', '2', '[2]'), ('JSON.GET', k1, '$.b.*', None, '[2]'), ('JSON.NUMMULTBY', k1, '$.b[*]', '2', '[4]'), ('JSON.GET', k1, '$.b[*]', None, '[4]'), ('JSON.NUMMULTBY', k1, '$.d.*', '2', '[2,4,6]'), ('JSON.GET', k1, '$.d.*', None, '[2,4,6]'), ('JSON.NUMMULTBY', k1, '$.d[*]', '2', '[4,8,12]'), ('JSON.GET', k1, '$.d[*]', None, '[4,8,12]'), ('JSON.NUMMULTBY', k2, '$.a.*', '2', '[]'), ('JSON.GET', k2, '$.a.*', None, '[]'), ('JSON.NUMMULTBY', k2, '$.b.*', '2', '[2]'), ('JSON.GET', k2, '$.b.*', None, '[2]'), ('JSON.NUMMULTBY', k2, '$.d.*', '2', '[2,4,6]'), ('JSON.GET', k2, '$.d.*', None, '[2,4,6]'), ('JSON.NUMMULTBY', k3, '$.a.*', '2', '[null]'), ('JSON.GET', k3, '$.a.*', None, '["a"]'), ('JSON.NUMMULTBY', k3, '$.b.*', '2', '[null,2]'), ('JSON.GET', k3, '$.b.*', None, '["a",2]'), ('JSON.NUMMULTBY', k3, '$.c.*', '2', '[null,null]'), ('JSON.GET', k3, '$.c.*', None, '["a","b"]'), ('JSON.NUMMULTBY', k3, '$.d.*', '2', '[2,null,6]'), ('JSON.GET', k3, '$.d.*', None, '[2,"b",6]') ]: if incr_num is not None: assert exp.encode() == client.execute_command(cmd, key, path, incr_num) else: assert exp.encode() == client.execute_command(cmd, key, path) def test_json_nummultby_legacy_path_and_wildcard(self): client = self.server.get_new_client() client.execute_command( 'JSON.SET', k1, '.', '{"a":[], "b":[1], "c":[1,2], "d":[1,2,3]}') client.execute_command( 'JSON.SET', k2, '.', '{"a":{}, "b":{"a":1}, "c":{"a":1, "b":2}, "d":{"a":1, "b":2, "c":3}}') client.execute_command( 'JSON.SET', k3, '.', '{"a":{"a":"a"}, "b":{"a":"a", "b":1}, "c":{"a":"a", "b":"b"}, "d":{"a":1, "b":"b", "c":3}}') # Legacy path: return NONEXISTENT error if no value is selected for (cmd, key, path, incr_num, exp) in [ ('JSON.NUMMULTBY', k1, '.a.*', '2', None), ('JSON.NUMMULTBY', k2, '.a.*', '2', None) ]: with pytest.raises(ResponseError) as e: assert exp == client.execute_command( cmd, key, path, incr_num) assert self.error_class.is_nonexistent_error(str(e.value)) with pytest.raises(ResponseError) as e: assert exp == client.execute_command( 'JSON.GET', key, path) assert self.error_class.is_nonexistent_error(str(e.value)) # Legacy path: return WRONGTYPE error if no number value is selected for (cmd, key, path, incr_num, exp) in [ ('JSON.NUMMULTBY', k3, '.a.*', '2', None), ('JSON.NUMMULTBY', k3, '.c.*', '2', None) ]: with pytest.raises(ResponseError) as e: assert exp == client.execute_command( cmd, key, path, incr_num) assert self.error_class.is_wrongtype_error(str(e.value)) # Legacy path: return a single value, which is the last updated value. # NOTE: The expected value has accounted for the outcome of previous commands on the same key. for (cmd, key, path, incr_num, exp) in [ ('JSON.NUMMULTBY', k1, '.b.*', '2', '2'), ('JSON.GET', k1, '.b.*', None, '2'), ('JSON.NUMMULTBY', k1, '.b[*]', '2', '4'), ('JSON.GET', k1, '.b[*]', None, '4'), ('JSON.NUMMULTBY', k1, '.d.*', '2', '6'), ('JSON.GET', k1, '.d.*', None, '2'), ('JSON.NUMMULTBY', k1, '.d[*]', '2', '12'), ('JSON.GET', k1, '.d[*]', None, '4'), ('JSON.NUMMULTBY', k2, '.b.*', '2', '2'), ('JSON.GET', k2, '.b.*', None, '2'), ('JSON.NUMMULTBY', k2, '.d.*', '2', '6'), ('JSON.GET', k2, '.d.*', None, '2'), ('JSON.NUMMULTBY', k3, '.b.*', '2', '2'), ('JSON.GET', k3, '.b.*', None, '"a"'), ('JSON.NUMMULTBY', k3, '.d.*', '2', '6'), ('JSON.GET', k3, '.d.*', None, '2') ]: if incr_num is not None: assert exp.encode() == client.execute_command(cmd, key, path, incr_num) else: assert exp.encode() == client.execute_command(cmd, key, path) def test_json_digest(self): client = self.server.get_new_client() orig_digest = client.debug_digest() assert orig_digest != 0 client.execute_command("flushall") new_digest = client.debug_digest() assert int(new_digest) == 0 def test_big_dup(self): client = self.server.get_new_client() # This test is to test memory leak for fle in glob.glob("data/*.json"): with open(fle, 'r') as file: self.data = file.read() logging.debug("File %s is size %d" % (fle, len(self.data))) b0 = client.info(JSON_INFO_METRICS_SECTION)[ JSON_INFO_NAMES['total_memory_bytes']] try: client.execute_command( "json.set x .", self.data) except: pass try: client.execute_command("json.del x .") except: pass b2 = client.info(JSON_INFO_METRICS_SECTION)[ JSON_INFO_NAMES['total_memory_bytes']] assert b2 == b0 def test_jsonpath_filter_expression(self): client = self.server.get_new_client() store = ''' { "store": { "books": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99, "movies": [ { "title": "Sword of Honour", "realisator": { "first_name": "Bill", "last_name": "Anderson" } } ] }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 9 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } } } ''' assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', store) assert b'OK' == client.execute_command( 'JSON.SET', k2, '.', '[1,2,3,4,5]') assert b'OK' == client.execute_command( 'JSON.SET', k3, '.', '[true,false,true,false,null,1,2,3,4]') assert b'OK' == client.execute_command('JSON.SET', k4, '.', '{"books": [{"price":5,"sold":true,"in-stock":true,"title":"foo"}, {"price":15,"sold":false,"title":"abc"}]}') assert b'OK' == client.execute_command( 'JSON.SET', k5, '.', '[1,2,3,4,5,6,7,8,9]') for (key, path, exp) in [ (k1, '$.store.books[?(@.isbn)].price', b'[9,22.99]'), (k1, '$.store.books[?( @.isbn )].price', b'[9,22.99]'), (k1, '$.store.books[?(@["isbn"])]["price"]', b'[9,22.99]'), (k1, '$.store.books[?(@[ "isbn" ])][ "price" ]', b'[9,22.99]'), (k1, '$.store.books[?(@[\'isbn\'])][\'price\']', b'[9,22.99]'), (k1, '$.store.books[?(@.category == "reference")].price', b'[8.95]'), (k1, '$.store.books[?(@.["category"] == "fiction")].price', b'[12.99,9,22.99]'), (k1, '$.store.books[?(@.price<1.0E1)].price', b'[8.95,9]'), (k1, '$.store.books[?(@["price"]<1.0E1)]["price"]', b'[8.95,9]'), (k1, '$.store.books[?(@.["price"]<1.0E1)]["price"]', b'[8.95,9]'), (k1, '$.store.books[?(@[\'price\']<1.0E1)][\'price\']', b'[8.95,9]'), (k1, '$.store.books[?(@.price>-1.23e1&&@.price<1.0E1)].price', b'[8.95,9]'), (k1, '$.store.books[?(@["price"]>-1.23e1&&@["price"]<1.0E1)]["price"]', b'[8.95,9]'), (k1, '$.store.books[?(@.["price"]>-1.23e1&&@.["price"]<1.0E1)].["price"]', b'[8.95,9]'), (k1, '$.store.books[?(@["price"] > -1.23e1 && @["price"] < 1.0E1)]["price"]', b'[8.95,9]'), (k1, '$.store.books[?(@.price==22.99)].title', b'["The Lord of the Rings"]'), (k1, '$.store.books[?(@["price"]==22.99)].title', b'["The Lord of the Rings"]'), (k1, '$.store.books[?(@.price<10.0&&@.isbn)].price', b'[9]'), (k1, '$.store.books[?(@.price<9||@.price>20)].price', b'[8.95,22.99]'), # precedence test (k1, '$.store.books[?(@.price<9||@.price>10&&@.isbn)].price', b'[8.95,22.99]'), # precedence test (k1, '$.store.books[?((@.price<9||@.price>10)&&@.isbn)].price', b'[22.99]'), # precedence test (k1, '$.store.books[?((@.price < 9 || @.price>10) && @.isbn)].price', b'[22.99]'), # precedence test (k1, '$.store.books[?((@["price"]<9||@["price"]>10)&&@["isbn"])]["price"]', b'[22.99]'), # precedence test (k1, '$.store.books[?((@["price"] < 9 || @["price"] > 10) && @["isbn"])]["price"]', b'[22.99]'), (k2, '$.*.[?(@>2)]', b'[3,4,5]'), (k2, '$.*.[?(@ > 2)]', b'[3,4,5]'), (k2, '$.*[?(@>2)]', b'[3,4,5]'), (k2, '$[*][?(@>2)]', b'[3,4,5]'), (k2, '$[ * ][?( @ > 2 )]', b'[3,4,5]'), (k2, '$.*[?(@ == 3)]', b'[3]'), (k2, '$.*[?(@ != 3)]', b'[1,2,4,5]'), (k3, '$.*.[?(@==true)]', b'[true,true]'), (k3, '$[*][?(@==true)]', b'[true,true]'), (k3, '$[*][?(@ == true)]', b'[true,true]'), (k3, '$.*.[?(@>1)]', b'[2,3,4]'), (k3, '$.*.[?( @ > 1 ) ]', b'[2,3,4]'), (k3, '$[*][?(@>1)]', b'[2,3,4]'), (k3, '$[ * ][?( @ > 1 ) ]', b'[2,3,4]'), (k4, '$.books[?(@.price>1&&@.price<20&&@.in-stock)]', b'[{"price":5,"sold":true,"in-stock":true,"title":"foo"}]'), (k4, '$.books[?(@[\'price\']>1&&@.price<20&&@["in-stock"])]', b'[{"price":5,"sold":true,"in-stock":true,"title":"foo"}]'), (k4, '$.books[?((@.price>1&&@.price<20)&&(@.sold==false))]', b'[{"price":15,"sold":false,"title":"abc"}]'), (k4, '$["books"][?((@["price"]>1&&@["price"]<20)&&(@["sold"]==false))]', b'[{"price":15,"sold":false,"title":"abc"}]'), (k5, '$.*[?(@ > 7 || @ < 3)]', b'[8,9,1,2]'), # order test (k5, '$.*[?(@ < 3 || @ > 7)]', b'[1,2,8,9]'), # order test (k5, '$.*[?(@ > 3 && @ < 7)]', b'[4,5,6]') ]: assert exp == client.execute_command( 'JSON.GET', key, path) assert b'OK' == client.execute_command( 'JSON.SET', k1, '$.store.books[?(@.price<10.0)].price', '10.01') for (key, path, exp) in [ (k1, '$.store.books[?(@.price<10.0)]', b'[]'), (k1, '$.store.books[?(@.price<=10.02)].price', b'[10.01,10.01]'), (k1, '$.store.books[?(@.price <= 10.02)].price', b'[10.01,10.01]'), (k1, '$.store.books[?(@.price==10.01)].title', b'["Sayings of the Century","Moby Dick"]'), ]: assert exp == client.execute_command( 'JSON.GET', key, path) for (key, path, new_val, exp) in [ (k4, '$.books[?((@.price>1&&@.price<20)&&(@.sold==false))].price', '13.13', b'[13.13]'), (k4, '$.books[?((@.price > 1 && @.price < 20) && (@.sold == false))].price', '13.13', b'[13.13]'), (k4, '$["books"][?((@["price"]>1&&@["price"]<20)&&(@["sold"]==false))]["price"]', '13.13', b'[13.13]'), (k4, '$["books"][?((@["price"] > 1 && @["price"] < 20) && (@["sold"] == false))]["price"]', '13.13', b'[13.13]') ]: assert b'OK' == client.execute_command( 'JSON.SET', key, path, new_val) assert exp == client.execute_command( 'JSON.GET', key, path) # test delete with filter expression assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', store) assert b'OK' == client.execute_command( 'JSON.SET', k2, '.', store) for (key, path, exp) in [ (k1, '$.store.books[?(@.["category"] == "fiction")].price', 3), (k2, '$.store.books[?((@["price"] < 9 || @["price"] > 10) && @["isbn"])]["price"]', 1) ]: assert exp == client.execute_command( 'JSON.DEL', key, path) assert b'[]' == client.execute_command( 'JSON.GET', key, path) def test_jsonpath_recursive_descent(self): client = self.server.get_new_client() for (key, val) in [ (k1, '{"a":{"a":1}}'), (k2, '{"a":{"a":{"a":{"a":1}}}}'), (k3, '{"x": {}, "y": {"a":"a"}, "z": {"a":"", "b":"b"}}'), (k4, '{"a":{"b":{"z":{"y":1}}, "c":{"z":{"y":2}}, "z":{"y":3}}}'), (k5, '{"a":1, "b": {"e":[0,1,2]}, "c":{"e":[10,11,12]}}'), (k6, '{"a":[1], "b": {"a": [2,3]}, "c": {"a": [4,5,6]}}') ]: assert b'OK' == client.execute_command( 'JSON.SET', key, '.', val) for (key, path, exp) in [ (k1, '$..a', b'[{"a":1},1]'), (k2, '$..a', b'[{"a":{"a":{"a":1}}},{"a":{"a":1}},{"a":1},1]'), (k2, '$..a..a', b'[{"a":{"a":1}},{"a":1},1]'), (k2, '$..a..a..a', b'[{"a":1},1]'), (k2, '$..a..a..a..a', b'[1]'), (k2, '$..a..a..a..a..a', b'[]'), (k3, '$..a', b'["a",""]'), (k4, '$.a..z.y', b'[3,1,2]'), (k4, '$.a..z.*', b'[3,1,2]'), (k4, '$.a.*..y', b'[1,2,3]'), (k5, '$..e.[*]', b'[0,1,2,10,11,12]'), (k5, '$..e[1]', b'[1,11]'), (k5, '$..e.[1]', b'[1,11]'), (k5, '$..e.[1]', b'[1,11]'), (k5, '$..e[0:2]', b'[0,1,10,11]'), (k5, '$..["e"][1]', b'[1,11]'), (k5, '$..["e"][1]', b'[1,11]'), (k5, '$..["e"][0:2]', b'[0,1,10,11]'), (k5, '$..[ "e" ][ 0 : 2 ]', b'[0,1,10,11]') ]: assert exp == client.execute_command( 'JSON.GET', key, path) # recursive ARRAPPEND assert [2, 3, 4] == client.execute_command( 'JSON.ARRAPPEND', k6, '$..a', 0) assert b'{"a":[1,0],"b":{"a":[2,3,0]},"c":{"a":[4,5,6,0]}}' == client.execute_command( 'JSON.GET', k6) # recursive delete assert 2 == client.execute_command( 'JSON.DEL', k3, '$..a') assert b'{"x":{},"y":{},"z":{"b":"b"}}' == client.execute_command( 'JSON.GET', k3) # This is the only case that diverges from ReJSON v2. We deleted 4 elements while they deleted 1. # The divergence comes from the order of deletion. # Note that "JSON.GET k2 $..a" returns 4 elements. assert 4 == client.execute_command( 'JSON.DEL', k2, '$..a') assert b'{}' == client.execute_command('JSON.GET', k2) def test_jsonpath_recursive_insert_update_delete(self): ''' Test recursive insert, update and delete. ''' client = self.server.get_new_client() data_store = ''' { "store": { "books": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95, "in-stock": true }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99, "in-stock": true, "movies": [ { "title": "Sword of Honour - movie", "realisator": { "first_name": "Bill", "last_name": "Anderson" } } ] }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 9, "in-stock": false }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-115-03266-2", "price": 22.99, "in-stock": true }, { "category": "reference", "author": "William Jr. Strunk", "title": "The Elements of Style", "price": 6.99, "in-stock": false }, { "category": "fiction", "author": "Leo Tolstoy", "title": "Anna Karenina", "price": 22.99, "in-stock": true }, { "category": "reference", "author": "Sarah Janssen", "title": "The World Almanac and Book of Facts 2021", "isbn": "0-925-23305-2", "price": 10.69, "in-stock": false }, { "category": "reference", "author": "Kate L. Turabian", "title": "Manual for Writers of Research Papers", "isbn": "0-675-16695-1", "price": 8.59, "in-stock": true } ], "bicycle": { "color": "red", "price": 19.64, "in-stock": true } } } ''' data_store2 = ''' { "store": { "title": "foo", "bicycle": { "title": "foo2", "color": "red", "price": 19.64, "in-stock": true } } } ''' for (key, val) in [ (store, data_store), (store2, data_store2) ]: assert b'OK' == client.execute_command( 'JSON.SET', key, '.', val) # recursive delete assert 2 == client.execute_command( 'JSON.DEL', store2, '$..title') assert b'[]' == client.execute_command( 'JSON.GET', store2, '$..title') assert b'{"store":{"bicycle":{"color":"red","price":19.64,"in-stock":true}}}' == client.execute_command( 'JSON.GET', store2) # recursive insert, update and delete assert b'["Sayings of the Century","Sword of Honour","Sword of Honour - movie","Moby Dick","The Lord of the Rings","The Elements of Style","Anna Karenina","The World Almanac and Book of Facts 2021","Manual for Writers of Research Papers"]'\ == client.execute_command('JSON.GET', store, '$..title') assert b'OK' == client.execute_command( 'JSON.SET', store, '$..title', '"foo"') assert b'["foo","foo","foo","foo","foo","foo","foo","foo","foo"]'\ == client.execute_command('JSON.GET', store, '$..title') for (key, path, exp) in [ (store, '$.title', b'[]'), (store, '$.store.title', b'[]'), (store, '$.store.bicycle.title', b'[]'), (store, '$.store.books[1].title', b'["foo"]'), (store, '$.store.books[1].movies[0].title', b'["foo"]'), (store, '$.store.books[1].movies[0].realisator.title', b'[]') ]: assert exp == client.execute_command( 'JSON.GET', key, path) assert 9 == client.execute_command( 'JSON.DEL', store, '$..title') assert b'[]' == client.execute_command( 'JSON.GET', store, '$..title') assert True == client.execute_command('save') def test_jsonpath_recursive_insert_update_delete2(self): ''' Test recursive insert, update and delete. ''' client = self.server.get_new_client() data_input = ''' { "input": { "a": 1, "b": { "e": [0,1,2] }, "c": { "e": [10,11,12] } } } ''' data_input2 = ''' { "input": { "a": 1, "b": { "e": [0,1,2] }, "c": { "e": [10,11,12] } } } ''' for (key, val) in [ (input, data_input), (input2, data_input2) ]: assert b'OK' == client.execute_command( 'JSON.SET', key, '.', val) # recursive delete assert 2 == client.execute_command( 'JSON.DEL', input2, '$..e') assert b'[]' == client.execute_command( 'JSON.GET', input2, '$..e') assert b'{"input":{"a":1,"b":{},"c":{}}}' == client.execute_command( 'JSON.GET', input2) # recursive insert, update and delete assert b'[0,1,2,10,11,12]' == client.execute_command( 'JSON.GET', input, '$..e[*]') assert b'OK' == client.execute_command( 'JSON.SET', input, '$..e[*]', '4') assert b'[4,4,4,4,4,4]' == client.execute_command( 'JSON.GET', input, '$..e[*]') assert b'[4,4,4]' == client.execute_command( 'JSON.GET', input, '$.input.b.e[*]') for (key, path, exp) in [ (input, '$.e', b'[]'), (input, '$.input.e', b'[]'), (input, '$.input.a.e', b'[]'), (input, '$.input.b.e[*]', b'[4,4,4]'), (input, '$.input.c.e[*]', b'[4,4,4]'), ]: assert exp == client.execute_command( 'JSON.GET', key, path) assert 2 == client.execute_command( 'JSON.DEL', input, '$..e') assert 0 == client.execute_command( 'JSON.DEL', input, '$..e') assert b'[]' == client.execute_command( 'JSON.GET', input, '$..e') assert True == client.execute_command('save') def test_jsonpath_compatibility_invalidArrayIndex(self): client = self.server.get_new_client() # Array index is not integer for (key, path) in [ (wikipedia, '.phoneNumbers[]'), (wikipedia, '.phoneNumbers[x]'), (wikipedia, '$.phoneNumbers[]'), (wikipedia, '$.phoneNumbers[x]') ]: with pytest.raises(ResponseError) as e: client.execute_command('JSON.GET', key, path) assert self.error_class.is_syntax_error(str(e.value)) def test_jsonpath_compatibility_unquotedMemberName(self): client = self.server.get_new_client() # Unquoted member name can contain any symbol except terminator characters json = '''{ "%x22key%x22":1, "+2":2, "-3":3, "/4":4, ")5":5, "6)":6, "(7":7, "8(":8, "]9":9, "10]":10, "[11":11, "12[":12, ">13":13, "14>":14, "<15":15, "16<":16, "=17":17, "18=":18, "!19":19, "20!":20, "21.21":21 }''' assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', json) test_cases = [ (k1, '$.%x22key%x22', b'[1]'), (k1, '$.+2', b'[2]'), (k1, '$.-3', b'[3]'), (k1, '$./4', b'[4]'), # The following should return empty array because unquoted member name cannot contain terminator characters. (k1, '$.6)', b'[]'), (k1, '$.8(', b'[]'), (k1, '$.10]', b'[]'), # Bracketed/Quoted member name should work (k1, '$["6)"]', b'[6]'), (k1, '$["8("]', b'[8]'), (k1, '$["10]"]', b'[10]'), (k1, '$["12["]', b'[12]'), (k1, '$["14>"]', b'[14]'), (k1, '$["16<"]', b'[16]'), (k1, '$["18="]', b'[18]'), (k1, '$["20!"]', b'[20]'), (k1, '$["21.21"]', b'[21]'), (k1, '$.12[', b'[]'), (k1, '$.14>', b'[]'), (k1, '$.16<', b'[]'), (k1, '$.18=', b'[]'), (k1, '$.20!', b'[]'), (k1, '$.21.21', b'[]'), ] for (key, path, exp) in test_cases: assert exp == client.execute_command( 'JSON.GET', key, path) # Unquoted object member cannot start with a character that is a member name terminator. for (key, path) in [ (k1, '$.)5'), (k1, '$.]7'), (k1, '$.]9'), (k1, '$.[11'), (k1, '$.>13'), (k1, '$.<15'), (k1, '$.=17'), (k1, '$.!19') ]: with pytest.raises(ResponseError) as e: client.execute_command('JSON.GET', key, path) assert self.error_class.is_syntax_error(str(e.value)) def test_write_commands_duplicate_values(self): ''' Test all WRITE JSON commands for the situation that if the json path results in multiple values with duplicates, the write operation should apply to all unique values once. ''' client = self.server.get_new_client() for (key, val) in [ (k1, '[0,1,2,3,4,5,6,7,8,9]'), (k2, '[0,1,2,3,4,5,6,7,8,9]'), (k3, '[0,1,2,3,4,5,6,7,8,9]'), (k4, '["0","1","2","3","4","5","6","7","8","9"]'), (k5, '[[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]'), (k6, '[true,true,true,true,true]'), (k7, '[[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]'), (k8, '[[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]'), (k9, '[[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]'), (k10, '[[0,1,2,3,4],[0,1,2,3,4],[0,1,2,3,4]]'), (k11, '[0,1,2,3,4,5,6,7,8,9]'), (k12, '[0,1,2,3,4,5,6,7,8,9]') ]: assert b'OK' == client.execute_command( 'JSON.SET', key, '.', val) # NUMINCRBY assert b'[10]' == client.execute_command( 'JSON.NUMINCRBY', k1, '$[0,0,0,0,0]', 10) assert b'[10,1,2,3,4,5,6,7,8,9]' == client.execute_command( 'JSON.GET', k1) # NUMMULTBY assert b'[18]' == client.execute_command( 'JSON.NUMMULTBY', k2, '$[9,9,9,9,9]', 2) assert b'[0,1,2,3,4,5,6,7,8,18]' == client.execute_command( 'JSON.GET', k2) # CLEAR assert 1 == client.execute_command( 'JSON.CLEAR', k5, '$[0,0,0,0,0]') assert b'[[],[1],[2],[3],[4],[5],[6],[7],[8],[9]]' == client.execute_command( 'JSON.GET', k5) # TOGGLE assert [0] == client.execute_command( 'JSON.TOGGLE', k6, '$[0,0,0,0]') assert b'[false,true,true,true,true]' == client.execute_command( 'JSON.GET', k6) # STRAPPEND assert [4] == client.execute_command( 'JSON.STRAPPEND', k4, '$[0,0,0,0,0]', '"foo"') assert b'["0foo","1","2","3","4","5","6","7","8","9"]' == client.execute_command( 'JSON.GET', k4) # ARRAPPEND assert [3] == client.execute_command( 'JSON.ARRAPPEND', k7, '$[0,0,0,0,0]', 8, 9) assert b'[[0,8,9],[1],[2],[3],[4],[5],[6],[7],[8],[9]]' == client.execute_command( 'JSON.GET', k7) # ARRINSERT assert [2] == client.execute_command( 'JSON.ARRINSERT', k8, '$[0,0,0,0,0]', 0, 9) assert b'[[9,0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]' == client.execute_command( 'JSON.GET', k8) # ARRPOP assert [b"0"] == client.execute_command( 'JSON.ARRPOP', k9, '$[0,0,0,0,0]') assert b'[[],[1],[2],[3],[4],[5],[6],[7],[8],[9]]' == client.execute_command( 'JSON.GET', k9) # ARRTRIM assert [2] == client.execute_command( 'JSON.ARRTRIM', k10, '$[0,0,0,0,0]', 0, 1) assert b'[[0,1],[0,1,2,3,4],[0,1,2,3,4]]' == client.execute_command( 'JSON.GET', k10) # DEL assert 1 == client.execute_command( 'JSON.DEL', k3, '$[0,0,0,0,0]') assert b'[1,2,3,4,5,6,7,8,9]' == client.execute_command( 'JSON.GET', k3) # DEL: delete arbitrary elements with duplicates assert 5 == client.execute_command( 'JSON.DEL', k12, '$[1,4,7,0,0,3,3]') assert b'[2,5,6,8,9]' == client.execute_command( 'JSON.GET', k12) # SET assert b'OK' == client.execute_command( 'JSON.SET', k11, '$[0,0,0,0,0]', 9) assert b'[9,1,2,3,4,5,6,7,8,9]' == client.execute_command( 'JSON.GET', k11) def test_jsonpath_compatibility_filterOnObject_and_stringComparison(self): client = self.server.get_new_client() json = ''' { "key for key" : "key inside here", "an object" : { "weight" : 300, "a value" : 300, "my key" : "key inside here" }, "another object" : { "weight" : 400, "a value" : 400, "my key" : "key inside there" }, "objects": [ { "weight" : 100, "a value" : 100, "my key" : "key inside here" }, { "weight" : 200, "a value" : 200, "my key" : "key inside there" }, { "weight" : 300, "a value" : 300, "my key" : "key inside here" }, { "weight" : 400, "a value" : 400, "my key" : "key inside there" } ] } ''' assert b'OK' == client.execute_command( 'JSON.SET', k1, '.', json) for (key, path, exp) in [ (k1, '$["an object"].[?(@.weight > 200)].["a value"]', b'[300]'), (k1, '$["an object"].[?(@.weight == 300)].["a value"]', b'[300]'), (k1, '$["an object"].[?(@.weight > 300)].["a value"]', b'[]'), (k1, '$["another object"].[?(@["a value"] > 200)].weight', b'[400]'), (k1, '$["another object"].[?(@.["a value"] > 200)].weight', b'[400]'), (k1, '$["another object"].[?(@.["my key"] == "key inside there")].weight', b'[400]'), (k1, '$["objects"].[?(@.weight > 200)].["a value"]', b'[300,400]'), (k1, '$["objects"].[?(@.["my key"] == "key inside there")].weight', b'[200,400]'), (k1, '$["objects"].[?(@.["my key"] != "key inside there")].weight', b'[100,300]'), (k1, '$["objects"].[?(@.["my key"] == "key inside here")].weight', b'[100,300]'), (k1, '$["objects"].[?(@.["my key"] != "key inside here")].weight', b'[200,400]'), (k1, '$["objects"].[?(@.["my key"] <= "key inside here")].weight', b'[100,300]'), (k1, '$["objects"].[?(@.["my key"] >= "key inside here")].weight', b'[100,200,300,400]'), (k1, '$["objects"].[?(@.["my key"] < "key inside herf")].weight', b'[100,300]'), (k1, '$["objects"].[?(@.["my key"] > "key insidd here")].weight', b'[100,200,300,400]'), (k1, '$["key for key"].[?(@.["a value"] > 200)]', b'[]') ]: assert exp == client.execute_command( 'JSON.GET', key, path) def test_jsonpath_compatibility_union_of_object_members(self): client = self.server.get_new_client() test_cases = [ (wikipedia, '$.["firstName","lastName"]', b'["John","Smith"]'), (organism, '$.animals["2","2"].mammals..weight', b'[]'), (organism, '$.animals["2","Junk"]', b'[]'), (organism, '$.animals["Junk","Junk"]', b'[]'), # test unique values in recursive descent (organism, '$..[?(@.weight>400)].name', b'["Redwood","Horse"]'), (organism, '$..[?(@.weight>=60&&@.name=="Chimpanzee")].name', b'["Chimpanzee"]'), (wikipedia, '$.[ "firstName", "lastName" ]', b'["John","Smith"]'), (wikipedia, '$.address.[ "street", "city", "state", "zipcode" ]', b'["21 2nd Street","New York","NY","10021-3100"]'), ] assert b'OK' == client.execute_command( 'JSON.SET', organism, '.', DATA_ORGANISM) for (key, path, exp) in test_cases: assert exp == self.client.execute_command('JSON.GET', key, path) def test_jsonpath_malformed_path(self): client = self.server.get_new_client() for (key, val) in [ (k1, '{"store":{"book":[{"price":5,"sold":true,"in-stock":true,"title":"foo","author":"me","isbn":"978-3-16-148410-0"}]}}'), (k2, '[1,2,3,4,5,6,7,8,9,10]') ]: assert b'OK' == client.execute_command( 'JSON.SET', key, '.', val) test_cases = [ (k1, '$[0:2]$[0:1]$[0:2]$[0:2]$[0<2065>:2]$[0:2]', b'[]'), (k1, '$[0,1]', b'[]'), (k2, '$.[\"a\"].[\"b\"].[\"c\"]', b'[]'), (k1, '$a.b.c.d', b'[]'), (k2, '$a$b$c$d', b'[]'), ] for (key, path, exp) in test_cases: assert exp == client.execute_command( 'JSON.GET', key, path) for (key, path) in [ (k1, '.[0:2].[0:1].[0:2].[0:2].[0<2065>:2].[0:2]'), (k1, '.[0:2]$[0:1]$[0:2]$[0:2]$[0<2065>:2]$[0:2]') ]: with pytest.raises(ResponseError) as e: client.execute_command('JSON.GET', key, path) assert self.error_class.is_wrongtype_error( str(e.value)) or self.error_class.is_nonexistent_error(str(e.value)) for (key, path) in [ (k1, '&&$.store..price'), (k1, '!$.store..price'), (k1, '=$.store..price'), (k1, '=.store..price'), (k1, '||.store..price') ]: with pytest.raises(ResponseError) as e: client.execute_command('JSON.GET', key, path) assert self.error_class.is_syntax_error(str(e.value)) with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', key, path, '0') assert self.error_class.is_syntax_error(str(e.value)) def test_v2_path_limit_recursive_descent(self): client = self.server.get_new_client() depth_limit = 10 client.execute_command( 'config set json.max-path-limit ' + str(depth_limit)) assert b'OK' == client.execute_command( 'JSON.SET', k1, '$', '{"a":0}') # repeatedly increasing path depth by 1 till the limit is reached. for _ in range(0, depth_limit-1): client.execute_command( 'JSON.SET', k1, '$..a', '{"a":0}') # verify the path depth reaches the limit assert depth_limit == client.execute_command( 'JSON.DEBUG', 'DEPTH', k1) # one more increase should exceed the path limit with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', k1, '$..a', '{"a":0}') assert str(e.value).startswith("LIMIT") def test_v2_path_limit_insert_member(self): client = self.server.get_new_client() depth_limit = 3 client.config_set('json.max-path-limit', depth_limit) assert b'OK' == client.execute_command( 'JSON.SET', k1, '$', '{"a":{"a":{"a":0}}}') assert depth_limit == client.execute_command( 'JSON.DEBUG DEPTH', k1) # insert a nesting object with pytest.raises(ResponseError) as e: client.execute_command( 'JSON.SET', k1, '$.a.a.b', '{"b":0}') assert str(e.value).startswith("LIMIT") # increase limit by 1 client.config_set( 'json.max-path-limit', depth_limit + 1) assert b'OK' == client.execute_command( 'JSON.SET', k1, '$.a.a.b', '{"b":0}') def test_debug_command_getkeys_api(self): client = self.server.get_new_client() for (subcmd, res) in [ ('memory k1', [b'k1']), ('fields k1', [b'k1']), ('depth k1', [b'k1']), ('memory k1', [b'k1']) ]: assert res == client.execute_command( 'command getkeys json.debug ' + subcmd) for subcmd in [ '', 'memory', 'mem', 'memo' 'fields' 'depth' 'help', 'max-depth-key', 'max-size-key' ]: with pytest.raises(ResponseError) as e: client.execute_command( 'command getkeys json.debug ' + subcmd) assert self.error_class.is_wrong_number_of_arguments_error(str(e.value)) or \ str(e.value).lower().find("invalid command") >= 0 or \ str(e.value).lower().find( "the command has no key arguments") >= 0 def test_debug_command_depth(self): client = self.server.get_new_client() for (key, val, depth) in [ (k1, '1', 0), (k2, '"a"', 0), (k3, 'null', 0), (k4, 'true', 0), (k5, '{}', 0), (k6, '{"a":0}', 1), (k7, '{"a":{"a":0}}', 2), (k7, '{"a":{"a":{"a":0}}}', 3), (k8, '[]', 0), (k9, '[0]', 1), (k10, '[[0]]', 2), (k11, '[[0],[[0]]]', 3), ]: assert b'OK' == client.execute_command( 'JSON.SET', key, '$', val) assert depth == client.execute_command( 'JSON.DEBUG DEPTH', key) # what if the key does not exist? assert None == client.execute_command( 'JSON.DEBUG DEPTH', 'foobar') def test_insert_update_delete_mode(self): client = self.server.get_new_client() assert b'OK' == client.execute_command( 'JSON.SET', organism, '.', DATA_ORGANISM) assert b'OK' == client.execute_command( 'JSON.SET', organism2, '.', DATA_ORGANISM) # delete key = organism path = '$.animals[*].mammals[*].primates[*].apes[?(@.weight<400)].weight' assert b'[130,300]' == client.execute_command( 'JSON.GET', key, path) assert 2 == client.execute_command( 'JSON.DEL', key, path) assert b'[]' == client.execute_command( 'JSON.GET', key, path) # insert assert b'OK' == client.execute_command( 'JSON.SET', key, path, 25) assert b'[]' == client.execute_command( 'JSON.GET', key, path) # update key = organism2 assert b'[130,300]' == client.execute_command( 'JSON.GET', key, path) assert b'OK' == client.execute_command( 'JSON.SET', key, path, 25) assert b'[25,25]' == client.execute_command( 'JSON.GET', key, path) assert 2 == client.execute_command( 'JSON.DEL', key, path) assert b'[]' == client.execute_command( 'JSON.GET', key, path) def test_json_arity_per_command(self): client = self.server.get_new_client() # These commands should only get the single key cmd_arity = [('SET', -4), ('GET', -2), ('MGET', -3), ('DEL', -2), ('FORGET', -2), ('NUMINCRBY', 4), ('NUMMULTBY', 4), ('STRLEN', -2), ('STRAPPEND', - 3), ('TOGGLE', -2), ('OBJLEN', -2), ('OBJKEYS', -2), ('ARRLEN', -2), ('ARRAPPEND', -4), ('ARRPOP', - 2), ('ARRINSERT', -5), ('ARRTRIM', 5), ('CLEAR', -2), ('ARRINDEX', -4), ('TYPE', -2), ('RESP', -2), ('DEBUG', -2)] for cmd, arity in cmd_arity: assert arity == client.execute_command('COMMAND', 'INFO', f'JSON.{cmd}')[ f'JSON.{cmd}']['arity'] cmd_arity = [('MEMORY', -3), ('FIELDS', -3), ('DEPTH', 3), ('HELP', 2), ('MAX-DEPTH-KEY', 2), ('MAX-SIZE-KEY', 2), ('KEYTABLE-CHECK', 2), ('KEYTABLE-CORRUPT', 3), ('KEYTABLE-DISTRIBUTION', 3)] subcmd_dict = {f'JSON.DEBUG|{cmd}': arity for cmd, arity in cmd_arity} output = client.execute_command( 'COMMAND', 'INFO', f'JSON.DEBUG')[f'JSON.DEBUG']['subcommands'] assert len(output) == len(subcmd_dict) for i in range(len(output)): assert subcmd_dict[output[i][0].decode('ascii')] == output[i][1]