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

tarantool / crud / 8518810120

02 Apr 2024 07:09AM UTC coverage: 88.753% (+0.06%) from 88.694%
8518810120

push

github

DifferentialOrange
storage: use async bootstrap by default for tarantool 3

Closes #412
Part of #415

8 of 9 new or added lines in 2 files covered. (88.89%)

209 existing lines in 16 files now uncovered.

4806 of 5415 relevant lines covered (88.75%)

6165.34 hits per line

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

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

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

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

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

27
local utils = {}
331✔
28

29
utils.STORAGE_NAMESPACE = '_crud'
331✔
30

31
--- Returns a full call string for a storage function name.
32
--
33
--  @param string name a base name of the storage function.
34
--
35
--  @return a full string for the call.
36
function utils.get_storage_call(name)
331✔
37
    dev_checks('string')
8,674✔
38

39
    return ('%s.%s'):format(utils.STORAGE_NAMESPACE, name)
8,674✔
40
end
41

42
local space_format_cache = setmetatable({}, {__mode = 'k'})
331✔
43

44
-- copy from LuaJIT lj_char.c
45
local lj_char_bits = {
331✔
46
    0,
47
    1,  1,  1,  1,  1,  1,  1,  1,  1,  3,  3,  3,  3,  3,  1,  1,
48
    1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
49
    2,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
50
    152,152,152,152,152,152,152,152,152,152,  4,  4,  4,  4,  4,  4,
51
    4,176,176,176,176,176,176,160,160,160,160,160,160,160,160,160,
52
    160,160,160,160,160,160,160,160,160,160,160,  4,  4,  4,  4,132,
53
    4,208,208,208,208,208,208,192,192,192,192,192,192,192,192,192,
54
    192,192,192,192,192,192,192,192,192,192,192,  4,  4,  4,  4,  1,
55
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
56
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
57
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
58
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
59
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
60
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
61
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,
62
    128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128
63
}
64

65
local LJ_CHAR_IDENT = 0x80
331✔
66
local LJ_CHAR_DIGIT = 0x08
331✔
67

68
local LUA_KEYWORDS = {
331✔
69
    ['and'] = true,
70
    ['end'] = true,
71
    ['in'] = true,
72
    ['repeat'] = true,
73
    ['break'] = true,
74
    ['false'] = true,
75
    ['local'] = true,
76
    ['return'] = true,
77
    ['do'] = true,
78
    ['for'] = true,
79
    ['nil'] = true,
80
    ['then'] = true,
81
    ['else'] = true,
82
    ['function'] = true,
83
    ['not'] = true,
84
    ['true'] = true,
85
    ['elseif'] = true,
86
    ['if'] = true,
87
    ['or'] = true,
88
    ['until'] = true,
89
    ['while'] = true,
90
}
91

92
function utils.table_count(table)
331✔
93
    dev_checks("table")
4✔
94

95
    local cnt = 0
4✔
96
    for _, _ in pairs(table) do
23✔
97
        cnt = cnt + 1
15✔
98
    end
99

100
    return cnt
4✔
101
end
102

103
function utils.format_replicaset_error(replicaset_id, msg, ...)
331✔
104
    dev_checks("string", "string")
424✔
105

106
    return string.format(
424✔
107
        "Failed for %s: %s",
424✔
108
        replicaset_id,
424✔
109
        string.format(msg, ...)
424✔
110
    )
424✔
111
end
112

113
local function get_replicaset_by_replica_id(replicasets, id)
114
    for replicaset_id, replicaset in pairs(replicasets) do
29✔
115
        for replica_id, _ in pairs(replicaset.replicas) do
66✔
116
            if replica_id == id then
38✔
117
                return replicaset_id, replicaset
10✔
118
            end
119
        end
120
    end
121

UNCOV
122
    return nil, nil
×
123
end
124

125
function utils.get_spaces(vshard_router, timeout, replica_id)
331✔
126
    local replicasets, replicaset, replicaset_id, master
127

128
    timeout = timeout or const.DEFAULT_VSHARD_CALL_TIMEOUT
77,599✔
129
    local deadline = fiber.clock() + timeout
155,198✔
130
    local iter_sleep = math.min(timeout / 100, 0.1)
77,599✔
131
    while (
132
        -- Break if the deadline condition is exceeded.
133
        -- Handling for deadline errors are below in the code.
134
        fiber.clock() < deadline
155,198✔
135
    ) do
77,599✔
136
        -- Try to get master with timeout.
137
        replicasets = vshard_router:routeall()
155,198✔
138
        if replica_id ~= nil then
77,599✔
139
            -- Get the same replica on which the last DML operation was performed.
140
            -- This approach is temporary and is related to [1], [2].
141
            -- [1] https://github.com/tarantool/crud/issues/236
142
            -- [2] https://github.com/tarantool/crud/issues/361
143
            replicaset_id, replicaset = get_replicaset_by_replica_id(replicasets, replica_id)
20✔
144
            break
10✔
145
        else
146
            replicaset_id, replicaset = next(replicasets)
77,589✔
147
        end
148

149
        if replicaset ~= nil then
77,589✔
150
            -- Get cached, reload (if required) will be processed in other place.
151
            master = utils.get_replicaset_master(replicaset, {cached = true})
155,178✔
152
            if master ~= nil and master.conn.error == nil then
77,589✔
153
                break
77,589✔
154
            end
155
        end
156

UNCOV
157
        fiber.sleep(iter_sleep)
×
158
    end
159

160
    if replicaset == nil then
77,599✔
UNCOV
161
        return nil, GetSpaceError:new(
×
162
            'The router returned empty replicasets: ' ..
×
163
            'perhaps other instances are unavailable or you have configured only the router')
×
164
    end
165

166
    master = utils.get_replicaset_master(replicaset, {cached = true})
155,198✔
167

168
    if master == nil then
77,599✔
UNCOV
169
        local error_msg = string.format(
×
170
            'The master was not found in replicaset %s, ' ..
×
171
            'check status of the master and repeat the operation later',
UNCOV
172
             replicaset_id)
×
173
        return nil, GetSpaceError:new(error_msg)
