Skip to content

Commit 7504da3

Browse files
api: option to skip nullability check on flatten
Add `skip_nullability_check_on_flatten` option for `insert_object`, `insert_object_many`, `replace_object`, `replace_object_many`. `false` by default. By setting the option to `true` one allows setting null values to non-nullable fields, which can be useful if non-nullable field value is generated by sequence [1]. **Warning**: there is no native support of sequences in sharded systems since each replicaset has its own sequence. If sequence field is a part of the sharding key (which is true by default), choosing the bucket id is the sole responsibility of the developer (#328). The option wasn't added to `upsert_object*` methods since upsert operation doesn't support sequence generated fields: ``` box.schema.space.create('seq', { format = { {name = 'id', type = 'unsigned'}, {name = 'payload', type = 'string'}, }, }) box.schema.sequence.create('id', {if_not_exists = true}) box.space.seq:create_index('id', { parts = {{field = 'id'}}, unique = true, sequence = 'id' } ) box.space.seq:upsert({nil, 'payload'}, {{'=', 'payload', 'payload'}}) ``` ``` --- - error: 'Tuple field 1 (id) type does not match one required by operation: expected unsigned, got nil' ... ``` Refer to #328 discussion regarding solution design. 1. https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/ Closes #328
1 parent 704bd6f commit 7504da3

File tree

10 files changed

+253
-14
lines changed

10 files changed

+253
-14
lines changed

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
* `skip_nullability_check_on_flatten` option for `insert_object`,
12+
`insert_object_many`, `replace_object`, `replace_object_many`.
13+
`false` by default. By setting the option to `true` you allow
14+
setting null values to non-nullable fields, which can be useful
15+
if non-nullable field value is generated by
16+
[sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).
17+
**Warning**: there is no native support for sequences in sharded systems
18+
since each replicaset has its own sequence. If sequence field is a part
19+
of the sharding key (which is true by default), choosing the bucket id is
20+
the sole responsibility of the developer (#328).
21+
1022
### Changed
1123
* Rework `NonInitialized` error message to be more helpful for
1224
troubleshooting (#326).

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ where:
210210
* `vshard_router` (`?string|table`) - Cartridge vshard group name or
211211
vshard router instance. Set this parameter if your space is not
212212
a part of the default vshard cluster
213+
* `skip_nullability_check_on_flatten` (`?boolean`) - option for
214+
`insert_object` only. `false` by default. Set this parameter to
215+
`true` if you want to allow setting null values to non-nullable
216+
fields, which can be useful if non-nullable field value is generated by
217+
[sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).
218+
**Warning**: there is no native support for sequences in sharded systems
219+
since each replicaset has its own sequence. If sequence field is a part
220+
of the sharding key (which is true by default), choosing the bucket id is
221+
the sole responsibility of the developer
213222

214223
Returns metadata and array contains one inserted row, error.
215224

@@ -265,6 +274,15 @@ where:
265274
* `vshard_router` (`?string|table`) - Cartridge vshard group name or
266275
vshard router instance. Set this parameter if your space is not
267276
a part of the default vshard cluster
277+
* `skip_nullability_check_on_flatten` (`?boolean`) - option for
278+
`insert_object_many` only. `false` by default. Set this parameter to
279+
`true` if you want to allow setting null values to non-nullable
280+
fields, which can be useful if non-nullable field value is generated by
281+
[sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).
282+
**Warning**: there is no native support for sequences in sharded systems
283+
since each replicaset has its own sequence. If sequence field is a part
284+
of the sharding key (which is true by default), choosing the bucket id is
285+
the sole responsibility of the developer
268286

269287
Returns metadata and array with inserted rows, array of errors.
270288
Each error object can contain field `operation_data`.
@@ -510,6 +528,15 @@ where:
510528
* `vshard_router` (`?string|table`) - Cartridge vshard group name or
511529
vshard router instance. Set this parameter if your space is not
512530
a part of the default vshard cluster
531+
* `skip_nullability_check_on_flatten` (`?boolean`) - option for
532+
`replace_object` only. `false` by default. Set this parameter to
533+
`true` if you want to allow setting null values to non-nullable
534+
fields, which can be useful if non-nullable field value is generated by
535+
[sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).
536+
**Warning**: there is no native support for sequences in sharded systems
537+
since each replicaset has its own sequence. If sequence field is a part
538+
of the sharding key (which is true by default), choosing the bucket id is
539+
the sole responsibility of the developer
513540

514541
Returns inserted or replaced rows and metadata or nil with error.
515542

@@ -565,6 +592,15 @@ where:
565592
* `vshard_router` (`?string|table`) - Cartridge vshard group name or
566593
vshard router instance. Set this parameter if your space is not
567594
a part of the default vshard cluster
595+
* `skip_nullability_check_on_flatten` (`?boolean`) - option for
596+
`replace_object_many` only. `false` by default. Set this parameter to
597+
`true` if you want to allow setting null values to non-nullable
598+
fields, which can be useful if non-nullable field value is generated by
599+
[sequence](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_schema_sequence/create_index/).
600+
**Warning**: there is no native support for sequences in sharded systems
601+
since each replicaset has its own sequence. If sequence field is a part
602+
of the sharding key (which is true by default), choosing the bucket id is
603+
the sole responsibility of the developer
568604

569605
Returns metadata and array with inserted/replaced rows, array of errors.
570606
Each error object can contain field `operation_data`.

crud/common/utils.lua

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,18 @@ end
119119

120120
local flatten_functions_cache = setmetatable({}, {__mode = 'k'})
121121

122-
function utils.flatten(object, space_format, bucket_id)
122+
function utils.flatten(object, space_format, bucket_id, skip_nullability_check)
123123
local flatten_func = flatten_functions_cache[space_format]
124124
if flatten_func ~= nil then
125-
local data, err = flatten_func(object, bucket_id)
125+
local data, err = flatten_func(object, bucket_id, skip_nullability_check)
126126
if err ~= nil then
127127
return nil, FlattenError:new(err)
128128
end
129129
return data
130130
end
131131

132132
local lines = {}
133-
append(lines, 'local object, bucket_id = ...')
133+
append(lines, 'local object, bucket_id, skip_nullability_check = ...')
134134

135135
append(lines, 'for k in pairs(object) do')
136136
append(lines, ' if fieldmap[k] == nil then')
@@ -149,8 +149,10 @@ function utils.flatten(object, space_format, bucket_id)
149149
append(lines, 'if object[%q] ~= nil then', field.name)
150150
append(lines, ' result[%d] = object[%q]', i, field.name)
151151
if field.is_nullable ~= true then
152-
append(lines, 'else')
153-
append(lines, ' return nil, \'Field %q isn\\\'t nullable\'', field.name)
152+
append(lines, 'elseif skip_nullability_check ~= true then')
153+
append(lines, ' return nil, \'Field %q isn\\\'t nullable' ..
154+
' (set skip_nullability_check_on_flatten option to true to skip check)\'',
155+
field.name)
154156
end
155157
append(lines, 'end')
156158
else
@@ -173,7 +175,7 @@ function utils.flatten(object, space_format, bucket_id)
173175
flatten_func = assert(load(code, '@flatten', 't', env))
174176

175177
flatten_functions_cache[space_format] = flatten_func
176-
local data, err = flatten_func(object, bucket_id)
178+
local data, err = flatten_func(object, bucket_id, skip_nullability_check)
177179
if err ~= nil then
178180
return nil, FlattenError:new(err)
179181
end
@@ -636,22 +638,22 @@ function utils.cut_rows(rows, metadata, field_names)
636638
}
637639
end
638640

639-
local function flatten_obj(vshard_router, space_name, obj)
641+
local function flatten_obj(vshard_router, space_name, obj, skip_nullability_check)
640642
local space_format, err = utils.get_space_format(space_name, vshard_router:routeall())
641643
if err ~= nil then
642644
return nil, FlattenError:new("Failed to get space format: %s", err), const.NEED_SCHEMA_RELOAD
643645
end
644646

645-
local tuple, err = utils.flatten(obj, space_format)
647+
local tuple, err = utils.flatten(obj, space_format, nil, skip_nullability_check)
646648
if err ~= nil then
647649
return nil, FlattenError:new("Object is specified in bad format: %s", err), const.NEED_SCHEMA_RELOAD
648650
end
649651

650652
return tuple
651653
end
652654

653-
function utils.flatten_obj_reload(vshard_router, space_name, obj)
654-
return schema.wrap_func_reload(vshard_router, flatten_obj, space_name, obj)
655+
function utils.flatten_obj_reload(vshard_router, space_name, obj, skip_nullability_check)
656+
return schema.wrap_func_reload(vshard_router, flatten_obj, space_name, obj, skip_nullability_check)
655657
end
656658

657659
-- Merge two options map.

crud/insert.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ function insert.object(space_name, obj, opts)
190190
add_space_schema_hash = '?boolean',
191191
fields = '?table',
192192
vshard_router = '?string|table',
193+
skip_nullability_check_on_flatten = '?boolean',
193194
})
194195

