• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tarantool / crud / 6493431653

12 Oct 2023 08:50AM UTC coverage: 89.162% (+0.02%) from 89.14%
6493431653

push

github

DifferentialOrange
schema: support cached schema

6 of 6 new or added lines in 1 file covered. (100.0%)

4615 of 5176 relevant lines covered (89.16%)

17856.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.3
/crud/common/utils.lua
1
local errors = require('errors')
457✔
2
local ffi = require('ffi')
457✔
3
local vshard = require('vshard')
457✔
4
local fun = require('fun')
457✔
5
local bit = require('bit')
457✔
6
local log = require('log')
457✔
7

8
local is_cartridge, cartridge = pcall(require, 'cartridge')
457✔
9
local is_cartridge_hotreload, cartridge_hotreload = pcall(require, 'cartridge.hotreload')
457✔
10

11
local const = require('crud.common.const')
457✔
12
local schema = require('crud.common.schema')
457✔
13
local dev_checks = require('crud.common.dev_checks')
457✔
14

15
local FlattenError = errors.new_class("FlattenError", {capture_stack = false})
457✔
16
local UnflattenError = errors.new_class("UnflattenError", {capture_stack = false})
457✔
17
local ParseOperationsError = errors.new_class('ParseOperationsError', {capture_stack = false})
457✔
18
local ShardingError = errors.new_class('ShardingError', {capture_stack = false})
457✔
19
local GetSpaceError = errors.new_class('GetSpaceError')
457✔
20
local GetSpaceFormatError = errors.new_class('GetSpaceFormatError', {capture_stack = false})
457✔
21
local FilterFieldsError = errors.new_class('FilterFieldsError', {capture_stack = false})
457✔
22
local NotInitializedError = errors.new_class('NotInitialized')
457✔
23
local StorageInfoError = errors.new_class('StorageInfoError')
457✔
24
local VshardRouterError = errors.new_class('VshardRouterError', {capture_stack = false})
457✔
25
local UtilsInternalError = errors.new_class('UtilsInternalError', {capture_stack = false})
457✔
26
local fiber = require('fiber')
457✔
27

28
local utils = {}
457✔
29

30
local space_format_cache = setmetatable({}, {__mode = 'k'})
457✔
31

32
-- copy from LuaJIT lj_char.c
33
local lj_char_bits = {
457✔
34
    0,
35
    1,  1,  1,  1,  1,  1,  1,  1,  1,  3,  3,  3,  3,  3,  1,  1,
36
    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
37
    2,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
38
    152,152,152,152,152,152,152,152,152,152,  4,  4,  4,  4,  4,  4,
39
    4,176,176,176,176,176,176,160,160,160,160,160,160,160,160,160,
40
    160,160,160,160,160,160,160,160,160,160,160,  4,  4,  4,  4,132,
41
    4,208,208,208,208,208,208,192,192,192,192,192,192,192,192,192,
42
    192,192,192,192,192,192,192,192,192,192,192,  4,  4,  4,  4,  1,
43
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
44
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
45
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
46
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
47
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
48
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
49
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
50
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128
51
}
52

53
local LJ_CHAR_IDENT = 0x80
457✔
54
local LJ_CHAR_DIGIT = 0x08
457✔
55

56
local LUA_KEYWORDS = {
457✔
57
    ['and'] = true,
58
    ['end'] = true,
59
    ['in'] = true,
60
    ['repeat'] = true,
61
    ['break'] = true,
62
    ['false'] = true,
63
    ['local'] = true,
64
    ['return'] = true,
65
    ['do'] = true,
66
    ['for'] = true,
67
    ['nil'] = true,
68
    ['then'] = true,
69
    ['else'] = true,
70
    ['function'] = true,
71
    ['not'] = true,
72
    ['true'] = true,
73
    ['elseif'] = true,
74
    ['if'] = true,
75
    ['or'] = true,
76
    ['until'] = true,
77
    ['while'] = true,
78
}
79

80
function utils.table_count(table)
457✔
81
    dev_checks("table")
4✔
82

83
    local cnt = 0
4✔
84
    for _, _ in pairs(table) do
23✔
85
        cnt = cnt + 1
15✔
86
    end
87

88
    return cnt
4✔
89
end
90

91
function utils.format_replicaset_error(replicaset_uuid, msg, ...)
457✔
92
    dev_checks("string", "string")
628✔
93

94
    return string.format(
628✔
95
        "Failed for %s: %s",
628✔
96
        replicaset_uuid,
628✔
97
        string.format(msg, ...)
628✔
98
    )
628✔
99
end
100

101
local function get_replicaset_by_replica_uuid(replicasets, uuid)
102
    for replicaset_uuid, replicaset in pairs(replicasets) do
54✔
103
        for replica_uuid, _ in pairs(replicaset.replicas) do
126✔
104
            if replica_uuid == uuid then
72✔
105
                return replicasets[replicaset_uuid]
18✔
106
            end
107
        end
108
    end
109

110
    return nil
×
111
end
112

113
function utils.get_spaces(vshard_router, timeout, replica_uuid)
457✔
114
    local replicasets, replicaset
115
    timeout = timeout or const.DEFAULT_VSHARD_CALL_TIMEOUT
172,790✔
116
    local deadline = fiber.clock() + timeout
345,580✔
117
    while (
118
        -- Break if the deadline condition is exceeded.
119
        -- Handling for deadline errors are below in the code.
120
        fiber.clock() < deadline
345,580✔
121
    ) do
172,790✔
122
        -- Try to get master with timeout.
123
        fiber.yield()
172,790✔
124
        replicasets = vshard_router:routeall()
345,580✔
125
        if replica_uuid ~= nil then
172,790✔
126
            -- Get the same replica on which the last DML operation was performed.
127
            -- This approach is temporary and is related to [1], [2].
128
            -- [1] https://github.com/tarantool/crud/issues/236
129
            -- [2] https://github.com/tarantool/crud/issues/361
130
            replicaset = get_replicaset_by_replica_uuid(replicasets, replica_uuid)
36✔
131
            break
18✔
132
        else
133
            replicaset = select(2, next(replicasets))
172,772✔
134
        end
135
        if replicaset ~= nil and
172,772✔
136
           replicaset.master ~= nil and
172,772✔
137
           replicaset.master.conn.error == nil then
172,772✔
138
            break
172,772✔
139
        end
140
    end
141

142
    if replicaset == nil then