×
174
    end
175

176
    if master.conn.error ~= nil then
77,599✔
UNCOV
177
        local error_msg = string.format(
×
178
            'The connection to the master of replicaset %s is not valid: %s',
UNCOV
179
             replicaset_id, master.conn.error)
×
180
        return nil, GetSpaceError:new(error_msg)
×
181
    end
182

183
    return master.conn.space, nil, master.conn.schema_version
77,599✔
184
end
185

186
function utils.get_space(space_name, vshard_router, timeout, replica_id)
331✔
187
    local spaces, err, schema_version = utils.get_spaces(vshard_router, timeout, replica_id)
77,583✔
188

189
    if spaces == nil then
77,583✔
UNCOV
190
        return nil, err
×
191
    end
192

193
    return spaces[space_name], err, schema_version
77,583✔
194
end
195

196
function utils.get_space_format(space_name, vshard_router)
331✔
197
    local space, err = utils.get_space(space_name, vshard_router)
5,636✔
198
    if err ~= nil then
5,636✔
UNCOV
199
        return nil, GetSpaceFormatError:new("An error occurred during the operation: %s", err)
×
200
    end
201
    if space == nil then
5,636✔
202
        return nil, GetSpaceFormatError:new("Space %q doesn't exist", space_name)
202✔
203
    end
204

205
    local space_format = space:format()
5,535✔
206

207
    return space_format
5,535✔
208
end
209

210
function utils.fetch_latest_metadata_when_single_storage(space, space_name, netbox_schema_version,
331✔
211
                                                         vshard_router, opts, storage_info)
212
    -- Checking the relevance of the schema version is necessary
213
    -- to prevent the irrelevant metadata of the DML operation.
214
    -- This approach is temporary and is related to [1], [2].
215
    -- [1] https://github.com/tarantool/crud/issues/236
216
    -- [2] https://github.com/tarantool/crud/issues/361
217
    local latest_space, err
218

219
    assert(storage_info.replica_schema_version ~= nil,
20✔
220
           'check the replica_schema_version value from storage ' ..
10✔
221
           'for correct use of the fetch_latest_metadata opt')
10✔
222

223
    local replica_id
224
    if storage_info.replica_id == nil then -- Backward compatibility.
10✔
UNCOV
225
        assert(storage_info.replica_uuid ~= nil,
×
226
               'check the replica_uuid value from storage ' ..
×
227
               'for correct use of the fetch_latest_metadata opt')
×
228
        replica_id = storage_info.replica_uuid
×
229
    else
230
        replica_id = storage_info.replica_id
10✔
231
    end
232

233
    assert(netbox_schema_version ~= nil,
20✔
234
           'check the netbox_schema_version value from net_box conn on router ' ..
10✔
235
           'for correct use of the fetch_latest_metadata opt')
10✔
236

237
    if storage_info.replica_schema_version ~= netbox_schema_version then
10✔
238
        local ok, reload_schema_err = schema.reload_schema(vshard_router)
10✔
239
        if ok then
10✔
240
            latest_space, err = utils.get_space(space_name, vshard_router,
20✔
241
                                                opts.timeout, replica_id)
20✔
242
            if err ~= nil then
10✔
UNCOV
243
                local warn_msg = "Failed to fetch space for latest schema actualization, metadata may be outdated: %s"
×
244
                log.warn(warn_msg, err)
×
245
            end
246
            if latest_space == nil then
10✔
UNCOV
247
                log.warn("Failed to find space for latest schema actualization, metadata may be outdated")
×
248
            end
249
        else
UNCOV
250
            log.warn("Failed to reload schema, metadata may be outdated: %s", reload_schema_err)
×
251
        end
252
    end
253
    if err == nil and latest_space ~= nil then
10✔
254
        space = latest_space
10✔
255
    end
256

257
    return space
10✔
258
end
259

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

297
    return space
8✔
298
end
299

300
function utils.fetch_latest_metadata_for_select(space_name, vshard_router, opts,
331✔
301
                                                storages_info, iter)
302
    -- Checking the relevance of the schema version is necessary
303
    -- to prevent the irrelevant metadata of the DML operation.
304
    -- This approach is temporary and is related to [1], [2].
305
    -- [1] https://github.com/tarantool/crud/issues/236
306
    -- [2] https://github.com/tarantool/crud/issues/361
307
    for _, storage_info in pairs(storages_info) do
4✔
308
        assert(storage_info.replica_schema_version ~= nil,
4✔
309
               'check the replica_schema_version value from storage ' ..
2✔
310
               'for correct use of the fetch_latest_metadata opt')
2✔
311
        assert(iter.netbox_schema_version ~= nil,
4✔
312
               'check the netbox_schema_version value from net_box conn on router ' ..
2✔
313
               'for correct use of the fetch_latest_metadata opt')
2✔
314
        if storage_info.replica_schema_version ~= iter.netbox_schema_version then
2✔
315
            local ok, reload_schema_err = schema.reload_schema(vshard_router)
2✔
316
            if ok then
2✔
317
                local err
318
                iter.space, err = utils.get_space(space_name, vshard_router, opts.timeout)
4✔
319
                if err ~= nil then
2✔
UNCOV
320
                    local warn_msg = "Failed to fetch space for latest schema actualization, " ..
×
321
                                     "metadata may be outdated: %s"
UNCOV
322
                    log.warn(warn_msg, err)
×
323
                end
324
            else
UNCOV
325
                log.warn("Failed to reload schema, metadata may be outdated: %s", reload_schema_err)
×
326
            end
327
            break
328
        end
329
    end
330

331
    return iter
2✔
332
end
333

334
local function append(lines, s, ...)
335
    table.insert(lines, string.format(s, ...))
3,968✔
336
end
337

338
local flatten_functions_cache = setmetatable({}, {__mode = 'k'})
331✔
339

340
function utils.flatten(object, space_format, bucket_id, skip_nullability_check)
331✔
341
    local flatten_func = flatten_functions_cache[space_format]
5,627✔
342
    if flatten_func ~= nil then
