Packages
vtc
0.10.10
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(atom(), 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
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(), atom(), 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()), Keyword.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()) :: 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
end