diff --git a/CHANGELOG.md b/CHANGELOG.md index 83c5ef13..bdd93735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added * `crud.storage_info` function to get storages status (#229). +* Support `vshard_router` option in operations for Cartridge vshard groups + or non-default vshard routers (#44). ### Changed * Deprecate using space id in `crud.len` (#255). diff --git a/README.md b/README.md index 873bcda1..ff37064f 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,9 @@ where: * `timeout` (`?number`) - `vshard.call` timeout (in seconds) * `bucket_id` (`?number|cdata`) - bucket ID * `fields` (`?table`) - field names for getting only a subset of fields + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and array contains one inserted row, error. @@ -259,6 +262,9 @@ where: * `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` + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and array with inserted rows, array of errors. Each error object can contain field `operation_data`. @@ -393,6 +399,9 @@ where: * `prefer_replica` (`?boolean`) - if `true` then the preferred target is one of the replicas * `balance` (`?boolean`) - use replica according to vshard load balancing policy + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and array contains one row, error. @@ -426,6 +435,9 @@ where: * `timeout` (`?number`) - `vshard.call` timeout (in seconds) * `bucket_id` (`?number|cdata`) - bucket ID * `fields` (`?table`) - field names for getting only a subset of fields + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and array contains one updated row, error. @@ -458,6 +470,9 @@ where: * `timeout` (`?number`) - `vshard.call` timeout (in seconds) * `bucket_id` (`?number|cdata`) - bucket ID * `fields` (`?table`) - field names for getting only a subset of fields + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and array contains one deleted row (empty for vinyl), error. @@ -492,6 +507,9 @@ where: * `timeout` (`?number`) - `vshard.call` timeout (in seconds) * `bucket_id` (`?number|cdata`) - bucket ID * `fields` (`?table`) - field names for getting only a subset of fields + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns inserted or replaced rows and metadata or nil with error. @@ -544,6 +562,9 @@ where: * `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` + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and array with inserted/replaced rows, array of errors. Each error object can contain field `operation_data`. @@ -676,6 +697,9 @@ where: * `timeout` (`?number`) - `vshard.call` timeout (in seconds) * `bucket_id` (`?number|cdata`) - bucket ID * `fields` (`?table`) - field names for getting only a subset of fields + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and empty array of rows or nil, error. @@ -733,6 +757,9 @@ where: * `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` + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and array of errors. Each error object can contain field `operation_data`. @@ -869,6 +896,9 @@ where: * `prefer_replica` (`?boolean`) - if `true` then the preferred target is one of the replicas * `balance` (`?boolean`) - use replica according to vshard load balancing policy + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns metadata and array of rows, error. @@ -1007,6 +1037,9 @@ where: * `space_name` (`string`) - name of the space * `opts`: * `timeout` (`?number`) - `vshard.call` timeout (in seconds) + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns true or nil with error. @@ -1040,6 +1073,9 @@ where: * `space_name` (`string`) - name of the space * `opts`: * `timeout` (`?number`) - `vshard.call` timeout (in seconds) + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster Returns number or nil with error. @@ -1089,6 +1125,7 @@ where: * `opts`: * `timeout` (`?number`) - maximum time (in seconds, default: 2) to wait for response from cluster instances. + * `vshard_router` (`?string|table`) - Cartridge vshard group name or vshard router instance. Returns storages status table by instance UUID or nil with error. Status table fields: * `status` contains a string representing the status: @@ -1158,6 +1195,9 @@ where: * `balance` (`?boolean`) - use replica according to [vshard load balancing policy](https://www.tarantool.io/en/doc/latest/reference/reference_rock/vshard/vshard_api/#router-api-call), default value is `false` + * `vshard_router` (`?string|table`) - Cartridge vshard group name or + vshard router instance. Set this parameter if your space is not + a part of the default vshard cluster ```lua crud.count('customers', {{'==', 'age', 35}}) diff --git a/crud/borders.lua b/crud/borders.lua index 82827fc6..13e96098 100644 --- a/crud/borders.lua +++ b/crud/borders.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local const = require('crud.common.const') local dev_checks = require('crud.common.dev_checks') @@ -71,10 +70,9 @@ local function call_get_border_on_router(vshard_router, border_name, space_name, checks('table', 'string', 'string', '?string|number', { timeout = '?number', fields = '?table', + vshard_router = '?string|table', }) - opts = opts or {} - local replicasets = vshard_router:routeall() local space = utils.get_space(space_name, replicasets) if space == nil then @@ -160,7 +158,11 @@ local function call_get_border_on_router(vshard_router, border_name, space_name, end local function get_border(border_name, space_name, index_name, opts) - local vshard_router = vshard.router.static + opts = opts or {} + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, BorderError:new(err) + end return schema.wrap_func_reload(vshard_router, call_get_border_on_router, border_name, space_name, index_name, opts @@ -183,6 +185,11 @@ end -- @tparam ?table opts.fields -- Field names for getting only a subset of fields -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] result -- @treturn[2] nil -- @treturn[2] table Error description @@ -206,6 +213,11 @@ end -- @tparam ?table opts.fields -- Field names for getting only a subset of fields -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] result -- @treturn[2] nil -- @treturn[2] table Error description diff --git a/crud/common/sharding_func.lua b/crud/common/sharding_func.lua index 19aa81e6..7b6840d3 100644 --- a/crud/common/sharding_func.lua +++ b/crud/common/sharding_func.lua @@ -1,7 +1,7 @@ local log = require('log') -local vshard = require('vshard') local sharding_metadata_module = require('crud.common.sharding.sharding_metadata') +local utils = require('crud.common.utils') local sharding_func_cache = {} @@ -15,8 +15,9 @@ function sharding_func_cache.update_cache(space_name, vshard_router) log.warn("require('crud.common.sharding_func').update_cache()" .. "is deprecated and will be removed in future releases") - if vshard_router == nil then - vshard_router = vshard.router.static + local vshard_router, err = utils.get_vshard_router_instance(vshard_router) + if err ~= nil then + return nil, err end return sharding_metadata_module.update_sharding_func_cache(vshard_router, space_name) diff --git a/crud/common/sharding_key.lua b/crud/common/sharding_key.lua index f5fc3c90..1552e8c5 100644 --- a/crud/common/sharding_key.lua +++ b/crud/common/sharding_key.lua @@ -1,7 +1,7 @@ local log = require('log') -local vshard = require('vshard') local sharding_metadata_module = require('crud.common.sharding.sharding_metadata') +local utils = require('crud.common.utils') local sharding_key_cache = {} @@ -13,8 +13,9 @@ function sharding_key_cache.update_cache(space_name, vshard_router) log.warn("require('crud.common.sharding_key').update_cache()" .. "is deprecated and will be removed in future releases") - if vshard_router == nil then - vshard_router = vshard.router.static + local vshard_router, err = utils.get_vshard_router_instance(vshard_router) + if err ~= nil then + return nil, err end return sharding_metadata_module.update_sharding_key_cache(vshard_router, space_name) diff --git a/crud/common/utils.lua b/crud/common/utils.lua index c59dfa90..a1137835 100644 --- a/crud/common/utils.lua +++ b/crud/common/utils.lua @@ -5,6 +5,8 @@ local fun = require('fun') local bit = require('bit') local log = require('log') +local is_cartridge, cartridge = pcall(require, 'cartridge') + local const = require('crud.common.const') local schema = require('crud.common.schema') local dev_checks = require('crud.common.dev_checks') @@ -17,6 +19,7 @@ local GetSpaceFormatError = errors.new_class('GetSpaceFormatError', {capture_sta local FilterFieldsError = errors.new_class('FilterFieldsError', {capture_stack = false}) local NotInitializedError = errors.new_class('NotInitialized') local StorageInfoError = errors.new_class('StorageInfoError') +local VshardRouterError = errors.new_class('VshardRouterError', {capture_stack = false}) local fiber_clock = require('fiber').clock local utils = {} @@ -758,16 +761,23 @@ end -- @tparam ?number opts.timeout -- Function call timeout -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- -- @return a table of storage states by replica uuid. function utils.storage_info(opts) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, StorageInfoError:new(err) + end + local replicasets, err = vshard_router:routeall() if replicasets == nil then - return nil, StorageInfoError:new("Failed to get all replicasets: %s", err.err) + return nil, StorageInfoError:new("Failed to get router replicasets: %s", err.err) end - opts = opts or {} - local futures_by_replicas = {} local replica_state_by_uuid = {} local async_opts = {is_async = true} @@ -835,4 +845,75 @@ function utils.storage_info_on_storage() return {status = "running"} end +local expected_vshard_api = { + 'routeall', 'route', 'bucket_id_strcrc32', + 'callrw', 'callro', 'callbro', 'callre', + 'callbre', 'map_callrw' +} + +--- Verifies that a table has expected vshard +-- router handles. +local function verify_vshard_router(router) + dev_checks("table") + + for _, func_name in ipairs(expected_vshard_api) do + if type(router[func_name]) ~= 'function' then + return false + end + end + + return true +end + +--- Get a vshard router instance from a parameter. +-- +-- If a string passed, extract router instance from +-- Cartridge vshard groups. If table passed, verifies +-- that a table is a vshard router instance. +-- +-- @function get_vshard_router_instance +-- +-- @param[opt] router name of a vshard group or a vshard router +-- instance +-- +-- @return[1] table vshard router instance +-- @treturn[2] nil +-- @treturn[2] table Error description +function utils.get_vshard_router_instance(router) + dev_checks('?string|table') + + local router_instance + + if type(router) == 'string' then + if not is_cartridge then + return nil, VshardRouterError:new("Vshard groups are supported only in Tarantool Cartridge") + end + + local router_service = cartridge.service_get('vshard-router') + assert(router_service ~= nil) + + router_instance = router_service.get(router) + if router_instance == nil then + return nil, VshardRouterError:new("Vshard group %s is not found", router) + end + elseif type(router) == 'table' then + if not verify_vshard_router(router) then + return nil, VshardRouterError:new("Invalid opts.vshard_router table value, " .. + "a vshard router instance has been expected") + end + + router_instance = router + else + assert(type(router) == 'nil') + router_instance = vshard.router.static + + if router_instance == nil then + return nil, VshardRouterError:new("Default vshard group is not found and custom " .. + "is not specified with opts.vshard_router") + end + end + + return router_instance +end + return utils diff --git a/crud/count.lua b/crud/count.lua index 0aba0423..f24a68dd 100644 --- a/crud/count.lua +++ b/crud/count.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local fiber = require('fiber') local call = require('crud.common.call') @@ -120,10 +119,9 @@ local function call_count_on_router(vshard_router, space_name, user_conditions, prefer_replica = '?boolean', balance = '?boolean', mode = '?string', + vshard_router = '?string|table', }) - opts = opts or {} - if opts.yield_every ~= nil and opts.yield_every < 1 then return nil, CountError:new("yield_every should be > 0") end @@ -308,6 +306,11 @@ end -- @tparam ?string opts.mode -- vshard call mode, default value is `read` -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] number -- @treturn[2] nil -- @treturn[2] table Error description @@ -322,9 +325,15 @@ function count.call(space_name, user_conditions, opts) prefer_replica = '?boolean', balance = '?boolean', mode = '?string', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, CountError:new(err) + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_count_on_router, space_name, user_conditions, opts) diff --git a/crud/delete.lua b/crud/delete.lua index 3bca594e..e30401af 100644 --- a/crud/delete.lua +++ b/crud/delete.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -60,10 +59,9 @@ local function call_delete_on_router(vshard_router, space_name, key, opts) timeout = '?number', bucket_id = '?number|cdata', fields = '?table', + vshard_router = '?string|table', }) - opts = opts or {} - local space = utils.get_space(space_name, vshard_router:routeall()) if space == nil then return nil, DeleteError:new("Space %q doesn't exist", space_name), const.NEED_SCHEMA_RELOAD @@ -156,6 +154,11 @@ end -- Bucket ID -- (by default, it's vshard.router.bucket_id_strcrc32 of primary key) -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] object -- @treturn[2] nil -- @treturn[2] table Error description @@ -165,9 +168,15 @@ function delete.call(space_name, key, opts) timeout = '?number', bucket_id = '?number|cdata', fields = '?table', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, DeleteError:new(err) + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_delete_on_router, space_name, key, opts) diff --git a/crud/get.lua b/crud/get.lua index c88bec40..c86fc0eb 100644 --- a/crud/get.lua +++ b/crud/get.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -63,10 +62,9 @@ local function call_get_on_router(vshard_router, space_name, key, opts) prefer_replica = '?boolean', balance = '?boolean', mode = '?string', + vshard_router = '?string|table', }) - opts = opts or {} - local space = utils.get_space(space_name, vshard_router:routeall()) if space == nil then return nil, GetError:new("Space %q doesn't exist", space_name), const.NEED_SCHEMA_RELOAD @@ -172,6 +170,11 @@ end -- @tparam ?boolean opts.balance -- Use replica according to round-robin load balancing -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] object -- @treturn[2] nil -- @treturn[2] table Error description @@ -184,9 +187,15 @@ function get.call(space_name, key, opts) prefer_replica = '?boolean', balance = '?boolean', mode = '?string', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, GetError:new(err) + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_get_on_router, space_name, key, opts) diff --git a/crud/insert.lua b/crud/insert.lua index 5412c85f..a58a6c1a 100644 --- a/crud/insert.lua +++ b/crud/insert.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -62,10 +61,9 @@ local function call_insert_on_router(vshard_router, space_name, original_tuple, bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - opts = opts or {} - local space = utils.get_space(space_name, vshard_router:routeall()) if space == nil then return nil, InsertError:new("Space %q doesn't exist", space_name), const.NEED_SCHEMA_RELOAD @@ -139,6 +137,11 @@ end -- Bucket ID -- (by default, it's vshard.router.bucket_id_strcrc32 of primary key) -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] tuple -- @treturn[2] nil -- @treturn[2] table Error description @@ -149,9 +152,15 @@ function insert.tuple(space_name, tuple, opts) bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, InsertError:new(err) + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_insert_on_router, space_name, tuple, opts) @@ -180,9 +189,15 @@ function insert.object(space_name, obj, opts) bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, InsertError:new(err) + end -- insert can fail if router uses outdated schema to flatten object opts = utils.merge_options(opts, {add_space_schema_hash = true}) diff --git a/crud/insert_many.lua b/crud/insert_many.lua index bdb07661..58e91c83 100644 --- a/crud/insert_many.lua +++ b/crud/insert_many.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -129,10 +128,9 @@ local function call_insert_many_on_router(vshard_router, space_name, original_tu add_space_schema_hash = '?boolean', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - opts = opts or {} - local space = utils.get_space(space_name, vshard_router:routeall()) if space == nil then return nil, {InsertManyError:new("Space %q doesn't exist", space_name)}, const.NEED_SCHEMA_RELOAD @@ -215,9 +213,15 @@ function insert_many.tuples(space_name, tuples, opts) add_space_schema_hash = '?boolean', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, {InsertManyError:new(err)} + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_insert_many_on_router, space_name, tuples, opts) @@ -246,9 +250,15 @@ function insert_many.objects(space_name, objs, opts) fields = '?table', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, {InsertManyError:new(err)} + end -- insert can fail if router uses outdated schema to flatten object opts = utils.merge_options(opts, {add_space_schema_hash = true}) diff --git a/crud/len.lua b/crud/len.lua index bb3ef956..27d387f8 100644 --- a/crud/len.lua +++ b/crud/len.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local log = require('log') local utils = require('crud.common.utils') @@ -33,6 +32,11 @@ end -- @tparam ?number opts.timeout -- Function call timeout -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] number -- @treturn[2] nil -- @treturn[2] table Error description @@ -40,6 +44,7 @@ end function len.call(space_name, opts) checks('string|number', { timeout = '?number', + vshard_router = '?string|table', }) opts = opts or {} @@ -49,7 +54,11 @@ function len.call(space_name, opts) 'Please, use space name instead.') end - local vshard_router = vshard.router.static + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, LenError:new(err) + end + local space = utils.get_space(space_name, vshard_router:routeall()) if space == nil then return nil, LenError:new("Space %q doesn't exist", space_name) diff --git a/crud/replace.lua b/crud/replace.lua index 9234f9b3..c0dcbdc5 100644 --- a/crud/replace.lua +++ b/crud/replace.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -62,10 +61,9 @@ local function call_replace_on_router(vshard_router, space_name, original_tuple, bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - opts = opts or {} - local space, err = utils.get_space(space_name, vshard_router:routeall()) if err ~= nil then return nil, ReplaceError:new("Failed to get space %q: %s", space_name, err), const.NEED_SCHEMA_RELOAD @@ -142,6 +140,11 @@ end -- Bucket ID -- (by default, it's vshard.router.bucket_id_strcrc32 of primary key) -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] object -- @treturn[2] nil -- @treturn[2] table Error description @@ -152,9 +155,15 @@ function replace.tuple(space_name, tuple, opts) bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, ReplaceError:new(err) + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_replace_on_router, space_name, tuple, opts) @@ -183,9 +192,15 @@ function replace.object(space_name, obj, opts) bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, ReplaceError:new(err) + end -- replace can fail if router uses outdated schema to flatten object opts = utils.merge_options(opts, {add_space_schema_hash = true}) diff --git a/crud/replace_many.lua b/crud/replace_many.lua index c75cf29e..c7930e4e 100644 --- a/crud/replace_many.lua +++ b/crud/replace_many.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -131,10 +130,9 @@ local function call_replace_many_on_router(vshard_router, space_name, original_t add_space_schema_hash = '?boolean', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - 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 @@ -217,9 +215,15 @@ function replace_many.tuples(space_name, tuples, opts) add_space_schema_hash = '?boolean', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, {ReplaceManyError:new(err)} + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_replace_many_on_router, space_name, tuples, opts) @@ -248,9 +252,15 @@ function replace_many.objects(space_name, objs, opts) fields = '?table', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, {ReplaceManyError:new(err)} + end -- insert can fail if router uses outdated schema to flatten object opts = utils.merge_options(opts, {add_space_schema_hash = true}) diff --git a/crud/select/compat/select.lua b/crud/select/compat/select.lua index f9d6a189..ac12ebf7 100644 --- a/crud/select/compat/select.lua +++ b/crud/select/compat/select.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local const = require('crud.common.const') local utils = require('crud.common.utils') @@ -200,6 +199,8 @@ function select_module.pairs(space_name, user_conditions, opts) prefer_replica = '?boolean', balance = '?boolean', timeout = '?number', + + vshard_router = '?string|table', }) opts = opts or {} @@ -208,7 +209,10 @@ function select_module.pairs(space_name, user_conditions, opts) error(string.format("Negative first isn't allowed for pairs")) end - local vshard_router = vshard.router.static + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + error(err) + end local iterator_opts = { after = opts.after, @@ -266,9 +270,9 @@ local function select_module_call_xc(vshard_router, space_name, user_conditions, prefer_replica = '?boolean', balance = '?boolean', timeout = '?number', - }) - opts = opts or {} + vshard_router = '?string|table', + }) if opts.first ~= nil and opts.first < 0 then if opts.after == nil then @@ -324,7 +328,12 @@ local function select_module_call_xc(vshard_router, space_name, user_conditions, end function select_module.call(space_name, user_conditions, opts) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, SelectError:new(err) + end return SelectError:pcall(sharding.wrap_select_method, vshard_router, select_module_call_xc, space_name, user_conditions, opts) diff --git a/crud/select/compat/select_old.lua b/crud/select/compat/select_old.lua index 640bff60..512b2e5a 100644 --- a/crud/select/compat/select_old.lua +++ b/crud/select/compat/select_old.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local fun = require('fun') local call = require('crud.common.call') @@ -233,6 +232,8 @@ function select_module.pairs(space_name, user_conditions, opts) prefer_replica = '?boolean', balance = '?boolean', timeout = '?number', + + vshard_router = '?string|table', }) opts = opts or {} @@ -241,7 +242,10 @@ function select_module.pairs(space_name, user_conditions, opts) error(string.format("Negative first isn't allowed for pairs")) end - local vshard_router = vshard.router.static + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + error(err) + end local iterator_opts = { after = opts.after, @@ -306,9 +310,7 @@ function select_module.pairs(space_name, user_conditions, opts) end local function select_module_call_xc(vshard_router, space_name, user_conditions, opts) - dev_checks('table', 'string', '?table', '?table') - - opts = opts or {} + dev_checks('table', 'string', '?table', 'table') if opts.first ~= nil and opts.first < 0 then if opts.after == nil then @@ -382,9 +384,16 @@ function select_module.call(space_name, user_conditions, opts) prefer_replica = '?boolean', balance = '?boolean', timeout = '?number', + + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, SelectError:new(err) + end return sharding.wrap_method(vshard_router, select_module_call_xc, space_name, user_conditions, opts) end diff --git a/crud/stats/init.lua b/crud/stats/init.lua index 1b013f58..994e4440 100644 --- a/crud/stats/init.lua +++ b/crud/stats/init.lua @@ -252,15 +252,20 @@ end local function resolve_space_name(space_id) local vshard_router = vshard.router.static + if vshard_router == nil then + log.warn('Failed to resolve space name for stats: default vshard router not found') + return nil + end + local replicasets = vshard_router:routeall() if next(replicasets) == nil then - log.warn('Failed to resolve space name for stats: no replicasets found') + log.warn('Failed to resolve space name for stats: no replicasets found with default router') return nil end local space = utils.get_space(space_id, replicasets) if space == nil then - log.warn('Failed to resolve space name for stats: no space found for id %d', space_id) + log.warn('Failed to resolve space name for stats: no space found for id %d with default router', space_id) return nil end diff --git a/crud/truncate.lua b/crud/truncate.lua index bd91e889..31cc5842 100644 --- a/crud/truncate.lua +++ b/crud/truncate.lua @@ -1,9 +1,9 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local dev_checks = require('crud.common.dev_checks') local call = require('crud.common.call') +local utils = require('crud.common.utils') local TruncateError = errors.new_class('TruncateError', {capture_stack = false}) @@ -36,6 +36,11 @@ end -- @tparam ?number opts.timeout -- Function call timeout -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] boolean true -- @treturn[2] nil -- @treturn[2] table Error description @@ -43,11 +48,16 @@ end function truncate.call(space_name, opts) checks('string', { timeout = '?number', + vshard_router = '?string|table', }) opts = opts or {} - local vshard_router = vshard.router.static + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, TruncateError:new(err) + end + local replicasets = vshard_router:routeall() local _, err = call.map(vshard_router, TRUNCATE_FUNC_NAME, {space_name}, { mode = 'write', diff --git a/crud/update.lua b/crud/update.lua index 328a756e..16960744 100644 --- a/crud/update.lua +++ b/crud/update.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -82,10 +81,9 @@ local function call_update_on_router(vshard_router, space_name, key, user_operat timeout = '?number', bucket_id = '?number|cdata', fields = '?table', + vshard_router = '?string|table', }) - opts = opts or {} - local space, err = utils.get_space(space_name, vshard_router:routeall()) if err ~= nil then return nil, UpdateError:new("Failed to get space %q: %s", space_name, err), const.NEED_SCHEMA_RELOAD @@ -196,6 +194,11 @@ end -- Bucket ID -- (by default, it's vshard.router.bucket_id_strcrc32 of primary key) -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] object -- @treturn[2] nil -- @treturn[2] table Error description @@ -205,9 +208,14 @@ function update.call(space_name, key, user_operations, opts) timeout = '?number', bucket_id = '?number|cdata', fields = '?table', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, UpdateError:new(err) + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_update_on_router, space_name, key, user_operations, opts) diff --git a/crud/upsert.lua b/crud/upsert.lua index 55b996b8..6de7451b 100644 --- a/crud/upsert.lua +++ b/crud/upsert.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -60,10 +59,9 @@ local function call_upsert_on_router(vshard_router, space_name, original_tuple, bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - opts = opts or {} - local space, err = utils.get_space(space_name, vshard_router:routeall()) if err ~= nil then return nil, UpsertError:new("Failed to get space %q: %s", space_name, err), const.NEED_SCHEMA_RELOAD @@ -153,6 +151,11 @@ end -- Bucket ID -- (by default, it's vshard.router.bucket_id_strcrc32 of primary key) -- +-- @tparam ?string|table opts.vshard_router +-- Cartridge vshard group name or vshard router instance. +-- Set this parameter if your space is not a part of the +-- default vshard cluster. +-- -- @return[1] tuple -- @treturn[2] nil -- @treturn[2] table Error description @@ -163,9 +166,15 @@ function upsert.tuple(space_name, tuple, user_operations, opts) bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, UpsertError:new(err) + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_upsert_on_router, space_name, tuple, user_operations, opts) @@ -198,9 +207,16 @@ function upsert.object(space_name, obj, user_operations, opts) bucket_id = '?number|cdata', add_space_schema_hash = '?boolean', fields = '?table', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, UpsertError:new(err) + end + -- upsert can fail if router uses outdated schema to flatten object opts = utils.merge_options(opts, {add_space_schema_hash = true}) diff --git a/crud/upsert_many.lua b/crud/upsert_many.lua index d27f77e1..5980c0d4 100644 --- a/crud/upsert_many.lua +++ b/crud/upsert_many.lua @@ -1,6 +1,5 @@ local checks = require('checks') local errors = require('errors') -local vshard = require('vshard') local call = require('crud.common.call') local const = require('crud.common.const') @@ -127,10 +126,9 @@ local function call_upsert_many_on_router(vshard_router, space_name, original_tu add_space_schema_hash = '?boolean', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - opts = opts or {} - local space = utils.get_space(space_name, vshard_router:routeall()) if space == nil then return nil, {UpsertManyError:new("Space %q doesn't exist", space_name)}, const.NEED_SCHEMA_RELOAD @@ -232,9 +230,15 @@ function upsert_many.tuples(space_name, tuples_operation_data, opts) add_space_schema_hash = '?boolean', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, {UpsertManyError:new(err)} + end return schema.wrap_func_reload(vshard_router, sharding.wrap_method, call_upsert_many_on_router, space_name, tuples_operation_data, opts) @@ -264,9 +268,15 @@ function upsert_many.objects(space_name, objs_operation_data, opts) fields = '?table', stop_on_error = '?boolean', rollback_on_error = '?boolean', + vshard_router = '?string|table', }) - local vshard_router = vshard.router.static + opts = opts or {} + + local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router) + if err ~= nil then + return nil, {UpsertManyError:new(err)} + end -- upsert can fail if router uses outdated schema to flatten object opts = utils.merge_options(opts, {add_space_schema_hash = true}) diff --git a/doc/dev/schema.md b/doc/dev/schema.md index 58cbba9a..727c6465 100644 --- a/doc/dev/schema.md +++ b/doc/dev/schema.md @@ -44,6 +44,11 @@ call.) Each ``net.box`` connection has space schema for an instance it is connected to. Router can access space schema by using ``net.box`` connection object contents. +Tarantool instance may have several vshard routers. For each request, only +one router is used (specified in `vshard_router` option or a default one). +Each router has its own ``net.box`` connections. Fetching and reload processes +are different for each router and do not affect each other. + ### When schema is used Space schema is used @@ -164,6 +169,12 @@ sharding schema: thus, you don't obliged to use ``ddl`` module and can setup only ``_ddl_*`` spaces manually, if you want. ``crud`` module uses plain Lua tables to store sharding info on routers and storages. +Tarantool instance may have several vshard routers. For each request, only +one router is used (specified in `vshard_router` option or a default one). +Each router has its own ``net.box`` connections. Fetching and reload processes +are different for each router and do not affect each other since they +have separate caches. + ### When schema is used Sharding schema is used diff --git a/test/entrypoint/srv_vshard_custom.lua b/test/entrypoint/srv_vshard_custom.lua new file mode 100755 index 00000000..7cbf8168 --- /dev/null +++ b/test/entrypoint/srv_vshard_custom.lua @@ -0,0 +1,249 @@ +#!/usr/bin/env tarantool + +require('strict').on() +_G.is_initialized = function() return false end + +local log = require('log') +local errors = require('errors') +local cartridge = require('cartridge') +local ddl = require('ddl') + +package.preload['customers-storage'] = function() + return { + role_name = 'customers-storage', + init = function(opts) + local engine = os.getenv('ENGINE') or 'memtx' + + if opts.is_master then + local customers_space = box.schema.space.create('customers', { + format = { + {name = 'id', is_nullable = false, type = 'unsigned'}, + {name = 'bucket_id', is_nullable = false, type = 'unsigned'}, + {name = 'name', is_nullable = false, type = 'string'}, + {name = 'age', is_nullable = false, type = 'number'}, + }, + if_not_exists = true, + engine = engine, + }) + + customers_space:create_index('pk', { + parts = { {field = 'id'} }, + if_not_exists = true, + unique = true, + }) + customers_space:create_index('bucket_id', { + parts = { {field = 'bucket_id'} }, + unique = false, + if_not_exists = true, + }) + customers_space:create_index('age', { + parts = { {field = 'age'} }, + unique = false, + if_not_exists = true, + }) + end + end, + dependencies = { 'cartridge.roles.crud-storage' } + } +end + +package.preload['customers-storage-ddl'] = function() + return { + role_name = 'customers-storage-ddl', + init = function() + local engine = os.getenv('ENGINE') or 'memtx' + + local customers_schema = { + engine = engine, + temporary = false, + is_local = false, + format = { + {name = 'id', is_nullable = false, type = 'unsigned'}, + {name = 'bucket_id', is_nullable = false, type = 'unsigned'}, + {name = 'name', is_nullable = false, type = 'string'}, + {name = 'age', is_nullable = false, type = 'number'}, + }, + indexes = { + { + name = 'pk', + type = 'TREE', + unique = true, + parts = { + {path = 'id', is_nullable = false, type = 'unsigned'}, + {path = 'name', is_nullable = false, type = 'string'}, + }, + }, + { + name = 'bucket_id', + type = 'TREE', + unique = false, + parts = { + {path = 'bucket_id', is_nullable = false, type = 'unsigned'}, + }, + }, + { + name = 'name', + type = 'TREE', + unique = false, + parts = { + {path = 'name', is_nullable = false, type = 'string'}, + }, + }, + { + name = 'age', + type = 'TREE', + unique = false, + parts = { + {path = 'age', is_nullable = false, type = 'number'}, + }, + }, + }, + sharding_key = { 'name' } + } + + local schema = { + spaces = { + ['customers_ddl'] = customers_schema, + } + } + + local _, err = ddl.set_schema(schema) + if err ~= nil then + error(err) + end + end, + dependencies = { 'cartridge.roles.crud-storage' } + } +end + +package.preload['locations-storage'] = function() + return { + role_name = 'locations-storage', + init = function(opts) + local engine = os.getenv('ENGINE') or 'memtx' + + if opts.is_master then + local locations_space = box.schema.space.create('locations', { + format = { + {name = 'name', is_nullable = false, type = 'string'}, + {name = 'bucket_id', is_nullable = false, type = 'unsigned'}, + {name = 'type', is_nullable = false, type = 'string'}, + {name = 'workers', is_nullable = false, type = 'number'}, + }, + if_not_exists = true, + engine = engine, + }) + + locations_space:create_index('pk', { + parts = { {field = 'name'} }, + if_not_exists = true, + unique = true, + }) + locations_space:create_index('bucket_id', { + parts = { {field = 'bucket_id'} }, + unique = false, + if_not_exists = true, + }) + locations_space:create_index('workers', { + parts = { {field = 'workers'} }, + unique = false, + if_not_exists = true, + }) + end + end, + dependencies = { 'cartridge.roles.crud-storage' } + } +end + + +package.preload['locations-storage-ddl'] = function() + return { + role_name = 'locations-storage-ddl', + init = function() + local engine = os.getenv('ENGINE') or 'memtx' + + local locations_schema = { + engine = engine, + temporary = false, + is_local = false, + format = { + {name = 'name', is_nullable = false, type = 'string'}, + {name = 'bucket_id', is_nullable = false, type = 'unsigned'}, + {name = 'type', is_nullable = false, type = 'string'}, + {name = 'workers', is_nullable = false, type = 'unsigned'}, + }, + indexes = { + { + name = 'pk', + type = 'TREE', + unique = true, + parts = { + {path = 'name', is_nullable = false, type = 'string'}, + {path = 'type', is_nullable = false, type = 'string'}, + }, + }, + { + name = 'bucket_id', + type = 'TREE', + unique = false, + parts = { + {path = 'bucket_id', is_nullable = false, type = 'unsigned'}, + }, + }, + { + name = 'type', + type = 'TREE', + unique = false, + parts = { + {path = 'type', is_nullable = false, type = 'string'}, + }, + }, + { + name = 'workers', + type = 'TREE', + unique = false, + parts = { + {path = 'workers', is_nullable = false, type = 'unsigned'}, + }, + }, + }, + sharding_key = { 'type' } + } + + local schema = { + spaces = { + ['locations_ddl'] = locations_schema, + } + } + + local _, err = ddl.set_schema(schema) + if err ~= nil then + error(err) + end + end, + dependencies = { 'cartridge.roles.crud-storage' } + } +end + +local ok, err = errors.pcall('CartridgeCfgError', cartridge.cfg, { + bucket_count = nil, + vshard_groups = { + 'customers', + 'locations', + }, + roles = { + 'customers-storage', + 'customers-storage-ddl', + 'locations-storage', + 'locations-storage-ddl', + 'cartridge.roles.crud-router', + 'cartridge.roles.crud-storage', + }, +}) + +if not ok then + log.error('%s', err) + os.exit(1) +end + +_G.is_initialized = cartridge.is_healthy diff --git a/test/integration/vshard_custom_test.lua b/test/integration/vshard_custom_test.lua new file mode 100644 index 00000000..c5801b7a --- /dev/null +++ b/test/integration/vshard_custom_test.lua @@ -0,0 +1,1631 @@ +local fio = require('fio') + +local t = require('luatest') + +local helpers = require('test.helper') + +local pgroup = t.group('vshard_custom', { + {engine = 'memtx', option = 'group_name'}, + {engine = 'memtx', option = 'router_object'}, + {engine = 'vinyl', option = 'group_name'}, + {engine = 'vinyl', option = 'router_object'}, +}) + +pgroup.before_all(function(g) + g.cluster = helpers.Cluster:new({ + datadir = fio.tempdir(), + server_command = helpers.entrypoint('srv_vshard_custom'), + use_vshard = true, + replicasets = { + { + alias = 'router', + uuid = helpers.uuid('a'), + roles = { 'crud-router' }, + servers = {{ + alias = 'router', + http_port = 8081, + advertise_port = 13301, + instance_uuid = helpers.uuid('a', 'a', 1) + }}, + }, + { + alias = 'customers-storage-1', + uuid = helpers.uuid('b1'), + roles = { 'customers-storage', 'customers-storage-ddl' }, + vshard_group = 'customers', + servers = {{ + alias = 'customers-storage-1', + http_port = 8082, + advertise_port = 13302, + instance_uuid = helpers.uuid('b1', 'b1', 2) + }}, + }, + { + alias = 'customers-storage-2', + uuid = helpers.uuid('b2'), + roles = { 'customers-storage', 'customers-storage-ddl' }, + vshard_group = 'customers', + servers = {{ + alias = 'customers-storage-2', + http_port = 8083, + advertise_port = 13303, + instance_uuid = helpers.uuid('b2', 'b2', 2) + }}, + }, + { + alias = 'locations-storage-1', + uuid = helpers.uuid('c1'), + roles = { 'locations-storage', 'locations-storage-ddl' }, + vshard_group = 'locations', + servers = {{ + alias = 'locations-storage-1', + http_port = 8084, + advertise_port = 13304, + instance_uuid = helpers.uuid('c1', 'c1', 2) + }}, + }, + { + alias = 'locations-storage-2', + uuid = helpers.uuid('c2'), + roles = { 'locations-storage', 'locations-storage-ddl' }, + vshard_group = 'locations', + servers = {{ + alias = 'locations-storage-2', + http_port = 8085, + advertise_port = 13305, + instance_uuid = helpers.uuid('c2', 'c2', 2) + }}, + }, + }, + env = { + ['ENGINE'] = g.params.engine, + }, + }) + + g.cluster:start() + g.router = g.cluster:server('router').net_box +end) + +pgroup.after_all(function(g) helpers.stop_cluster(g.cluster) end) + +pgroup.before_all(function(g) + g.router:eval([[ + local checks = require('checks') + local cartridge = require('cartridge') + local router_service = cartridge.service_get('vshard-router') + + local function prepare_data(space_name, vshard_router, tuple) + checks('string', 'string', 'table') + + local router = router_service.get(vshard_router) + assert(router ~= nil) + + local bucket_id_field = 2 + local sharding_field + if space_name:find('ddl') ~= nil then + sharding_field = 3 + else + sharding_field = 1 + end + + local bucket_id = router:bucket_id_strcrc32(tuple[sharding_field]) + tuple[bucket_id_field] = bucket_id + + local replicaset = router:route(bucket_id) + assert(replicaset ~= nil) + + local space = replicaset.master.conn.space[space_name] + assert(space ~= nil) + + local res, err = space:replace(tuple) + + if err ~= nil then + error(err) + end + + return res + end + + rawset(_G, 'prepare_data', prepare_data) + + local function call_wrapper_opts2(option, func, space_name, opts) + if option == 'router_object' and opts ~= nil and type(opts.vshard_router) == 'string' then + local router = router_service.get(opts.vshard_router) + opts.vshard_router = router + end + + return crud[func](space_name, opts) + end + + rawset(_G, 'call_wrapper_opts2', call_wrapper_opts2) + + local function call_wrapper_opts3(option, func, space_name, arg, opts) + if option == 'router_object' and opts ~= nil and type(opts.vshard_router) == 'string' then + local router = router_service.get(opts.vshard_router) + opts.vshard_router = router + end + + return crud[func](space_name, arg, opts) + end + + rawset(_G, 'call_wrapper_opts3', call_wrapper_opts3) + + local function call_wrapper_opts4(option, func, space_name, arg1, arg2, opts) + if option == 'router_object' and opts ~= nil and type(opts.vshard_router) == 'string' then + local router = router_service.get(opts.vshard_router) + opts.vshard_router = router + end + + return crud[func](space_name, arg1, arg2, opts) + end + + rawset(_G, 'call_wrapper_opts4', call_wrapper_opts4) + + local function call_pairs_wrapper(option, space_name, arg1, opts) + if option == 'router_object' and opts ~= nil and type(opts.vshard_router) == 'string' then + local router = router_service.get(opts.vshard_router) + opts.vshard_router = router + end + + local result = {} + for _, v in crud.pairs(space_name, arg1, opts) do + table.insert(result, v) + end + + return result + end + + rawset(_G, 'call_pairs_wrapper', call_pairs_wrapper) + ]]) + + g.call_router_opts2 = function(g, ...) + return g.router:call('call_wrapper_opts2', {g.params.option, ...}) + end + + g.call_router_opts3 = function(g, ...) + return g.router:call('call_wrapper_opts3', {g.params.option, ...}) + end + + g.call_router_opts4 = function(g, ...) + return g.router:call('call_wrapper_opts4', {g.params.option, ...}) + end + + g.call_router_pairs = function(g, ...) + return g.router:call('call_pairs_wrapper', {g.params.option, ...}) + end +end) + +pgroup.before_each(function(g) + helpers.truncate_space_on_cluster(g.cluster, 'customers') + helpers.truncate_space_on_cluster(g.cluster, 'customers_ddl') + helpers.truncate_space_on_cluster(g.cluster, 'locations') + helpers.truncate_space_on_cluster(g.cluster, 'locations_ddl') + + g.router:call('prepare_data', {'customers', 'customers', {1, box.NULL, 'Akiyama Shun', 32}}) + g.router:call('prepare_data', {'customers', 'customers', {2, box.NULL, 'Kazuma Kiryu', 41}}) + g.router:call('prepare_data', {'customers_ddl', 'customers', {1, box.NULL, 'Akiyama Shun', 32}}) + g.router:call('prepare_data', {'customers_ddl', 'customers', {2, box.NULL, 'Kazuma Kiryu', 41}}) + + g.router:call('prepare_data', {'locations', 'locations', {'Sky Finance', box.NULL, 'Credit company', 2}}) + g.router:call('prepare_data', {'locations', 'locations', {'Sunflower', box.NULL, 'Orphanage', 1}}) + g.router:call('prepare_data', {'locations_ddl', 'locations', {'Sky Finance', box.NULL, 'Credit company', 2}}) + g.router:call('prepare_data', {'locations_ddl', 'locations', {'Sunflower', box.NULL, 'Orphanage', 1}}) +end) + +pgroup.test_call_min = function(g) + local result, err = g:call_router_opts3( + 'min', 'customers', 'age', {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{1, 12477, 'Akiyama Shun', 32}}) +end + +pgroup.test_call_min_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'min', 'customers', 'age', {vshard_router = 'locations'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"customers\" doesn't exist") +end + +pgroup.test_call_min_no_router = function(g) + local result, err = g:call_router_opts3( + 'min', 'customers', 'age') + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_min_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'min', 'customers', 'age', {vshard_router = {group = 'customers'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_max = function(g) + local result, err = g:call_router_opts3( + 'max', 'locations', 'workers', {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{'Sky Finance', 26826, 'Credit company', 2}}) +end + +pgroup.test_call_max_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'max', 'locations', 'workers', {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_max_no_router = function(g) + local result, err = g:call_router_opts3( + 'max', 'locations', 'workers') + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_max_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'max', 'locations', 'workers', {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_count_with_default_sharding = function(g) + local result, err = g:call_router_opts3( + 'count', 'locations', {{'=', 'name', 'Sunflower'}}, {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_equals(result, 1) +end + +pgroup.test_call_count_with_ddl_sharding = function(g) + local result, err = g:call_router_opts3( + 'count', 'customers_ddl', {{'=', 'age', 41}}, {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_equals(result, 1) +end + +pgroup.test_call_count_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'count', 'locations', {{'=', 'name', 'Sunflower'}}, {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_count_no_router = function(g) + local result, err = g:call_router_opts3( + 'count', 'locations', {{'=', 'name', 'Sunflower'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_count_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'count', 'locations', {{'=', 'name', 'Sunflower'}}, {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.before_test('test_call_delete_with_default_sharding', function(g) + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Sky Finance'} + t.assert_equals(storage_result, {'Sky Finance', 26826, 'Credit company', 2}) +end) + +pgroup.test_call_delete_with_default_sharding = function(g) + local result, err = g:call_router_opts3( + 'delete', 'locations', {'Sky Finance'}, {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + if g.params.engine == 'memtx' then + t.assert_items_equals(result.rows, {{'Sky Finance', 26826, 'Credit company', 2}}) + else + t.assert_equals(#result.rows, 0) + end + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Sky Finance'} + t.assert_equals(storage_result, nil) +end + +pgroup.before_test('test_call_delete_with_ddl_sharding', function(g) + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{2, 'Kazuma Kiryu'} + t.assert_equals(storage_result, {2, 8768, 'Kazuma Kiryu', 41}) +end) + +pgroup.test_call_delete_with_ddl_sharding = function(g) + local result, err = g:call_router_opts3( + 'delete', 'customers_ddl', {2, 'Kazuma Kiryu'}, {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + if g.params.engine == 'memtx' then + t.assert_items_equals(result.rows, {{2, 8768, 'Kazuma Kiryu', 41}}) + else + t.assert_equals(#result.rows, 0) + end + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{2, 'Kazuma Kiryu'} + t.assert_equals(storage_result, nil) +end + +pgroup.test_call_delete_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'delete', 'locations', {'Sky Finance'}, {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_delete_no_router = function(g) + local result, err = g:call_router_opts3( + 'delete', 'locations', {'Sky Finance'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_delete_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'delete', 'locations', {'Sky Finance'}, {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_get_with_default_sharding = function(g) + local result, err = g:call_router_opts3( + 'get', 'locations', {'Sky Finance'}, {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{'Sky Finance', 26826, 'Credit company', 2}}) +end + +pgroup.test_call_get_with_ddl_sharding = function(g) + local result, err = g:call_router_opts3( + 'get', 'customers_ddl', {2, 'Kazuma Kiryu'}, {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{2, 8768, 'Kazuma Kiryu', 41}}) +end + +pgroup.test_call_get_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'get', 'locations', {'Sky Finance'}, {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_get_no_router = function(g) + local result, err = g:call_router_opts3( + 'get', 'locations', {'Sky Finance'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_get_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'get', 'locations', {'Sky Finance'}, {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_insert_with_default_sharding = function(g) + local result, err = g:call_router_opts3( + 'insert', + 'customers_ddl', + {4, box.NULL, 'Taiga Saejima', 45}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{4, 4344, 'Taiga Saejima', 45}}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_insert_with_ddl_sharding = function(g) + local result, err = g:call_router_opts3( + 'insert', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_insert_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'insert', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_insert_no_router = function(g) + local result, err = g:call_router_opts3( + 'insert', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_insert_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'insert', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_insert_object_with_default_sharding = function(g) + local result, err = g:call_router_opts3( + 'insert_object', + 'customers_ddl', + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{4, 4344, 'Taiga Saejima', 45}}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_insert_object_with_ddl_sharding = function(g) + local result, err = g:call_router_opts3( + 'insert_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_insert_object_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'insert_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_insert_object_no_router = function(g) + local result, err = g:call_router_opts3( + 'insert_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_insert_object_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'insert_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_insert_many_with_default_sharding = function(g) + local result, errs = g:call_router_opts3( + 'insert_many', + 'customers', + { + {3, box.NULL, 'Masayoshi Tanimura', 29}, + {4, box.NULL, 'Taiga Saejima', 45}, + }, + {vshard_router = 'customers'}) + + t.assert_equals(errs, nil) + t.assert_items_equals(result.rows, + { + {3, 11804, 'Masayoshi Tanimura', 29}, + {4, 28161, 'Taiga Saejima', 45} + }) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers']:get{3} + t.assert_equals(storage_result, {3, 11804, 'Masayoshi Tanimura', 29}) + + local storage = g.cluster:server('customers-storage-1').net_box + local storage_result = storage.space['customers']:get{4} + t.assert_equals(storage_result, {4, 28161, 'Taiga Saejima', 45}) +end + +pgroup.test_call_insert_many_with_ddl_sharding = function(g) + local result, errs = g:call_router_opts3( + 'insert_many', + 'locations_ddl', + { + {'Tokyo Police Department', box.NULL, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100} + }, + {vshard_router = 'locations'}) + + t.assert_equals(errs, nil) + t.assert_items_equals(result.rows, + { + {'Tokyo Police Department', 28259, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', 6427, 'Prison', 100} + }) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations_ddl']:get{'Tokyo Police Department', 'Police'} + t.assert_equals(storage_result, {'Tokyo Police Department', 28259, 'Police', 40000}) + + local storage = g.cluster:server('locations-storage-2').net_box + local storage_result = storage.space['locations_ddl']:get{'Okinawa Penitentiary No. 2', 'Prison'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 6427, 'Prison', 100}) +end + +pgroup.test_call_insert_many_wrong_router = function(g) + local result, errs = g:call_router_opts3( + 'insert_many', + 'locations', + { + {'Tokyo Police Department', box.NULL, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + }, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_insert_many_no_router = function(g) + local result, errs = g:call_router_opts3( + 'insert_many', + 'locations', + { + {'Tokyo Police Department', box.NULL, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100} + }) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_insert_many_wrong_option = function(g) + local result, errs = g:call_router_opts3( + 'insert_many', + 'locations', + { + {'Tokyo Police Department', box.NULL, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100} + }, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_insert_object_many_with_default_sharding = function(g) + local result, errs = g:call_router_opts3( + 'insert_object_many', + 'locations', + { + {name = 'Tokyo Police Department', bucket_id = box.NULL, type = 'Police', workers = 40000}, + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100} + }, + {vshard_router = 'locations'} + ) + + t.assert_equals(errs, nil) + t.assert_items_equals(result.rows, + { + {'Tokyo Police Department', 9017, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}, + }) + + local storage = g.cluster:server('locations-storage-2').net_box + local storage_result = storage.space['locations']:get{'Tokyo Police Department'} + t.assert_equals(storage_result, {'Tokyo Police Department', 9017, 'Police', 40000}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_insert_object_many_with_ddl_sharding = function(g) + local result, errs = g:call_router_opts3( + 'insert_object_many', + 'customers_ddl', + { + {id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + }, + {vshard_router = 'customers'} + ) + + t.assert_equals(errs, nil) + t.assert_items_equals(result.rows, + { + {3, 2900, 'Masayoshi Tanimura', 29}, + {4, 4344, 'Taiga Saejima', 45} + }) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{3, 'Masayoshi Tanimura'} + t.assert_equals(storage_result, {3, 2900, 'Masayoshi Tanimura', 29}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_insert_object_many_wrong_router = function(g) + local result, errs = g:call_router_opts3( + 'insert_object_many', + 'customers', + { + {id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + }, + {vshard_router = 'locations'}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, "Space \"customers\" doesn't exist") +end + +pgroup.test_call_insert_object_many_no_router = function(g) + local result, errs = g:call_router_opts3( + 'insert_object_many', + 'customers', + { + {id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + }) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_insert_object_many_wrong_option = function(g) + local result, errs = g:call_router_opts3( + 'insert_object_many', + 'customers', + { + {id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + }, + {vshard_router = {group = 'customers'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_len = function(g) + local result, err = g:call_router_opts2( + 'len', 'customers', {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_equals(result, 2) +end + +pgroup.test_call_len_wrong_router = function(g) + local result, err = g:call_router_opts2( + 'len', 'customers', {vshard_router = 'locations'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"customers\" doesn't exist") +end + +pgroup.test_call_len_no_router = function(g) + local result, err = g:call_router_opts2( + 'len', 'customers') + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_len_wrong_option = function(g) + local result, err = g:call_router_opts2( + 'len', 'customers', {vshard_router = {group = 'customers'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_replace_with_default_sharding = function(g) + local result, err = g:call_router_opts3( + 'replace', + 'customers_ddl', + {4, box.NULL, 'Taiga Saejima', 45}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{4, 4344, 'Taiga Saejima', 45}}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_replace_with_ddl_sharding = function(g) + local result, err = g:call_router_opts3( + 'replace', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_replace_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'replace', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_replace_no_router = function(g) + local result, err = g:call_router_opts3( + 'replace', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_replace_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'replace', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_replace_object_with_default_sharding = function(g) + local result, err = g:call_router_opts3( + 'replace_object', + 'customers_ddl', + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{4, 4344, 'Taiga Saejima', 45}}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_replace_object_with_ddl_sharding = function(g) + local result, err = g:call_router_opts3( + 'replace_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_replace_object_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'replace_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_replace_object_no_router = function(g) + local result, err = g:call_router_opts3( + 'replace_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_replace_object_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'replace_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_replace_many_with_default_sharding = function(g) + local result, errs = g:call_router_opts3( + 'replace_many', + 'customers', + { + {3, box.NULL, 'Masayoshi Tanimura', 29}, + {4, box.NULL, 'Taiga Saejima', 45}, + }, + {vshard_router = 'customers'}) + + t.assert_equals(errs, nil) + t.assert_items_equals(result.rows, + { + {3, 11804, 'Masayoshi Tanimura', 29}, + {4, 28161, 'Taiga Saejima', 45} + }) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers']:get{3} + t.assert_equals(storage_result, {3, 11804, 'Masayoshi Tanimura', 29}) + + local storage = g.cluster:server('customers-storage-1').net_box + local storage_result = storage.space['customers']:get{4} + t.assert_equals(storage_result, {4, 28161, 'Taiga Saejima', 45}) +end + +pgroup.test_call_replace_many_with_ddl_sharding = function(g) + local result, errs = g:call_router_opts3( + 'replace_many', + 'locations_ddl', + { + {'Tokyo Police Department', box.NULL, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100} + }, + {vshard_router = 'locations'}) + + t.assert_equals(errs, nil) + t.assert_items_equals(result.rows, + { + {'Tokyo Police Department', 28259, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', 6427, 'Prison', 100} + }) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations_ddl']:get{'Tokyo Police Department', 'Police'} + t.assert_equals(storage_result, {'Tokyo Police Department', 28259, 'Police', 40000}) + + local storage = g.cluster:server('locations-storage-2').net_box + local storage_result = storage.space['locations_ddl']:get{'Okinawa Penitentiary No. 2', 'Prison'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 6427, 'Prison', 100}) +end + +pgroup.test_call_replace_many_wrong_router = function(g) + local result, errs = g:call_router_opts3( + 'replace_many', + 'locations', + { + {'Tokyo Police Department', box.NULL, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + }, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_replace_many_no_router = function(g) + local result, errs = g:call_router_opts3( + 'replace_many', + 'locations', + { + {'Tokyo Police Department', box.NULL, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100} + }) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_replace_many_wrong_option = function(g) + local result, errs = g:call_router_opts3( + 'replace_many', + 'locations', + { + {'Tokyo Police Department', box.NULL, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100} + }, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_replace_object_many_with_default_sharding = function(g) + local result, errs = g:call_router_opts3( + 'replace_object_many', + 'locations', + { + {name = 'Tokyo Police Department', bucket_id = box.NULL, type = 'Police', workers = 40000}, + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100} + }, + {vshard_router = 'locations'} + ) + + t.assert_equals(errs, nil) + t.assert_items_equals(result.rows, + { + {'Tokyo Police Department', 9017, 'Police', 40000}, + {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}, + }) + + local storage = g.cluster:server('locations-storage-2').net_box + local storage_result = storage.space['locations']:get{'Tokyo Police Department'} + t.assert_equals(storage_result, {'Tokyo Police Department', 9017, 'Police', 40000}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_replace_object_many_with_ddl_sharding = function(g) + local result, errs = g:call_router_opts3( + 'replace_object_many', + 'customers_ddl', + { + {id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + }, + {vshard_router = 'customers'} + ) + + t.assert_equals(errs, nil) + t.assert_items_equals(result.rows, + { + {3, 2900, 'Masayoshi Tanimura', 29}, + {4, 4344, 'Taiga Saejima', 45} + }) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{3, 'Masayoshi Tanimura'} + t.assert_equals(storage_result, {3, 2900, 'Masayoshi Tanimura', 29}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_replace_object_many_wrong_router = function(g) + local result, errs = g:call_router_opts3( + 'replace_object_many', + 'customers', + { + {id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + }, + {vshard_router = 'locations'}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, "Space \"customers\" doesn't exist") +end + +pgroup.test_call_replace_object_many_no_router = function(g) + local result, errs = g:call_router_opts3( + 'replace_object_many', + 'customers', + { + {id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + }) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_replace_object_many_wrong_option = function(g) + local result, errs = g:call_router_opts3( + 'replace_object_many', + 'customers', + { + {id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + }, + {vshard_router = {group = 'customers'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_select_with_default_sharding = function(g) + local result, err = g:call_router_opts3( + 'select', + 'locations', + {{'=', 'name', 'Sky Finance'}}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{'Sky Finance', 26826, 'Credit company', 2}}) +end + +pgroup.test_call_select_with_ddl_sharding = function(g) + local result, err = g:call_router_opts3( + 'select', + 'customers_ddl', + {{'=', 'id', 2}, {'=', 'name', 'Kazuma Kiryu'}}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{2, 8768, 'Kazuma Kiryu', 41}}) +end + +pgroup.test_call_select_wrong_router = function(g) + local result, err = g:call_router_opts3( + 'select', + 'locations', + {{'=', 'name', 'Sky Finance'}}, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_select_no_router = function(g) + local result, err = g:call_router_opts3( + 'select', 'locations', {{'=', 'name', 'Sky Finance'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_select_wrong_option = function(g) + local result, err = g:call_router_opts3( + 'select', 'locations', {{'=', 'name', 'Sky Finance'}}, {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_pairs_with_default_sharding = function(g) + local result, err = g:call_router_pairs( + 'locations', + {{'=', 'name', 'Sky Finance'}}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result, {{'Sky Finance', 26826, 'Credit company', 2}}) +end + +pgroup.test_call_pairs_with_ddl_sharding = function(g) + local result, err = g:call_router_pairs( + 'customers_ddl', + {{'=', 'id', 2}, {'=', 'name', 'Kazuma Kiryu'}}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result, {{2, 8768, 'Kazuma Kiryu', 41}}) +end + +pgroup.test_call_pairs_wrong_router = function(g) + t.assert_error_msg_contains( + "Space \"locations\" doesn't exist", + g.call_router_pairs, g, 'locations', {{'=', 'name', 'Sky Finance'}}, {vshard_router = 'customers'}) +end + +pgroup.test_call_pairs_no_router = function(g) + t.assert_error_msg_contains( + "Default vshard group is not found and custom is not specified with opts.vshard_router", + g.call_router_pairs, g, 'locations', {{'=', 'name', 'Sky Finance'}}) +end + +pgroup.test_call_pairs_wrong_option = function(g) + t.assert_error_msg_contains( + "Invalid opts.vshard_router table value, a vshard router instance has been expected", + g.call_router_pairs, g, 'locations', {{'=', 'name', 'Sky Finance'}}, {vshard_router = {group = 'locations'}}) +end + +pgroup.before_test('test_call_truncate', function(g) + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Sky Finance'} + t.assert_equals(storage_result, {'Sky Finance', 26826, 'Credit company', 2}) + + local storage = g.cluster:server('locations-storage-2').net_box + local storage_result = storage.space['locations']:get{'Sunflower'} + t.assert_equals(storage_result, {'Sunflower', 12261, 'Orphanage', 1}) +end) + +pgroup.test_call_truncate = function(g) + local result, err = g:call_router_opts2( + 'truncate', 'locations', {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_equals(result, true) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Sky Finance'} + t.assert_equals(storage_result, nil) + + local storage = g.cluster:server('locations-storage-2').net_box + local storage_result = storage.space['locations']:get{'Sunflower'} + t.assert_equals(storage_result, nil) +end + +pgroup.test_call_truncate_wrong_router = function(g) + local result, err = g:call_router_opts2( + 'truncate', 'customers', {vshard_router = 'locations'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"customers\" doesn't exist") +end + +pgroup.test_call_truncate_no_router = function(g) + local result, err = g:call_router_opts2( + 'truncate', 'customers') + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_truncate_wrong_option = function(g) + local result, err = g:call_router_opts2( + 'truncate', 'customers', {vshard_router = {group = 'customers'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_update_with_default_sharding = function(g) + local result, err = g:call_router_opts4( + 'update', + 'locations', + {'Sky Finance'}, + {{'-', 'workers', 1}}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{'Sky Finance', 26826, 'Credit company', 1}}) +end + +pgroup.test_call_update_with_ddl_sharding = function(g) + local result, err = g:call_router_opts4( + 'update', + 'customers_ddl', + {2, 'Kazuma Kiryu'}, + {{'+', 'age', 1}}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {{2, 8768, 'Kazuma Kiryu', 42}}) +end + +pgroup.test_call_update_wrong_router = function(g) + local result, err = g:call_router_opts4( + 'update', + 'locations', + {'Sky Finance'}, + {{'-', 'workers', 1}}, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_update_no_router = function(g) + local result, err = g:call_router_opts4( + 'update', + 'locations', + {'Sky Finance'}, + {{'-', 'workers', 1}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_update_wrong_option = function(g) + local result, err = g:call_router_opts4( + 'update', + 'locations', + {'Sky Finance'}, + {{'-', 'workers', 1}}, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_upsert_with_default_sharding = function(g) + local result, err = g:call_router_opts4( + 'upsert', + 'customers_ddl', + {4, box.NULL, 'Taiga Saejima', 45}, + {{'+', 'age', 1}}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_upsert_with_ddl_sharding = function(g) + local result, err = g:call_router_opts4( + 'upsert', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {{'-', 'workers', 1}}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_upsert_wrong_router = function(g) + local result, err = g:call_router_opts4( + 'upsert', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {{'-', 'workers', 1}}, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_upsert_no_router = function(g) + local result, err = g:call_router_opts4( + 'upsert', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {{'-', 'workers', 1}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_upsert_wrong_option = function(g) + local result, err = g:call_router_opts4( + 'upsert', + 'locations', + {'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, + {{'-', 'workers', 1}}, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_upsert_object_with_default_sharding = function(g) + local result, err = g:call_router_opts4( + 'upsert_object', + 'customers_ddl', + {id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, + {{'+', 'age', 1}}, + {vshard_router = 'customers'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_upsert_object_with_ddl_sharding = function(g) + local result, err = g:call_router_opts4( + 'upsert_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {{'-', 'workers', 1}}, + {vshard_router = 'locations'}) + + t.assert_equals(err, nil) + t.assert_items_equals(result.rows, {}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_upsert_object_wrong_router = function(g) + local result, err = g:call_router_opts4( + 'upsert_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {{'-', 'workers', 1}}, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_upsert_object_no_router = function(g) + local result, err = g:call_router_opts4( + 'upsert_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {{'-', 'workers', 1}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_upsert_object_wrong_option = function(g) + local result, err = g:call_router_opts4( + 'upsert_object', + 'locations', + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {{'-', 'workers', 1}}, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(err.err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_upsert_many_with_default_sharding = function(g) + local result, errs = g:call_router_opts3( + 'upsert_many', + 'customers', + { + {{3, box.NULL, 'Masayoshi Tanimura', 29}, {{'+', 'age', 1}}}, + {{4, box.NULL, 'Taiga Saejima', 45}, {{'+', 'age', 1}}} + }, + {vshard_router = 'customers'}) + + t.assert_equals(errs, nil) + t.assert_equals(result.rows, nil) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers']:get{3} + t.assert_equals(storage_result, {3, 11804, 'Masayoshi Tanimura', 29}) + + local storage = g.cluster:server('customers-storage-1').net_box + local storage_result = storage.space['customers']:get{4} + t.assert_equals(storage_result, {4, 28161, 'Taiga Saejima', 45}) +end + +pgroup.test_call_upsert_many_with_ddl_sharding = function(g) + local result, errs = g:call_router_opts3( + 'upsert_many', + 'locations_ddl', + { + {{'Tokyo Police Department', box.NULL, 'Police', 40000}, {{'-', 'workers', 1}}}, + {{'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, {{'-', 'workers', 1}}} + }, + {vshard_router = 'locations'}) + + t.assert_equals(errs, nil) + t.assert_equals(result.rows, nil) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations_ddl']:get{'Tokyo Police Department', 'Police'} + t.assert_equals(storage_result, {'Tokyo Police Department', 28259, 'Police', 40000}) + + local storage = g.cluster:server('locations-storage-2').net_box + local storage_result = storage.space['locations_ddl']:get{'Okinawa Penitentiary No. 2', 'Prison'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 6427, 'Prison', 100}) +end + +pgroup.test_call_upsert_many_wrong_router = function(g) + local result, errs = g:call_router_opts3( + 'upsert_many', + 'locations', + { + {{'Tokyo Police Department', box.NULL, 'Police', 40000}, {{'-', 'workers', 1}}}, + {{'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, {{'-', 'workers', 1}}} + }, + {vshard_router = 'customers'}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, "Space \"locations\" doesn't exist") +end + +pgroup.test_call_upsert_many_no_router = function(g) + local result, errs = g:call_router_opts3( + 'upsert_many', + 'locations', + { + {{'Tokyo Police Department', box.NULL, 'Police', 40000}, {{'-', 'workers', 1}}}, + {{'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, {{'-', 'workers', 1}}} + }) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_upsert_many_wrong_option = function(g) + local result, errs = g:call_router_opts3( + 'upsert_many', + 'locations', + { + {{'Tokyo Police Department', box.NULL, 'Police', 40000}, {{'-', 'workers', 1}}}, + {{'Okinawa Penitentiary No. 2', box.NULL, 'Prison', 100}, {{'-', 'workers', 1}}} + }, + {vshard_router = {group = 'locations'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end + +pgroup.test_call_upsert_object_many_with_default_sharding = function(g) + local result, errs = g:call_router_opts3( + 'upsert_object_many', + 'locations', + { + { + {name = 'Tokyo Police Department', bucket_id = box.NULL, type = 'Police', workers = 40000}, + {{'-', 'workers', 1}} + }, + { + {name = 'Okinawa Penitentiary No. 2', bucket_id = box.NULL, type = 'Prison', workers = 100}, + {{'-', 'workers', 1}} + } + }, + {vshard_router = 'locations'} + ) + + t.assert_equals(errs, nil) + t.assert_equals(result.rows, nil) + + local storage = g.cluster:server('locations-storage-2').net_box + local storage_result = storage.space['locations']:get{'Tokyo Police Department'} + t.assert_equals(storage_result, {'Tokyo Police Department', 9017, 'Police', 40000}) + + local storage = g.cluster:server('locations-storage-1').net_box + local storage_result = storage.space['locations']:get{'Okinawa Penitentiary No. 2'} + t.assert_equals(storage_result, {'Okinawa Penitentiary No. 2', 19088, 'Prison', 100}) +end + +pgroup.test_call_upsert_object_many_with_ddl_sharding = function(g) + local result, errs = g:call_router_opts3( + 'upsert_object_many', + 'customers_ddl', + { + {{id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, {{'+', 'age', 1}}}, + {{id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, {{'+', 'age', 1}}}, + }, + {vshard_router = 'customers'} + ) + + t.assert_equals(errs, nil) + t.assert_equals(result.rows, nil) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{3, 'Masayoshi Tanimura'} + t.assert_equals(storage_result, {3, 2900, 'Masayoshi Tanimura', 29}) + + local storage = g.cluster:server('customers-storage-2').net_box + local storage_result = storage.space['customers_ddl']:get{4, 'Taiga Saejima'} + t.assert_equals(storage_result, {4, 4344, 'Taiga Saejima', 45}) +end + +pgroup.test_call_upsert_object_many_wrong_router = function(g) + local result, errs = g:call_router_opts3( + 'upsert_object_many', + 'customers', + { + {{id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, {{'+', 'age', 1}}}, + {{id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, {{'+', 'age', 1}}}, + }, + {vshard_router = 'locations'}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, "Space \"customers\" doesn't exist") +end + +pgroup.test_call_upsert_object_many_no_router = function(g) + local result, errs = g:call_router_opts3( + 'upsert_object_many', + 'customers', + { + {{id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, {{'+', 'age', 1}}}, + {{id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, {{'+', 'age', 1}}}, + }) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Default vshard group is not found and custom is not specified with opts.vshard_router") +end + +pgroup.test_call_upsert_object_many_wrong_option = function(g) + local result, errs = g:call_router_opts3( + 'upsert_object_many', + 'customers', + { + {{id = 3, bucket_id = box.NULL, name = 'Masayoshi Tanimura', age = 29}, {{'+', 'age', 1}}}, + {{id = 4, bucket_id = box.NULL, name = 'Taiga Saejima', age = 45}, {{'+', 'age', 1}}}, + }, + {vshard_router = {group = 'customers'}}) + + t.assert_equals(result, nil) + t.assert_str_contains(errs[1].err, + "Invalid opts.vshard_router table value, a vshard router instance has been expected") +end