172,790✔
143
        return nil, GetSpaceError:new(
×
144
            'The router returned empty replicasets: ' ..
×
145
            'perhaps other instances are unavailable or you have configured only the router')
×
146
    end
147

148
    if replicaset.master == nil then
172,790✔
149
        local error_msg = string.format(
×
150
            'The master was not found in replicaset %s, ' ..
×
151
            'check status of the master and repeat the operation later',
152
             replicaset.uuid)
×
153
        return nil, GetSpaceError:new(error_msg)
×
154
    end
155

156
    if replicaset.master.conn.error ~= nil then
172,790✔
157
        local error_msg = string.format(
×
158
            'The connection to the master of replicaset %s is not valid: %s',
159
             replicaset.uuid, replicaset.master.conn.error)
×
160
        return nil, GetSpaceError:new(error_msg)
×
161
    end
162

163
    return replicaset.master.conn.space, nil, replicaset.master.conn.schema_version
172,790✔
164
end
165

166
function utils.get_space(space_name, vshard_router, timeout, replica_uuid)
457✔
167
    local spaces, err, schema_version = utils.get_spaces(vshard_router, timeout, replica_uuid)
172,758✔
168

169
    if spaces == nil then
172,758✔
170
        return nil, err
×
171
    end
172

173
    return spaces[space_name], err, schema_version
172,758✔
174
end
175

176
function utils.get_space_format(space_name, vshard_router)
457✔
177
    local space, err = utils.get_space(space_name, vshard_router)
7,115✔
178
    if err ~= nil then
7,115✔
179
        return nil, GetSpaceFormatError:new("An error occurred during the operation: %s", err)
×
180
    end
181
    if space == nil then
7,115✔
182
        return nil, GetSpaceFormatError:new("Space %q doesn't exist", space_name)
298✔
183
    end
184

185
    local space_format = space:format()
6,966✔
186

187
    return space_format
6,966✔
188
end
189

190
function utils.fetch_latest_metadata_when_single_storage(space, space_name, netbox_schema_version,
457✔
191
                                                         vshard_router, opts, storage_info)
192
    -- Checking the relevance of the schema version is necessary
193
    -- to prevent the irrelevant metadata of the DML operation.
194
    -- This approach is temporary and is related to [1], [2].
195
    -- [1] https://github.com/tarantool/crud/issues/236
196
    -- [2] https://github.com/tarantool/crud/issues/361
197
    local latest_space, err
198
    assert(storage_info.replica_schema_version ~= nil,
36✔
199
           'check the replica_schema_version value from storage ' ..
18✔
200
           'for correct use of the fetch_latest_metadata opt')
18✔
201
    assert(storage_info.replica_uuid ~= nil,
36✔
202
           'check the replica_uuid value from storage ' ..
18✔
203
           'for correct use of the fetch_latest_metadata opt')
18✔
204
    assert(netbox_schema_version ~= nil,
36✔
205
           'check the netbox_schema_version value from net_box conn on router ' ..
18✔
206
           'for correct use of the fetch_latest_metadata opt')
18✔
207
    if storage_info.replica_schema_version ~= netbox_schema_version then
18✔
208
        local ok, reload_schema_err = schema.reload_schema(vshard_router)
18✔
209
        if ok then
18✔
210
            latest_space, err = utils.get_space(space_name, vshard_router,
36✔
211
                                                opts.timeout, storage_info.replica_uuid)
36✔
212
            if err ~= nil then
18✔
213
                local warn_msg = "Failed to fetch space for latest schema actualization, metadata may be outdated: %s"
×
214
                log.warn(warn_msg, err)
×
215
            end
216
            if latest_space == nil then
18✔
217
                log.warn("Failed to find space for latest schema actualization, metadata may be outdated")
×
218
            end
219
        else
220
            log.warn("Failed to reload schema, metadata may be outdated: %s", reload_schema_err)
×
221
        end
222
    end
223
    if err == nil and latest_space ~= nil then
18✔
224
        space = latest_space
18✔
225
    end
226

227
    return space
18✔
228
end
229

230
function utils.fetch_latest_metadata_when_map_storages(space, space_name, vshard_router, opts,
457✔
231
                                                       storages_info, netbox_schema_version)
232
    -- Checking the relevance of the schema version is necessary
233
    -- to prevent the irrelevant metadata of the DML operation.
234
    -- This approach is temporary and is related to [1], [2].
235
    -- [1] https://github.com/tarantool/crud/issues/236
236
    -- [2] https://github.com/tarantool/crud/issues/361
237
    local latest_space, err
238
    for _, storage_info in pairs(storages_info) do
32✔
239
        assert(storage_info.replica_schema_version ~= nil,
32✔
240
            'check the replica_schema_version value from storage ' ..
16✔
241
            'for correct use of the fetch_latest_metadata opt')
16✔
242
        assert(netbox_schema_version ~= nil,
32✔
243
               'check the netbox_schema_version value from net_box conn on router ' ..
16✔
244
               'for correct use of the fetch_latest_metadata opt')
16✔
245
        if storage_info.replica_schema_version ~= netbox_schema_version then
16✔
246
            local ok, reload_schema_err = schema.reload_schema(vshard_router)
16✔
247
            if ok then
16✔
248
                latest_space, err = utils.get_space(space_name, vshard_router, opts.timeout)
32✔
249
                if err ~= nil then
16✔
250
                    local warn_msg = "Failed to fetch space for latest schema actualization, " ..
×
251
                                     "metadata may be outdated: %s"
252
                    log.warn(warn_msg, err)
×
253
                end
254
                if latest_space == nil then
16✔
255
                    log.warn("Failed to find space for latest schema actualization, metadata may be outdated")
×
256
                end
257
            else
258
                log.warn("Failed to reload schema, metadata may be outdated: %s", reload_schema_err)
×
259
            end
260
            if err == nil and latest_space ~= nil then
16✔
261
                space = latest_space
16✔
262
            end
263
            break
16✔
264
        end
265
    end
266

267
    return space
16✔
268
end
269

270
function utils.fetch_latest_metadata_for_select(space_name, vshard_router, opts,
457✔
271
                                                storages_info, iter)
272
    -- Checking the relevance of the schema version is necessary
273
    -- to prevent the irrelevant metadata of the DML operation.
274
    -- This approach is temporary and is related to [1], [2].
275
    -- [1] https://github.com/tarantool/crud/issues/236
276
    -- [2] https://github.com/tarantool/crud/issues/361
277
    for _, storage_info in pairs(storages_info) do
