From 98ea4a14673a5d619fdb16ae9dff290748b921b3 Mon Sep 17 00:00:00 2001 From: igorcoding Date: Sat, 24 Mar 2018 20:03:31 +0300 Subject: [PATCH 1/3] Added space format signature checkings (closes #5) --- .gitignore | 3 +++ spacer.lua | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 6fd0a37..d77dfa0 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ luac.out *.x86_64 *.hex +# Custom +.vscode +.tmp diff --git a/spacer.lua b/spacer.lua index 2d394d6..e6baf3a 100644 --- a/spacer.lua +++ b/spacer.lua @@ -48,8 +48,11 @@ 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 SPACER_V1_SCHEMA_PREFIX = '_spacer_v1' local spacer = {} local F = {} @@ -103,6 +106,54 @@ local function init_tuple_info(space_name, format) T[space_name].tuple = _hash2tuple( F[space_name] ) end +local function _space_format_hash(format) + if format == nil then + return nil + end + + local sig = '' + 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 + + if sig == '' then + return nil + 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) + if sig == nil then + return false + end + + 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 @@ -317,7 +368,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) @@ -350,7 +405,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 = {} From f5807f1111d0b67b07f0ec5b5ac6309129b1e854 Mon Sep 17 00:00:00 2001 From: igorcoding Date: Sun, 25 Mar 2018 00:12:06 +0300 Subject: [PATCH 2/3] small changes --- spacer.lua | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/spacer.lua b/spacer.lua index e6baf3a..b0693ea 100644 --- a/spacer.lua +++ b/spacer.lua @@ -91,6 +91,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 @@ -107,25 +111,22 @@ local function init_tuple_info(space_name, format) end local function _space_format_hash(format) - if format == nil then - return nil - end - local sig = '' - 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 + 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 - sig = sig .. ';' .. part.name .. ':' .. part.type - end + if part.type == nil then + error('part type cannot be null') + end - if sig == '' then - return nil + sig = sig .. ';' .. part.name .. ':' .. part.type + end end sig = _TARANTOOL .. '!' .. sig @@ -135,16 +136,11 @@ end local function _check_space_format_changed(space, format) assert(space ~= nil, 'space name must be non-nil') local sig = _space_format_hash(format) - if sig == nil then - return false - end 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 From b06431f0472898cda146290e733bd65b92ee2987 Mon Sep 17 00:00:00 2001 From: igorcoding Date: Sun, 25 Mar 2018 10:27:19 +0300 Subject: [PATCH 3/3] compat type copmares --- spacer-scm-1.rockspec | 1 + spacer.lua | 15 ++- spacer/compat.lua | 238 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 spacer/compat.lua diff --git a/spacer-scm-1.rockspec b/spacer-scm-1.rockspec index 5d1239a..1b06a87 100644 --- a/spacer-scm-1.rockspec +++ b/spacer-scm-1.rockspec @@ -16,6 +16,7 @@ build = { type = 'builtin', modules = { ['spacer'] = 'spacer.lua', + ['spacer.compat'] = 'spacer/compat.lua', } } diff --git a/spacer.lua b/spacer.lua index b0693ea..ae5962e 100644 --- a/spacer.lua +++ b/spacer.lua @@ -52,6 +52,8 @@ 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 = {} @@ -165,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 @@ -248,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 @@ -279,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 diff --git a/spacer/compat.lua b/spacer/compat.lua new file mode 100644 index 0000000..676df90 --- /dev/null +++ b/spacer/compat.lua @@ -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, +}