5,627✔
343
        local data, err = flatten_func(object, bucket_id, skip_nullability_check)
5,488✔
344
        if err ~= nil then
5,488✔
345
            return nil, FlattenError:new(err)
1,210✔
346
        end
347
        return data
4,883✔
348
    end
349

350
    local lines = {}
139✔
351
    append(lines, 'local object, bucket_id, skip_nullability_check = ...')
139✔
352

353
    append(lines, 'for k in pairs(object) do')
139✔
354
    append(lines, '    if fieldmap[k] == nil then')
139✔
355
    append(lines, '        return nil, format(\'Unknown field %%q is specified\', k)')
139✔
356
    append(lines, '    end')
139✔
357
    append(lines, 'end')
139✔
358

359
    local len = #space_format
139✔
360
    append(lines, 'local result = {%s}', string.rep('NULL,', len))
139✔
361

362
    local fieldmap = {}
139✔
363

364
    for i, field in ipairs(space_format) do
735✔
365
        fieldmap[field.name] = true
596✔
366
        if field.name ~= 'bucket_id' then
596✔
367
            append(lines, 'if object[%q] ~= nil then', field.name)
457✔
368
            append(lines, '    result[%d] = object[%q]', i, field.name)
457✔
369
            if field.is_nullable ~= true then
457✔
370
                append(lines, 'elseif skip_nullability_check ~= true then')
395✔
371
                append(lines, '    return nil, \'Field %q isn\\\'t nullable' ..
790✔
372
                              ' (set skip_nullability_check_on_flatten option to true to skip check)\'',
395✔
373
                              field.name)
395✔
374
            end
375
            append(lines, 'end')
914✔
376
        else
377
            append(lines, 'if bucket_id ~= nil then')
139✔
378
            append(lines, '    result[%d] = bucket_id', i, field.name)
139✔
379
            append(lines, 'else')
139✔
380
            append(lines, '    result[%d] = object[%q]', i, field.name)
139✔
381
            append(lines, 'end')
139✔
382
        end
383
    end
384
    append(lines, 'return result')
139✔
385

386
    local code = table.concat(lines, '\n')
139✔
387
    local env = {
139✔
388
        pairs = pairs,
139✔
389
        format = string.format,
139✔
390
        fieldmap = fieldmap,
139✔
391
        NULL = box.NULL,
139✔
392
    }
393
    flatten_func = assert(load(code, nil, 't', env))
139✔
394

395
    flatten_functions_cache[space_format] = flatten_func
139✔
396
    local data, err = flatten_func(object, bucket_id, skip_nullability_check)
139✔
397
    if err ~= nil then
139✔
398
        return nil, FlattenError:new(err)
18✔
399
    end
400
    return data
130✔
401
end
402

403
function utils.unflatten(tuple, space_format)
331✔
404
    if tuple == nil then return nil end
10,128✔
405

406
    local object = {}
10,128✔
407

408
    for fieldno, field_format in ipairs(space_format) do
57,508✔
409
        local value = tuple[fieldno]
47,381✔
410

411
        if not field_format.is_nullable and value == nil then
47,381✔
412
            return nil, UnflattenError:new("Field %s isn't nullable", fieldno)
2✔
413
        end
414

415
        object[field_format.name] = value
47,380✔
416
    end
417

418
    return object
10,127✔
419
end
420

421
function utils.extract_key(tuple, key_parts)
331✔
422
    local key = {}
180,410✔
423
    for i, part in ipairs(key_parts) do
361,531✔
424
        key[i] = tuple[part.fieldno]
181,121✔
425
    end
426
    return key
180,410✔
427
end
428

429
function utils.merge_primary_key_parts(key_parts, pk_parts)
331✔
430
    local merged_parts = {}
5,302✔
431
    local key_fieldnos = {}
5,302✔
432

433
    for _, part in ipairs(key_parts) do
10,748✔
434
        table.insert(merged_parts, part)
5,446✔
435
        key_fieldnos[part.fieldno] = true
5,446✔
436
    end
437

438
    for _, pk_part in ipairs(pk_parts) do
11,508✔
439
        if not key_fieldnos[pk_part.fieldno] then
6,206✔
440
            table.insert(merged_parts, pk_part)
2,902✔
441
        end
442
    end
443

444
    return merged_parts
5,302✔
445
end
446

447
function utils.enrich_field_names_with_cmp_key(field_names, key_parts, space_format)
331✔
448
    if field_names == nil then
3,559✔
449
        return nil
3,516✔
450
    end
451

452
    local enriched_field_names = {}
43✔
453
    local key_field_names = {}
43✔
454

455
    for _, field_name in ipairs(field_names) do
127✔
456
        table.insert(enriched_field_names, field_name)
84✔
457
        key_field_names[field_name] = true
84✔
458
    end
459

460
    for _, part in ipairs(key_parts) do
113✔
461
        local field_name = space_format[part.fieldno].name
70✔
462
        if not key_field_names[field_name] then
70✔
463
            table.insert(enriched_field_names, field_name)
55✔
464
            key_field_names[field_name] = true
55✔
465
        end
466
    end
467

468
    return enriched_field_names
43✔
469
end
470

471

472
local function get_version_suffix(suffix_candidate)
473
    if type(suffix_candidate) ~= 'string' then
1,166✔
UNCOV
474
        return nil
×
475
    end
476

477
    if suffix_candidate:find('^entrypoint$')
1,166✔
478
    or suffix_candidate:find('^alpha%d$')
1,166✔
479
    or suffix_candidate:find('^beta%d$')
1,165✔
480
    or suffix_candidate:find('^rc%d$') then
1,163✔
481
        return suffix_candidate
7✔
482
    end
483

484
    return nil
1,159✔
485
end
486

487
utils.get_version_suffix = get_version_suffix
331✔
488

489

490
local suffix_with_digit_weight = {
331✔
491
    alpha = -3000,
492
    beta  = -2000,
493
    rc    = -1000,
494
}
495

496
local function get_version_suffix_weight(suffix)
497
    if suffix == nil then
9,034✔
498
        return 0
7,998✔
499
    end
500

