Hex.pm Hexdocs License

Tracked, ordered, one-way data transformations for Ecto applications. Mirrors the Ecto migration system — same conventions, same workflow — but for data, not schema.

Installation

Add pollard to your list of dependencies in mix.exs:

def deps do
  [
    {:pollard, "~> 0.1.0"}
  ]
end

Then generate the tracking table migration:

mix pollard.gen.migration
mix ecto.migrate

Usage

Generate a new transform:

mix pollard.gen backfill_shop_details

This creates a timestamped file in priv/repo/transforms/:

defmodule MyApp.Transforms.BackfillShopDetails do
  use Pollard

  transform "Backfill missing shop details" do
    from(s in "shops", where: is_nil(s.details))
    |> MyApp.Repo.update_all(set: [details: %{}])
  end
end

Each transform block runs in its own database transaction. Use multiple blocks to break large operations into smaller transactions:

defmodule MyApp.Transforms.SeedResourceTypes do
  use Pollard

  transform "Seed productvariant type" do
    MyApp.Repo.insert_all("resource_types", [
      %{name: "productvariant", inserted_at: DateTime.utc_now()}
    ], on_conflict: :nothing)
  end

  transform "Seed metafield type" do
    MyApp.Repo.insert_all("resource_types", [
      %{name: "metafield", inserted_at: DateTime.utc_now()}
    ], on_conflict: :nothing)
  end
end

Run pending transforms:

mix pollard.run

Idempotency

Transforms must be safe to re-run. If a file has three transform blocks and the second fails, blocks one and two will both execute on retry. Use ON CONFLICT DO NOTHING, WHERE clauses, upserts, and state checks.

Locking

The runner acquires a lock before executing to prevent concurrent runs across nodes. The lock strategy is configurable:

# Postgres advisory locks (default)
Pollard.Runner.run(MyApp.Repo, path)

# No locking (single-node / SQLite)
Pollard.Runner.run(MyApp.Repo, path, lock: Pollard.Lock.None)

# Custom lock implementation
Pollard.Runner.run(MyApp.Repo, path, lock: MyApp.Lock.Redis)

Release Support

defmodule MyApp.Release do
  def transform do
    Pollard.Runner.run(MyApp.Repo, transforms_path())
  end

  defp transforms_path do
    Application.app_dir(:my_app, "priv/repo/transforms")
  end
end

Deploy order:

bin/migrate      # schema migrations (Ecto)
bin/transform    # data transforms (Pollard)
bin/server       # start the application

Documentation

Documentation is available at HexDocs.