diff --git a/testing/json.test b/testing/json.test index 45a4ecf8e..61c5e5177 100755 --- a/testing/json.test +++ b/testing/json.test @@ -980,6 +980,240 @@ do_execsql_test json_control_chars { SELECT json(jsonb('{"control": "Bell: \u0007 Backspace: \u0008 Form feed: \u000C"}')); } {{{"control":"Bell: \u0007 Backspace: \u0008 Form feed: \u000C"}}} + +# Tests for json_replace() function + +# Basic replacement tests +do_execsql_test json_replace_basic_1 { + SELECT json_replace('{"a": 1, "b": 2}', '$.a', 42) +} {{{"a":42,"b":2}}} + +do_execsql_test json_replace_basic_2 { + SELECT json_replace('{"a": 1, "b": 2}', '$.c', 3) +} {{{"a":1,"b":2}}} + +do_execsql_test json_replace_multiple_paths { + SELECT json_replace('{"a": 1, "b": 2, "c": 3}', '$.a', 10, '$.c', 30) +} {{{"a":10,"b":2,"c":30}}} + +# Testing different JSON types +do_execsql_test json_replace_string { + SELECT json_replace('{"name": "Alice"}', '$.name', 'Bob') +} {{{"name":"Bob"}}} + +do_execsql_test json_replace_number_with_string { + SELECT json_replace('{"age": 25}', '$.age', 'unknown') +} {{{"age":"unknown"}}} + +do_execsql_test json_replace_with_null { + SELECT json_replace('{"a": 1, "b": 2}', '$.a', NULL) +} {{{"a":null,"b":2}}} + +do_execsql_test json_replace_with_json_object { + SELECT json_replace('{"user": {"name": "Alice"}}', '$.user', '{"name": "Bob", "age": 30}') +} {{{"user":"{\"name\": \"Bob\", \"age\": 30}"}}} + +# Array tests +do_execsql_test json_replace_array_element { + SELECT json_replace('[1, 2, 3, 4]', '$[1]', 99) +} {{[1,99,3,4]}} + +do_execsql_test json_replace_array_negative_index { + SELECT json_replace('[1, 2, 3, 4]', '$[#-1]', 99) +} {{[1,2,3,99]}} + +do_execsql_test json_replace_array_out_of_bounds { + SELECT json_replace('[1, 2, 3]', '$[5]', 99) +} {{[1,2,3]}} + +do_execsql_test json_replace_entire_array { + SELECT json_replace('[1, 2, 3]', '$', '{"replaced": true}') +} {{"{\"replaced\": true}"}} + +# Nested structures +do_execsql_test json_replace_nested_object { + SELECT json_replace('{"user": {"name": "Alice", "age": 30}}', '$.user.age', 31) +} {{{"user":{"name":"Alice","age":31}}}} + +do_execsql_test json_replace_nested_array { + SELECT json_replace('{"data": [10, 20, 30]}', '$.data[1]', 99) +} {{{"data":[10,99,30]}}} + +do_execsql_test json_replace_deep_nesting { + SELECT json_replace( + '{"level1": {"level2": {"level3": {"value": 0}}}}', + '$.level1.level2.level3.value', + 42 + ) +} {{{"level1":{"level2":{"level3":{"value":42}}}}}} + +# Edge cases +do_execsql_test json_replace_empty_object { + SELECT json_replace('{}', '$.anything', 42) +} {{{}}} + +do_execsql_test json_replace_empty_array { + SELECT json_replace('[]', '$[0]', 42) +} {{[]}} + +do_execsql_test json_replace_quoted_key { + SELECT json_replace('{"key.with.dots": 1}', '$."key.with.dots"', 42) +} {{{"key.with.dots":42}}} + +do_execsql_test json_replace_root { + SELECT json_replace('{"old": "value"}', '$', '{"new": "object"}') +} {{"{\"new\": \"object\"}"}} + +do_execsql_test json_replace_types_boolean { + SELECT typeof(json_extract(json_replace('{"flag": null}', '$.flag', 1=1), '$.flag')) +} {{integer}} + +do_execsql_test json_replace_types_integer { + SELECT typeof(json_extract(json_replace('{"num": "text"}', '$.num', 42), '$.num')) +} {{integer}} + +do_execsql_test json_replace_types_real { + SELECT typeof(json_extract(json_replace('{"num": 1}', '$.num', 3.14), '$.num')) +} {{real}} + +do_execsql_test json_replace_types_text { + SELECT typeof(json_extract(json_replace('{"val": 1}', '$.val', 'text'), '$.val')) +} {{text}} + +# Tests for json_remove() function + +# Basic removal tests +do_execsql_test json_remove_basic_1 { + SELECT json_remove('{"a": 1, "b": 2, "c": 3}', '$.b') +} {{{"a":1,"c":3}}} + +do_execsql_test json_remove_basic_2 { + SELECT json_remove('{"a": 1, "b": 2}', '$.c') +} {{{"a":1,"b":2}}} + +do_execsql_test json_remove_multiple_paths { + SELECT json_remove('{"a": 1, "b": 2, "c": 3, "d": 4}', '$.a', '$.c') +} {{{"b":2,"d":4}}} + +# Array tests +do_execsql_test json_remove_array_element { + SELECT json_remove('[1, 2, 3, 4]', '$[1]') +} {{[1,3,4]}} + +do_execsql_test json_remove_array_negative_index { + SELECT json_remove('[1, 2, 3, 4]', '$[#-1]') +} {{[1,2,3]}} + +do_execsql_test json_remove_array_multiple_elements { + SELECT json_remove('[0, 1, 2, 3, 4, 5]', '$[1]', '$[3]') +} {{[0,2,3,5]}} + +do_execsql_test json_remove_array_out_of_bounds { + SELECT json_remove('[1, 2, 3]', '$[5]') +} {{[1,2,3]}} + +# Nested structures +do_execsql_test json_remove_nested_object { + SELECT json_remove('{"user": {"name": "Alice", "age": 30, "email": "alice@example.com"}}', '$.user.email') +} {{{"user":{"name":"Alice","age":30}}}} + +do_execsql_test json_remove_nested_array { + SELECT json_remove('{"data": [10, 20, 30, 40]}', '$.data[2]') +} {{{"data":[10,20,40]}}} + +do_execsql_test json_remove_deep_nesting { + SELECT json_remove( + '{"level1": {"level2": {"level3": {"a": 1, "b": 2, "c": 3}}}}', + '$.level1.level2.level3.b' + ) +} {{{"level1":{"level2":{"level3":{"a":1,"c":3}}}}}} + +# Edge cases +do_execsql_test json_remove_empty_object { + SELECT json_remove('{}', '$.anything') +} {{{}}} + +do_execsql_test json_remove_empty_array { + SELECT json_remove('[]', '$[0]') +} {{[]}} + +do_execsql_test json_remove_quoted_key { + SELECT json_remove('{"key.with.dots": 1, "normal": 2}', '$."key.with.dots"') +} {{{"normal":2}}} + +do_execsql_test json_remove_all_properties { + SELECT json_remove('{"a": 1, "b": 2}', '$.a', '$.b') +} {{{}}} + +do_execsql_test json_remove_all_array_elements { + SELECT json_remove('[1, 2, 3]', '$[0]', '$[0]', '$[0]') +} {{[]}} + +do_execsql_test json_remove_root { + SELECT json_remove('{"a": 1}', '$') +} {} + +# Complex example tests + +do_execsql_test json_remove_complex_1 { + SELECT json_remove( + '{"store": {"book": [ + {"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "price": 8.99}, + {"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "price": 22.99} + ], "bicycle": {"color": "red", "price": 19.95}}}', + '$.store.book[0].price', + '$.store.bicycle' + ) +} {{{"store":{"book":[{"category":"fiction","author":"Herman Melville","title":"Moby Dick"},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","price":22.99}]}}}} + +do_execsql_test json_replace_complex_1 { + SELECT json_replace( + '{"store": {"book": [ + {"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "price": 8.99}, + {"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "price": 22.99} + ], "bicycle": {"color": "red", "price": 19.95}}}', + '$.store.book[0].price', 10.99, + '$.store.bicycle.color', 'blue', + '$.store.book[1].title', 'The Hobbit' + ) +} {{{"store":{"book":[{"category":"fiction","author":"Herman Melville","title":"Moby Dick","price":10.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Hobbit","price":22.99}],"bicycle":{"color":"blue","price":19.95}}}}} + +# Combination of replace and remove +do_execsql_test json_replace_after_remove { + SELECT json_replace(json_remove('{"a": 1, "b": 2, "c": 3}', '$.a'), '$.b', 42) +} {{{"b":42,"c":3}}} + +do_execsql_test json_remove_after_replace { + SELECT json_remove(json_replace('{"a": 1, "b": 2, "c": 3}', '$.b', 42), '$.c') +} {{{"a":1,"b":42}}} + +# Tests for idempotence +do_execsql_test json_replace_idempotence { + SELECT json_replace('{"a": 1}', '$.a', 1) +} {{{"a":1}}} + +do_execsql_test json_remove_idempotence { + SELECT json_remove(json_remove('{"a": 1, "b": 2}', '$.a'), '$.a') +} {{{"b":2}}} + +# Compare with extracted values +do_execsql_test json_remove_with_extract { + SELECT json_extract(json_remove('{"a": 1, "b": 2, "c": {"d": 3}}', '$.b'), '$.c.d') +} {{3}} + +do_execsql_test json_replace_with_extract { + SELECT json_extract(json_replace('{"a": 1, "b": 2}', '$.a', 42), '$.a') +} {{42}} + +# Check for consistency between -> operator and json_extract after mutations +do_execsql_test json_replace_with_arrow { + SELECT json_replace('{"a": 1, "b": 2}', '$.a', 42) -> '$.a' +} {{42}} + +do_execsql_test json_remove_with_arrow { + SELECT json_remove('{"a": 1, "b": {"c": 3}}', '$.a') -> '$.b.c' +} {{3}} + # Escape character tests in sqlite source depend on json_valid and in some syntax that is not implemented # yet in limbo. # See https://github.com/sqlite/sqlite/blob/255548562b125e6c148bb27d49aaa01b2fe61dba/test/json102.test#L690