501
    if suffix:find('^entrypoint$') then
1,036✔
502
        return -math.huge
9✔
503
    end
504

505
    for header, weight in pairs(suffix_with_digit_weight) do
3,410✔
506
        local pos, _, digits = suffix:find('^' .. header .. '(%d)$')
2,381✔
507
        if pos ~= nil then
2,381✔
508
            return weight + tonumber(digits)
1,025✔
509
        end
510
    end
511

512
    UtilsInternalError:assert(false,
4✔
513
        'Unexpected suffix %q, parse with "utils.get_version_suffix" first', suffix)
2✔
514
end
515

516
utils.get_version_suffix_weight = get_version_suffix_weight
331✔
517

518

519
local function is_version_ge(major, minor,
520
                             patch, suffix,
521
                             major_to_compare, minor_to_compare,
522
                             patch_to_compare, suffix_to_compare)
523
    major = major or 0
4,512✔
524
    minor = minor or 0
4,512✔
525
    patch = patch or 0
4,512✔
526
    local suffix_weight = get_version_suffix_weight(suffix)
4,512✔
527

528
    major_to_compare = major_to_compare or 0
4,512✔
529
    minor_to_compare = minor_to_compare or 0
4,512✔
530
    patch_to_compare = patch_to_compare or 0
4,512✔
531
    local suffix_weight_to_compare = get_version_suffix_weight(suffix_to_compare)
4,512✔
532

533
    if major > major_to_compare then return true end
4,512✔
534
    if major < major_to_compare then return false end
4,499✔
535

536
    if minor > minor_to_compare then return true end
4,157✔
537
    if minor < minor_to_compare then return false end
419✔
538

539
    if patch > patch_to_compare then return true end
414✔
540
    if patch < patch_to_compare then return false end
11✔
541

542
    if suffix_weight > suffix_weight_to_compare then return true end
10✔
543
    if suffix_weight < suffix_weight_to_compare then return false end
7✔
544

545
    return true
4✔
546
end
547

548
utils.is_version_ge = is_version_ge
331✔
549

550

551
local function is_version_in_range(major, minor,
552
                                   patch, suffix,
553
                                   major_left_side, minor_left_side,
554
                                   patch_left_side, suffix_left_side,
555
                                   major_right_side, minor_right_side,
556
                                   patch_right_side, suffix_right_side)
557
    return is_version_ge(major, minor,
4✔
558
                         patch, suffix,
2✔
559
                         major_left_side, minor_left_side,
2✔
560
                         patch_left_side, suffix_left_side)
2✔
561
       and is_version_ge(major_right_side, minor_right_side,
4✔
562
                         patch_right_side, suffix_right_side,
2✔
563
                         major, minor,
2✔
564
                         patch, suffix)
4✔
565
end
566

567
utils.is_version_in_range = is_version_in_range
331✔
568

569

570
local function get_tarantool_version()
571
    local version_parts = rawget(_G, '_TARANTOOL'):split('-', 1)
1,157✔
572

573
    local major_minor_patch_parts = version_parts[1]:split('.', 2)
1,157✔
574
    local major = tonumber(major_minor_patch_parts[1])
1,157✔
575
    local minor = tonumber(major_minor_patch_parts[2])
1,157✔
576
    local patch = tonumber(major_minor_patch_parts[3])
1,157✔
577

578
    local suffix = get_version_suffix(version_parts[2])
1,157✔
579

580
    return major, minor, patch, suffix
1,157✔
581
end
582

583
utils.get_tarantool_version = get_tarantool_version
331✔
584

585

586
local function tarantool_version_at_least(wanted_major, wanted_minor, wanted_patch)
587
    local major, minor, patch, suffix = get_tarantool_version()
825✔
588

589
    return is_version_ge(major, minor, patch, suffix,
825✔
590
                         wanted_major, wanted_minor, wanted_patch, nil)
825✔
591
end
592

593
utils.tarantool_version_at_least = tarantool_version_at_least
331✔
594

595

596
local enabled_tarantool_features = {}
331✔
597

598
local function determine_enabled_features()
599
    local major, minor, patch, suffix = get_tarantool_version()
331✔
600

601
    -- since Tarantool 2.3.1
602
    enabled_tarantool_features.fieldpaths = is_version_ge(major, minor, patch, suffix,
662✔
603
                                                          2, 3, 1, nil)
662✔
604

605
    -- Full support (Lua type, space format type and indexes) for decimal type
606
    -- is since Tarantool 2.3.1 [1]
607
    --
608
    -- [1] https://github.com/tarantool/tarantool/commit/485439e33196e26d120e622175f88b4edc7a5aa1
609
    enabled_tarantool_features.decimals = is_version_ge(major, minor, patch, suffix,
662✔
610
                                                        2, 3, 1, nil)
662✔
611

612
    -- Full support (Lua type, space format type and indexes) for uuid type
613
    -- is since Tarantool 2.4.1 [1]
614
    --
615
    -- [1] https://github.com/tarantool/tarantool/commit/b238def8065d20070dcdc50b54c2536f1de4c7c7
616
    enabled_tarantool_features.uuids = is_version_ge(major, minor, patch, suffix,
662✔
617
                                                     2, 4, 1, nil)
662✔
618

619
    -- Full support (Lua type, space format type and indexes) for datetime type
620
    -- is since Tarantool 2.10.0-beta2 [1]
621
    --
622
    -- [1] https://github.com/tarantool/tarantool/commit/3bd870261c462416c29226414fe0a2d79aba0c74
623
    enabled_tarantool_features.datetimes = is_version_ge(major, minor, patch, suffix,
662✔
624
                                                         2, 10, 0, 'beta2')
662✔
625

626
    -- Full support (Lua type, space format type and indexes) for datetime type
627
    -- is since Tarantool 2.10.0-rc1 [1]
628
    --
629
    -- [1] https://github.com/tarantool/tarantool/commit/38f0c904af4882756c6dc802f1895117d3deae6a
630
    enabled_tarantool_features.intervals = is_version_ge(major, minor, patch, suffix,
662✔
631
                                                         2, 10, 0, 'rc1')