195196
opts = opts or {}
@@ -202,7 +203,8 @@ function insert.object(space_name, obj, opts)
202203
-- insert can fail if router uses outdated schema to flatten object
203204
opts = utils.merge_options(opts, {add_space_schema_hash = true})
204205

205-
local tuple, err = utils.flatten_obj_reload(vshard_router, space_name, obj)
206+
local tuple, err = utils.flatten_obj_reload(vshard_router, space_name, obj,
207+
opts.skip_nullability_check_on_flatten)
206208
if err ~= nil then
207209
return nil, InsertError:new("Failed to flatten object: %s", err)
208210
end

crud/insert_many.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ function insert_many.objects(space_name, objs, opts)
251251
stop_on_error = '?boolean',
252252
rollback_on_error = '?boolean',
253253
vshard_router = '?string|table',
254+
skip_nullability_check_on_flatten = '?boolean',
254255
})
255256

256257
opts = opts or {}
@@ -268,7 +269,8 @@ function insert_many.objects(space_name, objs, opts)
268269

269270
for _, obj in ipairs(objs) do
270271

271-
local tuple, err = utils.flatten_obj_reload(vshard_router, space_name, obj)
272+
local tuple, err = utils.flatten_obj_reload(vshard_router, space_name, obj,
273+
opts.skip_nullability_check_on_flatten)
272274
if err ~= nil then
273275
local err_obj = InsertManyError:new("Failed to flatten object: %s", err)
274276
err_obj.operation_data = obj

