Packages
vtc
0.13.1
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.2
0.16.1
0.16.0
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.10
0.10.9
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
A SMPTE timecode library for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/postgres/pg_utils.ex
defmodule Vtc.Ecto.Postgres.Utils do
@moduledoc false
## Exposes a macro for defining modules that will only be compiled if the caller
## has set `:vtc, Postrgrex, :include?` to `true` in their application config.
@spec __using__(Keyword.t()) :: Macro.t()
defmacro __using__(_) do
quote do
import Vtc.Ecto.Postgres.Utils, only: [defpgmodule: 2, when_pg_enabled: 1]
require Vtc.Ecto.Postgres.Utils
end
end
@doc """
Wraps defmodule, conditionally declaring the module during compilation
based on caller configuration.
"""
@spec defpgmodule(module(), do: Macro.t()) :: Macro.t()
defmacro defpgmodule(name, do: body) do
if_pg_enabled(fn ->
quote do
defmodule unquote(name) do
unquote(body)
end
end
end)
end
@doc """
Only executes if the calling application has Postgres types enabled.
"""
@spec when_pg_enabled(do: Macro.t()) :: Macro.t()
defmacro when_pg_enabled(do: body) do
if_pg_enabled(fn ->
quote do
unquote(body)
end
end)
end
defp if_pg_enabled(action, otherwise \\ fn -> nil end) do
if get_config(:include?, false) do
:ok = enforce_dep(Ecto, :ecto)
:ok = enforce_dep(Postgrex, :postgrex)
action.()
else
otherwise.()
end
end
@doc """
Fetches a config for `:vtc, Postgrex`
"""
@spec get_config(atom(), result) :: result when result: any()
def get_config(opt, default), do: :vtc |> Application.get_env(Postgres, []) |> Keyword.get(opt, default)
@doc """
Affirms that module from dep is present, throwing otherwise.
"""
@spec enforce_dep(module(), atom()) :: :ok
def enforce_dep(module, name) do
if not Code.ensure_loaded?(module) do
throw(
":vtc, Postgres, `:include?` config is true, but `#{module}` module not found. Add `#{name}` to your dependencies"
)
end
:ok
end
@typedoc """
Alias of String.t() that hints raw SQL text.
"""
@type raw_sql() :: String.t()
@typedoc """
Options type for `plpgsql_add_function/2`
"""
@type create_func_opts() :: [
args: Keyword.t(atom()),
returns: atom(),
declares: Keyword.t(atom() | {atom(), raw_sql()}),
body: raw_sql()
]
@doc """
Builds a [plpgsql](https://www.postgresql.org/docs/current/plpgsql.html) function,
taking care of all the biolerplate.
## Args
- `name`: The name of the function, including schema namespace.
## Options
- `args`: The arguments the function takes and their types in a `arg: type` keyword
list.
- `returns`: The type the function returns.
- `declares`: A `name: type` keyword list of variables that should be declared in the
function's "DECLARES" block. Optionally can pass `name: {type, calculation}` to
declare a short calculation to set the variable.
- `body`: The function body.
"""
@spec create_plpgsql_function(String.t(), create_func_opts()) :: raw_sql()
def create_plpgsql_function(name, opts) do
args = Keyword.get(opts, :args, [])
returns = Keyword.fetch!(opts, :returns)
declares = Keyword.get(opts, :declares, nil)
body = Keyword.fetch!(opts, :body)
cost = Keyword.get(opts, :cost, 100)
args = Enum.map_join(args, ", ", fn {arg, type} -> "#{arg} #{type}" end)
declare =
if is_nil(declares) do
""
else
vars =
Enum.map_join(declares, fn
{var, {type, value}} -> "#{var} #{type} := #{value};\n"
{var, type} -> "#{var} #{type};\n"
end)
"""
DECLARE
#{vars}
"""
end
"""
DO $wrapper$ BEGIN
CREATE FUNCTION #{name}(#{args})
RETURNS #{returns}
LANGUAGE plpgsql
STRICT
IMMUTABLE
LEAKPROOF
PARALLEL SAFE
COST #{cost}
AS $func$
#{declare}
BEGIN
#{body}
END;
$func$;
EXCEPTION WHEN duplicate_function
THEN null;
END $wrapper$;
"""
end
@doc """
Builds an SQL query for creating a new native operator.
"""
@spec create_operator(atom(), atom(), atom(), String.t(), commutator: atom(), negator: atom()) :: raw_sql()
def create_operator(name, left_type, right_type, func_name, opts \\ []) do
commutator = Keyword.get(opts, :commutator)
negator = Keyword.get(opts, :negator)
commutator_sql = if is_nil(commutator), do: "", else: "COMMUTATOR = #{commutator},"
negator_sql = if is_nil(negator), do: "", else: "NEGATOR = #{negator},"
"""
DO $wrapper$ BEGIN
CREATE OPERATOR #{name} (
LEFTARG = #{left_type},
RIGHTARG = #{right_type},
#{commutator_sql}
#{negator_sql}
FUNCTION = #{func_name}
);
EXCEPTION WHEN duplicate_function
THEN null;
END $wrapper$;
"""
end
@doc """
Builds an SQL query for creating a new native CAST
"""
@spec create_operator_class(atom(), atom(), atom(), Keyword.t(pos_integer()), [{String.t(), pos_integer()}]) ::
raw_sql()
def create_operator_class(name, type, index_type, operators, functions) do
operators_sql_list =
Enum.map(operators, fn {operator, index} ->
"operator #{index} #{operator}"
end)
functions_sql_list =
Enum.map(functions, fn {function, index} ->
"function #{index} #{function}(#{type}, #{type})"
end)
sql_list = operators_sql_list |> Enum.concat(functions_sql_list) |> Enum.join(",")
"""
DO $wrapper$ BEGIN
CREATE OPERATOR CLASS #{name}
DEFAULT FOR TYPE #{type} USING #{index_type} AS
#{sql_list};
EXCEPTION WHEN duplicate_object
THEN null;
END $wrapper$;
"""
end
@doc """
Builds an SQL query for creating a new native CAST
"""
@spec create_cast(atom(), atom(), atom() | String.t()) :: raw_sql()
def create_cast(left_type, right_type, func_name) do
"""
DO $wrapper$ BEGIN
CREATE CAST (#{left_type} AS #{right_type}) WITH FUNCTION #{func_name}(#{left_type});
EXCEPTION WHEN duplicate_object
THEN null;
END $wrapper$;
"""
end
@doc """
Returns a configuration option for a specific vtc Postgres type and Repo.
"""
@spec get_type_config(Ecto.Repo.t(), atom(), atom(), Keyword.value()) :: Keyword.value()
def get_type_config(repo, type_name, opt, default),
do: repo.config() |> Keyword.get(:vtc, []) |> Keyword.get(type_name) |> Keyword.get(opt, default)
@doc """
Returns a the public function prefix for a specific vtc Postgres type and Repo.
"""
@spec type_function_prefix(Ecto.Repo.t(), atom()) :: String.t()
def type_function_prefix(repo, type_name), do: calculate_prefix(repo, type_name, :functions_schema)
@spec type_private_function_prefix(Ecto.Repo.t(), atom()) :: String.t()
def type_private_function_prefix(repo, type_name), do: calculate_prefix(repo, type_name, :functions_private_schema)
# Calculate a function prefix for a specific schema and vtc postgres type based on the
# Repo configuration.
@spec calculate_prefix(Ecto.Repo.t(), atom(), atom()) :: String.t()
defp calculate_prefix(repo, type_name, schema_config_opt) do
functions_schema = get_type_config(repo, type_name, schema_config_opt, :public)
custom_prefix = get_type_config(repo, type_name, :functions_prefix, "")
functions_prefix =
cond do
functions_schema == :public and custom_prefix == "" -> "rational_"
custom_prefix != "" -> "#{custom_prefix}_"
true -> ""
end
"#{functions_schema}.#{functions_prefix}"
end
end