12✔
278
        assert(storage_info.replica_schema_version ~= nil,
12✔
279
               'check the replica_schema_version value from storage ' ..
6✔
280
               'for correct use of the fetch_latest_metadata opt')
6✔
281
        assert(iter.netbox_schema_version ~= nil,
12✔
282
               'check the netbox_schema_version value from net_box conn on router ' ..
6✔
283
               'for correct use of the fetch_latest_metadata opt')
6✔
284
        if storage_info.replica_schema_version ~= iter.netbox_schema_version then
6✔
285
            local ok, reload_schema_err = schema.reload_schema(vshard_router)
2✔
286
            if ok then
2✔
287
                local err
288
                iter.space, err = utils.get_space(space_name, vshard_router, opts.timeout)
4✔
289
                if err ~= nil then
2✔
290
                    local warn_msg = "Failed to fetch space for latest schema actualization, " ..
×
291
                                     "metadata may be outdated: %s"
292
                    log.warn(warn_msg, err)
×
293
                end
294
            else
295
                log.warn("Failed to reload schema, metadata may be outdated: %s", reload_schema_err)
×
296
            end
297
            break
298
        end
299
    end
300

301
    return iter
4✔
302
end
303

304
local function append(lines, s, ...)
305
    table.insert(lines, string.format(s, ...))
4,666✔
306
end
307

308
local flatten_functions_cache = setmetatable({}, {__mode = 'k'})
457✔
309

310
function utils.flatten(object, space_format, bucket_id, skip_nullability_check)
457✔
311
    local flatten_func = flatten_functions_cache[space_format]
7,144✔
312
    if flatten_func ~= nil then
7,144✔
313
        local data, err = flatten_func(object, bucket_id, skip_nullability_check)
6,996✔
314
        if err ~= nil then
6,996✔
315
            return nil, FlattenError:new(err)
1,354✔
316
        end
317
        return data
6,319✔
318
    end
319

320
    local lines = {}
148✔
321
    append(lines, 'local object, bucket_id, skip_nullability_check = ...')
148✔
322

323
    append(lines, 'for k in pairs(object) do')
148✔
324
    append(lines, '    if fieldmap[k] == nil then')
148✔
325
    append(lines, '        return nil, format(\'Unknown field %%q is specified\', k)')
148✔
326
    append(lines, '    end')
148✔
327
    append(lines, 'end')
148✔
328

329
    local len = #space_format
148✔
330
    append(lines, 'local result = {%s}', string.rep('NULL,', len))
148✔
331

332
    local fieldmap = {}
148✔
333

334
    for i, field in ipairs(space_format) do
872✔
335
        fieldmap[field.name] = true
724✔
336
        if field.name ~= 'bucket_id' then
724✔
337
            append(lines, 'if object[%q] ~= nil then', field.name)
576✔
338
            append(lines, '    result[%d] = object[%q]', i, field.name)
576✔
339
            if field.is_nullable ~= true then
576✔
340
                append(lines, 'elseif skip_nullability_check ~= true then')
507✔
341
                append(lines, '    return nil, \'Field %q isn\\\'t nullable' ..
1,014✔
342
                              ' (set skip_nullability_check_on_flatten option to true to skip check)\'',
507✔
343
                              field.name)
507✔
344
            end
345
            append(lines, 'end')
1,152✔
346
        else
347
            append(lines, 'if bucket_id ~= nil then')
148✔
348
            append(lines, '    result[%d] = bucket_id', i, field.name)
148✔
349
            append(lines, 'else')
148✔
350
            append(lines, '    result[%d] = object[%q]', i, field.name)
148✔
351
            append(lines, 'end')
148✔
352
        end
353
    end
354
    append(lines, 'return result')
148✔
355

356
    local code = table.concat(lines, '\n')
148✔
357
    local env = {
148✔
358
        pairs = pairs,
148✔
359
        format = string.format,
148✔
360
        fieldmap = fieldmap,
148✔
361
        NULL = box.NULL,
148✔
362
    }
363
    flatten_func = assert(load(code, nil, 't', env))
148✔
364

365
    flatten_functions_cache[space_format] = flatten_func
148✔
366
    local data, err = flatten_func(object, bucket_id, skip_nullability_check)
148✔
367
    if err ~= nil then
148✔
368
        return nil, FlattenError:new(err)
18✔
369
    end
370
    return data
139✔
371
end
372

373
function utils.unflatten(tuple, space_format)
457✔
374
    if tuple == nil then return nil end
11,437✔
375

376
    local object = {}
11,437✔
377

378
    for fieldno, field_format in ipairs(space_format) do
77,626✔
379
        local value = tuple[fieldno]
66,190✔
380

381
        if not field_format.is_nullable and value == nil then
66,190✔
382
            return nil, UnflattenError:new("Field %s isn't nullable", fieldno)
2✔
383
        end
384

385
        object[field_format.name] = value
66,189✔
386
    end
387

388
    return object
11,436✔
389
end
390

391
function utils.extract_key(tuple, key_parts)
457✔
392
    local key = {}
304,470✔
393
    for i, part in ipairs(key_parts) do
609,660✔
394
        key[i] = tuple[part.fieldno]
305,190✔
395
    end
396
    return key
304,470✔
397
end
398

399
function utils.merge_primary_key_parts(key_parts, pk_parts)
457✔
400
    local merged_parts = {}
51,964✔
401
    local key_fieldnos = {}
51,964✔
402

403
    for _, part in ipairs(key_parts) do
104,143✔
404
        table.insert(merged_parts, part)
52,179✔
405
        key_fieldnos[part.fieldno] = true
52,179✔
406
    end
407

408
    for _, pk_part in ipairs(pk_parts) do
116,722✔
409
        if not key_fieldnos[pk_part.fieldno] then
64,758✔
410
            table.insert(merged_parts, pk_part)
34,842✔
411
        end
412
    end
413

414
    return merged_parts
51,964✔
415
end
416

417
function utils.enrich_field_names_with_cmp_key(field_names, key_parts, space_format)
457✔
418
    if field_names == nil then
37,978✔
419
        return nil
37,893✔
420
    end
421

422
    local enriched_field_names = {}
85✔
423
    local key_field_names = {}
85✔
424

425
    for _, field_name in ipairs(field_names) do
251✔
426
        table.insert(enriched_field_names, field_name)
166✔
427
        key_field_names[field_name] = true
166✔
428
    end
429

430
    for _, part in ipairs(key_parts) do
223✔
431
        local field_name = space_format[part.fieldno].name
