Trellis statements in an Ecto migration, so a transform is defined, and dropped, the way the tables it reads are created.
defmodule MyApp.Repo.Migrations.DefineWidgetPrices do
use Ecto.Migration
use Trellis.Migration
# Required: see "Transactions" below.
@disable_ddl_transaction true
def up do
create table(:widgets) do
add :price, :integer
end
define "TRANSFORM widget_prices FROM widgets SELECT price AS price"
end
def down do
apply "PAUSE TRANSFORM widget_prices"
apply "DROP TRANSFORM widget_prices"
drop table(:widgets)
end
enduse Trellis.Migration imports three helpers, each a thin call through
to the function of the same name on Trellis:
define/1: aTRANSFORMstatement, and nothing else (Trellis.define/2).apply/1: any statement of the grammar (Trellis.apply/2), such as thePAUSE TRANSFORMandDROP TRANSFORMadown/0undoes a define with. (DROPrefuses a transform that isn't paused, and drops its target table.)status/1: the status of the transform writing a target, ornil(Trellis.status/2).
define/1 and apply/1 are queued like Ecto's own commands, so they run
in order with them: a transform can read a table created earlier in the
same migration. status/1 runs when it's called, since a migration reads
it to decide what to queue. Each raises the Trellis.Error if the call
fails, which fails the migration.
Transactions
Trellis doesn't join the migration's transaction. It runs every call on connections of its own, and each call commits before it returns. If the migration's transaction rolled back after a define, the host's changes would be undone and the definition and its target table would not.
So a migration that uses these helpers has to turn its transaction off
with @disable_ddl_transaction true. define/1 and apply/1 check, as
they run, that the migration's repo has no transaction open, and raise
an Ecto.MigrationError before applying anything if it does. In a
migration that forgot the attribute, the migration's transaction then
rolls back with nothing applied on either side.
Without the transaction, a migration that fails partway leaves whatever ran before the failure in place, on both sides. Give a migration that defines transforms little else to do.
up/0 and down/0, not change/0
Ecto can reverse create table on its own, but not a Trellis statement:
the reverse of a define is a pause and a drop of a target that only the
statement names. So a module that uses Trellis.Migration must define
up/0 and down/0, and defining change/0 is a compile error, rather
than a rollback that fails partway.
Which Trellis
Ecto runs migrations without starting the application, so the
application's supervised Trellis isn't running. Each helper connects a
handle of its own instead, from the :trellis key of the repo's
configuration, runs Trellis.migrate/1 on it (creating Trellis's own
tables the first time), makes its one call, and shuts the handle down:
# config/runtime.exs
config :my_app, MyApp.Repo,
url: database_url,
trellis: [url: database_url]The options are Trellis.connect/1's. A migration's handle never runs
background work: :staging and :drain_threads are always the defaults,
whatever the configuration says.
Running a migration once
A define isn't idempotent: a second define of a target that exists fails
with a :conflict error. Ecto's schema_migrations already records which
migrations have run, so a migration's define runs once per database, and
these helpers add no guard of their own.
A migration that has to tolerate a transform defined some other way (by hand, or by a release task) can guard the define itself:
def up do
if status("widget_prices") == nil do
define "TRANSFORM widget_prices FROM widgets SELECT price AS price"
end
endThe guard checks the target's name, not its definition: a changed
statement for a target that exists is an ALTER TRANSFORM.
Summary
Functions
Queues any statement of Trellis's grammar, to run in order with the
migration's other commands. It is Trellis.apply/2.
Queues a TRANSFORM statement, to run in order with the migration's
other commands. It is Trellis.define/2: any other statement form is
refused before anything is applied.
The status of the transform that writes target_table, or nil if none
does. It is Trellis.status!/2, run as it's called rather than queued.
Functions
@spec apply(String.t()) :: :ok
Queues any statement of Trellis's grammar, to run in order with the
migration's other commands. It is Trellis.apply/2.
@spec define(String.t()) :: :ok
Queues a TRANSFORM statement, to run in order with the migration's
other commands. It is Trellis.define/2: any other statement form is
refused before anything is applied.
@spec status(String.t()) :: Trellis.Status.t() | nil
The status of the transform that writes target_table, or nil if none
does. It is Trellis.status!/2, run as it's called rather than queued.