662✔
632

633
    -- since Tarantool 2.6.3 / 2.7.2 / 2.8.1
634
    enabled_tarantool_features.jsonpath_indexes = is_version_ge(major, minor, patch, suffix,
662✔
635
                                                                2, 8, 1, nil)
331✔
636
                                               or is_version_in_range(major, minor, patch, suffix,
331✔
637
                                                                      2, 7, 2, nil,
UNCOV
638
                                                                      2, 7, math.huge, nil)
×
639
                                               or is_version_in_range(major, minor, patch, suffix,
×
640
                                                                      2, 6, 3, nil,
641
                                                                      2, 6, math.huge, nil)
331✔
642

643
    -- The merger module was implemented in 2.2.1, see [1].
644
    -- However it had the critical problem [2], which leads to
645
    -- segfault at attempt to use the module from a fiber serving
646
    -- iproto request. So we don't use it in versions before the
647
    -- fix.
648
    --
649
    -- [1]: https://github.com/tarantool/tarantool/issues/3276
650
    -- [2]: https://github.com/tarantool/tarantool/issues/4954
651
    enabled_tarantool_features.builtin_merger = is_version_ge(major, minor, patch, suffix,
662✔
652
                                                              2, 6, 0, nil)
331✔
653
                                             or is_version_in_range(major, minor, patch, suffix,
331✔
654
                                                                    2, 5, 1, nil,
UNCOV
655
                                                                    2, 5, math.huge, nil)
×
656
                                             or is_version_in_range(major, minor, patch, suffix,
×
657
                                                                    2, 4, 2, nil,
UNCOV
658
                                                                    2, 4, math.huge, nil)
×
659
                                             or is_version_in_range(major, minor, patch, suffix,
×
660
                                                                    2, 3, 3, nil,
661
                                                                    2, 3, math.huge, nil)
331✔
662

663
    -- The external merger module leans on a set of relatively
664
    -- new APIs in tarantool. So it works only on tarantool
665
    -- versions, which offer those APIs.
666
    --
667
    -- See README of the module:
668
    -- https://github.com/tarantool/tuple-merger
669
    enabled_tarantool_features.external_merger = is_version_ge(major, minor, patch, suffix,
662✔
670
                                                               2, 7, 0, nil)
331✔
671
                                              or is_version_in_range(major, minor, patch, suffix,
331✔
672
                                                                     2, 6, 1, nil,
UNCOV
673
                                                                     2, 6, math.huge, nil)
×
674
                                              or is_version_in_range(major, minor, patch, suffix,
×
675
                                                                     2, 5, 2, nil,
UNCOV
676
                                                                     2, 5, math.huge, nil)
×
677
                                              or is_version_in_range(major, minor, patch, suffix,
×
678
                                                                     2, 4, 3, nil,
UNCOV
679
                                                                     2, 4, math.huge, nil)
×
680
                                              or is_version_in_range(major, minor, patch, suffix,
×
681
                                                                     1, 10, 8, nil,
682
                                                                     1, 10, math.huge, nil)
331✔
683

684
    enabled_tarantool_features.netbox_skip_header_option = is_version_ge(major, minor, patch, suffix,
662✔
685
                                                                         2, 2, 0, nil)
662✔
686

687
    -- https://github.com/tarantool/tarantool/commit/11f2d999a92e45ee41b8c8d0014d8a09290fef7b
688
    enabled_tarantool_features.box_watch = is_version_ge(major, minor, patch, suffix,
662✔
689
                                                         2, 10, 0, 'beta2')
662✔
690

691
    enabled_tarantool_features.tarantool_3 = is_version_ge(major, minor, patch, suffix,
662✔
692
                                                           3, 0, 0, nil)
662✔
693
end
694

695
determine_enabled_features()
331✔
696

697
for feature_name, feature_enabled in pairs(enabled_tarantool_features) do
4,303✔
698
    local util_name
699
    if feature_name == 'tarantool_3' then
3,641✔
700
        util_name = ('is_%s'):format(feature_name)
331✔
701
    elseif feature_name == 'builtin_merger' then
3,310✔
702
        util_name = ('tarantool_has_%s'):format(feature_name)
331✔
703
    else
704
        util_name = ('tarantool_supports_%s'):format(feature_name)
2,979✔
705
    end
706

707
    local util_func = function() return feature_enabled end
18,734✔
708

709
    utils[util_name] = util_func
3,641✔
710
end
711

712
local function add_nullable_fields_recursive(operations, operations_map, space_format, tuple, id)
UNCOV
713
    if id < 2 or tuple[id - 1] ~= box.NULL then
×
714
        return operations
×
715
    end
716

UNCOV
717
    if space_format[id - 1].is_nullable and not operations_map[id - 1] then
×
UNCOV
718
        table.insert(operations, {'=', id - 1, box.NULL})
×
719
        return add_nullable_fields_recursive(operations, operations_map, space_format, tuple, id - 1)
×
720
    end
721

UNCOV
722
    return operations
×
723
end
724

725
-- Tarantool < 2.1 has no fields `box.error.NO_SUCH_FIELD_NO` and `box.error.NO_SUCH_FIELD_NAME`.
726
if tarantool_version_at_least(2, 1, 0, nil) then
662✔
727
    function utils.is_field_not_found(err_code)
331✔
728
        return err_code == box.error.NO_SUCH_FIELD_NO or err_code == box.error.NO_SUCH_FIELD_NAME
48✔
729
    end
730
else
UNCOV
731
    function utils.is_field_not_found(err_code)
×
UNCOV
732
        return err_code == box.error.NO_SUCH_FIELD
×
733
    end
734
end
735

736
local function get_operations_map(operations)
UNCOV
737
    local map = {}
×
UNCOV
738
    for _, operation in ipairs(operations) do
×
739
        map[operation[2]] = true
×
740
    end
741

UNCOV
742
    return map
×
743
end
744

745
function utils.add_intermediate_nullable_fields(operations, space_format, tuple)
331✔
746
    if tuple == nil then
1✔
UNCOV
747
        return operations
×
748
    end
749

