Generates the SQL that installs a tagged decimal type in PostgreSQL.
A tagged decimal — described by a Localize.Ecto.TaggedDecimal.t/0
specification — is stored as a composite type pairing a tag with a
decimal, such as money_with_currency(currency_code, amount) or
cldr_unit(unit, value). This module returns the SQL creating that
type together with the aggregate functions and operators that make it
usable in queries.
Aggregates over a tagged decimal are tag-guarded: summing a column
whose rows carry different tags raises rather than returning a
meaningless number. The generated functions compare the tag of each
incoming row against the tag accumulated so far and raise SQLSTATE
22033 (invalid_parameter_value) on a mismatch, so a query that adds
euros to yen fails in the database rather than silently succeeding.
The SQL is generated from the specification rather than kept as static files so that one implementation serves every domain. The names of the composite type, its fields and the generated functions come from the specification, because a deployed database already contains them and an aggregate must name the same fields as the type it was created against.
Statements
Each function returns one or more SQL statements in a single string,
separated by two blank lines. execute/1,2 and execute_each/1,2
wrap them for use in an Ecto migration — execute_each/1,2 emits one
execute per statement, which is what a migration needs when the
statements must run in order.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.create_type(spec)
"CREATE TYPE public.cldr_unit AS (unit varchar, value numeric);"
Summary
Functions
Returns the SQL creating the composite type.
Returns the SQL defining a tag-guarded avg aggregate.
Returns the SQL defining tag-guarded min and max aggregates.
Returns the SQL defining a tag-guarded operator over the type.
Returns the SQL defining a tag-guarded sum aggregate.
Returns the SQL dropping the avg aggregate, its functions and its
intermediate state type.
Returns the SQL dropping the min and max aggregates and their
functions.
Returns the SQL dropping an operator over the type and its function.
Returns the SQL dropping the sum aggregate and its functions.
Returns the SQL dropping the composite type.
Wraps SQL as a single execute call for an Ecto migration.
Wraps each statement of sql as its own execute call.
Functions
@spec create_type(Localize.Ecto.TaggedDecimal.t()) :: String.t()
Returns the SQL creating the composite type.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.
Returns
- The SQL statement as a string.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "money_with_currency", tag_field: "currency_code",
...> value_field: "amount", function_prefix: "money",
...> tag_description: "currency code"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.create_type(spec)
"CREATE TYPE public.money_with_currency AS (currency_code varchar, amount numeric);"
@spec define_avg_function(Localize.Ecto.TaggedDecimal.t()) :: String.t()
Returns the SQL defining a tag-guarded avg aggregate.
The aggregate accumulates into an intermediate composite type holding
a running sum and count, which the final function divides. That type is
created by this SQL and removed by drop_avg_function/1.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.
Returns
- The SQL statements as a string, separated by two blank lines.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.define_avg_function(spec) =~ "CREATE TYPE public.unit_avg_state"
true
@spec define_minmax_functions(Localize.Ecto.TaggedDecimal.t()) :: String.t()
Returns the SQL defining tag-guarded min and max aggregates.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.
Returns
- The SQL statements as a string, separated by two blank lines.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.define_minmax_functions(spec) =~ "AGGREGATE min(cldr_unit)"
true
@spec define_operator(Localize.Ecto.TaggedDecimal.t(), :add | :subtract | :negate) :: String.t()
Returns the SQL defining a tag-guarded operator over the type.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.operatoris:add,:subtractor:negate.:negatedefines a unary-; the others define the binary+and-.
Returns
- The SQL statements as a string, separated by two blank lines.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.define_operator(spec, :add) =~ "CREATE OPERATOR + ("
true
@spec define_sum_function(Localize.Ecto.TaggedDecimal.t()) :: String.t()
Returns the SQL defining a tag-guarded sum aggregate.
Three statements: the state function, the combine function that makes the aggregate safe under parallel query, and the aggregate itself.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.
Returns
- The SQL statements as a string, separated by two blank lines.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.define_sum_function(spec) =~ "CREATE OR REPLACE AGGREGATE sum(cldr_unit)"
true
@spec drop_avg_function(Localize.Ecto.TaggedDecimal.t()) :: String.t()
Returns the SQL dropping the avg aggregate, its functions and its
intermediate state type.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.
Returns
- The SQL statements as a string, separated by two blank lines.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.drop_avg_function(spec) =~ "DROP TYPE IF EXISTS public.unit_avg_state;"
true
@spec drop_minmax_functions(Localize.Ecto.TaggedDecimal.t()) :: String.t()
Returns the SQL dropping the min and max aggregates and their
functions.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.
Returns
- The SQL statements as a string, separated by two blank lines.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.drop_minmax_functions(spec) =~ "DROP AGGREGATE IF EXISTS max(cldr_unit);"
true
@spec drop_operator(Localize.Ecto.TaggedDecimal.t(), :add | :subtract | :negate) :: String.t()
Returns the SQL dropping an operator over the type and its function.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.operatoris:add,:subtractor:negate.
Returns
- The SQL statements as a string, separated by two blank lines.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.drop_operator(spec, :negate)
"DROP OPERATOR IF EXISTS - (NONE, cldr_unit);\n\n\nDROP FUNCTION IF EXISTS unit_negate(cldr_unit);"
@spec drop_sum_function(Localize.Ecto.TaggedDecimal.t()) :: String.t()
Returns the SQL dropping the sum aggregate and its functions.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.
Returns
- The SQL statements as a string, separated by two blank lines.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.drop_sum_function(spec) =~ "DROP AGGREGATE IF EXISTS sum(cldr_unit);"
true
@spec drop_type(Localize.Ecto.TaggedDecimal.t()) :: String.t()
Returns the SQL dropping the composite type.
Arguments
specis aLocalize.Ecto.TaggedDecimal.t/0.
Returns
- The SQL statement as a string.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> Localize.Ecto.TaggedDecimal.DDL.drop_type(spec)
"DROP TYPE public.cldr_unit;"
Wraps SQL as a single execute call for an Ecto migration.
Arguments
sqlis a SQL string.appendis a string appended inside the generatedexecutecall, such as a pipe into a migration-time adjustment. The default is"".
Returns
- The
executecall as a string.
Examples
iex> Localize.Ecto.TaggedDecimal.DDL.execute("DROP TYPE public.cldr_unit;")
"execute \"DROP TYPE public.cldr_unit;\""
Wraps each statement of sql as its own execute call.
Statements are separated by two blank lines, as the generating functions of this module emit them.
Arguments
sqlis a SQL string possibly containing several statements.appendis a string appended inside each generatedexecutecall. The default is"".
Returns
- The
executecalls as a string, one per statement.
Examples
iex> spec = Localize.Ecto.TaggedDecimal.new!(
...> type_name: "cldr_unit", tag_field: "unit", value_field: "value",
...> function_prefix: "unit", tag_description: "unit name"
...> )
iex> sql = Localize.Ecto.TaggedDecimal.DDL.drop_sum_function(spec)
iex> Localize.Ecto.TaggedDecimal.DDL.execute_each(sql) |> String.split("\n") |> length()
3