tuple: JSON path update intersection at maps
Previous commits introduced isolated JSON updates, and then allowed intersection at array. This one completes the puzzle, adding intersection at maps, so now both these samples work: Allowed in the previous commit: [1][2][3].a.b.c = 20 [1][2][4].e.f.g = 30 ^ First difference is [3] vs [4] - intersection by an array. Allowed in this commit: [1][2][3].a.b.c = 20 [1][2][3].a.e.f = 30 ^ First difference is 'b' vs 'e' - intersection by a map. Now JSON updates are fully available. Closes #1261 @TarantoolBot document Title: JSON updates Tuple/space/index:update/upsert now support JSON paths. All the same paths as allowed in tuple["..."]. Example: box.cfg{} format = {} format[1] = {'field1', 'unsigned'} format[2] = {'field2', 'map'} format[3] = {'field3', 'array'} format[4] = {'field4', 'string', is_nullable = true} s = box.schema.create_space('test', {format = format}) _ = s:create_index('pk') t = { 1, { key1 = 'value', key2 = 10 }, { 2, 3, {key3 = 20} } } t = s:replace(t) tarantool> t:update({{'=', 'field2.key1', 'new_value'}}) --- - [1, {'key1': 'new_value', 'key2': 10}, [2, 3, {'key3': 20}]] ... tarantool> t:update({{'+', 'field3[2]', 1}}) --- - [1, {'key1': 'value', 'key2': 10}, [2, 4, {'key3': 20}]] ... tarantool> s:update({1}, {{'!', 'field4', 'inserted value'}}) --- - [1, {'key1': 'value', 'key2': 10}, [2, 3, {'key3': 20}], 'inserted value'] ... tarantool> s:update({1}, {{'#', '[2].key2', 1}, {'=', '[3][3].key4', 'value4'}}) --- - [1, {'key1': 'value'}, [2, 3, {'key3': 20, 'key4': 'value4'}], 'inserted value'] ... tarantool> s:upsert({1, {k = 'v'}, {}}, {{'#', '[2].key1', 1}}) --- ... tarantool> s:select{} --- - - [1, {}, [2, 3, {'key3': 20, 'key4': 'value4'}], 'inserted value'] ... Note, that there is the same rule, as in tuple field access by JSON, for field names looking like JSON paths. The rule is that firstly the whole path is interpreted as a field name. If such a name does not exist, then it is treated as a path. For example, if there is a field name 'field.name.like.json', then this update <obj>:update({..., 'field.name.like.json', ...}) will update this field, instead of keys 'field' -> 'name' -> 'like' -> 'json'. If such a name is needed as a part of a bigger path, then it should be wrapped in quotes and []: <obj>:update({..., '["field.name.like.json"].next.fields', ...}) There are some new rules for JSON updates: - Operation '!' can't be used to create all intermediate nodes of a path. For example, {'!', 'field1[1].field3', ...} can't create fields 'field1' and '[1]', they should exist. - Operation '#', when applied to maps, can't delete more than one key at once. That is, its argument should be always 1 for maps. {'#', 'field1.field2', 1} - this is allowed; {'#', 'field1.field2', 10} - this is not. That limitation originates from a problem, that keys in a map are not ordered anyhow, and '#' with more than 1 key would lead to undefined behaviour. - Operation '!' on maps can't create a key, if it exists already. - If a map contains non-string keys (booleans, numbers, maps, arrays - anything), then these keys can't be updated via JSON paths. But it is still allowed to update string keys in such a map. Why JSON updates are good, and should be preferred when only a part of a tuple needs to be updated? - They consume less space in WAL, because for an update only its keys, operations, and arguments are stored. It is cheaper to store update of one deep field, than the whole tuple. - They are faster. Firstly, this is because they are implemented in C, and have no problems with Lua GC and dynamic typing. Secondly, some cases of JSON paths are highly optimized. For example, an update with a single JSON path costs O(1) memory regardless of how deep that path goes (not counting update arguments). - They are available from remote clients, as well as any other DML. Before JSON updates to update one deep part of a tuple it would be necessary to download that tuple, update it in memory, send it back - 2 network hops. With JSON paths it can be 1 when the update can be described in paths.
Showing
- src/box/CMakeLists.txt 1 addition, 0 deletionssrc/box/CMakeLists.txt
- src/box/xrow_update_field.c 4 additions, 0 deletionssrc/box/xrow_update_field.c
- src/box/xrow_update_field.h 84 additions, 1 deletionsrc/box/xrow_update_field.h
- src/box/xrow_update_map.c 453 additions, 0 deletionssrc/box/xrow_update_map.c
- src/box/xrow_update_route.c 46 additions, 3 deletionssrc/box/xrow_update_route.c
- test/box/update.result 152 additions, 0 deletionstest/box/update.result
- test/box/update.test.lua 75 additions, 0 deletionstest/box/update.test.lua
Loading
Please register or sign in to comment