Skip to content

Commit

Permalink
ddl: support sequences
Browse files Browse the repository at this point in the history
This patch adds the support of sequences through new schema section,
as well as proper support of sequence option for indexes. Before this
patch, ddl had half-baked support of sequences: one could provide
`sequence` as sequence name in indexes and it would be created. This
approach was broken in basic `set_schema(get_schema)` scenario since
there wasn't any sequences support in get. Current design is inspired
by existing "not implemented yet" tests and documentation examples.

Closes #122
  • Loading branch information
DifferentialOrange committed Mar 21, 2024
1 parent 88251b9 commit 5b951c5
Show file tree
Hide file tree
Showing 15 changed files with 847 additions and 57 deletions.
2 changes: 2 additions & 0 deletions .luacheckrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
redefined = false
globals = {"box", "_TARANTOOL", "tonumber64", "utf8", "table"}
include_files = {"**/*.lua", "*.rockspec", "*.luacheckrc"}
exclude_files = {".rocks/", "tmp/", ".history/"}
max_line_length = 120
max_comment_line_length = 150
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

### Added

- Added support of sequences in schema and space indexes (#122).

## [1.6.5] - 2023-10-23

### Added
Expand Down
69 changes: 56 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,17 +205,16 @@ format = {
},
...
},
sequences = { -- Not implemented yet
[seqence_name] = {
start
min
max
cycle
cache
step

}
}
sequences = {
[sequence_name] = {
start = start,
min = min,
max = max,
cycle = cycle,
cache = cache,
step = step,
},
},
}
```

Expand Down Expand Up @@ -292,8 +291,52 @@ local schema = {
}},
sharding_key = {'customer_id'},
sharding_func = 'vshard.router.bucket_id_mpcrc32',
}
}
},
tickets = {
engine = 'memtx',
is_local = false,
temporary = false,
format = {
{name = 'ticket_id', is_nullable = false, type = 'unsigned'},
{name = 'customer_id', is_nullable = false, type = 'unsigned'},
{name = 'bucket_id', is_nullable = false, type = 'unsigned'},
{name = 'contents', is_nullable = false, type = 'string'},
},
indexes = {{
name = 'ticket_id',
type = 'TREE',
unique = true,
parts = {
{path = 'ticket_id', is_nullable = false, type = 'unsigned'}
},
sequence = 'ticket_seq',
}, {,
name = 'customer_id',
type = 'TREE',
unique = false,
parts = {
{path = 'customer_id', is_nullable = false, type = 'unsigned'}
}
}, {
name = 'bucket_id',
type = 'TREE',
unique = false,
parts = {
{path = 'bucket_id', is_nullable = false, type = 'unsigned'}
}
}},
sharding_key = {'customer_id'},
sharding_func = 'vshard.router.bucket_id_mpcrc32',
},
},
sequences = {
ticket_seq = {
start = 1,
min = 1,
max = 10000,
cycle = false,
},
},
}
```

Expand Down
64 changes: 60 additions & 4 deletions ddl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local ddl_get = require('ddl.get')
local ddl_set = require('ddl.set')
local ddl_check = require('ddl.check')
local ddl_db = require('ddl.db')
local ddl_compare = require('ddl.compare')
local utils = require('ddl.utils')

local function check_schema_format(schema)
Expand All @@ -22,12 +23,14 @@ local function check_schema_format(schema)
return nil, string.format("functions: not supported")
end

if type(schema.sequences) ~= 'nil' then
return nil, string.format("sequences: not supported")
if type(schema.sequences) ~= 'nil' and type(schema.sequences) ~= 'table' then
return nil, string.format(
"sequences: must be a table or nil, got %s", type(schema.sequences)
)
end

do -- check redundant keys
local k = utils.redundant_key(schema, {'spaces'})
local k = utils.redundant_key(schema, {'spaces', 'sequences'})
if k ~= nil then
return nil, string.format(
"Invalid schema: redundant key %q", k
Expand All @@ -39,11 +42,44 @@ local function check_schema_format(schema)
end

local function _check_schema(schema)
-- Create sequences before spaces since space indexes use sequences.
local sequences = schema.sequences or {}
for sequence_name, sequence_schema in pairs(sequences) do
local ok, err = ddl_check.check_sequence(sequence_name, sequence_schema)
if not ok then
return nil, err
end

if box.sequence[sequence_name] ~= nil then
local current_schema = ddl_get.get_sequence_schema(sequence_name)
local _, err = ddl_compare.assert_equiv_sequence_schema(sequence_schema, current_schema)
if err ~= nil then
return nil, string.format(
"Incompatible schema: sequences[%q] %s", sequence_name, err)
end
else
local ok, err = pcall(
ddl_set.create_sequence,
sequence_name, sequence_schema, {dummy = true}
)

local dummy = box.sequence['_ddl_dummy']
if dummy then
pcall(box.schema.sequence.drop, dummy.id)
end

if not ok then
return nil, tostring(err):gsub('_ddl_dummy', sequence_name)
end
end
end

for space_name, space_schema in pairs(schema.spaces) do
local ok, err = ddl_check.check_space(space_name, space_schema)
local ok, err = ddl_check.check_space(space_name, space_schema, sequences)
if not ok then
return nil, err
end

if box.space[space_name] ~= nil then
local diff = {}
local current_schema = ddl_get.get_space_schema(space_name)
Expand Down Expand Up @@ -127,6 +163,12 @@ local function _set_schema(schema)
}
)