138✔
432
        if not key_field_names[field_name] then
138✔
433
            table.insert(enriched_field_names, field_name)
108✔
434
            key_field_names[field_name] = true
108✔
435
        end
436
    end
437

438
    return enriched_field_names
85✔
439
end
440

441

442
local function get_version_suffix(suffix_candidate)
443
    if type(suffix_candidate) ~= 'string' then
1,629✔
444
        return nil
×
445
    end
446

447
    if suffix_candidate:find('^entrypoint$')
1,629✔
448
    or suffix_candidate:find('^alpha%d$')
1,629✔
449
    or suffix_candidate:find('^beta%d$')
1,628✔
450
    or suffix_candidate:find('^rc%d$') then
1,626✔
451
        return suffix_candidate
7✔
452
    end
453

454
    return nil
1,622✔
455
end
456

457
utils.get_version_suffix = get_version_suffix
457✔
458

459

460
local suffix_with_digit_weight = {
457✔
461
    alpha = -3000,
462
    beta  = -2000,
463
    rc    = -1000,
464
}
465

466
local function get_version_suffix_weight(suffix)
467
    if suffix == nil then
6,996✔
468
        return 0
6,953✔
469
    end
470

471
    if suffix:find('^entrypoint$') then
43✔
472
        return -math.huge
9✔
473
    end
474

475
    for header, weight in pairs(suffix_with_digit_weight) do
100✔
476
        local pos, _, digits = suffix:find('^' .. header .. '(%d)$')
64✔
477
        if pos ~= nil then
64✔
478
            return weight + tonumber(digits)
32✔
479
        end
480
    end
481

482
    UtilsInternalError:assert(false,
4✔
483
        'Unexpected suffix %q, parse with "utils.get_version_suffix" first', suffix)
2✔
484
end
485

486
utils.get_version_suffix_weight = get_version_suffix_weight
457✔
487

488

489
local function is_version_ge(major, minor,
490
                             patch, suffix,
491
                             major_to_compare, minor_to_compare,
492
                             patch_to_compare, suffix_to_compare)
493
    major = major or 0
3,493✔
494
    minor = minor or 0
3,493✔
495
    patch = patch or 0
3,493✔
496
    local suffix_weight = get_version_suffix_weight(suffix)
3,493✔
497

498
    major_to_compare = major_to_compare or 0
3,493✔
499
    minor_to_compare = minor_to_compare or 0
3,493✔
500
    patch_to_compare = patch_to_compare or 0
3,493✔
501
    local suffix_weight_to_compare = get_version_suffix_weight(suffix_to_compare)
3,493✔
502

503
    if major > major_to_compare then return true end
3,493✔
504
    if major < major_to_compare then return false end
3,480✔
505

506
    if minor > minor_to_compare then return true end
3,469✔
507
    if minor < minor_to_compare then return false end
546✔
508

509
    if patch > patch_to_compare then return true end
541✔
510
    if patch < patch_to_compare then return false end
11✔
511

512
    if suffix_weight > suffix_weight_to_compare then return true end
10✔
513
    if suffix_weight < suffix_weight_to_compare then return false end
7✔
514

515
    return true
4✔
516
end
517

518
utils.is_version_ge = is_version_ge
457✔
519

520

521
local function is_version_in_range(major, minor,
522
                                   patch, suffix,
523
                                   major_left_side, minor_left_side,
524
                                   patch_left_side, suffix_left_side,
525
                                   major_right_side, minor_right_side,
526
                                   patch_right_side, suffix_right_side)
527
    return is_version_ge(major, minor,
4✔
528
                         patch, suffix,
2✔
529
                         major_left_side, minor_left_side,
2✔
530
                         patch_left_side, suffix_left_side)
2✔
531
       and is_version_ge(major_right_side, minor_right_side,
4✔
532
                         patch_right_side, suffix_right_side,
2✔
533
                         major, minor,
2✔
534
                         patch, suffix)
4✔
535
end
536

537
utils.is_version_in_range = is_version_in_range
457✔
538

539

540
local function get_tarantool_version()
541
    local version_parts = rawget(_G, '_TARANTOOL'):split('-', 1)
1,620✔
542

543
    local major_minor_patch_parts = version_parts[1]:split('.', 2)
1,620✔
544
    local major = tonumber(major_minor_patch_parts[1])
1,620✔
545
    local minor = tonumber(major_minor_patch_parts[2])
1,620✔
546
    local patch = tonumber(major_minor_patch_parts[3])
1,620✔
547

548
    local suffix = get_version_suffix(version_parts[2])
1,620✔
549

550
    return major, minor, patch, suffix
1,620✔
551
end
552

553
utils.get_tarantool_version = get_tarantool_version
457✔
554

555

556
local function tarantool_version_at_least(wanted_major, wanted_minor, wanted_patch)
557
    local major, minor, patch, suffix = get_tarantool_version()
1,162✔
558

559
    return is_version_ge(major, minor, patch, suffix,
1,162✔
560
                         wanted_major, wanted_minor, wanted_patch, nil)
1,162✔
561
end
562

563
utils.tarantool_version_at_least = tarantool_version_at_least
457✔
564

565

566
local enabled_tarantool_features = {}
457✔
567

568
local function determine_enabled_features()
569
    local major, minor, patch, suffix = get_tarantool_version()
457✔
570

571
    -- since Tarantool 2.3.1
572
    enabled_tarantool_features.fieldpaths = is_version_ge(major, minor, patch, suffix,
914✔
573
                                                          2, 3, 1, nil)
914✔
574

575
    -- since Tarantool 2.4.1
576
    enabled_tarantool_features.uuids = is_version_ge(major, minor, patch, suffix,
914✔
577
                                                     2, 4, 1, nil)
914✔
578

579
    -- since Tarantool 2.6.3 / 2.7.2 / 2.8.1
580
    enabled_tarantool_features.jsonpath_indexes = is_version_ge(major, minor, patch, suffix,
914✔
581
                                                                2, 8, 1, nil)
457✔
582
                                               or is_version_in_range(major, minor, patch, suffix,
457✔
583
                                                                      2, 7, 2, nil,
584
                                                                      2, 7, math.huge, nil)
×
585
                                               or is_version_in_range(major, minor, patch, suffix,
×
586
                                                                      2, 6, 3, nil,
587
                                                                      2, 6, math.huge, nil)
457✔
588

589
    -- The merger module was implemented in 2.2.1, see [1].
590
    -- However it had the critical problem [2], which leads to
