Tarantool Spacer. Automatic schema migrations.
Tarantool Spacer. Automatic models migrations.
unique
type
sequence
dimension
and distance
(for RTREE indexes)parts
is_nullable
and collation
changestype
and name
changes are prohibited
The latest spacer
release is 3.0.1
.
Use luarocks
to install this package by one of the following rockspec from the rockspecs
folders:
rockspecs/spacer-scm-3.rockspec
- Installs current version 3 from master
branchrockspecs/spacer-3.0.1-1.rockspec
- Installs tagged version 3 from v3.0.1
tagThere is also a rockspecs/spacer-scm-1.rockspec
which installs an old v1 version of spacer (from branch v1
). This is left here for compatibility reasons for projects that still use spacer v1. Please do not use this version as it is not supported anymore.
Spacer depends on 2 libraries:
inspect
- available from rocks.moonscript.orgmoonwalker
- available from rocks.tarantool.orgSo you shold put the following to your ~/.luarocks/config.lua
file:
rocks_servers = {
[[http://rocks.tarantool.org]],
[[https://rocks.moonscript.org]],
}
And after that you can run
luarocks install https://raw.githubusercontent.com/igorcoding/tarantool-spacer/master/rockspecs/spacer-scm-3.rockspec
to install from master
or
luarocks install https://raw.githubusercontent.com/igorcoding/tarantool-spacer/master/rockspecs/spacer-3.0.1-1.rockspec
to install from the tag.
Initialized spacer somewhere in the beginning of your init.lua
:
require 'spacer'.new({
migrations = 'path/to/migrations/folder',
})
You can assign spacer to a some global variable for easy access:
box.spacer = require 'spacer'.new({
migrations = 'path/to/migrations/folder',
})
migrations
(required) - Path to migrations foldername
(default is ''
) - Spacer instance name. You can have multiple independent instancesglobal_ft
(default is true
) - Expose F
and T
variables to the global _G
table (see Fields and Transformations sections).keep_obsolete_spaces
(default is false
) - do not track space removalskeep_obsolete_indexes
(default is false
) - do not track indexes removalsdown_migration_fail_on_impossible
(default is true
) - Generate an assert(false)
statement in a down migration when detecting new fields in format, so user can perform proper actions on a down migration.You can easily define new spaces in a separate file (e.g. models.lua
) and all
you will need to do is to require
it from init.lua
right after spacer initialization:
box.spacer = require 'spacer'.new({
migrations = 'path/to/migrations/folder',
})
require 'models'
models.lua
example:Note that spacer now has methods
local spacer = require 'spacer'.get()
spacer:space({
name = 'object',
format = {
{ name = 'id', type = 'unsigned' },
{ name = 'name', type = 'string', is_nullable = true },
},
indexes = {
{ name = 'primary', type = 'tree', unique = true, parts = {'id'}, sequence = true },
{ name = 'name', type = 'tree', unique = false, parts = {'name', 'id'} },
}
})
spacer:space
has 4 parameters:
name
- space name (required)format
- space format (required)indexes
- space indexes array (required)opts
- any box.schema.create_space options (optional). Please refer to Tarantool documentation for details.Indexes parts must be defined using only field names.
You can autogenerate migration by just running the following snippet in Tarantool console:
box.spacer:makemigration('init_object')
There are 2 arguments to the makemigration
method:
autogenerate
(true
/false
) - Autogenerate migration (default is true
). If false
then empty migration file is generatedcheck_alter
(true
/false
) - Default is true
- so spacer will check spaces and indexes for changes and create alter migrations. If false
then spacer will assume that spaces never existed. Useful when you want to add spacer to already existing project.allow_empty
(true
, false
) - Default is true
. If false
no migration files will be created if there are no schema changes. If true
an empty migration file will be created instead.After executing this command a new migrations file will be generated under name <timestamp>_<migration_name>.lua
inside your migrations
folder:
return {
up = function()
box.schema.create_space("object", nil)
box.space.object:format({ {
name = "id",
type = "unsigned"
}, {
is_nullable = false,
name = "name",
type = "string"
} })
box.space.object:create_index("primary", {
parts = { { 1, "unsigned",
is_nullable = false
} },
sequence = true,
type = "tree",
unique = true
})
box.space.object:create_index("name", {
parts = { { 2, "string",
is_nullable = false
}, { 1, "unsigned",
is_nullable = false
} },
type = "tree",
unique = false
})
end,
down = function()
box.space.object:drop()
end,
}
Any migration file consists of 2 exported functions (up
and down
).
You are free to edit this migration any way you want.
You can apply not yet applied migrations by running:
box.spacer:migrate_up(n)
It accepts n
- number of migrations to apply (by default n
is infinity, i.e. apply till the end)
Current migration version number is stored in the _schema
space under _spacer_ver
key:
tarantool> box.space._schema:select{'_spacer_ver'}
---
- - ['_spacer_ver', 1516561029, 'init']
...
If you want to roll back migration you need to run:
box.spacer:migrate_down(n)
It accepts n
- number of migrations to rollback (by default n
is 1, i.e. roll back obly the latest migration).
To rollback all migration just pass any huge number.
box.spacer:list()
Returns list of migrations.
tarantool> box.spacer:list()
---
- - 1517144699_events.lua
- 1517228368_events_sequence.lua
...
tarantool> box.spacer:list(true)
---
- - version: '1517144699_events'
filename: 1517144699_events.lua
path: ./migrations/1517144699_events.lua
- version: '1517228368_events_sequence'
filename: 1517228368_events_sequence.lua
path: ./migrations/1517228368_events_sequence.lua
...
verbose
(default is false) - return verbose information about migrations. If false returns only a list of names.box.spacer:get(name)
Returns information about a migration in the following format:
tarantool> box.spacer:get('1517144699_events')
---
- version: 1517144699_events
path: ./migrations/1517144699_events.lua
migration:
up: 'function: 0x40de9090'
down: 'function: 0x40de90b0'
filename: 1517144699_events.lua
...
name
(optional) - Can be either a filename or migration version. If not specified - the latest migration is returnedcompile
(default is true) - Perform migration compilation. If false returns only the text of migration.tarantool> box.spacer:version()
---
- 1517144699_events
...
Returns current migration's version
You can force spacer to think that the specified migration is already migrated by setting the appropriate _schema
space key and registering in _spacer_models
space all models, registered by calling spacer:space(...)
function. Convinient when you are migrating an already working project to using spacer.
box.spacer:migrate_dummy(name)
Automatically applies migrations without creating an actual migration file. Useful when changing schema a lot in development. Highly discouraged to be used in production. Call this method after all spacer:space(...) calls like this:
spacer:space({
name = "object1",
format = {
{name = "id", type = "unsigned"}
},
indexes = {
{name = "primary", type = "tree", unique = true, parts = {"id"}}
}
})
spacer:space({
name = "object2",
format = {
{name = "id", type = "unsigned"}
},
indexes = {
{name = "primary", type = "tree", unique = true, parts = {"id"}}
}
})
spacer:automigrate()
But when you decide that you need to grenerate migrations - either drop your db and create migrations from scratch or have a loot at Migrating a project to using spacer.
name
(required) - Can be either a filename or version number or full migration name.In order to use spacer in an already running project you will need to do the following:
spacer:space()
function as usual.spacer:makemigration(<migration_name>, {check_alter = false})
spacer:migrate_dummy(<migration_name>)
After that you can make any changes to the spaces delcarations and track those changes my calling spacer:makemigration()
function normally and applying migrations with spacer:migrate_up()
as usual from now on.
Space fields can be accessed by the global variable F
, which is set by spacer
or in the spacer directly (box.spacer.F
):
box.space.space1:update(
{1},
{
{'=', F.object.name, 'John Watson'},
}
)
You can easily transform a given tuple to a dictionary-like object and vice-a-versa.
These are the functions:
T.space_name.dict
or T.space_name.hash
- transforms a tuple to a dictionaryT.space_name.tuple
- transforms a dictionary back to a tupleT can be accessed through the global variable T
or in the spacer directly (box.spacer.T
)
local john = box.space.object:get({1})
local john_dict = T.object.dict(john) -- or T.object.hash(john)
--[[
john_dict = {
id = 1,
name = 'John Watson',
...
}
--]]
... or vice-a-versa:
local john_dict = {
id = 1,
name = 'John Watson',
-- ...
}
local john = T.object.tuple(john_dict)
--[[
john = [1, 'John Watson', ...]
--]]