750
    -- If tarantool doesn't supports the fieldpaths, we already
751
    -- have converted operations (see this function call in update.lua)
752
    if utils.tarantool_supports_fieldpaths() then
2✔
753
        local formatted_operations, err = utils.convert_operations(operations, space_format)
1✔
754
        if err ~= nil then
1✔
755
            return operations
1✔
756
        end
757

UNCOV
758
        operations = formatted_operations
×
759
    end
760

761
    -- We need this map to check if there is a field update
762
    -- operation with constant complexity
UNCOV
763
    local operations_map = get_operations_map(operations)
×
764
    for _, operation in ipairs(operations) do
×
UNCOV
765
        operations = add_nullable_fields_recursive(
×
766
            operations, operations_map,
UNCOV
767
            space_format, tuple, operation[2]
×
768
        )
769
    end
770

UNCOV
771
    table.sort(operations, function(v1, v2) return v1[2] < v2[2] end)
×
UNCOV
772
    return operations
×
773
end
774

775
function utils.convert_operations(user_operations, space_format)
331✔
776
    local converted_operations = {}
1✔
777

778
    for _, operation in ipairs(user_operations) do
1✔
779
        if type(operation[2]) == 'string' then
1✔
780
            local field_id
781
            for fieldno, field_format in ipairs(space_format) do
5✔
782
                if field_format.name == operation[2] then
4✔
UNCOV
783
                    field_id = fieldno
×
784
                    break
785
                end
786
            end
787

788
            if field_id == nil then
1✔
789
                return nil, ParseOperationsError:new(
2✔
790
                        "Space format doesn't contain field named %q", operation[2])
2✔
791
            end
792

UNCOV
793
            table.insert(converted_operations, {
×
794
                operation[1], field_id, operation[3]
×
795
            })
796
        else
UNCOV
797
            table.insert(converted_operations, operation)
×
798
        end
799
    end
800

UNCOV
801
    return converted_operations
×
802
end
803

804
function utils.unflatten_rows(rows, metadata)
331✔
805
    if metadata == nil then
8,885✔
UNCOV
806
        return nil, UnflattenError:new('Metadata is not provided')
×
807
    end
808