crud/replace.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ function replace.object(space_name, obj, opts)
193193
add_space_schema_hash = '?boolean',
194194
fields = '?table',
195195
vshard_router = '?string|table',
196+
skip_nullability_check_on_flatten = '?boolean',
196197
})
197198

198199
opts = opts or {}
@@ -205,7 +206,8 @@ function replace.object(space_name, obj, opts)
205206
-- replace can fail if router uses outdated schema to flatten object
206207
opts = utils.merge_options(opts, {add_space_schema_hash = true})
207208

208-
local tuple, err = utils.flatten_obj_reload(vshard_router, space_name, obj)
209+
local tuple, err = utils.flatten_obj_reload(vshard_router, space_name, obj,
210+
opts.skip_nullability_check_on_flatten)
209211
if err ~= nil then
210212
return nil, ReplaceError:new("Failed to flatten object: %s", err)
211213
end

crud/replace_many.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ function replace_many.objects(space_name, objs, opts)
253253
stop_on_error = '?boolean',
254254
rollback_on_error = '?boolean',
255255
vshard_router = '?string|table',
256+
skip_nullability_check_on_flatten = '?boolean',
256257
})
257258

258259
opts = opts or {}
@@ -270,7 +271,8 @@ function replace_many.objects(space_name, objs, opts)
270271

271272
for _, obj in ipairs(objs) do
272273

273-
local tuple, err = utils.flatten_obj_reload(vshard_router, space_name, obj)
274+
local tuple, err = utils.flatten_obj_reload(vshard_router, space_name, obj,
275+
opts.skip_nullability_check_on_flatten)
274276
if err ~= nil then
275277
local err_obj = ReplaceManyError:new("Failed to flatten object: %s", err)
276278
err_obj.operation_data = obj

test/entrypoint/srv_simple_operations.lua

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,30 @@ package.preload['customers-storage'] = function()
8888
unique = false,
8989
if_not_exists = true,
9090
})
91+
92+
local sequence_space = box.schema.space.create('notebook', {
93+
format = {
94+
{name = 'local_id', type = 'unsigned', is_nullable = false},
95+
{name = 'bucket_id', type = 'unsigned', is_nullable = false},
96+
{name = 'record', type = 'string', is_nullable = false},
97+
},
98+
if_not_exists = true,
99+
engine = engine,
100+
})
101+
102+
box.schema.sequence.create('local_id', {if_not_exists = true})
103+
104+
sequence_space:create_index('local_id', {
105+
parts = { {field = 'local_id'} },
106+
unique = true,
107+
if_not_exists = true,
108+
sequence = 'local_id',
109+
})
110+
sequence_space:create_index('bucket_id', {
111+
parts = { {field = 'bucket_id'} },
112+
unique = false,
113+
if_not_exists = true,
114+
})
91115
end,
92116
}
93117
end

test/helper.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,19 @@ function helpers.truncate_space_on_cluster(cluster, space_name)
141141
end
142142
end
143143

144+
function helpers.reset_sequence_on_cluster(cluster, sequence_name)
145+
assert(cluster ~= nil)
146+
for _, server in ipairs(cluster.servers) do
147+
server.net_box:eval([[
148+
local sequence_name = ...
149+
local sequence = box.sequence[sequence_name]
150+
if sequence ~= nil and not box.cfg.read_only then
151+
sequence:reset()
152+
end
153+
]], {sequence_name})
154+
end
155+
end
156+
144157
function helpers.get_test_replicasets()
145158
return {
146159
{

0 commit comments

Comments
 (0)