Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1 format sig #6

Open
wants to merge 3 commits into
base: v1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ luac.out
*.x86_64
*.hex

# Custom
.vscode
.tmp
1 change: 1 addition & 0 deletions spacer-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ build = {
type = 'builtin',
modules = {
['spacer'] = 'spacer.lua',
['spacer.compat'] = 'spacer/compat.lua',
}
}

Expand Down
77 changes: 71 additions & 6 deletions spacer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ spacer.duplicate_space('vy_space1', 'space1', {
}) -- will be identical to space1 (but in engine = 'vinyl')
--]]

local log = require('log')
local msgpack = require('msgpack')
local digest = require 'digest'
local log = require 'log'
local msgpack = require 'msgpack'

local compat = require 'spacer.compat'

local SPACER_V1_SCHEMA_PREFIX = '_spacer_v1'

local spacer = {}
local F = {}
Expand Down Expand Up @@ -88,6 +93,10 @@ local function _hash2tuple ( f )
end

local function init_tuple_info(space_name, format)
if format == nil then
return
end

F[space_name] = {}
F[space_name]['_'] = {}
for k, v in pairs(format) do
Expand All @@ -103,6 +112,46 @@ local function init_tuple_info(space_name, format)
T[space_name].tuple = _hash2tuple( F[space_name] )
end

local function _space_format_hash(format)
local sig = ''

if format == nil then
sig = 'nil'
else
for _, part in ipairs(format) do
if part.name == nil then
error('part name cannot be null')
end

if part.type == nil then
error('part type cannot be null')
end

sig = sig .. ';' .. part.name .. ':' .. part.type
end
end

sig = _TARANTOOL .. '!' .. sig
return digest.md5_hex(sig)
end

local function _check_space_format_changed(space, format)
assert(space ~= nil, 'space name must be non-nil')
local sig = _space_format_hash(format)

local key = SPACER_V1_SCHEMA_PREFIX .. ':' .. space .. ':formatsig'
local t = box.space._schema:get({key})
if t ~= nil then
local cur_sig = t[2]
if sig == cur_sig then
return false
end
end

box.space._schema:replace({key, sig})
return true
end

local function init_all_spaces_info()
local spaces = box.space._space:select{}
for _, sp in pairs(spaces) do
Expand All @@ -118,6 +167,12 @@ local function get_changed_opts_for_index(existing_index, ind_opts)
local changed_opts = {}
local changed_opts_count = 0

if ind_opts.type == nil then
ind_opts.type = 'tree'
else
ind_opts.type = string.lower(ind_opts.type)
end

if ind_opts.unique == nil then
ind_opts.unique = true -- default value of unique
end
Expand Down Expand Up @@ -201,7 +256,10 @@ local function get_changed_opts_for_index(existing_index, ind_opts)
local want_field_no = ind_opts.parts[j]
local want_field_type = ind_opts.parts[j + 1]

if want_field_no ~= part.fieldno or string.lower(want_field_type) ~= string.lower(part.type) then
local want_field_type = compat.compat_type(want_field_type)
local have_field_type = compat.compat_type(part.type)

if want_field_no ~= part.fieldno or want_field_type ~= have_field_type then
parts_changed = true
end
end
Expand Down Expand Up @@ -232,7 +290,7 @@ local function init_indexes(space_name, indexes, keep_obsolete)
assert(ind.name ~= nil, "Index name cannot be null")
local ind_opts = {}
ind_opts.id = ind.id
ind_opts.type = string.lower(ind.type)
ind_opts.type = ind.type
ind_opts.unique = ind.unique
ind_opts.if_not_exists = ind.if_not_exists
ind_opts.sequence = ind.sequence
Expand Down Expand Up @@ -317,7 +375,11 @@ local function create_space(name, format, indexes, opts)
else
log.info("Space '%s' is already created. Updating meta information.", name)
end
sp:format(format)

if _check_space_format_changed(name, format) then
log.info("Updating format for space '%s'", name)
sp:format(format)
end
init_tuple_info(name, format)

init_indexes(name, indexes)
Expand Down Expand Up @@ -350,7 +412,10 @@ local function duplicate_space(new_space, old_space, opts)
end
local format = box.space._space.index.name:get({old_space})[F._space.format]

sp:format(format)
if _check_space_format_changed(new_space, format) then
log.info("Updating format for space '%s'", new_space)
sp:format(format)
end
init_tuple_info(new_space, format)

local new_indexes = {}
Expand Down
238 changes: 238 additions & 0 deletions spacer/compat.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
local ver = _TARANTOOL


local function compat_type(type)
type = string.lower(type)
if ver >= "1.7" then
if type == 'string' or type == 'str' then
return 'string'
end