809
    local result = table.new(#rows, 0)
8,885✔
810
    local err
811
    for i, row in ipairs(rows) do
18,494✔
812
        result[i], err = utils.unflatten(row, metadata)
19,218✔
813
        if err ~= nil then
9,609✔
UNCOV
814
            return nil, err
×
815
        end
816
    end
817
    return result
8,885✔
818
end
819

820
local inverted_tarantool_iters = {
331✔
821
    [box.index.EQ] = box.index.REQ,
331✔
822
    [box.index.GT] = box.index.LT,
331✔
823
    [box.index.GE] = box.index.LE,
331✔
824
    [box.index.LT] = box.index.GT,
331✔
825
    [box.index.LE] = box.index.GE,
331✔
826
    [box.index.REQ] = box.index.EQ,
331✔
827
}
828

829
function utils.invert_tarantool_iter(iter)
331✔
830
    local inverted_iter = inverted_tarantool_iters[iter]
28✔
831
    assert(inverted_iter ~= nil, "Unsupported Tarantool iterator: " .. tostring(iter))
28✔
832
    return inverted_iter
28✔
833
end
834

835
function utils.reverse_inplace(t)
331✔
836
    for i = 1,math.floor(#t / 2) do
55✔
837
        t[i], t[#t - i + 1] = t[#t - i + 1], t[i]
28✔
838
    end
839
    return t
27✔
840
end
841

842
function utils.get_bucket_id_fieldno(space, shard_index_name)
331✔
843
    shard_index_name = shard_index_name or 'bucket_id'
180,692✔
844
    local bucket_id_index = space.index[shard_index_name]
180,692✔
845
    if bucket_id_index == nil then
180,692✔
846
        return nil, ShardingError:new('%q index is not found', shard_index_name)
14✔
847
    end
848

849
    return bucket_id_index.parts[1].fieldno
180,685✔
850
end
851

852
-- Build a map with field number as a keys and part number
853
-- as a values using index parts as a source.
854
function utils.get_index_fieldno_map(index_parts)
331✔
855
    dev_checks('table')
59✔
856

857
    local fieldno_map = {}
59✔
858
    for i, part in ipairs(index_parts) do
153✔
859
        local fieldno = part.fieldno
94✔
860
        fieldno_map[fieldno] = i
94✔
861
    end
862

863
    return fieldno_map
59✔
864
end
865

866
-- Build a map with field names as a keys and fieldno's
867
-- as a values using space format as a source.
868
function utils.get_format_fieldno_map(space_format)
331✔
869
    dev_checks('table')
3,940✔
870

871
    local fieldno_map = {}
3,940✔
872
    for fieldno, field_format in ipairs(space_format) do
20,244✔
873
        fieldno_map[field_format.name] = fieldno
16,304✔
874
    end
875

876
    return fieldno_map
3,940✔
877
end
878

879
local uuid_t = ffi.typeof('struct tt_uuid')
331✔
880
function utils.is_uuid(value)
331✔
881
    return ffi.istype(uuid_t, value)
397✔
882
end
883

884
local function get_field_format(space_format, field_name)
885
    dev_checks('table', 'string')
233✔
886

887
    local metadata = space_format_cache[space_format]
233✔
888
    if metadata ~= nil then
233✔
889
        return metadata[field_name]
223✔
890
    end
891

892
    space_format_cache[space_format] = {}
10✔
893
    for _, field in ipairs(space_format) do
67✔
894
        space_format_cache[space_format][field.name] = field
57✔
895
    end
896

897
    return space_format_cache[space_format][field_name]
10✔
898
end
899

900
local function filter_format_fields(space_format, field_names)
901
    dev_checks('table', 'table')
91✔
902

903
    local filtered_space_format = {}
91✔
904

905
    for i, field_name in ipairs(field_names) do
309✔
906
        filtered_space_format[i] = get_field_format(space_format, field_name)
466✔
907
        if filtered_space_format[i] == nil then
233✔
908
            return nil, FilterFieldsError:new(
30✔
909
                    'Space format doesn\'t contain field named %q', field_name
15✔
910
            )
30✔
911
        end
912
    end
913

914
    return filtered_space_format
76✔
915
end
916

917
function utils.get_fields_format(space_format, field_names)
331✔
918
    dev_checks('table', '?table')
2,847✔
919

920
    if field_names == nil then
2,847✔
921
        return table.copy(space_format)
2,815✔
922
    end
923

924
    local filtered_space_format, err = filter_format_fields(space_format, field_names)
32✔
925

926
    if err ~= nil then
32✔
927
        return nil, err
1✔
928
    end
929

930
    return filtered_space_format
31✔
931
end
932

933
function utils.format_result(rows, space, field_names)
331✔
934
    local result = {}
65,979✔
935
    local err
936
    local space_format = space:format()
65,979✔
937
    result.rows = rows
65,979✔
938

939
    if field_names == nil then
65,979✔
940
        result.metadata = table.copy(space_format)
131,840✔
941
        return result
65,920✔
942
    end
943

944
    result.metadata, err = filter_format_fields(space_format, field_names)
118✔
945

946
    if err ~= nil then
59✔
947
        return nil, err
14✔
948
    end
949

950
    return result
45✔
951
end
952

953
local function truncate_tuple_metadata(tuple_metadata, field_names)
954
    dev_checks('?table', 'table')
18✔
955

956
    if tuple_metadata == nil then
18✔
957
        return nil
2✔
958
    end
959

960
    local truncated_metadata = {}
16✔
961

962
    if #tuple_metadata < #field_names then
16✔
UNCOV
963
        return nil, FilterFieldsError:new(
×
964
                'Field names don\'t match to tuple metadata'
965
        )
966
    end
967

968
    for i, name in ipairs(field_names) do
44✔
969
        if tuple_metadata[i].name ~= name then
30✔
970
            return nil, FilterFieldsError:new(
4✔
971
                    'Field names don\'t match to tuple metadata'
972
            )
4✔
973
        end
974

975
        table.insert(truncated_metadata, tuple_metadata[i])
28✔
976
    end
977

978
    return truncated_metadata
14✔
979
end
980

981
function utils.cut_objects(objs, field_names)
331✔
982
    dev_checks('table', 'table')
4✔
983

984
    for i, obj in ipairs(objs) do
16✔
985
        objs[i] = schema.filter_obj_fields(obj, field_names)
24✔
986
    end
987

988
    return objs
4✔
989
end
990

991
function utils.cut_rows(rows, metadata, field_names)
331✔
992
    dev_checks('table', '?table', 'table')
18✔
993

994
    local truncated_metadata, err = truncate_tuple_metadata(metadata, field_names)
18✔
995

996
    if err ~= nil then
18✔
997
        return nil, err
2✔
998
    end
999

1000
    for i, row in ipairs(rows) do
42✔
1001
        rows[i] = schema.truncate_row_trailing_fields(row, field_names)
52✔
1002
    end
1003

1004
    return {
16✔
1005
        metadata = truncated_metadata,
16✔
1006
        rows = rows,
16✔
1007
    }
16✔
1008
end
1009

1010
local function flatten_obj(vshard_router, space_name, obj, skip_nullability_check)
1011
    local space_format, err = utils.get_space_format(space_name, vshard_router)
5,636✔
1012
    if err ~= nil then
5,636✔
1013
        return nil, FlattenError:new("Failed to get space format: %s", err), const.NEED_SCHEMA_RELOAD
202✔
1014
    end
1015

1016
    local tuple, err = utils.flatten(obj, space_format, nil, skip_nullability_check)
5,535✔
1017
    if err ~= nil then
5,535✔
1018
        return nil, FlattenError:new("Object is specified in bad format: %s", err), const.NEED_SCHEMA_RELOAD
1,224✔
1019
    end
1020

1021
    return tuple
4,923✔
1022
end
1023

1024
function utils.flatten_obj_reload(vshard_router, space_name, obj, skip_nullability_check)
331✔
1025
    return schema.wrap_func_reload(vshard_router, flatten_obj, space_name, obj, skip_nullability_check)
5,263✔
1026
end
1027

1028
-- Merge two options map.
1029
--
1030
-- `opts_a` and/or `opts_b` can be `nil`.
1031
--
1032
-- If `opts_a.foo` and `opts_b.foo` exists, prefer `opts_b.foo`.
1033
function utils.merge_options(opts_a, opts_b)
331✔
1034
    return fun.chain(opts_a or {}, opts_b or {}):tomap()
9,838✔
1035
end
1036

1037
local function lj_char_isident(n)
1038
    return bit.band(lj_char_bits[n + 2], LJ_CHAR_IDENT) == LJ_CHAR_IDENT
6,044✔
1039
end
1040

1041
local function lj_char_isdigit(n)
1042
    return bit.band(lj_char_bits[n + 2], LJ_CHAR_DIGIT) == LJ_CHAR_DIGIT
370✔
1043
end
1044

1045
function utils.check_name_isident(name)
331✔
1046
    dev_checks('string')
371✔
1047

1048
    -- sharding function name cannot
1049
    -- be equal to lua keyword
1050
    if LUA_KEYWORDS[name] then
371✔
1051
        return false
1✔
1052
    end
1053

1054
    -- sharding function name cannot
1055
    -- begin with a digit
1056
    local char_number = string.byte(name:sub(1,1))
740✔
1057
    if lj_char_isdigit(char_number) then
740✔
1058
        return false
1✔
1059
    end
1060

1061
    -- sharding func name must be sequence
1062
    -- of letters, digits, or underscore symbols
1063
    for i = 1, #name do
6,412✔
1064
        local char_number = string.byte(name:sub(i,i))
12,088✔
1065
        if not lj_char_isident(char_number) then
12,088✔
1066
            return false
1✔
1067
        end
1068
    end
1069

1070
    return true
368✔
1071
end
1072

1073
function utils.update_storage_call_error_description(err, func_name, replicaset_id)
331✔
1074
    if err == nil then
477✔
UNCOV
1075
        return nil
×
1076
    end
1077

1078
    if (err.type == 'ClientError' or err.type == 'AccessDeniedError')
885✔
1079
        and type(err.message) == 'string' then
724✔
1080
        local not_defined_str = string.format("Procedure '%s' is not defined", func_name)
364✔
1081
        local access_denied_str = string.format("Execute access to function '%s' is denied", func_name)
364✔
1082
        if err.message == not_defined_str or err.message:startswith(access_denied_str) then
1,442✔
1083
            if func_name:startswith('_crud.') then
12✔
1084
                err = NotInitializedError:new("Function %s is not registered: " ..
8✔
1085
                    "crud isn't initialized on replicaset %q or crud module versions mismatch " ..
4✔
1086
                    "between router and storage",
4✔
1087
                    func_name, replicaset_id or "Unknown")
8✔
1088
            else
1089
                err = NotInitializedError:new("Function %s is not registered", func_name)
4✔
1090
            end
1091
        end
1092
    end
1093
    return err
477✔
1094
end
1095

1096
--- Insert each value from values to list
1097
--
1098
-- @function list_extend
1099
--
1100
-- @param table list
1101
--  List to be extended
1102
--
1103
-- @param table values
1104
--  Values to be inserted to list
1105
--
1106
-- @return[1] list
1107
--  List with old values and inserted values
1108
function utils.list_extend(list, values)
331✔
1109
    dev_checks('table', 'table')
2,422✔
1110

1111
    for _, value in ipairs(values) do
49,923✔
1112
        table.insert(list, value)
47,501✔
1113
    end
1114

1115
    return list
2,422✔
1116
end
1117

1118
function utils.list_slice(list, start_index, end_index)
331✔
1119
    dev_checks('table', 'number', '?number')
24✔
1120

1121
    if end_index == nil then
24✔
1122
        end_index = table.maxn(list)
24✔
1123
    end
1124

1125
    local slice = {}
24✔
1126
    for i = start_index, end_index do
60✔
1127
        table.insert(slice, list[i])
36✔
1128
    end
1129

1130
    return slice
24✔
1131
end
1132

1133
local expected_vshard_api = {
331✔
1134
    'routeall', 'route', 'bucket_id_strcrc32',
1135
    'callrw', 'callro', 'callbro', 'callre',
1136
    'callbre', 'map_callrw'
1137
}
1138

1139
--- Verifies that a table has expected vshard
1140
--  router handles.
1141
local function verify_vshard_router(router)
1142
    dev_checks("table")
106✔
1143

1144
    for _, func_name in ipairs(expected_vshard_api) do
664✔
1145
        if type(router[func_name]) ~= 'function' then
602✔
1146
            return false
44✔
1147
        end
1148
    end
1149

1150
    return true
62✔
1151
end
1152

1153
--- Get a vshard router instance from a parameter.
1154
--
1155
--  If a string passed, extract router instance from
1156
--  Cartridge vshard groups. If table passed, verifies
1157
--  that a table is a vshard router instance.
1158
--
1159
-- @function get_vshard_router_instance
1160
--
1161
-- @param[opt] router name of a vshard group or a vshard router
1162
--  instance
1163
--
1164
-- @return[1] table vshard router instance
1165
-- @treturn[2] nil
1166
-- @treturn[2] table Error description
1167
function utils.get_vshard_router_instance(router)
331✔
1168
    dev_checks('?string|table')
72,172✔
1169

1170
    local router_instance
1171

1172
    if type(router) == 'string' then
72,172✔
1173
        if not is_cartridge then
70✔
UNCOV
1174
            return nil, VshardRouterError:new("Vshard groups are supported only in Tarantool Cartridge")
×
1175
        end
1176

1177
        local router_service = cartridge.service_get('vshard-router')
70✔
1178
        assert(router_service ~= nil)
70✔
1179

1180
        router_instance = router_service.get(router)
140✔
1181
        if router_instance == nil then
70✔
UNCOV
1182
            return nil, VshardRouterError:new("Vshard group %s is not found", router)
×
1183
        end
1184
    elseif type(router) == 'table' then
72,102✔
1185
        if not verify_vshard_router(router) then
212✔
1186
            return nil, VshardRouterError:new("Invalid opts.vshard_router table value, " ..
88✔
1187
                                              "a vshard router instance has been expected")
88✔
1188
        end
1189

1190
        router_instance = router
62✔
1191
    else
1192
        assert(type(router) == 'nil')
71,996✔
1193
        router_instance = vshard.router.static
71,996✔
1194

1195
        if router_instance == nil then
71,996✔
1196
            return nil, VshardRouterError:new("Default vshard group is not found and custom " ..
88✔
1197
                                              "is not specified with opts.vshard_router")
88✔
1198
        end
1199
    end
1200

1201
    return router_instance
72,084✔
1202
end
1203

1204
--- Check if Tarantool Cartridge hotreload supported
1205
--  and get its implementaion.
1206
--
1207
-- @function is_cartridge_hotreload_supported
1208
--
1209
-- @return[1] true or false
1210
-- @return[1] module table, if supported
1211
function utils.is_cartridge_hotreload_supported()
331✔
1212
    if not is_cartridge_hotreload then
218✔
UNCOV
1213
        return false
×
1214
    end
1215

1216
    return true, cartridge_hotreload
218✔
1217
end
1218

1219
if utils.tarantool_supports_intervals() then
662✔
1220
    -- https://github.com/tarantool/tarantool/blob/0510ffa07afd84a70c9c6f1a4c28aacd73a393d6/src/lua/datetime.lua#L175-179
1221
    local interval_t = ffi.typeof('struct interval')
331✔
1222

1223
    utils.is_interval = function(o)
1224
        return ffi.istype(interval_t, o)
12✔
1225
    end
1226
else
1227
    utils.is_interval = function()
UNCOV
1228
        return false
×
1229
    end
1230
end
1231

1232
for k, v in pairs(require('crud.common.vshard_utils')) do
2,979✔
1233
    utils[k] = v
1,986✔
1234
end
1235

1236
return utils
331✔
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