Skip to content

Commit d281e1d

Browse files
savolginsavolgin
andauthored
Support replace and upsert operations (#11)
Co-authored-by: savolgin <[email protected]>
1 parent 8f4b248 commit d281e1d

File tree

11 files changed

+490
-79
lines changed

11 files changed

+490
-79
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
### Added
11+
* CRUD operations:
12+
* replace
13+
* upsert
14+
1015
## [0.1.0] - 2020-09-23
1116

1217
### Added

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,61 @@ crud.delete('customers', 1)
129129
...
130130
```
131131

132+
### Replace
133+
134+
```lua
135+
local object, err = crud.replace(space_name, object, opts)
136+
```
137+
138+
where:
139+
140+
* `space_name` (`string`) - name of the space
141+
* `object` (`table`) - object to insert or replace exist one
142+
* `opts`:
143+
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
144+
145+
Returns inserted or replaced object, error.
146+
147+
**Example:**
148+
149+
```lua
150+
crud.replace('customers', {
151+
id = 1, name = 'Alice', age = 22,
152+
})
153+
---
154+
- bucket_id: 7614
155+
age: 22
156+
name: Alice
157+
id: 1
158+
...
159+
```
160+
161+
### Upsert
162+
163+
```lua
164+
local object, err = crud.upsert(space_name, object, operations, opts)
165+
```
166+
167+
where:
168+
169+
* `space_name` (`string`) - name of the space
170+
* `object` (`table`) - object to insert if there is no existing tuple which matches the key fields
171+
* `operations` (`table`) - update [operations](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/#box-space-update) if there is an existing tuple which matches the key fields of tuple
172+
* `opts`:
173+
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
174+
175+
Returns nil, error.
176+
177+
**Example:**
178+
179+
```lua
180+
crud.upsert('customers', {id = 1, name = 'Alice', age = 22,}, {{'+', 'age', 1}})
181+
---
182+
- nil
183+
...
184+
```
185+
186+
132187
### Select
133188

134189
`CRUD` supports multi-conditional selects, treating a cluster as a single space.

crud.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
local registry = require('crud.common.registry')
66
local call = require('crud.common.call')
77
local insert = require('crud.insert')
8+
local replace = require('crud.replace')
89
local get = require('crud.get')
910
local update = require('crud.update')
11+
local upsert = require('crud.upsert')
1012
local delete = require('crud.delete')
1113
local select = require('crud.select')
1214

@@ -30,10 +32,18 @@ crud.insert = insert.call
3032
-- @function get
3133
crud.get = get.call
3234

35+
-- @refer replace.call
36+
-- @function replace
37+
crud.replace = replace.call
38+
3339
-- @refer update.call
3440
-- @function update
3541
crud.update = update.call
3642

43+
-- @refer upsert.call
44+
-- @function upsert
45+
crud.upsert = upsert.call
46+
3747
-- @refer delete.call
3848
-- @function delete
3949
crud.delete = delete.call
@@ -57,7 +67,9 @@ function crud.init()
5767
call.init()
5868
insert.init()
5969
get.init()
70+
replace.init()
6071
update.init()
72+
upsert.init()
6173
delete.init()
6274
select.init()
6375
end

crud/common/utils.lua

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local errors = require('errors')
33

44
local FlattenError = errors.new_class("FlattenError", {capture_stack = false})
55
local UnflattenError = errors.new_class("UnflattenError", {capture_stack = false})
6+
local ParseOperationsError = errors.new_class('ParseOperationsError', {capture_stack = false})
67

78
local utils = {}
89

@@ -35,7 +36,7 @@ end
3536

3637
local system_fields = { bucket_id = true }
3738

38-
function utils.flatten(object, space_format)
39+
function utils.flatten(object, space_format, bucket_id)
3940
if object == nil then return nil end
4041

4142
local tuple = {}
@@ -49,6 +50,10 @@ function utils.flatten(object, space_format)
4950
end
5051
end
5152

53+
if bucket_id ~= nil and field_format.name == 'bucket_id' then
54+
value = bucket_id
55+
end
56+
5257
tuple[fieldno] = value
5358
end
5459

@@ -99,4 +104,56 @@ function utils.merge_primary_key_parts(key_parts, pk_parts)
99104
return merged_parts
100105
end
101106

107+
local __tarantool_supports_fieldpaths
108+
local function tarantool_supports_fieldpaths()
109+
if __tarantool_supports_fieldpaths ~= nil then
110+
return __tarantool_supports_fieldpaths
111+
end
112+
113+
local major_minor_patch = _G._TARANTOOL:split('-', 1)[1]
114+
local major_minor_patch_parts = major_minor_patch:split('.', 2)
115+
116+
local major = tonumber(major_minor_patch_parts[1])
117+
local minor = tonumber(major_minor_patch_parts[2])
118+
local patch = tonumber(major_minor_patch_parts[3])
119+
120+
-- since Tarantool 2.3
121+
__tarantool_supports_fieldpaths = major >= 2 and (minor > 3 or minor == 3 and patch >= 1)
122+
123+
return __tarantool_supports_fieldpaths
124+
end
125+
126+
function utils.convert_operations(user_operations, space_format)
127+
if tarantool_supports_fieldpaths() then
128+
return user_operations
129+
end
130+
131+
local converted_operations = {}
132+
133+
for _, operation in ipairs(user_operations) do
134+
if type(operation[2]) == 'string' then
135+
local field_id
136+
for fieldno, field_format in ipairs(space_format) do
137+
if field_format.name == operation[2] then
138+
field_id = fieldno
139+
break
140+
end
141+
end
142+
143+
if field_id == nil then
144+
return nil, ParseOperationsError:new(
145+
"Space format doesn't contain field named %q", operation[2])
146+
end
147+
148+
table.insert(converted_operations, {
149+
operation[1], field_id, operation[3]
150+
})
151+
else
152+
table.insert(converted_operations, operation)
153+
end
154+
end
155+
156+
return converted_operations
157+
end
158+
102159
return utils

crud/insert.lua

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ function insert.call(space_name, obj, opts)
6262
if space == nil then
6363
return nil, InsertError:new("Space %q doesn't exists", space_name)
6464
end
65+
local space_format = space:format()
6566

6667
-- compute default buckect_id
67-
local tuple, err = utils.flatten(obj, space:format(), true)
68+
local tuple, err = utils.flatten(obj, space_format)
6869
if err ~= nil then
6970
return nil, InsertError:new("Object is specified in bad format: %s", err)
7071
end
@@ -77,10 +78,7 @@ function insert.call(space_name, obj, opts)
7778
return nil, InsertError:new("Failed to get replicaset for bucket_id %s: %s", bucket_id, err.err)
7879
end
7980

80-
obj = table.copy(obj)
81-
obj.bucket_id = bucket_id
82-
83-
local tuple, err = utils.flatten(obj, space:format())
81+
local tuple, err = utils.flatten(obj, space_format, bucket_id)
8482
if err ~= nil then
8583
return nil, InsertError:new("Object is specified in bad format: %s", err)
8684
end
@@ -95,7 +93,7 @@ function insert.call(space_name, obj, opts)
9593
end
9694

9795
local tuple = results[replicaset.uuid]
98-
local object, err = utils.unflatten(tuple, space:format())
96+
local object, err = utils.unflatten(tuple, space_format)
9997
if err ~= nil then
10098
return nil, InsertError:new("Received tuple that doesn't match space format: %s", err)
10199
end

crud/replace.lua

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
local checks = require('checks')
2+
local errors = require('errors')
3+
local vshard = require('vshard')
4+
5+
local call = require('crud.common.call')
6+
local registry = require('crud.common.registry')
7+
local utils = require('crud.common.utils')
8+
9+
require('crud.common.checkers')
10+
11+
local ReplaceError = errors.new_class('Replace', { capture_stack = false })
12+
13+
local replace = {}
14+
15+
local REPLACE_FUNC_NAME = '__replace'
16+
17+
local function call_replace_on_storage(space_name, tuple)
18+
checks('string', 'table')
19+
20+
local space = box.space[space_name]
21+
if space == nil then
22+
return nil, ReplaceError:new("Space %q doesn't exists", space_name)
23+
end
24+
25+
return space:replace(tuple)
26+
end
27+
28+
function replace.init()
29+
registry.add({
30+
[REPLACE_FUNC_NAME] = call_replace_on_storage,
31+
})
32+
end
33+
34+
--- Insert or replace a tuple in the specifed space
35+
--
36+
-- @function call
37+
--
38+
-- @param string space_name
39+
-- A space name
40+
--
41+
-- @param table obj
42+
-- Tuple object (according to space format)
43+
--
44+
-- @tparam ?number opts.timeout
45+
-- Function call timeout
46+
--
47+
-- @return[1] object
48+
-- @treturn[2] nil
49+
-- @treturn[2] table Error description
50+
--
51+
function replace.call(space_name, obj, opts)
52+
checks('string', 'table', {
53+
timeout = '?number',
54+
})
55+
56+
opts = opts or {}
57+
58+
local space = utils.get_space(space_name, vshard.router.routeall())
59+
if space == nil then
60+
return nil, ReplaceError:new("Space %q doesn't exists", space_name)
61+
end
62+
63+
local space_format = space:format()
64+
-- compute default buckect_id
65+
local tuple, err = utils.flatten(obj, space_format)
66+
if err ~= nil then
67+
return nil, ReplaceError:new("Object is specified in bad format: %s", err)
68+
end
69+
70+
local key = utils.extract_key(tuple, space.index[0].parts)
71+
72+
local bucket_id = vshard.router.bucket_id_strcrc32(key)
73+
local replicaset, err = vshard.router.route(bucket_id)
74+
if replicaset == nil then
75+
return nil, ReplaceError:new("Failed to get replicaset for bucket_id %s: %s", bucket_id, err.err)
76+
end
77+
78+
local tuple, err = utils.flatten(obj, space_format, bucket_id)
79+
if err ~= nil then
80+
return nil, ReplaceError:new("Object is specified in bad format: %s", err)
81+
end
82+
83+
local results, err = call.rw(REPLACE_FUNC_NAME, {space_name, tuple}, {
84+
replicasets = {replicaset},
85+
timeout = opts.timeout,
86+
})
87+
88+
if err ~= nil then
89+
return nil, ReplaceError:new("Failed to replace: %s", err)
90+
end
91+
92+
local tuple = results[replicaset.uuid]
93+
local object, err = utils.unflatten(tuple, space_format)
94+
if err ~= nil then
95+
return nil, ReplaceError:new("Received tuple that doesn't match space format: %s", err)
96+
end
97+
98+
return object
99+
end
100+
101+
return replace

0 commit comments

Comments
 (0)