if type == 'unsigned' or type =='uint' or type == 'num' then
return 'unsigned'
end

if type == 'integer' or type == 'int' then
return 'integer'
end
else
if type == 'string' or type == 'str' then
return 'str'
end

if type == 'unsigned' or type =='uint' or type == 'num' then
return 'num'
end

if type == 'integer' or type == 'int' then
return 'int'
end
end

return type
end

local function index_parts_from_fields(space_name, fields, f_extra)
if fields == nil then
if ver >= "1.7" then
return {{1, 'unsigned'}}
else
return {1, 'NUM'}
end
end

if ver >= "1.7" then
local parts = {}
for _, p in ipairs(fields) do
local part = {}
local f_info = f_extra[p]
if f_info ~= nil then
table.insert(part, f_info.fieldno)
table.insert(part, compat_type(f_info.type))

if ver >= "1.7.6" then
part.is_nullable = f_info.is_nullable
part.collation = f_info.collation
end

table.insert(parts, part)
else
error(string.format("Field %s.%s not found", space_name, p))
end
end
return parts
else
local parts = {}
for _, p in ipairs(fields) do
local f_info = f_extra[p]
if f_info ~= nil then
table.insert(parts, f_info.fieldno)
table.insert(parts, compat_type(f_info.type))
else
error(string.format("Field %s.%s not found", space_name, p))
end
end
return parts
end
end

local function normalize_index_tuple_format(format, is_raw_tuple)
if is_raw_tuple == nil then
is_raw_tuple = false
end

if format == nil then
return nil
end

if #format == 0 then
return {}
end

if type(format[1]) == 'table' then
-- 1.7+ format

local parts = {}
for _, p in ipairs(format) do
local part
if #format[1] == 0 then
-- 1.7.6+ format (with is_nullable or collation) like { {fieldno = 1, type = 'unsigned', is_nullable = true}, ... }
local fieldno = p.fieldno
if fieldno == nil then
fieldno = p.field + 1 -- because fields are indexed from 0 in raw tuples
end
part = {
fieldno = fieldno,
['type'] = compat_type(p.type),
is_nullable = p.is_nullable,
collation = p.collation
}
else
-- <1.7.6 format { {1, 'unsigned'}, {2, 'string'}, ... }
-- but it can contain is_nullable and collation in a 'map' of each field
local fieldno = p[1]
if is_raw_tuple then
fieldno = fieldno + 1
end
part = {
fieldno = fieldno,
['type'] = compat_type(p[2]),
is_nullable = p.is_nullable,
collation = p.collation
}
end

table.insert(parts, part)
end
return parts
else
-- 1.6 format like {1, 'num', 2, 'string', ...}
local parts = {}
assert(#format % 2 == 0, 'format length must be even')
for i = 1, #format, 2 do
table.insert(parts, {
fieldno = format[i],
['type'] = compat_type(format[i + 1]),
is_nullable = false,
collation = nil
})
end
return parts
end
end


local function index_parts_from_normalized(normalized_parts)
if ver >= "1.7" then
local parts = {}
for _, p in ipairs(normalized_parts) do
local part = {p.fieldno, compat_type(p.type)}

if ver >= "1.7.6" then
part.is_nullable = p.is_nullable
part.collation = p.collation
end

table.insert(parts, part)
end
return parts
else
local parts = {}
for _, p in ipairs(normalized_parts) do
table.insert(parts, p.fieldno)
table.insert(parts, compat_type(p.type))
end
return parts
end
end


local function get_default_for_type(type, field_name, indexes_decl)
type = string.lower(type)
if indexes_decl == nil then
indexes_decl = {}
end

if type == 'unsigned' or type == 'uint' or type == 'num' then
return 0
end

if type == 'integer' or type == 'int' then
return 0
end

if type == 'number' then
return 0
end

if type == 'string' or type == 'str' then
return ""
end

if type == 'boolean' then
return false
end

if type == 'array' then
if field_name == nil then
return {}
end

for _, ind in ipairs(indexes_decl) do
if string.lower(ind.type) == 'rtree'
and #ind.parts > 0
and ind.parts[1] == field_name then
local dim = ind.dimension
if dim == nil then
dim = 2
end

local t = {}
for _ = 1,dim do
table.insert(t, 0)
end
return t
end
end

return {}
end

if type == 'map' then
return setmetatable({}, {__serialize = 'map'})
end

if type == 'scalar' then
return 0
end

error(string.format('unknown type "%s"', type))
end

return {
compat_type = compat_type,
index_parts_from_fields = index_parts_from_fields,
normalize_index_tuple_format = normalize_index_tuple_format,
index_parts_from_normalized = index_parts_from_normalized,
get_default_for_type = get_default_for_type,
}