for sequence_name, sequence_schema in pairs(schema.sequences or {}) do
if box.sequence[sequence_name] == nil then
ddl_set.create_sequence(sequence_name, sequence_schema)
end
end

for space_name, space_schema in pairs(schema.spaces) do
if box.space[space_name] == nil then
ddl_set.create_space(space_name, space_schema)
Expand All @@ -153,8 +195,22 @@ local function get_schema()
end
end

local sequences = {}
for _, sequence in box.space._sequence:pairs() do
sequences[sequence.name] = ddl_get.get_sequence_schema(sequence.name)
end

local next_k = next(sequences)
local no_sequences = next_k == nil

if no_sequences then
-- For backward compatibility.
sequences = nil
end

return {
spaces = spaces,
sequences = sequences,
}
end

Expand Down
124 changes: 118 additions & 6 deletions ddl/check.lua
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,9 @@ local function check_index_parts(index, space)
return true
end

local function check_index(i, index, space)
local function check_index(i, index, space, sequences)
sequences = sequences or {}

if type(index) ~= 'table' then
return nil, string.format(
"spaces[%q].indexes[%d]: bad value" ..
Expand Down Expand Up @@ -709,9 +711,30 @@ local function check_index(i, index, space)
end
end

local keys = {'type', 'name', 'unique', 'parts', 'field'}
do
local index_sequence_name = index.sequence

if index_sequence_name ~= nil then
if type(index_sequence_name) ~= 'string' then
return nil, string.format(
"spaces[%q].indexes[%q].sequence: incorrect value (string expected, got %s)",
space.name, index.name, type(index_sequence_name)
)
end

local sequence = sequences[index_sequence_name]
if sequence == nil then
return nil, string.format(
"spaces[%q].indexes[%q].sequence: missing sequence %q in sequences section",
space.name, index.name, index_sequence_name
)
end
end
end

local keys = {'type', 'name', 'unique', 'parts', 'field', 'sequence'}
if index.type == 'RTREE' then
keys = {'type', 'name', 'unique', 'parts', 'field', 'dimension', 'distance'}
keys = {'type', 'name', 'unique', 'parts', 'field', 'dimension', 'distance', 'sequence'}
end

local k = utils.redundant_key(index, keys)
Expand Down Expand Up @@ -961,7 +984,7 @@ local function check_sharding_metadata(space)
end


local function check_space(space_name, space)
local function check_space(space_name, space, sequences)
if type(space_name) ~= 'string' then
return nil, string.format(
"spaces[%s]: invalid space name (string expected, got %s)",
Expand Down Expand Up @@ -1074,7 +1097,7 @@ local function check_space(space_name, space)
name = space_name,
engine = space.engine,
fields = space_fields,
})
}, sequences)

if not ok then
return nil, err
Expand Down Expand Up @@ -1118,6 +1141,94 @@ local function check_space(space_name, space)
return true
end

local function check_sequence_nullable_option_type(sequence_name, sequence,
option_name, type_checker)
local option = sequence[option_name]
if option == nil then
return true
end

local _, err = type_checker(option)
if err ~= nil then
return nil, string.format(
"sequences[%q].%s: bad value (%s)",
sequence_name, option_name, err
)
end

return true
end

local function check_sequence_multiple_nullable_options_type(sequence_name, sequence,
option_names_array, type_checker)
for _, option_name in ipairs(option_names_array) do
local _, err = check_sequence_nullable_option_type(sequence_name, sequence,
option_name, type_checker)
if err ~= nil then
return err
end
end

return true
end

local function check_sequence(sequence_name, sequence)
if type(sequence_name) ~= 'string' then
return nil, string.format(
"sequences[%s]: invalid sequence name (string expected, got %s)",
sequence_name, type(sequence_name)
)
end

if type(sequence) ~= 'table' then
return nil, string.format(
"sequences[%q]: bad value (table expected, got %s)",
sequence_name, type(sequence)
)
end

local number_nullable_options = {'start', 'min', 'max', 'cache', 'step'}
local number_checker = function(v)
if not utils.is_number(v) then
local actual_type = type(v)
return nil, ('number expected, got %s'):format(actual_type)
else
return true
end
end
local _, err = check_sequence_multiple_nullable_options_type(sequence_name, sequence,
number_nullable_options, number_checker)
if err ~= nil then
return nil, err
end

local boolean_nullable_options = {'cycle'}
local boolean_checker = function(v)
if type(v) ~= 'boolean' then
local actual_type = type(v)
return nil, ('boolean expected, got %s'):format(actual_type)
else
return true
end
end
local _, err = check_sequence_multiple_nullable_options_type(sequence_name, sequence,
boolean_nullable_options, boolean_checker)
if err ~= nil then
return nil, err
end

local allowed_options = utils.concat_arrays(number_nullable_options, boolean_nullable_options)
local k = utils.redundant_key(sequence, allowed_options)
if k ~= nil then
return nil, string.format(
"sequences[%q]: redundant key %q",
sequence_name, k
)
end

return true
end


return {
check_space = check_space,
Expand All @@ -1128,7 +1239,8 @@ return {
check_index_parts = check_index_parts,
check_index = check_index,
check_field = check_field,
check_sequence = check_sequence,
internal = {
is_callable = is_callable,
}
},
}
Loading

0 comments on commit 5b951c5

Please sign in to comment.