lib/cldr/ddl.ex

defmodule Cldr.Unit.DDL do
  @moduledoc """
  Functions to return SQL DDL commands that support the
  creation and deletion of the `cldr_unit` database
  type and associated aggregate functions.
  """

  # @doc since: "2.7.0"

  @default_db :postgres

  @supported_db_types :code.priv_dir(:ex_cldr_units_sql)
                      |> Path.join("SQL")
                      |> File.ls!()
                      |> Enum.map(&String.to_atom/1)

  @doc """
  Returns the SQL string which when executed will
  define the `money_with_currency` data type.

  ## Arguments

  * `db_type`: the type of the database for which the SQL
    string should be returned.  Defaults to `:postgres` which
    is currently the only supported database type.

  """
  def create_cldr_unit(db_type \\ @default_db) do
    read_sql_file(db_type, "create_cldr_unit.sql")
  end

  @doc """
  Returns the SQL string which when executed will
  drop the `money_with_currency` data type.

  ## Arguments

  * `db_type`: the type of the database for which the SQL
    string should be returned.  Defaults to `:postgres` which
    is currently the only supported database type.

  """
  def drop_cldr_unit(db_type \\ @default_db) do
    read_sql_file(db_type, "drop_cldr_unit.sql")
  end

  @doc """
  Returns the SQL string which when executed will
  define aggregate functions for the `money_with_currency`
  data type.

  ## Arguments

  * `db_type`: the type of the database for which the SQL
    string should be returned.  Defaults to `:postgres` which
    is currently the only supported database type.

  """
  def define_aggregate_functions(db_type \\ @default_db) do
    read_sql_file(db_type, "define_aggregate_functions.sql")
  end

  @doc """
  Returns the SQL string which when executed will
  drop the aggregate functions for the `money_with_currency`
  data type.

  ## Arguments

  * `db_type`: the type of the database for which the SQL
    string should be returned.  Defaults to `:postgres` which
    is currently the only supported database type.

  """
  def drop_aggregate_functions(db_type \\ @default_db) do
    read_sql_file(db_type, "drop_aggregate_functions.sql")
  end

  @doc """
  Returns a string that will Ecto `execute` each SQL
  command.

  ## Arguments

  * `sql` is a string of SQL commands that are
    separated by three newlines ("\\n"),
    that is to say two blank lines between commands
    in the file.

  ## Example

      iex> Money.DDL.execute "SELECT name FROM customers;\n\n\nSELECT id FROM orders;"
      "execute \"\"\"\nSELECT name FROM customers;\n\n\nSELECT id FROM orders;\n\"\"\""

  """
  def execute_each(sql) do
    sql
    |> String.split("\n\n\n")
    |> Enum.map(&execute/1)
    |> Enum.join("\n")
  end

  @doc """
  Returns a string that will Ecto `execute` a single SQL
  command.

  ## Arguments

  * `sql` is a single SQL command

  ## Example

      iex> Money.DDL.execute "SELECT name FROM customers;"
      "execute \"SELECT name FROM customers;\""

  """
  def execute(sql) do
    sql = String.trim_trailing(sql, "\n")

    if String.contains?(sql, "\n") do
      "execute \"\"\"\n" <> sql <> "\n\"\"\""
    else
      "execute " <> inspect(sql)
    end
  end

  defp read_sql_file(db_type, file_name) when db_type in @supported_db_types do
    base_dir(db_type)
    |> Path.join(file_name)
    |> File.read!()
  end

  defp read_sql_file(db_type, file_name) do
    raise ArgumentError,
          "Database type #{db_type} does not have a SQL definition " <> "file #{inspect(file_name)}"
  end

  @app Mix.Project.config[:app]
  defp base_dir(db_type) do
    :code.priv_dir(@app)
    |> Path.join(["SQL", "/#{db_type}"])
  end
end