591
    -- segfault at attempt to use the module from a fiber serving
592
    -- iproto request. So we don't use it in versions before the
593
    -- fix.
594
    --
595
    -- [1]: https://github.com/tarantool/tarantool/issues/3276
596
    -- [2]: https://github.com/tarantool/tarantool/issues/4954
597
    enabled_tarantool_features.builtin_merger = is_version_ge(major, minor, patch, suffix,
914✔
598
                                                              2, 6, 0, nil)
457✔
599
                                             or is_version_in_range(major, minor, patch, suffix,
457✔
600
                                                                    2, 5, 1, nil,
601
                                                                    2, 5, math.huge, nil)
×
602
                                             or is_version_in_range(major, minor, patch, suffix,
×
603
                                                                    2, 4, 2, nil,
604
                                                                    2, 4, math.huge, nil)
×
605
                                             or is_version_in_range(major, minor, patch, suffix,
×
606
                                                                    2, 3, 3, nil,
607
                                                                    2, 3, math.huge, nil)
457✔
608

609
    -- The external merger module leans on a set of relatively
610
    -- new APIs in tarantool. So it works only on tarantool
611
    -- versions, which offer those APIs.
612
    --
613
    -- See README of the module:
614
    -- https://github.com/tarantool/tuple-merger
615
    enabled_tarantool_features.external_merger = is_version_ge(major, minor, patch, suffix,
914✔
616
                                                               2, 7, 0, nil)
457✔
617
                                              or is_version_in_range(major, minor, patch, suffix,
457✔
618
                                                                     2, 6, 1, nil,
619
                                                                     2, 6, math.huge, nil)
×
620
                                              or is_version_in_range(major, minor, patch, suffix,
×
621
                                                                     2, 5, 2, nil,
622
                                                                     2, 5, math.huge, nil)
×
623
                                              or is_version_in_range(major, minor, patch, suffix,
×
624
                                                                     2, 4, 3, nil,
625
                                                                     2, 4, math.huge, nil)
×
626
                                              or is_version_in_range(major, minor, patch, suffix,
×
627
                                                                     1, 10, 8, nil,
628
                                                                     1, 10, math.huge, nil)
457✔
629
end
630

631
function utils.tarantool_supports_fieldpaths()
457✔
632
    if enabled_tarantool_features.fieldpaths == nil then
1,144✔
633
        determine_enabled_features()
×
634
    end
635

636
    return enabled_tarantool_features.fieldpaths
1,144✔
637
end
638

639
function utils.tarantool_supports_uuids()
457✔
640
    if enabled_tarantool_features.uuids == nil then
32✔
641
        determine_enabled_features()
26✔
642
    end
643

644
    return enabled_tarantool_features.uuids
32✔
645
end
646

647
function utils.tarantool_supports_jsonpath_indexes()
457✔
648
    if enabled_tarantool_features.jsonpath_indexes == nil then
36✔
649
        determine_enabled_features()
×
650
    end
651

652
    return enabled_tarantool_features.jsonpath_indexes
36✔
653
end
654

655
function utils.tarantool_has_builtin_merger()
457✔
656
    if enabled_tarantool_features.builtin_merger == nil then
938✔
657
        determine_enabled_features()
×
658
    end
659

660
    return enabled_tarantool_features.builtin_merger
938✔
661
end
662

663
function utils.tarantool_supports_external_merger()
457✔
664
    if enabled_tarantool_features.external_merger == nil then
938✔
665
        determine_enabled_features()
431✔
666
    end
667

668
    return enabled_tarantool_features.external_merger
938✔
669
end
670

671
local function add_nullable_fields_recursive(operations, operations_map, space_format, tuple, id)
672
    if id < 2 or tuple[id - 1] ~= box.NULL then
×
673
        return operations
×
674
    end
675

676
    if space_format[id - 1].is_nullable and not operations_map[id - 1] then
×
677
        table.insert(operations, {'=', id - 1, box.NULL})
×
678
        return add_nullable_fields_recursive(operations, operations_map, space_format, tuple, id - 1)
×
679
    end
680

681
    return operations
×
682
end
683

684
-- Tarantool < 2.1 has no fields `box.error.NO_SUCH_FIELD_NO` and `box.error.NO_SUCH_FIELD_NAME`.
685
if tarantool_version_at_least(2, 1, 0, nil) then
914✔
686
    function utils.is_field_not_found(err_code)
457✔
687
        return err_code == box.error.NO_SUCH_FIELD_NO or err_code == box.error.NO_SUCH_FIELD_NAME
51✔
688
    end
689
else
690
    function utils.is_field_not_found(err_code)
×
691
        return err_code == box.error.NO_SUCH_FIELD
×
692
    end
693
end
694

695
local function get_operations_map(operations)
696
    local map = {}
×
697
    for _, operation in ipairs(operations) do
×
698
        map[operation[2]] = true
×
699
    end
700

701
    return map
×
702
end
703

704
function utils.add_intermediate_nullable_fields(operations, space_format, tuple)
457✔
705
    if tuple == nil then
2✔
706
        return operations
×
707
    end
708

709
    -- If tarantool doesn't supports the fieldpaths, we already
710
    -- have converted operations (see this function call in update.lua)
711
    if utils.tarantool_supports_fieldpaths() then
4✔
712
        local formatted_operations, err = utils.convert_operations(operations, space_format)
2✔
713
        if err ~= nil then
2✔
714
            return operations
2✔
715
        end
716

717
        operations = formatted_operations
×
718
    end
719

720
    -- We need this map to check if there is a field update
721
    -- operation with constant complexity
722
    local operations_map = get_operations_map(operations)
×
723
    for _, operation in ipairs(operations) do
×
724
        operations = add_nullable_fields_recursive(
×
725
            operations, operations_map,
726
            space_format, tuple, operation[2]
×
727
        )
728
    end
729

730
    table.sort(operations, function(v1, v2) return v1[2] < v2[2] end)
×
731
    return operations
×
732
end
733

734
function utils.convert_operations(user_operations, space_format)
457✔
735
    local converted_operations = {}
2✔
736

737
    for _, operation in ipairs(user_operations) do
2✔
738
        if type(operation[2]) == 'string' then
2✔
739
            local field_id
740
            for fieldno, field_format in ipairs(space_format) do
10✔
741
                if field_format.name == operation[2] then
8✔
742
                    field_id = fieldno
×
743
                    break
744
                end
745
            end
746

747
            if field_id == nil then
