Packages

Composable data schema definition, validation, and syntax manipulation for Elixir

Current section

Files

Jump to
raggio lib raggio bigquery migrator generator.ex
Raw

lib/raggio/bigquery/migrator/generator.ex

defmodule Raggio.BigQuery.Migrator.Generator do
@moduledoc """
Generates migration files from schema changes.
"""
alias Raggio.BigQuery.DDL
alias Raggio.BigQuery.Differ.Change
alias Raggio.BigQuery.Migration
@spec generate_version :: String.t()
def generate_version do
generate_version(DateTime.utc_now())
end
@spec generate_version(DateTime.t()) :: String.t()
def generate_version(%DateTime{} = dt) do
Calendar.strftime(dt, "%Y%m%d%H%M%S")
end
@spec normalize_name(String.t()) :: String.t()
def normalize_name(name) do
name
|> String.downcase()
|> String.replace(~r/[^a-z0-9_]/, "_")
|> String.replace(~r/_+/, "_")
|> String.trim("_")
end
@spec create_migration(String.t(), keyword()) :: Migration.t()
def create_migration(name, opts \\ []) do
version = Keyword.get(opts, :version) || generate_version()
normalized_name = normalize_name(name)
Migration.new(version, normalized_name, opts)
end
@spec generate_sql([Change.t()], keyword()) ::
{:ok, String.t(), String.t()} | {:error, term()}
def generate_sql(changes, opts \\ []) do
sorted_changes = Change.sort(changes)
with {:ok, up_statements} <- DDL.generate_up(sorted_changes, opts),
{:ok, down_statements} <- DDL.generate_down(sorted_changes, opts) do
up_sql = format_sql(up_statements, :up)
down_sql = format_sql(down_statements, :down)
{:ok, up_sql, down_sql}
end
end
defp format_sql(statements, direction) do
header = sql_header(direction)
body = DDL.join_statements(statements)
"#{header}\n\n#{body}"
end
defp sql_header(direction) do
timestamp = DateTime.utc_now() |> DateTime.to_iso8601()
dir_label = if direction == :up, do: "UP", else: "DOWN"
"""
-- Generated by Raggio.BigQuery
-- Direction: #{dir_label}
-- Timestamp: #{timestamp}
--\
"""
end
@spec write_files(Migration.t(), {String.t(), String.t()}, String.t()) ::
{:ok, map()} | {:error, term()}
def write_files(migration, {up_sql, down_sql}, base_path) do
migration_dir = Migration.migration_dir(migration, base_path)
with :ok <- File.mkdir_p(migration_dir) do
up_path = Path.join(migration_dir, "up.sql")
down_path = Path.join(migration_dir, "down.sql")
with :ok <- File.write(up_path, up_sql),
:ok <- File.write(down_path, down_sql) do
{:ok, %{up: up_path, down: down_path, dir: migration_dir}}
end
end
end
@spec generate(String.t(), [Change.t()], String.t(), keyword()) ::
{:ok, Migration.t(), map()} | {:error, term()}
def generate(name, changes, base_path, opts \\ [])
def generate(_name, [], _base_path, _opts), do: {:error, :no_changes}
def generate(name, changes, base_path, opts) do
migration = create_migration(name, opts)
with {:ok, up_sql, down_sql} <- generate_sql(changes, opts),
{:ok, paths} <- write_files(migration, {up_sql, down_sql}, base_path) do
{:ok, migration, paths}
end
end
end