tarantool-spacer
tarantool-spacer copied to clipboard
Tarantool Spacer. Automatic schema migrations.
spacer
Tarantool Spacer. Automatic models migrations.
Changes detected by spacer
- Space create and drop
- Index create and drop
- Index options alteration
uniquetypesequencedimensionanddistance(for RTREE indexes)parts
- Format alterations
- New fields (only to the end of format list). Setting values for existing tuples is handled by the moonwalker library
- Field's
is_nullableandcollationchanges - [IMPORTANT]
typeandnamechanges are prohibited
Installation
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 frommasterbranchrockspecs/spacer-3.0.1-1.rockspec- Installs tagged version 3 fromv3.0.1tag
There 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.org
So 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.
Usage
Initialize
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',
})
Spacer options
migrations(required) - Path to migrations foldername(default is'') - Spacer instance name. You can have multiple independent instancesglobal_ft(default istrue) - ExposeFandTvariables to the global_Gtable (see Fields and Transformations sections).keep_obsolete_spaces(default isfalse) - do not track space removalskeep_obsolete_indexes(default isfalse) - do not track indexes removalsdown_migration_fail_on_impossible(default istrue) - Generate anassert(false)statement in a down migration when detecting new fields in format, so user can perform proper actions on a down migration.
Define spaces
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.
Creating migration
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:
- Migration name (required)
- Options
autogenerate(true/false) - Autogenerate migration (default istrue). Iffalsethen empty migration file is generatedcheck_alter(true/false) - Default istrue- so spacer will check spaces and indexes for changes and create alter migrations. Iffalsethen spacer will assume that spaces never existed. Useful when you want to add spacer to already existing project.allow_empty(true,false) - Default istrue. Iffalseno migration files will be created if there are no schema changes. Iftruean 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.
Applying migrations
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']
...
Rolling back migrations
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.
List migrations
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
...
Options
verbose(default is false) - return verbose information about migrations. If false returns only a list of names.
Get migration
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
...
Options
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.
Get current migration version
tarantool> box.spacer:version()
---
- 1517144699_events
...
Returns current migration's version
migrate_dummy
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)
automigrate
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.
Options
name(required) - Can be either a filename or version number or full migration name.
Migrating a project to using spacer
In order to use spacer in an already running project you will need to do the following:
- Initialize spacer.
- Define all your spaces you want to track by spacer by using
spacer:space()function as usual. - Create a non-altering migration (meaning that it will assume that spaces does not exist yet) by calling
spacer:makemigration(<migration_name>, {check_alter = false})
- Force-apply migration without actually calling it by using
spacer:migrate_dummy(<migration_name>)
- That's all
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.
Fields
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'},
}
)
Transformations
You can easily transform a given tuple to a dictionary-like object and vice-a-versa.
These are the functions:
T.space_name.dictorT.space_name.hash- transforms a tuple to a dictionaryT.space_name.tuple- transforms a dictionary back to a tuple
T 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', ...]
--]]