2✔
748
                return nil, ParseOperationsError:new(
4✔
749
                        "Space format doesn't contain field named %q", operation[2])
4✔
750
            end
751

752
            table.insert(converted_operations, {
×
753
                operation[1], field_id, operation[3]
×
754
            })
755
        else
756
            table.insert(converted_operations, operation)
×
757
        end
758
    end
759

760
    return converted_operations
×
761
end
762

763
function utils.unflatten_rows(rows, metadata)
457✔
764
    if metadata == nil then
10,545✔
765
        return nil, UnflattenError:new('Metadata is not provided')
×
766
    end
767

768
    local result = table.new(#rows, 0)
10,545✔
769
    local err
770
    for i, row in ipairs(rows) do
21,890✔
771
        result[i], err = utils.unflatten(row, metadata)
22,690✔
772
        if err ~= nil then
11,345✔
773
            return nil, err
×
774
        end
775
    end
776
    return result
10,545✔
777
end
778

779
local inverted_tarantool_iters = {
457✔
780
    [box.index.EQ] = box.index.REQ,
457✔
781
    [box.index.GT] = box.index.LT,
457✔
782
    [box.index.GE] = box.index.LE,
457✔
783
    [box.index.LT] = box.index.GT,
457✔
784
    [box.index.LE] = box.index.GE,
457✔
785
    [box.index.REQ] = box.index.EQ,
457✔
786
}
787

788
function utils.invert_tarantool_iter(iter)
457✔
789
    local inverted_iter = inverted_tarantool_iters[iter]
44✔
790
    assert(inverted_iter ~= nil, "Unsupported Tarantool iterator: " .. tostring(iter))
44✔
791
    return inverted_iter
44✔
792
end
793

794
function utils.reverse_inplace(t)
457✔
795
    for i = 1,math.floor(#t / 2) do
91✔
796
        t[i], t[#t - i + 1] = t[#t - i + 1], t[i]
43✔
797
    end
798
    return t
48✔
799
end
800

801
function utils.get_bucket_id_fieldno(space, shard_index_name)
457✔
802
    shard_index_name = shard_index_name or 'bucket_id'
304,807✔
803
    local bucket_id_index = space.index[shard_index_name]
304,807✔
804
    if bucket_id_index == nil then
304,807✔
805
        return nil, ShardingError:new('%q index is not found', shard_index_name)
24✔
806
    end
807

808
    return bucket_id_index.parts[1].fieldno
304,795✔
809
end
810

811
-- Build a map with field number as a keys and part number
812
-- as a values using index parts as a source.
813
function utils.get_index_fieldno_map(index_parts)
457✔
814
    dev_checks('table')
111✔
815

816
    local fieldno_map = {}
111✔
817
    for i, part in ipairs(index_parts) do
284✔
818
        local fieldno = part.fieldno
173✔
819
        fieldno_map[fieldno] = i
173✔
820
    end
821

822
    return fieldno_map
111✔
823
end
824

825
-- Build a map with field names as a keys and fieldno's
826
-- as a values using space format as a source.
827
function utils.get_format_fieldno_map(space_format)
457✔
828
    dev_checks('table')
38,796✔
829

830
    local fieldno_map = {}
38,796✔
831
    for fieldno, field_format in ipairs(space_format) do
195,718✔
832
        fieldno_map[field_format.name] = fieldno
156,922✔
833
    end
834

835
    return fieldno_map
38,796✔
836
end
837

838
local uuid_t = ffi.typeof('struct tt_uuid')
457✔
839
function utils.is_uuid(value)
457✔
840
    return ffi.istype(uuid_t, value)
5✔
841
end
842

843
local function get_field_format(space_format, field_name)
844
    dev_checks('table', 'string')
466✔
845

846
    local metadata = space_format_cache[space_format]
466✔
847
    if metadata ~= nil then
466✔
848
        return metadata[field_name]
446✔
849
    end
850

851
    space_format_cache[space_format] = {}
20✔
852
    for _, field in ipairs(space_format) do
134✔
853
        space_format_cache[space_format][field.name] = field
114✔
854
    end
855

856
    return space_format_cache[space_format][field_name]
20✔
857
end
858

859
local function filter_format_fields(space_format, field_names)
860
    dev_checks('table', 'table')
182✔
861

862
    local filtered_space_format = {}
182✔
863

864
    for i, field_name in ipairs(field_names) do
618✔
865
        filtered_space_format[i] = get_field_format(space_format, field_name)
932✔
866
        if filtered_space_format[i] == nil then
466✔
867
            return nil, FilterFieldsError:new(
60✔
868
                    'Space format doesn\'t contain field named %q', field_name
30✔
869
            )
60✔
870
        end
871
    end
872

873
    return filtered_space_format
152✔
874
end
875

876
function utils.get_fields_format(space_format, field_names)
457✔
877
    dev_checks('table', '?table')
37,531✔
878

879
    if field_names == nil then
37,531✔
880
        return table.copy(space_format)
37,467✔
881
    end
882

883
    local filtered_space_format, err = filter_format_fields(space_format, field_names)
64✔
884

885
    if err ~= nil then
64✔
886
        return nil, err
2✔
887
    end
888

889
    return filtered_space_format
62✔
890
end
891

892
function utils.format_result(rows, space, field_names)
457✔
893
    local result = {}
109,585✔
894
    local err
895
    local space_format = space:format()
109,585✔
896
    result.rows = rows
109,585✔
897

898
    if field_names == nil then
109,585✔
899
        result.metadata = table.copy(space_format)
218,934✔
900
        return result
109,467✔
901
    end
902

903
    result.metadata, err = filter_format_fields(space_format, field_names)
236✔
904

905
    if err ~= nil then
118✔
906
        return nil, err
28✔
907
    end
908

909
    return result
90✔
910
end
911

912
local function truncate_tuple_metadata(tuple_metadata, field_names)
913
    dev_checks('?table', 'table')
31✔
914

915
    if tuple_metadata == nil then
31✔
916
        return nil
3✔
917
    end
918

919
    local truncated_metadata = {}
28✔
920

921
    if #tuple_metadata < #field_names then
28✔
922
        return nil, FilterFieldsError:new(
×
923
                'Field names don\'t match to tuple metadata'
924
        )
925
    end
926

927
    for i, name in ipairs(field_names) do
79✔
928
        if tuple_metadata[i].name ~= name then
53✔
929
            return nil, FilterFieldsError:new(
4✔
930
                    'Field names don\'t match to tuple metadata'
931
            )
4✔
932
        end
933

934
        table.insert(truncated_metadata, tuple_metadata[i])
51✔
935
    end
936

937
    return truncated_metadata
26✔
938
end
939

940
function utils.cut_objects(objs, field_names)
457✔
941
    dev_checks('table', 'table')
5✔
942

943
    for i, obj in ipairs(objs) do
20✔
944
        objs[i] = schema.filter_obj_fields(obj, field_names)
30✔
945
    end
946

947
    return objs
5✔
948
end
949

950
function utils.cut_rows(rows, metadata, field_names)
457✔
951
    dev_checks('table', '?table', 'table')
31✔
952

953
    local truncated_metadata, err = truncate_tuple_metadata(metadata, field_names)
31✔
954

955
    if err ~= nil then
31✔
956
        return nil, err
2✔
957
    end
958

959
    for i, row in ipairs(rows) do
72✔
960
        rows[i] = schema.truncate_row_trailing_fields(row, field_names)
86✔
961
    end
962

963
    return {
29✔
964
        metadata = truncated_metadata,
29✔
965
        rows = rows,
29✔
966
    }
29✔
967
end
968

969
local function flatten_obj(vshard_router, space_name, obj, skip_nullability_check)
970
    local space_format, err = utils.get_space_format(space_name, vshard_router)
7,115✔
971
    if err ~= nil then
7,115✔
972
        return nil, FlattenError:new("Failed to get space format: %s", err), const.NEED_SCHEMA_RELOAD
298✔
973
    end
974

975
    local tuple, err = utils.flatten(obj, space_format, nil, skip_nullability_check)
6,966✔
976
    if err ~= nil then
6,966✔
977
        return nil, FlattenError:new("Object is specified in bad format: %s", err), const.NEED_SCHEMA_RELOAD
1,368✔
978
    end
979

980
    return tuple
6,282✔
981
end
982

983
function utils.flatten_obj_reload(vshard_router, space_name, obj, skip_nullability_check)
457✔
984
    return schema.wrap_func_reload(vshard_router, flatten_obj, space_name, obj, skip_nullability_check)
6,692✔
985
end
986

987
-- Merge two options map.
988
--
989
-- `opts_a` and/or `opts_b` can be `nil`.
990
--
991
-- If `opts_a.foo` and `opts_b.foo` exists, prefer `opts_b.foo`.
992
function utils.merge_options(opts_a, opts_b)
457✔
993
    return fun.chain(opts_a or {}, opts_b or {}):tomap()
12,278✔
994
end
995

996
local function lj_char_isident(n)
997
    return bit.band(lj_char_bits[n + 2], LJ_CHAR_IDENT) == LJ_CHAR_IDENT
11,959✔
998
end
999

1000
local function lj_char_isdigit(n)
1001
    return bit.band(lj_char_bits[n + 2], LJ_CHAR_DIGIT) == LJ_CHAR_DIGIT
728✔
1002
end
1003

1004
function utils.check_name_isident(name)
457✔
1005
    dev_checks('string')
729✔
1006

1007
    -- sharding function name cannot
1008
    -- be equal to lua keyword
1009
    if LUA_KEYWORDS[name] then
729✔
1010
        return false
1✔
1011
    end
1012

1013
    -- sharding function name cannot
1014
    -- begin with a digit
1015
    local char_number = string.byte(name:sub(1,1))
1,456✔
1016
    if lj_char_isdigit(char_number) then
1,456✔
1017
        return false
1✔
1018
    end
1019

1020
    -- sharding func name must be sequence
1021
    -- of letters, digits, or underscore symbols
1022
    for i = 1, #name do
12,685✔
1023
        local char_number = string.byte(name:sub(i,i))
23,918✔
1024
        if not lj_char_isident(char_number) then
23,918✔
1025
            return false
1✔
1026
        end
1027
    end
1028

1029
    return true
726✔
1030
end
1031

1032
function utils.update_storage_call_error_description(err, func_name, replicaset_uuid)
457✔
1033
    if err == nil then
677✔
1034
        return nil
×
1035
    end
1036

1037
    if err.type == 'ClientError' and type(err.message) == 'string' then
1,672✔
1038
        if err.message == string.format("Procedure '%s' is not defined", func_name) then
993✔
1039
            if func_name:startswith('_crud.') then
16✔
1040
                err = NotInitializedError:new("Function %s is not registered: " ..
12✔
1041
                    "crud isn't initialized on replicaset %q or crud module versions mismatch " ..
6✔
1042
                    "between router and storage",
6✔
1043
                    func_name, replicaset_uuid or "Unknown")
12✔
1044
            else
1045
                err = NotInitializedError:new("Function %s is not registered", func_name)
4✔
1046
            end
1047
        end
1048
    end
1049
    return err
677✔
1050
end
1051

1052
--- Insert each value from values to list
1053
--
1054
-- @function list_extend
1055
--
1056
-- @param table list
1057
--  List to be extended
1058
--
1059
-- @param table values
1060
--  Values to be inserted to list
1061
--
1062
-- @return[1] list
1063
--  List with old values and inserted values
1064
function utils.list_extend(list, values)
457✔
1065
    dev_checks('table', 'table')
37,338✔
1066

1067
    for _, value in ipairs(values) do
132,391✔
1068
        table.insert(list, value)
95,053✔
1069
    end
1070

1071
    return list
37,338✔
1072
end
1073

1074
function utils.list_slice(list, start_index, end_index)
457✔
1075
    dev_checks('table', 'number', '?number')
48✔
1076

1077
    if end_index == nil then
48✔
1078
        end_index = table.maxn(list)
48✔
1079
    end
1080

1081
    local slice = {}
48✔
1082
    for i = start_index, end_index do
120✔
1083
        table.insert(slice, list[i])
72✔
1084
    end
1085

1086
    return slice
48✔
1087
end
1088

1089
--- Polls replicas for storage state
1090
--
1091
-- @function storage_info
1092
--
1093
-- @tparam ?number opts.timeout
1094
--  Function call timeout
1095
--
1096
-- @tparam ?string|table opts.vshard_router
1097
--  Cartridge vshard group name or vshard router instance.
1098
--
1099
-- @return a table of storage states by replica uuid.
1100
function utils.storage_info(opts)
457✔
1101
    opts = opts or {}
5✔
1102

1103
    local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router)
5✔
1104
    if err ~= nil then
5✔
1105
        return nil, StorageInfoError:new(err)
×
1106
    end
1107

1108
    local replicasets, err = vshard_router:routeall()
5✔
1109
    if replicasets == nil then
5✔
1110
        return nil, StorageInfoError:new("Failed to get router replicasets: %s", err.err)
×
1111
    end
1112

1113
    local futures_by_replicas = {}
5✔
1114
    local replica_state_by_uuid = {}
5✔
1115
    local async_opts = {is_async = true}
5✔
1116
    local timeout = opts.timeout or const.DEFAULT_VSHARD_CALL_TIMEOUT
5✔
1117

1118
    for _, replicaset in pairs(replicasets) do
20✔
1119
        for replica_uuid, replica in pairs(replicaset.replicas) do
40✔
1120
            replica_state_by_uuid[replica_uuid] = {
20✔
1121
                status = "error",
1122
                is_master = replicaset.master == replica
20✔
1123
            }
20✔
1124
            local ok, res = pcall(replica.conn.call, replica.conn, "_crud.storage_info_on_storage",
40✔
1125
                                  {}, async_opts)
20✔
1126
            if ok then
20✔
1127
                futures_by_replicas[replica_uuid] = res
19✔
1128
            else
1129
                local err_msg = string.format("Error getting storage info for %s", replica_uuid)
1✔
1130
                if res ~= nil then
1✔
1131
                    log.error("%s: %s", err_msg, res)
1✔
1132
                    replica_state_by_uuid[replica_uuid].message = tostring(res)
2✔
1133
                else
1134
                    log.error(err_msg)
×
1135
                    replica_state_by_uuid[replica_uuid].message = err_msg
×
1136
                end
1137
            end
1138
        end
1139
    end
1140

1141
    local deadline = fiber.clock() + timeout
10✔
1142
    for replica_uuid, future in pairs(futures_by_replicas) do
29✔
1143
        local wait_timeout = deadline - fiber.clock()
38✔
1144
        if wait_timeout < 0 then
19✔
1145
            wait_timeout = 0
×
1146
        end
1147

1148
        local result, err = future:wait_result(wait_timeout)
19✔
1149
        if result == nil then
19✔
1150
            future:discard()
1✔
1151
            local err_msg = string.format("Error getting storage info for %s", replica_uuid)
1✔
1152
            if err ~= nil then
1✔
1153
                if err.type == 'ClientError' and err.code == box.error.NO_SUCH_PROC then
2✔
1154
                    replica_state_by_uuid[replica_uuid].status = "uninitialized"
×
1155
                else
1156
                    log.error("%s: %s", err_msg, err)
1✔
1157
                    replica_state_by_uuid[replica_uuid].message = tostring(err)
2✔
1158
                end
1159
            else
1160
                log.error(err_msg)
×
1161
                replica_state_by_uuid[replica_uuid].message = err_msg
×
1162
            end
1163
        else
1164
            replica_state_by_uuid[replica_uuid].status = result[1].status or "uninitialized"
18✔
1165
        end
1166
    end
1167

1168
    return replica_state_by_uuid
5✔
1169
end
1170

1171
--- Storage status information.
1172
--
1173
-- @function storage_info_on_storage
1174
--
1175
-- @return a table with storage status.
1176
function utils.storage_info_on_storage()
457✔
1177
    return {status = "running"}
18✔
1178
end
1179

1180
local expected_vshard_api = {
457✔
1181
    'routeall', 'route', 'bucket_id_strcrc32',
1182
    'callrw', 'callro', 'callbro', 'callre',
1183
    'callbre', 'map_callrw'
1184
}
1185

1186
--- Verifies that a table has expected vshard
1187
--  router handles.
1188
local function verify_vshard_router(router)
1189
    dev_checks("table")
212✔
1190

1191
    for _, func_name in ipairs(expected_vshard_api) do
1,328✔
1192
        if type(router[func_name]) ~= 'function' then
1,204✔
1193
            return false
88✔
1194
        end
1195
    end
1196

1197
    return true
124✔
1198
end
1199

1200
--- Get a vshard router instance from a parameter.
1201
--
1202
--  If a string passed, extract router instance from
1203
--  Cartridge vshard groups. If table passed, verifies
1204
--  that a table is a vshard router instance.
1205
--
1206
-- @function get_vshard_router_instance
1207
--
1208
-- @param[opt] router name of a vshard group or a vshard router
1209
--  instance
1210
--
1211
-- @return[1] table vshard router instance
1212
-- @treturn[2] nil
1213
-- @treturn[2] table Error description
1214
function utils.get_vshard_router_instance(router)
457✔
1215
    dev_checks('?string|table')
165,705✔
1216

1217
    local router_instance
1218

1219
    if type(router) == 'string' then
165,705✔
1220
        if not is_cartridge then
132✔
1221
            return nil, VshardRouterError:new("Vshard groups are supported only in Tarantool Cartridge")
×
1222
        end
1223

1224
        local router_service = cartridge.service_get('vshard-router')
132✔
1225
        assert(router_service ~= nil)
132✔
1226

1227
        router_instance = router_service.get(router)
264✔
1228
        if router_instance == nil then
132✔
1229
            return nil, VshardRouterError:new("Vshard group %s is not found", router)
×
1230
        end
1231
    elseif type(router) == 'table' then
165,573✔
1232
        if not verify_vshard_router(router) then
424✔
1233
            return nil, VshardRouterError:new("Invalid opts.vshard_router table value, " ..
176✔
1234
                                              "a vshard router instance has been expected")
176✔
1235
        end
1236

1237
        router_instance = router
124✔
1238
    else
1239
        assert(type(router) == 'nil')
165,361✔
1240
        router_instance = vshard.router.static
165,361✔
1241

1242
        if router_instance == nil then
165,361✔
1243
            return nil, VshardRouterError:new("Default vshard group is not found and custom " ..
176✔
1244
                                              "is not specified with opts.vshard_router")
176✔
1245
        end
1246
    end
1247

1248
    return router_instance
165,529✔
1249
end
1250

1251
--- Check if Tarantool Cartridge hotreload supported
1252
--  and get its implementaion.
1253
--
1254
-- @function is_cartridge_hotreload_supported
1255
--
1256
-- @return[1] true or false
1257
-- @return[1] module table, if supported
1258
function utils.is_cartridge_hotreload_supported()
457✔
1259
    if not is_cartridge_hotreload then
273✔
1260
        return false
×
1261
    end
1262

1263
    return true, cartridge_hotreload
273✔
1264
end
1265

1266
return utils
457✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc