View Source ArangoXEcto.Migrator (ArangoX Ecto v2.0.0)
Lower level API for managing migrations.
This module provides functions to manage migrations. The tasks mentioned below are provided to cover the general use cases but it may be necessary to use the lower level API in some cases. For example, when building an Elixir release, Mix is not available, so this module provides the functions needed to migrate the system.
Large parts of this module is based on the ecto_sql
Migrator
module, so a lot of credit to them.
Mix tasks
The following mix tasks are available. They are similar to ecto_sql
tasks but are appended with .arango
to allow for both ecto_sql
and arangox_ecto
to be used side by side.
mix arango.migrate
- migrates an arango repositorymix arango.rollback
- rolls back a particular migrationmix arango.gen.migration
- generates a migration file
Example: Running an individual migration
Imagine you have this migration:
defmodule MyApp.MigrationExample do
use ArangoXEcto.Migration
def up do
create collection("my_collection")
end
def down do
drop collection("my_collection")
end
end
You can execute it manually with:
ArangoXEcto.Migrator.up(Repo, 20240101120000, MyApp.MigrationExample)
Example: Running migrations in a release
Elixir v1.9 introduces mix release
, which generates a self-contained
directory that consists of your application code, all of its dependencies,
plus the whole Erlang Virtual Machine (VM) and runtime.
When a release is assembled, Mix is no longer available inside a release
and therefore none of the Mix tasks. Users may still need a mechanism to
migrate their databases. This can be achieved with using this ArangoXEcto.Migrator
module:
defmodule MyApp.Release do
@app :my_app
def migrate do
for repo <- repos() do
{:ok, _, _} = ArangoXEcto.Migrator.with_repo(repo, &ArangoXEcto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
{:ok, _, _} = ArangoXEcto.Migrator.with_repo(repo, &ArangoXEcto.Migrator.run(&1, :down, to: version))
end
defp repos do
Application.load(@app)
Application.fetch_env!(@app, :ecto_repos)
end
end
The example above uses with_repo/3
to make sure the repository is
started and then runs all migrations up or a given migration down.
Note you will have to replace MyApp
and :my_app
on the first two
lines by your actual application name. Once the file above is added
to your application, you can assemble a new release and invoke the
commands above in the release root like this:
$ bin/my_app eval "MyApp.Release.migrate"
$ bin/my_app eval "MyApp.Release.rollback(MyApp.Repo, 20231225190000)"
Example: Running migrations on application startup
Add the following to the top of your application child spec:
{ArangoXEcto.Migrator,
repos: Application.fetch_env!(:my_app, :ecto_repos),
skip: System.get_env("SKIP_MIGRATIONS") == "true")}
This will allow you to use the environment variable SKIP_MIGRATIONS
to skip running the
migrations on startup.
Additionally, the other options specified in up/4
can be passed. For example, if you wanted to
log the migrations and run them in a specific prefix you could do the following:
{ArangoXEcto.Migrator,
repos: Application.fetch_env!(:my_app, :ecto_repos),
log_migrator: true,
prefix: "super_cool_user"}
Rolling back would be done normally using the mix arango.rollback
mix task.
Summary
Functions
Returns a specification to start this module under a supervisor.
Runs a down migration on a given repo.
Executes a command on a repo
Gets all migrated versions.
Returns an array of the statuses of migrations for the given repo
Returns an array of the statuses of migrations for the given repo
Gets the migrations path for a repository.
Runs migrations for the given repository.
Apply migrations to a repository with a given strategy.
Runs migrations as part of your supervision tree.
Runs an up migration on a given repo.
Ensures the repo is started to perform migration operations.
Types
@type command() :: :create | :create_if_not_exists | :drop | :drop_if_exists
@type log() :: {Logger.level(), Logger.message(), Logger.metadata()}
@type subcommand() :: :add | :add_enum | :rename
Functions
Returns a specification to start this module under a supervisor.
See Supervisor
.
@spec down(Ecto.Repo.t(), integer(), module(), Keyword.t()) :: :ok | :already_down
Runs a down migration on a given repo.
Options
:log
- the level to use for logging of migration instructions. Defaults to:info
. Can be any ofLogger.level/0
values or a boolean. Iffalse
, it also avoids logging messages from the database.:log_migrations
- the level to use for logging of ArangoDB commands generated by migrations. Can be any of theLogger.level/0
values or a boolean. Iffalse
, logging is disabled. Iftrue
, uses the configured Repo logger level. Defaults tofalse
:log_migrator
- the level to use for logging of ArangoDB commands emitted by the migrator, such as transactions, locks, etc. Can be any of theLogger.level/0
values or a boolean. Iffalse
, logging is disabled. Iftrue
, uses the configured Repo logger level. Defaults tofalse
:prefix
- the prefix to run the migrations on
@spec execute_command( Ecto.Adapter.adapter_meta(), {command(), ArangoXEcto.Migration.Collection.t() | ArangoXEcto.Migration.Index.t() | ArangoXEcto.Migration.View.t() | ArangoXEcto.Migration.Analyzer.t(), [ArangoXEcto.Migration.JsonSchema.command()]}, Keyword.t() ) :: log()
Executes a command on a repo
The commands are passed from the migration module. A command essentially is the action of a migration, e.g. create, alter, rename, delete an object.
opts
is passed to the database query when the command is run. Available options are dependant on
the command being executed.
Available Commands
All commands are either a tuple or a string with AQL which is executed. The first element of the tuple is the action to be performed, the second is the object of the action. For some commands (such as creation of a collection) there is a third element of the tuple.
Creates (:create and :create_if_not_exists)
2nd element (Object) | Third element |
---|---|
Collection | Field commands passed to JsonSchema |
Index | |
View | View definition commands |
Analyzer |
Alter (:alter)
2nd element (Object) | Third element |
---|---|
Existing Collection | Field commands passed to JsonSchema (merged with existing schema) |
Existing View | View definition commands |
Drops (:drop and :drop_if_exists)
2nd element (Object) | Third element |
---|---|
Existing Collection | |
Existing Index | |
Existing View | |
Existing Analyzer |
Rename (:rename)
2nd element (Object) | Third element |
---|---|
Existing Collection | Collection with new name |
Existing View | View with new name |
@spec migrated_versions(repo :: Ecto.Repo.t(), opts :: Keyword.t()) :: [integer()]
Gets all migrated versions.
Ensures the migrations collection exists if it doesn't exist yet.
Options
:prefix
- the prefix to run the migrations on:dynamic_repo
- the name of the Repo supervisor process. SeeEcto.Repo.put_dynamic_repo/1
.:skip_collection_creation
- skips any attempt to create the migration collection Useful for situations where user needs to check migrations but has insufficient permissions to create the collection. Note that migrations commands may fail if this is set to true. Defaults tofalse
. Accepts a boolean.
@spec migrations(Ecto.Repo.t()) :: [ {:up | :down, id :: integer(), name :: String.t()} ]
Returns an array of the statuses of migrations for the given repo
Doesn't actually run the migrations.
This is a shortcut to
ArangoXEcto.Migrator.migrations(repo, [ArangoXEcto.Migrator.migrations_path(repo)])
@spec migrations(Ecto.Repo.t(), String.t() | [String.t()], Keyword.t()) :: [ {:up | :down, id :: integer(), name :: String.t()} ]
Returns an array of the statuses of migrations for the given repo
Doesn't actually run the migrations.
@spec migrations_path(repo :: Ecto.Repo.t(), directory :: String.t()) :: String.t()
Gets the migrations path for a repository.
Accepts a second option allowing to specify the name of / path to the directory for the migrations.
@spec run(Ecto.Repo.t(), atom(), Keyword.t()) :: [integer()]
Runs migrations for the given repository.
Equivalent to:
ArangoXEcto.Migrator.run(repo, [ArangoXEcto.Migrator.migrations_path(repo)], direction, opts)
See run/4
for more information.
@spec run( Ecto.Repo.t(), String.t() | [String.t()] | [{integer(), module()}], atom(), Keyword.t() ) :: [ integer() ]
Apply migrations to a repository with a given strategy.
The second argument identifies where the migrations are sourced from.
A binary representing directory (or a list of binaries representing
directories) may be passed, in which case we will load all files
following the "#{VERSION}_#{NAME}.exs" schema. The migration_source
may also be a list of tuples that identify the version number and
migration modules to be run, for example:
ArangoXEcto.Migrator.run(Repo, [{0, MyApp.Migration1}, {1, MyApp.Migration2}, ...], :up, opts)
A strategy (which is one of :all
, :step
, :to
, or :to_exclusive
) must be given as
an option.
Execution model
In order to run migrations, at least two database connections are necessary. One is used to lock the "_migrations" collection and the other one to effectively run the migrations. This allows multiple nodes to run migrations at the same time, but guarantee that only one of them will effectively migrate the database.
Options
:all
- runs all available iftrue
:step
- runs the specific number of migrations:to
- runs all until the supplied version is reached (including the version given in:to
):to_exclusive
- runs all until the supplied version is reached (excluding the version given in:to_exclusive
)
Plus all other options described in up/4
.
@spec start_link(Keyword.t()) :: GenServer.on_start()
Runs migrations as part of your supervision tree.
Options
:repos
- Required option to tell the migrator which Repo's to migrate. Example:repos: [MyApp.Repo]
:skip
- Option to skip migrations. Defaults tofalse
.
Plus all other options described in up/4
.
See "Example: Running migrations on application startup" for more info.
@spec up(Ecto.Repo.t(), integer(), module(), Keyword.t()) :: :ok | :already_up
Runs an up migration on a given repo.
Options
:log
- the level to use for logging of migration instructions. Defaults to:info
. Can be any ofLogger.level/0
values or a boolean. Iffalse
, it also avoids logging messages from the database.:log_migrations
- the level to use for logging of ArangoDB commands generated by migrations. Can be any of theLogger.level/0
values or a boolean. Iffalse
, logging is disabled. Iftrue
, uses the configured Repo logger level. Defaults tofalse
:log_migrator
- the level to use for logging of ArangoDB commands emitted by the migrator, such as transactions, locks, etc. Can be any of theLogger.level/0
values or a boolean. Iffalse
, logging is disabled. Iftrue
, uses the configured Repo logger level. Defaults tofalse
:prefix
- the prefix to run the migrations on:dynamic_repo
- the name of the Repo supervisor process. SeeEcto.Repo.put_dynamic_repo/1
.:strict_version_order
- abort when applying a migration with old timestamp (otherwise it emits a warning)
@spec with_repo(repo :: Ecto.Repo.t(), fun :: function(), opts :: Keyword.t()) :: {:ok, any(), list()} | {:error, any()}
Ensures the repo is started to perform migration operations.
All applications required to run the repo will be started before
with the chosen mode. If the repo has not yet been started,
it is manually started, with a :pool_size
of 2 (or otherwise passed),
before the given function is executed, and the repo is then terminated.
If the repo was already started, then the function is directly executed,
without terminating the repo afterwards.
Although this function was designed to start repositories for running migrations, it can be used by any code, Mix task, or release tooling that needs to briefly start a repository to perform a certain operation and then terminate.
The repo may also configure a :start_apps_before_migration
option
which is a list of applications to be started before the migration
runs.
It returns {:ok, fun_return, apps}
, with all apps that have been
started, or {:error, term}
.
Options
:pool_size
- The pool size to start the repo for migrations. Defaults to 2.:mode
- The mode to start all applications. Defaults to:permanent
.
Examples
{:ok, _, _} =
ArangoXEcto.Migrator.with_repo(repo, fn repo ->
ArangoXEcto.Migrator.run(repo, :up, all: true)
end)