diff --git a/CHANGELOG.md b/CHANGELOG.md index 1796a4731..21f1c9253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added +* Batch insert/upsert operation + `crud.insert_many()`/`crud.insert_object_many()`/ + `crud.upsert_many()`/`crud.upsert_object_many()` + `crud.replace_many()`/`crud.replace_object_many()` + with partial consistency ### Changed diff --git a/README.md b/README.md index 7a165f24c..84da5da61 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ It also provides the `crud-storage` and `crud-router` roles for - [Update](#update) - [Delete](#delete) - [Replace](#replace) + - [Replace many](#replace-many) - [Upsert](#upsert) - [Upsert many](#upsert-many) - [Select](#select) @@ -477,6 +478,101 @@ crud.replace_object('customers', { ... ``` +### Replace many + +```lua +-- Replace batch of tuples +local result, err = crud.replace_many(space_name, tuples, opts) +-- Replace batch of objects +local result, err = crud.replace_object_many(space_name, objects, opts) +``` + +where: + +* `space_name` (`string`) - name of the space to insert/replace an object +* `tuples` / `objects` (`table`) - array of tuples/objects to insert +* `opts`: + * `timeout` (`?number`) - `vshard.call` timeout (in seconds) + * `fields` (`?table`) - field names for getting only a subset of fields + * `stop_on_error` (`?boolean`) - stop on a first error and report error + regarding the failed operation and error about what tuples were not + performed, default is `false` + * `rollback_on_error` (`?boolean`) - any failed operation will lead to + rollback on a storage, where the operation is failed, report error + about what tuples were rollback, default is `false` + +Returns metadata and array contains inserted rows, array of errors. +Error object can contain field `operation_data`. + +This field can contain: +* tuple for which the error occurred; +* object with an incorrect format; +* tuple the operation on which was performed but + operation was rollback; +* tuple the operation on which was not performed + because operation was stopped by error. + +Right now CRUD cannot provide batch replace with full consistency. +CRUD offers batch replace with partial consistency. That means +that full consistency can be provided only on single replicaset +using `box` transactions. + +**Example:** + +```lua +crud.replace_many('developers', { + {1, box.NULL, 'Elizabeth', 'lizaaa'}, + {2, box.NULL, 'Anastasia', 'iamnewdeveloper'}, +}) +--- +- metadata: + - {'name': 'id', 'type': 'unsigned'} + - {'name': 'bucket_id', 'type': 'unsigned'} + - {'name': 'name', 'type': 'string'} + - {'name': 'login', 'type': 'string'} + rows: + - [1, 477, 'Elizabeth', 'lizaaa'] + - [2, 401, 'Anastasia', 'iamnewdeveloper'] +... +crud.replace_object_many('developers', { + {id = 1, name = 'Inga', login = 'mylogin'}, + {id = 10, name = 'Anastasia', login = 'qwerty'}, +}) +--- +- metadata: + - {'name': 'id', 'type': 'unsigned'} + - {'name': 'bucket_id', 'type': 'unsigned'} + - {'name': 'name', 'type': 'string'} + - {'name': 'age', 'type': 'number'} + rows: + - [1, 477, 'Inga', 'mylogin'] + - [10, 569, 'Anastasia', 'qwerty'] + +-- Partial success +-- Let's say login has unique secondary index +local res, errs = crud.replace_object_many('developers', { + {id = 22, name = 'Alex', login = 'pushkinn'}, + {id = 3, name = 'Anastasia', login = 'qwerty'}, + {id = 5, name = 'Sergey', login = 's.petrenko'}, +}) +--- +res +- metadata: + - {'name': 'id', 'type': 'unsigned'} + - {'name': 'bucket_id', 'type': 'unsigned'} + - {'name': 'name', 'type': 'string'} + - {'name': 'age', 'type': 'number'} + rows: + - [5, 1172, 'Sergey', 'crudisthebest'], + - [22, 655, 'Alex', 'pushkinn'], + +#errs -- 1 +errs[1].class_name -- ReplaceManyError +errs[1].err -- 'Duplicate key exists <...>' +errs[1].tuple -- {3, 2804, 'Anastasia', 'qwerty'} +... +``` + ### Upsert ```lua diff --git a/crud.lua b/crud.lua index 82665de35..d450884ac 100644 --- a/crud.lua +++ b/crud.lua @@ -6,6 +6,7 @@ local cfg = require('crud.cfg') local insert = require('crud.insert') local insert_many = require('crud.insert_many') local replace = require('crud.replace') +local replace_many = require('crud.replace_many') local get = require('crud.get') local update = require('crud.update') local upsert = require('crud.upsert') @@ -53,6 +54,14 @@ crud.replace = stats.wrap(replace.tuple, stats.op.REPLACE) -- @function replace_object crud.replace_object = stats.wrap(replace.object, stats.op.REPLACE) +-- @refer replace_many.tuples +-- @function replace_many +crud.replace_many = replace_many.tuples + +-- @refer replace_many.objects +-- @function replace_object_many +crud.replace_object_many = replace_many.objects + -- @refer update.call -- @function update crud.update = stats.wrap(update.call, stats.op.UPDATE) @@ -145,6 +154,7 @@ function crud.init_storage() insert_many.init() get.init() replace.init() + replace_many.init() update.init() upsert.init() upsert_many.init() diff --git a/crud/replace_many.lua b/crud/replace_many.lua new file mode 100644 index 000000000..1321ed420 --- /dev/null +++ b/crud/replace_many.lua @@ -0,0 +1,290 @@ +local checks = require('checks') +local errors = require('errors') +local vshard = require('vshard') + +local call = require('crud.common.call') +local const = require('crud.common.const') +local utils = require('crud.common.utils') +local batching_utils = require('crud.common.batching_utils') +local sharding = require('crud.common.sharding') +local dev_checks = require('crud.common.dev_checks') +local schema = require('crud.common.schema') + +local BatchInsertIterator = require('crud.common.map_call_cases.batch_insert_iter') +local BatchPostprocessor = require('crud.common.map_call_cases.batch_postprocessor') + +local ReplaceManyError = errors.new_class('ReplaceManyError', {capture_stack = false}) + +local replace_many = {} + +local REPLACE_MANY_FUNC_NAME = '_crud.replace_many_on_storage' + +local function replace_many_on_storage(space_name, tuples, opts) + dev_checks('string', 'table', { + add_space_schema_hash = '?boolean', + fields = '?table', + stop_on_error = '?boolean', + rollback_on_error = '?boolean', + sharding_key_hash = '?number', + sharding_func_hash = '?number', + skip_sharding_hash_check = '?boolean', + }) + + opts = opts or {} + + local space = box.space[space_name] + if space == nil then + return nil, {ReplaceManyError:new("Space %q doesn't exist", space_name)} + end + + local _, err = sharding.check_sharding_hash(space_name, + opts.sharding_func_hash, + opts.sharding_key_hash, + opts.skip_sharding_hash_check) + + if err ~= nil then + return nil, batching_utils.construct_sharding_hash_mismatch_errors(err.err, tuples) + end + + local inserted_tuples = {} + local errs = {} + + box.begin() + for i, tuple in ipairs(tuples) do + -- add_space_schema_hash is true only in case of replace_object_many + -- the only one case when reloading schema can avoid replace error + -- is flattening object on router + local insert_result = schema.wrap_box_space_func_result(space, 'replace', {tuple}, { + add_space_schema_hash = opts.add_space_schema_hash, + field_names = opts.fields, + }) + + table.insert(errs, err) + + if insert_result.err ~= nil then + local err = { + err = insert_result.err, + space_schema_hash = insert_result.space_schema_hash, + operation_data = tuple, + } + + table.insert(errs, err) + + if opts.stop_on_error == true then + local left_tuples = utils.list_slice(tuples, i + 1) + if next(left_tuples) then + errs = batching_utils.complement_batching_errors(errs, + batching_utils.stop_on_error_msg, left_tuples) + end + + if opts.rollback_on_error == true then + box.rollback() + if next(inserted_tuples) then + errs = batching_utils.complement_batching_errors(errs, + batching_utils.rollback_on_error_msg, inserted_tuples) + end + + return nil, errs + end + + box.commit() + + return inserted_tuples, errs + end + end + + table.insert(inserted_tuples, insert_result.res) + end + + if next(errs) ~= nil then + if opts.rollback_on_error == true then + box.rollback() + if next(inserted_tuples) then + errs = batching_utils.complement_batching_errors(errs, + batching_utils.rollback_on_error_msg, inserted_tuples) + end + + return nil, errs + end + + box.commit() + + return inserted_tuples, errs + end + + box.commit() + + return inserted_tuples +end + +function replace_many.init() + _G._crud.replace_many_on_storage = replace_many_on_storage +end + +-- returns result, err, need_reload +-- need_reload indicates if reloading schema could help +-- see crud.common.schema.wrap_func_reload() +local function call_replace_many_on_router(space_name, original_tuples, opts) + dev_checks('string', 'table', { + timeout = '?number', + fields = '?table', + add_space_schema_hash = '?boolean', + stop_on_error = '?boolean', + rollback_on_error = '?boolean', + }) + + opts = opts or {} + + local space = utils.get_space(space_name, vshard.router.routeall()) + if space == nil then + return nil, {ReplaceManyError:new("Space %q doesn't exist", space_name)}, const.NEED_SCHEMA_RELOAD + end + + local tuples = table.deepcopy(original_tuples) + + local replace_many_on_storage_opts = { + add_space_schema_hash = opts.add_space_schema_hash, + fields = opts.fields, + stop_on_error = opts.stop_on_error, + rollback_on_error = opts.rollback_on_error, + } + + local iter, err = BatchInsertIterator:new({ + tuples = tuples, + space = space, + execute_on_storage_opts = replace_many_on_storage_opts, + }) + if err ~= nil then + return nil, {err}, const.NEED_SCHEMA_RELOAD + end + + local postprocessor = BatchPostprocessor:new() + + local rows, errs = call.map(REPLACE_MANY_FUNC_NAME, nil, { + timeout = opts.timeout, + mode = 'write', + iter = iter, + postprocessor = postprocessor, + }) + + if errs ~= nil then + local tuples_count = utils.table_count(tuples) + if sharding.batching_result_needs_sharding_reload(errs, tuples_count) then + return nil, errs, const.NEED_SHARDING_RELOAD + end + + if schema.batching_result_needs_reload(space, errs, tuples_count) then + return nil, errs, const.NEED_SCHEMA_RELOAD + end + end + + if next(rows) == nil then + return nil, errs + end + + local res, err = utils.format_result(rows, space, opts.fields) + if err ~= nil then + errs = errs or {} + table.insert(errs, err) + return nil, errs + end + + return res, errs +end + +--- Replace batch of tuples to the specified space +-- +-- @function tuples +-- +-- @param string space_name +-- A space name +-- +-- @param table tuples +-- Tuples +-- +-- @tparam ?table opts +-- Options of batch_replace.tuples_batch +-- +-- @return[1] tuples +-- @treturn[2] nil +-- @treturn[2] table of tables Error description + +function replace_many.tuples(space_name, tuples, opts) + checks('string', 'table', { + timeout = '?number', + fields = '?table', + add_space_schema_hash = '?boolean', + stop_on_error = '?boolean', + rollback_on_error = '?boolean', + }) + + return schema.wrap_func_reload(sharding.wrap_method, + call_replace_many_on_router, space_name, tuples, opts) +end + +--- Replace batch of objects to the specified space +-- +-- @function objects +-- +-- @param string space_name +-- A space name +-- +-- @param table objs +-- Objects +-- +-- @tparam ?table opts +-- Options of batch_insert.tuples_batch +-- +-- @return[1] objects +-- @treturn[2] nil +-- @treturn[2] table of tables Error description + +function replace_many.objects(space_name, objs, opts) + checks('string', 'table', { + timeout = '?number', + fields = '?table', + stop_on_error = '?boolean', + rollback_on_error = '?boolean', + }) + + -- insert can fail if router uses outdated schema to flatten object + opts = utils.merge_options(opts, {add_space_schema_hash = true}) + + local tuples = {} + local format_errs = {} + + for _, obj in ipairs(objs) do + + local tuple, err = utils.flatten_obj_reload(space_name, obj) + if err ~= nil then + local err_obj = ReplaceManyError:new("Failed to flatten object: %s", err) + err_obj.operation_data = obj + + if opts.stop_on_error == true then + return nil, {err_obj} + end + + table.insert(format_errs, err_obj) + end + + table.insert(tuples, tuple) + end + + if next(tuples) == nil then + return nil, format_errs + end + + local res, errs = replace_many.tuples(space_name, tuples, opts) + + if next(format_errs) ~= nil then + if errs == nil then + errs = format_errs + else + errs = utils.list_extend(errs, format_errs) + end + end + + return res, errs +end + +return replace_many diff --git a/test/entrypoint/srv_batch_operations.lua b/test/entrypoint/srv_batch_operations.lua index 24aa05f3e..c95ae749b 100755 --- a/test/entrypoint/srv_batch_operations.lua +++ b/test/entrypoint/srv_batch_operations.lua @@ -31,6 +31,31 @@ package.preload['customers-storage'] = function() unique = false, if_not_exists = true, }) + + local developers_space = box.schema.space.create('developers', { + format = { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }, + if_not_exists = true, + engine = engine, + }) + developers_space:create_index('id', { + parts = { {field = 'id'} }, + if_not_exists = true, + }) + developers_space:create_index('bucket_id', { + parts = { {field = 'bucket_id'} }, + unique = false, + if_not_exists = true, + }) + developers_space:create_index('login', { + parts = { {field = 'login'} }, + unique = true, + if_not_exists = true, + }) end, } end diff --git a/test/integration/ddl_sharding_func_test.lua b/test/integration/ddl_sharding_func_test.lua index f5c8cc996..a38990773 100644 --- a/test/integration/ddl_sharding_func_test.lua +++ b/test/integration/ddl_sharding_func_test.lua @@ -245,6 +245,74 @@ pgroup.test_replace = function(g) t.assert_equals(result, {71, 1, 'Augustus', 21}) end +pgroup.test_replace_object_many = function(g) + -- bucket_id is 596, storage is s-2 + local tuple = {8, 596, 'Dimitrion', 20} + + -- Put tuple to s1 replicaset. + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space[g.params.space_name]:insert(tuple) + t.assert_not_equals(result, nil) + + -- Put tuple to s2 replicaset. + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space[g.params.space_name]:insert(tuple) + t.assert_not_equals(result, nil) + + -- Replace an object. + local result, err = g.cluster.main_server.net_box:call( + 'crud.replace_object_many', {g.params.space_name, {{id = 8, name = 'John Doe', age = 25}}}) + t.assert_equals(err, nil) + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_equals(objects, {{id = 8, bucket_id = 8, name = 'John Doe', age = 25}}) + + -- There is no replaced tuple on s1 replicaset. + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space[g.params.space_name]:get({8, 'John Doe'}) + t.assert_equals(result, nil) + + -- There is replaced tuple on s2 replicaset. + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space[g.params.space_name]:get({8, 'John Doe'}) + t.assert_equals(result, {8, 8, 'John Doe', 25}) +end + +pgroup.test_replace_many = function(g) + -- bucket_id is 596, storage is s-2 + local tuple = {71, 596, 'Dimitrion', 20} + + -- Put tuple to s1 replicaset. + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space[g.params.space_name]:insert(tuple) + t.assert_not_equals(result, nil) + + -- Put tuple to s2 replicaset. + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space[g.params.space_name]:insert(tuple) + t.assert_not_equals(result, nil) + + local tuple = {71, box.NULL, 'Augustus', 21} + + -- Replace a tuple. + local result, err = g.cluster.main_server.net_box:call('crud.replace_many', { + g.params.space_name, {tuple} + }) + t.assert_equals(err, nil) + t.assert_not_equals(result, nil) + t.assert_equals(#result.rows, 1) + t.assert_equals(result.rows[1], {71, 1, 'Augustus', 21}) + + -- There is no replaced tuple on s1 replicaset. + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space[g.params.space_name]:get({71, 'Augustus'}) + t.assert_equals(result, nil) + + -- There is replaced tuple on s2 replicaset. + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space[g.params.space_name]:get({71, 'Augustus'}) + t.assert_equals(result, {71, 1, 'Augustus', 21}) +end + pgroup.test_upsert_object = function(g) -- Upsert an object first time. local result, err = g.cluster.main_server.net_box:call( diff --git a/test/integration/ddl_sharding_info_reload_test.lua b/test/integration/ddl_sharding_info_reload_test.lua index 732181dd7..dc53a3f9b 100644 --- a/test/integration/ddl_sharding_info_reload_test.lua +++ b/test/integration/ddl_sharding_info_reload_test.lua @@ -353,6 +353,16 @@ local new_space_cases = { input = {'customers_new', test_object}, result = test_customers_new_result, }, + replace_many = { + func = 'crud.replace_many', + input = {'customers_new', test_tuples_batch}, + result = test_customers_new_batching_result, + }, + replace_object_many = { + func = 'crud.replace_object_many', + input = {'customers_new', test_objects_batch}, + result = test_customers_new_batching_result, + }, upsert = { func = 'crud.upsert', input = {'customers_new', test_tuple, {}}, @@ -457,6 +467,16 @@ local schema_change_sharding_key_cases = { input = {'customers', test_object}, result = test_customers_age_result, }, + replace_many = { + func = 'crud.replace_many', + input = {'customers', test_tuples_batch}, + result = test_customers_age_batching_result, + }, + replace_object_many = { + func = 'crud.replace_object_many', + input = {'customers', test_objects_batch}, + result = test_customers_age_batching_result, + }, upsert = { func = 'crud.upsert', input = {'customers', test_tuple, {}}, @@ -640,6 +660,16 @@ local schema_change_sharding_func_cases = { input = {'customers_pk', test_object}, result = test_customers_pk_func, }, + replace_many = { + func = 'crud.replace_many', + input = {'customers_pk', test_tuples_batch}, + result = test_customers_pk_batching_func, + }, + replace_object_many = { + func = 'crud.replace_object_many', + input = {'customers_pk', test_objects_batch}, + result = test_customers_pk_batching_func, + }, upsert = { func = 'crud.upsert', input = {'customers_pk', test_tuple, {}}, diff --git a/test/integration/ddl_sharding_key_test.lua b/test/integration/ddl_sharding_key_test.lua index 2ddf65b4e..d9697531e 100644 --- a/test/integration/ddl_sharding_key_test.lua +++ b/test/integration/ddl_sharding_key_test.lua @@ -222,6 +222,74 @@ pgroup.test_replace_object = function(g) t.assert_equals(result, {8, 1035, 'John Doe', 25}) end +pgroup.test_replace_many = function(g) + -- bucket_id is 596, storage is s-2 + local tuple = {7, 596, 'Dimitrion', 20} + + -- Put tuple to s1 replicaset. + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['customers_name_key']:insert(tuple) + t.assert_not_equals(result, nil) + + -- Put tuple to s2 replicaset. + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['customers_name_key']:insert(tuple) + t.assert_not_equals(result, nil) + + local tuple = {7, box.NULL, 'Augustus', 21} + + -- Replace a tuple. + local result, err = g.cluster.main_server.net_box:call('crud.replace_many', { + 'customers_name_key', {tuple} + }) + t.assert_equals(err, nil) + t.assert_not_equals(result, nil) + t.assert_equals(#result.rows, 1) + t.assert_equals(result.rows[1], {7, 782, 'Augustus', 21}) + + -- There is no replaced tuple on s1 replicaset. + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['customers_name_key']:get({7, 'Augustus'}) + t.assert_equals(result, nil) + + -- There is replaced tuple on s2 replicaset. + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['customers_name_key']:get({7, 'Augustus'}) + t.assert_equals(result, {7, 782, 'Augustus', 21}) +end + +pgroup.test_replace_object_many = function(g) + -- bucket_id is 596, storage is s-2 + local tuple = {8, 596, 'Dimitrion', 20} + + -- Put tuple to s1 replicaset. + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['customers_name_key']:insert(tuple) + t.assert_not_equals(result, nil) + + -- Put tuple to s2 replicaset. + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['customers_name_key']:insert(tuple) + t.assert_not_equals(result, nil) + + -- Replace an object. + local result, err = g.cluster.main_server.net_box:call( + 'crud.replace_object_many', {'customers_name_key', {{id = 8, name = 'John Doe', age = 25}}}) + t.assert_equals(err, nil) + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_equals(objects, {{id = 8, bucket_id = 1035, name = 'John Doe', age = 25}}) + + -- There is no replaced tuple on s1 replicaset. + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['customers_name_key']:get({8, 'John Doe'}) + t.assert_equals(result, nil) + + -- There is replaced tuple on s2 replicaset. + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['customers_name_key']:get({8, 'John Doe'}) + t.assert_equals(result, {8, 1035, 'John Doe', 25}) +end + pgroup.test_upsert_object = function(g) -- Upsert an object first time. local result, err = g.cluster.main_server.net_box:call( diff --git a/test/integration/replace_many_test.lua b/test/integration/replace_many_test.lua new file mode 100644 index 000000000..193d7dbfb --- /dev/null +++ b/test/integration/replace_many_test.lua @@ -0,0 +1,1964 @@ +local fio = require('fio') + +local t = require('luatest') +local crud = require('crud') + +local helpers = require('test.helper') + +local batching_utils = require('crud.common.batching_utils') + +local pgroup = t.group('replace_many', { + {engine = 'memtx'}, + {engine = 'vinyl'}, +}) + +pgroup.before_all(function(g) + g.cluster = helpers.Cluster:new({ + datadir = fio.tempdir(), + server_command = helpers.entrypoint('srv_batch_operations'), + use_vshard = true, + replicasets = helpers.get_test_replicasets(), + env = { + ['ENGINE'] = g.params.engine, + }, + }) + + g.cluster:start() +end) + +pgroup.after_all(function(g) helpers.stop_cluster(g.cluster) end) + +pgroup.before_each(function(g) + helpers.truncate_space_on_cluster(g.cluster, 'developers') +end) + +pgroup.test_non_existent_space = function(g) + -- replace_many + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'non_existent_space', + { + {1, box.NULL, 'Alex', 'alexpushkin'}, + {2, box.NULL, 'Anna', 'AnnaKar'}, + {3, box.NULL, 'Daria', 'mongendor'} + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 1) + t.assert_str_contains(errs[1].err, 'Space "non_existent_space" doesn\'t exist') + + -- replace_object_many + -- default: stop_on_error == false + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'non_existent_space', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna', login = 'AnnaKar'}, + {id = 3, name = 'Daria', login = 'mongendor'} + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + + -- we got 3 errors about non existent space, because it caused by flattening objects + t.assert_equals(#errs, 3) + t.assert_str_contains(errs[1].err, 'Space "non_existent_space" doesn\'t exist') + t.assert_str_contains(errs[2].err, 'Space "non_existent_space" doesn\'t exist') + t.assert_str_contains(errs[3].err, 'Space "non_existent_space" doesn\'t exist') + + -- replace_object_many + -- stop_on_error == true + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'non_existent_space', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna', login = 'AnnaKar'}, + {id = 3, name = 'Daria', login = 'mongendor'} + }, + { + stop_on_error = true, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + + -- we got 1 error about non existent space, because stop_on_error == true + t.assert_equals(#errs, 1) + t.assert_str_contains(errs[1].err, 'Space "non_existent_space" doesn\'t exist') +end + +pgroup.test_object_bad_format = function(g) + -- bad format + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna'}, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 1) + t.assert_str_contains(errs[1].err, 'Field \"login\" isn\'t nullable') + t.assert_equals(errs[1].operation_data, {id = 2, name = 'Anna'}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- bad format + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 4, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna'}, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 2) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {4, 1161, "Fedor", "FDost"}) + + t.assert_str_contains(errs[2].err, 'Field \"login\" isn\'t nullable') + t.assert_equals(errs[2].operation_data, {id = 2, name = 'Anna'}) + + -- get + -- primary key = 4 -> bucket_id = 1161 -> s1-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- bad format + -- two errors, default: stop_on_error == false + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor'}, + {id = 2, name = 'Anna'}, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 2) + + table.sort(errs, function(err1, err2) return err1.operation_data.id < err2.operation_data.id end) + + t.assert_str_contains(errs[1].err, 'Field \"login\" isn\'t nullable') + t.assert_equals(errs[1].operation_data, {id = 1, name = 'Fedor'}) + + t.assert_str_contains(errs[2].err, 'Field \"login\" isn\'t nullable') + t.assert_equals(errs[2].operation_data, {id = 2, name = 'Anna'}) +end + +pgroup.test_all_success = function(g) + -- replace_many + -- all success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {1, box.NULL, 'Fedor', 'FDost'}, + {2, box.NULL, 'Anna', 'AnnaKar'}, + {3, box.NULL, 'Daria', 'mongend'} + } + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_object_all_success = function(g) + -- insert + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({1, 477, 'Alex', 'alexpushkin'}) + t.assert_equals(result, {1, 477, 'Alex', 'alexpushkin'}) + + -- replace_object_many + -- all success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna', login = 'AnnaKar'}, + {id = 3, name = 'Daria', login = 'mongend'} + }, + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_one_error = function(g) + -- insert + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({1, 477, 'Alex', 'alexpushkin'}) + t.assert_equals(result, {1, 477, 'Alex', 'alexpushkin'}) + + -- replace_many + -- failed for s1-master + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {4, box.NULL, 'Fedor', 'alexpushkin'}, + {2, box.NULL, 'Anna', 'AnnaKar'}, + {3, box.NULL, 'Daria', 'mongend'} + }, + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 1) + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {4, 1161, "Fedor", "alexpushkin"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_object_one_error = function(g) + -- insert + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({1, 477, 'Alex', 'alexpushkin'}) + t.assert_equals(result, {1, 477, 'Alex', 'alexpushkin'}) + + -- replace_object_many + -- failed for s1-master + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 4, name = 'Fedor', login = 'alexpushkin'}, + {id = 2, name = 'Anna', login = 'AnnaKar'}, + {id = 3, name = 'Daria', login = 'mongend'} + }, + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 1) + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {4, 1161, "Fedor", "alexpushkin"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_object_many_errors = function(g) + -- insert + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({2, 401, 'Anna', 'a.petrova'}) + t.assert_equals(result, {2, 401, 'Anna', 'a.petrova'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'd.petrenko'}) + t.assert_equals(result, {3, 2804, 'Daria', 'd.petrenko'}) + + -- replace_object_many + -- fails for both: s1-master s2-master + -- one error on each storage, one success on each storage + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 4, name = 'Sergey', login = 's.ivanov'}, + {id = 71, name = 'Artur', login = 'a.orlov'}, + {id = 10, name = 'Anastasia', login = 'a.petrova'}, + {id = 92, name = 'Dmitriy', login = 'd.petrenko'}, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 2) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {10, 569, "Anastasia", "a.petrova"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {92, 2040, "Dmitriy", "d.petrenko"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 4, login = "s.ivanov", bucket_id = 1161, name = "Sergey"}, + {id = 71, login = "a.orlov", bucket_id = 1802, name = "Artur"}, + }) + + -- primary key = 4 -> bucket_id = 1161 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, {4, 1161, 'Sergey', 's.ivanov'}) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, {71, 1802, 'Artur', 'a.orlov'}) + + -- primary key = 10 -> bucket_id = 569 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(10) + t.assert_equals(result, nil) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, nil) +end + +pgroup.test_many_errors = function(g) + -- insert + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({2, 401, 'Anna', 'a.petrova'}) + t.assert_equals(result, {2, 401, 'Anna', 'a.petrova'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'd.petrenko'}) + t.assert_equals(result, {3, 2804, 'Daria', 'd.petrenko'}) + + -- replace_many + -- fails for both: s1-master s2-master + -- one error on each storage, one success on each storage + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {4, box.NULL, 'Sergey', 's.ivanov'}, + {71, box.NULL, 'Artur', 'a.orlov'}, + {10, box.NULL, 'Anastasia', 'a.petrova'}, + {92, box.NULL, 'Dmitriy', 'd.petrenko'}, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 2) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {10, 569, "Anastasia", "a.petrova"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {92, 2040, "Dmitriy", "d.petrenko"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 4, login = "s.ivanov", bucket_id = 1161, name = "Sergey"}, + {id = 71, login = "a.orlov", bucket_id = 1802, name = "Artur"}, + }) + + -- primary key = 4 -> bucket_id = 1161 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, {4, 1161, 'Sergey', 's.ivanov'}) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, {71, 1802, 'Artur', 'a.orlov'}) + + -- primary key = 10 -> bucket_id = 569 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(10) + t.assert_equals(result, nil) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, nil) +end + +pgroup.test_no_success = function(g) + -- insert + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({2, 401, 'Anna', 'a.smith'}) + t.assert_equals(result, {2, 401, 'Anna', 'a.smith'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'd.petrenko'}) + t.assert_equals(result, {3, 2804, 'Daria', 'd.petrenko'}) + + -- primary key = 10 -> bucket_id = 569 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({10, 569, 'Sergey', 's.serenko'}) + t.assert_equals(result, {10, 569, 'Sergey', 's.serenko'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({92, 2040, 'Artur', 'a.smirnov'}) + t.assert_equals(result, {92, 2040, 'Artur', 'a.smirnov'}) + + -- replace_many + -- fails for both: s1-master s2-master + -- no success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {4, box.NULL, 'Alex', 'a.smith'}, + {71, box.NULL, 'Dmitriy', 'd.petrenko'}, + {6, box.NULL, 'Semen', 's.serenko'}, + {9, box.NULL, 'Anton', 'a.smirnov'}, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 4) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {4, 1161, "Alex", "a.smith"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {6, 1064, "Semen", "s.serenko"}) + + t.assert_str_contains(errs[3].err, 'Duplicate key exists') + t.assert_equals(errs[3].operation_data, {9, 1644, "Anton", "a.smirnov"}) + + t.assert_str_contains(errs[4].err, 'Duplicate key exists') + t.assert_equals(errs[4].operation_data, {71, 1802, "Dmitriy", "d.petrenko"}) + + -- primary key = 4 -> bucket_id = 1161 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(9) + t.assert_equals(result, nil) + + -- primary key = 6 -> bucket_id = 1064 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(6) + t.assert_equals(result, nil) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, nil) +end + +pgroup.test_object_no_success = function(g) + -- insert + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({2, 401, 'Anna', 'a.smith'}) + t.assert_equals(result, {2, 401, 'Anna', 'a.smith'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'd.petrenko'}) + t.assert_equals(result, {3, 2804, 'Daria', 'd.petrenko'}) + + -- primary key = 10 -> bucket_id = 569 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({10, 569, 'Sergey', 's.serenko'}) + t.assert_equals(result, {10, 569, 'Sergey', 's.serenko'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({92, 2040, 'Artur', 'a.smirnov'}) + t.assert_equals(result, {92, 2040, 'Artur', 'a.smirnov'}) + + -- replace_object_many + -- fails for both: s1-master s2-master + -- no success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 4, name = 'Alex', login = 'a.smith'}, + {id = 71, name = 'Dmitriy', login = 'd.petrenko'}, + {id = 6, name = 'Semen', login = 's.serenko'}, + {id = 9, name = 'Anton', login = 'a.smirnov'}, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 4) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {4, 1161, "Alex", "a.smith"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {6, 1064, "Semen", "s.serenko"}) + + t.assert_str_contains(errs[3].err, 'Duplicate key exists') + t.assert_equals(errs[3].operation_data, {9, 1644, "Anton", "a.smirnov"}) + + t.assert_str_contains(errs[4].err, 'Duplicate key exists') + t.assert_equals(errs[4].operation_data, {71, 1802, "Dmitriy", "d.petrenko"}) + + -- primary key = 4 -> bucket_id = 1161 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(9) + t.assert_equals(result, nil) + + -- primary key = 6 -> bucket_id = 1064 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(6) + t.assert_equals(result, nil) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, nil) +end + +pgroup.test_object_bad_format_stop_on_error = function(g) + -- bad format + -- two errors, stop_on_error == true + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor'}, + {id = 2, name = 'Anna'}, + }, + { + stop_on_error = true, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 1) + + t.assert_str_contains(errs[1].err, 'Field \"login\" isn\'t nullable') + t.assert_equals(errs[1].operation_data, {id = 1, name = 'Fedor'}) +end + +pgroup.test_all_success_stop_on_error = function(g) + -- replace_many + -- all success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {1, box.NULL, 'Fedor', 'FDost'}, + {2, box.NULL, 'Anna', 'AnnaKar'}, + {3, box.NULL, 'Daria', 'mongend'} + }, + { + stop_on_error = true, + } + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_object_all_success_stop_on_error = function(g) + -- insert + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({1, 477, 'Alex', 'alexpushkin'}) + t.assert_equals(result, {1, 477, 'Alex', 'alexpushkin'}) + + -- replace_object_many + -- all success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna', login = 'AnnaKar'}, + {id = 3, name = 'Daria', login = 'mongend'} + }, + { + stop_on_error = true, + } + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_partial_success_stop_on_error = function(g) + -- insert + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'mongend'}) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({9, 1644, 'Eren', 'e.smith'}) + t.assert_equals(result, {9, 1644, 'Eren', 'e.smith'}) + + -- replace_many + -- stop_on_error = true, rollback_on_error = false + -- one error on one storage without rollback, inserts stop by error on this storage + -- inserts before error are successful + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {22, box.NULL, 'Alex', 'alexpushkin'}, + {92, box.NULL, 'Artur', 'AGolden'}, + {71, box.NULL, 'Erwin', 'e.smith'}, + {5, box.NULL, 'Sergey', 's.petrenko'}, + {11, box.NULL, 'Anna', 'mongend'}, + }, + { + stop_on_error = true, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 2) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[1].operation_data, {11, 2652, "Anna", "mongend"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {71, 1802, "Erwin", "e.smith"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 22, bucket_id = 655, login = "alexpushkin", name = "Alex"}, + {id = 5, bucket_id = 1172, login = "s.petrenko", name = "Sergey"}, + {id = 92, bucket_id = 2040, login = "AGolden", name = "Artur"}, + }) + + -- get + -- primary key = 22 -> bucket_id = 655 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(22) + t.assert_equals(result, {22, 655, 'Alex', 'alexpushkin'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, {92, 2040, 'Artur', 'AGolden'}) + + -- primary key = 5 -> bucket_id = 1172 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(5) + t.assert_equals(result, {5, 1172, 'Sergey', 's.petrenko'}) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, nil) + + -- primary key = 11 -> bucket_id = 2652 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(11) + t.assert_equals(result, nil) +end + +pgroup.test_object_partial_success_stop_on_error = function(g) + -- insert + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'mongend'}) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({9, 1644, 'Eren', 'e.smith'}) + t.assert_equals(result, {9, 1644, 'Eren', 'e.smith'}) + + -- replace_object_many + -- stop_on_error = true, rollback_on_error = false + -- one error on one storage without rollback, inserts stop by error on this storage + -- inserts before error are successful + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 22, name = 'Alex', login = 'alexpushkin'}, + {id = 92, name = 'Artur', login = 'AGolden'}, + {id = 71, name = 'Erwin', login = 'e.smith'}, + {id = 5, name = 'Sergey', login = 's.petrenko'}, + {id = 11, name = 'Anna', login = 'mongend'}, + }, + { + stop_on_error = true, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 2) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[1].operation_data, {11, 2652, "Anna", "mongend"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {71, 1802, "Erwin", "e.smith"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 22, bucket_id = 655, login = "alexpushkin", name = "Alex"}, + {id = 5, bucket_id = 1172, login = "s.petrenko", name = "Sergey"}, + {id = 92, bucket_id = 2040, login = "AGolden", name = "Artur"}, + }) + + -- get + -- primary key = 22 -> bucket_id = 655 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(22) + t.assert_equals(result, {22, 655, 'Alex', 'alexpushkin'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, {92, 2040, 'Artur', 'AGolden'}) + + -- primary key = 5 -> bucket_id = 1172 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(5) + t.assert_equals(result, {5, 1172, 'Sergey', 's.petrenko'}) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, nil) + + -- primary key = 11 -> bucket_id = 2652 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(11) + t.assert_equals(result, nil) +end + +pgroup.test_no_success_stop_on_error = function(g) + -- insert + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({2, 401, 'Anna', 'AnnaKar'}) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'mongend'}) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({92, 2040, 'Artur', 'AGolden'}) + t.assert_equals(result, {92, 2040, 'Artur', 'AGolden'}) + + -- replace_many + -- fails for both: s1-master s2-master + -- one error on each storage, all inserts stop by error + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {71, box.NULL, 'Alex', 'AGolden'}, + {4, box.NULL, 'Anastasia', 'AnnaKar'}, + {10, box.NULL, 'Sergey', 's.petrenko'}, + {9, box.NULL, 'Anna', 'a.smirnova'}, + {92, box.NULL, 'Leo', 'tolstoy_leo'}, + }, + { + stop_on_error = true, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 5) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {4, 1161, "Anastasia", "AnnaKar"}) + + t.assert_str_contains(errs[2].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[2].operation_data, {9, 1644, "Anna", "a.smirnova"}) + + t.assert_str_contains(errs[3].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[3].operation_data, {10, 569, "Sergey", "s.petrenko"}) + + t.assert_str_contains(errs[4].err, 'Duplicate key exists') + t.assert_equals(errs[4].operation_data, {71, 1802, "Alex", "AGolden"}) + + t.assert_str_contains(errs[5].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[5].operation_data, {92, 2040, "Leo", "tolstoy_leo"}) + + -- primary key = 4 -> bucket_id = 1161 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, nil) + + -- primary key = 10 -> bucket_id = 569 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(10) + t.assert_equals(result, nil) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(9) + t.assert_equals(result, nil) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, {92, 2040, 'Artur', 'AGolden'}) +end + +pgroup.test_object_no_success_stop_on_error = function(g) + -- insert + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({2, 401, 'Anna', 'AnnaKar'}) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'mongend'}) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({92, 2040, 'Artur', 'AGolden'}) + t.assert_equals(result, {92, 2040, 'Artur', 'AGolden'}) + + -- replace_object_many + -- fails for both: s1-master s2-master + -- one error on each storage, all inserts stop by error + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 71, name = 'Alex', login = 'AGolden'}, + {id = 4, name = 'Anastasia', login = 'AnnaKar'}, + {id = 10, name = 'Sergey', login = 's.petrenko'}, + {id = 9, name = 'Anna', login = 'a.smirnova'}, + {id = 92, name = 'Leo', login = 'tolstoy_leo'}, + }, + { + stop_on_error = true, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 5) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {4, 1161, "Anastasia", "AnnaKar"}) + + t.assert_str_contains(errs[2].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[2].operation_data, {9, 1644, "Anna", "a.smirnova"}) + + t.assert_str_contains(errs[3].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[3].operation_data, {10, 569, "Sergey", "s.petrenko"}) + + t.assert_str_contains(errs[4].err, 'Duplicate key exists') + t.assert_equals(errs[4].operation_data, {71, 1802, "Alex", "AGolden"}) + + t.assert_str_contains(errs[5].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[5].operation_data, {92, 2040, "Leo", "tolstoy_leo"}) + + -- primary key = 4 -> bucket_id = 1161 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, nil) + + -- primary key = 10 -> bucket_id = 569 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(10) + t.assert_equals(result, nil) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(9) + t.assert_equals(result, nil) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, {92, 2040, 'Artur', 'AGolden'}) +end + +pgroup.test_all_success_rollback_on_error = function(g) + -- replace_many + -- all success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {1, box.NULL, 'Fedor', 'FDost'}, + {2, box.NULL, 'Anna', 'AnnaKar'}, + {3, box.NULL, 'Daria', 'mongend'} + }, + { + rollback_on_error = true, + } + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_object_all_success_rollback_on_error = function(g) + -- insert + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({1, 477, 'Alex', 'alexpushkin'}) + t.assert_equals(result, {1, 477, 'Alex', 'alexpushkin'}) + + -- replace_object_many + -- all success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna', login = 'AnnaKar'}, + {id = 3, name = 'Daria', login = 'mongend'} + }, + { + rollback_on_error = true, + } + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_partial_success_rollback_on_error = function(g) + -- insert + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'mongend'}) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({9, 1644, 'Nicolo', 'n.black'}) + t.assert_equals(result, {9, 1644, 'Nicolo', 'n.black'}) + + -- replace_many + -- stop_on_error = false, rollback_on_error = true + -- two error on one storage with rollback + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {22, box.NULL, 'Alex', 'alexpushkin'}, + {92, box.NULL, 'Artur', 'AGolden'}, + {71, box.NULL, 'Anastasia', 'n.black'}, + {5, box.NULL, 'Sergey', 's.petrenko'}, + {11, box.NULL, 'Anna', 'mongend'}, + }, + { + rollback_on_error = true, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 3) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {11, 2652, "Anna", "mongend"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {71, 1802, "Anastasia", "n.black"}) + + t.assert_str_contains(errs[3].err, batching_utils.rollback_on_error_msg) + t.assert_equals(errs[3].operation_data, {92, 2040, "Artur", "AGolden"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 5, name = 'Sergey', login = "s.petrenko", bucket_id = 1172}, + {id = 22, name = 'Alex', login = "alexpushkin", bucket_id = 655}, + }) + + -- get + -- primary key = 22 -> bucket_id = 655 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(22) + t.assert_equals(result, {22, 655, 'Alex', 'alexpushkin'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, nil) + + -- primary key = 5 -> bucket_id = 1172 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(5) + t.assert_equals(result, {5, 1172, 'Sergey', 's.petrenko'}) + + -- primary key = 11 -> bucket_id = 2652 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(11) + t.assert_equals(result, nil) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, nil) +end + +pgroup.test_object_partial_success_rollback_on_error = function(g) + -- insert + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'mongend'}) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({9, 1644, 'Nicolo', 'n.black'}) + t.assert_equals(result, {9, 1644, 'Nicolo', 'n.black'}) + + -- replace_object_many + -- stop_on_error = false, rollback_on_error = true + -- two error on one storage with rollback + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 22, name = 'Alex', login = 'alexpushkin'}, + {id = 92, name = 'Artur', login = 'AGolden'}, + {id = 71, name = 'Anastasia', login = 'n.black'}, + {id = 5, name = 'Sergey', login = 's.petrenko'}, + {id = 11, name = 'Anna', login = 'mongend'}, + }, + { + rollback_on_error = true, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 3) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, 'Duplicate key exists') + t.assert_equals(errs[1].operation_data, {11, 2652, "Anna", "mongend"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {71, 1802, "Anastasia", "n.black"}) + + t.assert_str_contains(errs[3].err, batching_utils.rollback_on_error_msg) + t.assert_equals(errs[3].operation_data, {92, 2040, "Artur", "AGolden"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 5, name = 'Sergey', login = "s.petrenko", bucket_id = 1172}, + {id = 22, name = 'Alex', login = "alexpushkin", bucket_id = 655}, + }) + + -- get + -- primary key = 22 -> bucket_id = 655 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(22) + t.assert_equals(result, {22, 655, 'Alex', 'alexpushkin'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, nil) + + -- primary key = 5 -> bucket_id = 1172 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(5) + t.assert_equals(result, {5, 1172, 'Sergey', 's.petrenko'}) + + -- primary key = 11 -> bucket_id = 2652 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(11) + t.assert_equals(result, nil) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(71) + t.assert_equals(result, nil) +end + +pgroup.test_no_success_rollback_on_error = function(g) + -- insert + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({2, 401, 'Anna', 'a.leonhart'}) + t.assert_equals(result, {2, 401, 'Anna', 'a.leonhart'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'mongend'}) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) + + -- primary key = 5 -> bucket_id = 1172 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({5, 1172, 'Sergey', 's.petrenko'}) + t.assert_equals(result, {5, 1172, 'Sergey', 's.petrenko'}) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({71, 1802, 'Oleg', 'OKonov'}) + t.assert_equals(result, {71, 1802, 'Oleg', 'OKonov'}) + + -- replace_many + -- fails for both: s1-master s2-master + -- two errors on each storage with rollback + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {1, box.NULL, 'Eren', 'e.eger'}, + {92, box.NULL, 'Alexey', 'black_alex'}, + {11, box.NULL, 'Olga', 'OKonov'}, + {6, box.NULL, 'Anastasia', 'a.leonhart'}, + {4, box.NULL, 'Semen', 's.petrenko'}, + {9, box.NULL, 'Leo', 'mongend'}, + }, + { + rollback_on_error = true, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 6) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, batching_utils.rollback_on_error_msg) + t.assert_equals(errs[1].operation_data, {1, 477, "Eren", "e.eger"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {4, 1161, "Semen", "s.petrenko"}) + + t.assert_str_contains(errs[3].err, 'Duplicate key exists') + t.assert_equals(errs[3].operation_data, {6, 1064, "Anastasia", "a.leonhart"}) + + t.assert_str_contains(errs[4].err, 'Duplicate key exists') + t.assert_equals(errs[4].operation_data, {9, 1644, "Leo", "mongend"}) + + t.assert_str_contains(errs[5].err, 'Duplicate key exists') + t.assert_equals(errs[5].operation_data, {11, 2652, "Olga", "OKonov"}) + + t.assert_str_contains(errs[6].err, batching_utils.rollback_on_error_msg) + t.assert_equals(errs[6].operation_data, {92, 2040, "Alexey", "black_alex"}) + + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, nil) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, nil) + + -- primary key = 4 -> bucket_id = 1161 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(9) + t.assert_equals(result, nil) + + -- primary key = 6 -> bucket_id = 1064 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(6) + t.assert_equals(result, nil) + + -- primary key = 11 -> bucket_id = 2652 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(11) + t.assert_equals(result, nil) +end + +pgroup.test_object_no_success_rollback_on_error = function(g) + -- insert + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({2, 401, 'Anna', 'a.leonhart'}) + t.assert_equals(result, {2, 401, 'Anna', 'a.leonhart'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Daria', 'mongend'}) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) + + -- primary key = 5 -> bucket_id = 1172 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({5, 1172, 'Sergey', 's.petrenko'}) + t.assert_equals(result, {5, 1172, 'Sergey', 's.petrenko'}) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({71, 1802, 'Oleg', 'OKonov'}) + t.assert_equals(result, {71, 1802, 'Oleg', 'OKonov'}) + + -- replace_object_many + -- fails for both: s1-master s2-master + -- two errors on each storage with rollback + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Eren', login = 'e.eger'}, + {id = 92, name = 'Alexey', login = 'black_alex'}, + {id = 11, name = 'Olga', login = 'OKonov'}, + {id = 6, name = 'Anastasia', login = 'a.leonhart'}, + {id = 4, name = 'Semen', login = 's.petrenko'}, + {id = 9, name = 'Leo', login = 'mongend'}, + }, + { + rollback_on_error = true, + } + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 6) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, batching_utils.rollback_on_error_msg) + t.assert_equals(errs[1].operation_data, {1, 477, "Eren", "e.eger"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {4, 1161, "Semen", "s.petrenko"}) + + t.assert_str_contains(errs[3].err, 'Duplicate key exists') + t.assert_equals(errs[3].operation_data, {6, 1064, "Anastasia", "a.leonhart"}) + + t.assert_str_contains(errs[4].err, 'Duplicate key exists') + t.assert_equals(errs[4].operation_data, {9, 1644, "Leo", "mongend"}) + + t.assert_str_contains(errs[5].err, 'Duplicate key exists') + t.assert_equals(errs[5].operation_data, {11, 2652, "Olga", "OKonov"}) + + t.assert_str_contains(errs[6].err, batching_utils.rollback_on_error_msg) + t.assert_equals(errs[6].operation_data, {92, 2040, "Alexey", "black_alex"}) + + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, nil) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, nil) + + -- primary key = 4 -> bucket_id = 1161 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(4) + t.assert_equals(result, nil) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(9) + t.assert_equals(result, nil) + + -- primary key = 6 -> bucket_id = 1064 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(6) + t.assert_equals(result, nil) + + -- primary key = 11 -> bucket_id = 2652 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(11) + t.assert_equals(result, nil) +end + +pgroup.test_all_success_rollback_and_stop_on_error = function(g) + -- replace_many + -- all success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {1, box.NULL, 'Fedor', 'FDost'}, + {2, box.NULL, 'Anna', 'AnnaKar'}, + {3, box.NULL, 'Daria', 'mongend'} + }, + { + rollback_on_error = true, + stop_on_error = true, + } + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_object_all_success_rollback_and_stop_on_error = function(g) + -- insert + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:insert({1, 477, 'Alex', 'alexpushkin'}) + t.assert_equals(result, {1, 477, 'Alex', 'alexpushkin'}) + + -- replace_object_many + -- all success + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna', login = 'AnnaKar'}, + {id = 3, name = 'Daria', login = 'mongend'} + }, + { + rollback_on_error = true, + stop_on_error = true, + } + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 1, name = 'Fedor', login = 'FDost', bucket_id = 477}, + {id = 2, name = 'Anna', login = 'AnnaKar', bucket_id = 401}, + {id = 3, name = 'Daria', login = 'mongend', bucket_id = 2804}, + }) + + -- get + -- primary key = 1 -> bucket_id = 477 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(1) + t.assert_equals(result, {1, 477, 'Fedor', 'FDost'}) + + -- primary key = 2 -> bucket_id = 401 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(2) + t.assert_equals(result, {2, 401, 'Anna', 'AnnaKar'}) + + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(3) + t.assert_equals(result, {3, 2804, 'Daria', 'mongend'}) +end + +pgroup.test_partial_success_rollback_and_stop_on_error = function(g) + -- insert + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Anna', 'a.leonhart'}) + t.assert_equals(result, {3, 2804, 'Anna', 'a.leonhart'}) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({71, 1802, 'Oleg', 'OKonov'}) + t.assert_equals(result, {71, 1802, 'Oleg', 'OKonov'}) + + -- replace_many + -- stop_on_error = true, rollback_on_error = true + -- two error on one storage with rollback, inserts stop by error on this storage + -- inserts before error are rollbacked + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {22, box.NULL, 'Alex', 'alexpushkin'}, + {92, box.NULL, 'Artur', 'AGolden'}, + {11, box.NULL, 'Anastasia', 'a.leonhart'}, + {5, box.NULL, 'Sergey', 's.petrenko'}, + {9, box.NULL, 'Anna', 'AnnaBlack'}, + {17, box.NULL, 'Oksana', 'OKonov'}, + }, + { + stop_on_error = true, + rollback_on_error = true, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 4) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[1].operation_data, {9, 1644, "Anna", "AnnaBlack"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {11, 2652, "Anastasia", "a.leonhart"}) + + t.assert_str_contains(errs[3].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[3].operation_data, {17, 2900, "Oksana", "OKonov"}) + + t.assert_str_contains(errs[4].err, batching_utils.rollback_on_error_msg) + t.assert_equals(errs[4].operation_data, {92, 2040, "Artur", "AGolden"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 5, name = 'Sergey', login = "s.petrenko", bucket_id = 1172}, + {id = 22, name = 'Alex', login = "alexpushkin", bucket_id = 655}, + }) + + -- get + -- primary key = 22 -> bucket_id = 655 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(22) + t.assert_equals(result, {22, 655, 'Alex', 'alexpushkin'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, nil) + + -- primary key = 5 -> bucket_id = 1172 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(5) + t.assert_equals(result, {5, 1172, 'Sergey', 's.petrenko'}) + + -- primary key = 11 -> bucket_id = 2652 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(11) + t.assert_equals(result, nil) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(9) + t.assert_equals(result, nil) + + -- primary key = 17 -> bucket_id = 2900 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(17) + t.assert_equals(result, nil) +end + +pgroup.test_object_partial_success_rollback_and_stop_on_error = function(g) + -- insert + -- primary key = 3 -> bucket_id = 2804 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({3, 2804, 'Anna', 'a.leonhart'}) + t.assert_equals(result, {3, 2804, 'Anna', 'a.leonhart'}) + + -- primary key = 71 -> bucket_id = 1802 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:insert({71, 1802, 'Oleg', 'OKonov'}) + t.assert_equals(result, {71, 1802, 'Oleg', 'OKonov'}) + + -- replace_object_many + -- stop_on_error = true, rollback_on_error = true + -- two error on one storage with rollback, inserts stop by error on this storage + -- inserts before error are rollbacked + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 22, name = 'Alex', login = 'alexpushkin'}, + {id = 92, name = 'Artur', login = 'AGolden'}, + {id = 11, name = 'Anastasia', login = 'a.leonhart'}, + {id = 5, name = 'Sergey', login = 's.petrenko'}, + {id = 9, name = 'Anna', login = 'AnnaBlack'}, + {id = 17, name = 'Oksana', login = 'OKonov'}, + }, + { + stop_on_error = true, + rollback_on_error = true, + } + }) + + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 4) + + table.sort(errs, function(err1, err2) return err1.operation_data[1] < err2.operation_data[1] end) + + t.assert_str_contains(errs[1].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[1].operation_data, {9, 1644, "Anna", "AnnaBlack"}) + + t.assert_str_contains(errs[2].err, 'Duplicate key exists') + t.assert_equals(errs[2].operation_data, {11, 2652, "Anastasia", "a.leonhart"}) + + t.assert_str_contains(errs[3].err, batching_utils.stop_on_error_msg) + t.assert_equals(errs[3].operation_data, {17, 2900, "Oksana", "OKonov"}) + + t.assert_str_contains(errs[4].err, batching_utils.rollback_on_error_msg) + t.assert_equals(errs[4].operation_data, {92, 2040, "Artur", "AGolden"}) + + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'login', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, { + {id = 5, name = 'Sergey', login = "s.petrenko", bucket_id = 1172}, + {id = 22, name = 'Alex', login = "alexpushkin", bucket_id = 655}, + }) + + -- get + -- primary key = 22 -> bucket_id = 655 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(22) + t.assert_equals(result, {22, 655, 'Alex', 'alexpushkin'}) + + -- primary key = 92 -> bucket_id = 2040 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(92) + t.assert_equals(result, nil) + + -- primary key = 5 -> bucket_id = 1172 -> s2-master + local conn_s2 = g.cluster:server('s2-master').net_box + local result = conn_s2.space['developers']:get(5) + t.assert_equals(result, {5, 1172, 'Sergey', 's.petrenko'}) + + -- primary key = 11 -> bucket_id = 2652 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(11) + t.assert_equals(result, nil) + + -- primary key = 9 -> bucket_id = 1644 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(9) + t.assert_equals(result, nil) + + -- primary key = 17 -> bucket_id = 2900 -> s1-master + local conn_s1 = g.cluster:server('s1-master').net_box + local result = conn_s1.space['developers']:get(17) + t.assert_equals(result, nil) +end + +pgroup.test_partial_result = function(g) + -- bad fields format + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {1, box.NULL, 'Fedor', 'FDost'}, + {2, box.NULL, 'Anna', 'a.leonhart'}, + }, + {fields = {'id', 'invalid'}}, + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 1) + t.assert_str_contains(errs[1].err, 'Space format doesn\'t contain field named "invalid"') + + -- replace_many + local result, errs = g.cluster.main_server.net_box:call('crud.replace_many', { + 'developers', + { + {1, box.NULL, 'Fedor', 'FDost'}, + {2, box.NULL, 'Anna', 'a.leonhart'}, + {3, box.NULL, 'Daria', 'mongen'} + }, + {fields = {'id', 'name'}}, + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, {{id = 1, name = 'Fedor'}, {id = 2, name = 'Anna'}, {id = 3, name = 'Daria'}}) +end + +pgroup.test_object_partial_result = function(g) + -- bad fields format + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna', login = 'a.leonhart'}, + }, + {fields = {'id', 'invalid'}}, + }) + + t.assert_equals(result, nil) + t.assert_not_equals(errs, nil) + t.assert_equals(#errs, 1) + t.assert_str_contains(errs[1].err, 'Space format doesn\'t contain field named "invalid"') + + -- replace_object_many + local result, errs = g.cluster.main_server.net_box:call('crud.replace_object_many', { + 'developers', + { + {id = 1, name = 'Fedor', login = 'FDost'}, + {id = 2, name = 'Anna', login = 'a.leonhart'}, + {id = 3, name = 'Daria', login = 'mongen'} + }, + {fields = {'id', 'name'}}, + }) + + t.assert_equals(errs, nil) + t.assert_equals(result.metadata, { + {name = 'id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + }) + + local objects = crud.unflatten_rows(result.rows, result.metadata) + t.assert_items_equals(objects, {{id = 1, name = 'Fedor'}, {id = 2, name = 'Anna'}, {id = 3, name = 'Daria'}}) +end + +pgroup.test_opts_not_damaged = function(g) + -- replace_many + local batch_replace_opts = {timeout = 1, fields = {'name', 'login'}} + local new_batch_replace_opts, err = g.cluster.main_server:eval([[ + local crud = require('crud') + + local batch_replace_opts = ... + + local _, err = crud.replace_many('developers', { + {1, box.NULL, 'Alex', "alexpushkin"} + }, batch_replace_opts) + + return batch_replace_opts, err + ]], {batch_replace_opts}) + + t.assert_equals(err, nil) + t.assert_equals(new_batch_replace_opts, batch_replace_opts) + + -- replace_object_many + local batch_replace_opts = {timeout = 1, fields = {'name', 'login'}} + local new_batch_replace_opts, err = g.cluster.main_server:eval([[ + local crud = require('crud') + + local batch_replace_opts = ... + + local _, err = crud.replace_object_many('developers', { + {id = 2, name = 'Fedor', login = 'FDost'} + }, batch_replace_opts) + + return batch_replace_opts, err + ]], {batch_replace_opts}) + + t.assert_equals(err, nil) + t.assert_equals(new_batch_replace_opts, batch_replace_opts) +end