Packages
vtc
0.16.6
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_rational_migrations.ex
use Vtc.Ecto.Postgres.Utils
defpgmodule Vtc.Ecto.Postgres.PgRational.Migrations do
@moduledoc """
Migrations for adding rational types, casts, functions and constraints to a
Postgres database.
"""
use Vtc.Ecto.Postgres.PgTypeMigration
alias Ecto.Migration
alias Ecto.Migration.Constraint
alias Vtc.Ecto.Postgres.Fragments
alias Vtc.Ecto.Postgres.PgRational
alias Vtc.Ecto.Postgres.PgTypeMigration
require Ecto.Migration
@doc section: :migrations_full
@doc """
Adds raw SQL queries to a migration for creating the database types, associated
functions, casts, operators, and operator families.
This migration includes all migrations under the
[Pg Types](Vtc.Ecto.Postgres.PgRational.Migrations.html#pg-types),
[Pg Operators](Vtc.Ecto.Postgres.PgRational.Migrations.html#pg-operators),
[Pg Operator Classes](Vtc.Ecto.Postgres.PgRational.Migrations.html#pg-operator-classes),
[Pg Functions](Vtc.Ecto.Postgres.PgRational.Migrations.html#pg-functions),
[Pg Private Functions](Vtc.Ecto.Postgres.PgRational.Migrations.html#pg-private-functions),
headings, and
[Pg Casts](Vtc.Ecto.Postgres.PgRational.Migrations.html#pg-casts)
Safe to run multiple times when new functionality is added in updates to this library.
Existing values will be skipped.
Individual migration functions return raw sql commands in an
{up_command, down_command} tuple.
## Options
- `include`: A list of migration functions to include. If not set, all sub-migrations
will be included.
- `exclude`: A list of migration functions to exclude. If not set, no sub-migrations
will be excluded.
## Types Created
Calling this macro creates the following type definitions:
```sql
CREATE TYPE public.rational AS (
numerator bigint,
denominator bigint
);
```
## Schemas Created
Up to two schemas are created as detailed by the
[Configuring Database Objects](Vtc.Ecto.Postgres.PgRational.Migrations.html#create_all/0-configuring-database-objects)
section below.
## Configuring Database Objects
To change where supporting functions are created, add the following to your
Repo configuration:
```elixir
config :vtc, Vtc.Test.Support.Repo,
adapter: Ecto.Adapters.Postgres,
...
vtc: [
rational: [
functions_schema: :rational,
functions_prefix: "rational"
]
]
```
Option definitions are as follows:
- `functions_schema`: The postgres schema to store rational-related custom functions.
- `functions_prefix`: A prefix to add before all functions. Defaults to "rational"
for any function created in the `:public` schema, and "" otherwise.
## Examples
```elixir
defmodule MyMigration do
use Ecto.Migration
alias Vtc.Ecto.Postgres.PgRational
require PgRational.Migrations
def change do
PgRational.Migrations.run()
end
end
```
"""
@spec run(include: Keyword.t(atom()), exclude: Keyword.t(atom())) :: :ok
def run(opts \\ []), do: PgTypeMigration.run_for(__MODULE__, opts)
@doc false
@impl PgTypeMigration
def ecto_type, do: PgRational
@doc false
@impl PgTypeMigration
def migrations_list do
[
&create_type/0,
&create_function_schemas/0,
&create_func_simplify/0,
&create_func_minus/0,
&create_func_abs/0,
&create_func_sign/0,
&create_func_round/0,
&create_func_floor/0,
&create_func_add/0,
&create_func_sub/0,
&create_func_mult/0,
&create_func_div/0,
&create_func_floor_div/0,
&create_func_modulo/0,
&create_op_abs/0,
&create_op_minus/0,
&create_op_add/0,
&create_op_sub/0,
&create_op_mult/0,
&create_op_div/0,
&create_op_modulo/0,
&create_func_cmp/0,
&create_func_eq/0,
&create_func_neq/0,
&create_func_lt/0,
&create_func_lte/0,
&create_func_gt/0,
&create_func_gte/0,
&create_op_eq/0,
&create_op_neq/0,
&create_op_lt/0,
&create_op_lte/0,
&create_op_gt/0,
&create_op_gte/0,
&create_op_class_btree/0,
&create_func_cast_to_double_precision/0,
&create_func_cast_bigint_to_rational/0,
&create_cast_double_precision/0,
&create_cast_bigint_to_rational/0
]
end
@doc section: :migrations_types
@doc """
Adds:
- `rational` composite type
- `rationals` schema
- `rationals_helpers` schema
"""
@spec create_type() :: migration_info()
def create_type do
PgTypeMigration.create_type(:rational,
numerator: :bigint,
denominator: :bigint
)
end
@doc section: :migrations_types
@doc """
Creates function schema as described by the
[Configuring Database Objects](Vtc.Ecto.Postgres.PgRational.Migrations.html#create_all/0-configuring-database-objects)
section above.
"""
@spec create_function_schemas() :: migration_info()
def create_function_schemas, do: PgTypeMigration.create_type_schema(:rational)
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__simplify(rat)` function that simplifies a rational. Used at
the end of every rational operation to avoid overflows.
"""
@spec create_func_simplify() :: migration_info()
def create_func_simplify do
PgTypeMigration.create_plpgsql_function(
private_function(:simplify, Migration.repo()),
args: [input: :rational],
returns: :rational,
declares: [
greatest_denom: {:bigint, "GCD(input.numerator, input.denominator)"},
denominator: {:bigint, "ABS(input.denominator / greatest_denom)"},
numerator: {:bigint, "input.numerator / greatest_denom"}
],
body: """
numerator := numerator * SIGN((input).denominator);
RETURN (numerator, denominator);
"""
)
end
@doc section: :migrations_functions
@doc """
Creates `rational.__private__minus(rat)` function that flips the sign of the input
value -- makes a positive value negative and a negative value positive.
"""
@spec create_func_minus() :: migration_info()
def create_func_minus do
PgTypeMigration.create_plpgsql_function(
private_function(:minus, Migration.repo()),
args: [input: :rational],
returns: :rational,
body: """
RETURN ((input).numerator * -1, (input).denominator);
"""
)
end
@doc section: :migrations_functions
@doc """
Creates `ABS(rational)` function that returns the absolute value of the rational
value.
"""
@spec create_func_abs() :: migration_info()
def create_func_abs do
PgTypeMigration.create_plpgsql_function(
"ABS",
args: [input: :rational],
returns: :rational,
body: """
RETURN (ABS((input).numerator), ABS((input).denominator));
"""
)
end
@doc section: :migrations_functions
@doc """
Creates `ABS(rational)` function that returns the absolute value of the rational
value.
"""
@spec create_func_sign() :: migration_info()
def create_func_sign do
PgTypeMigration.create_plpgsql_function(
"SIGN",
args: [input: :rational],
returns: :integer,
body: """
RETURN SIGN((input).numerator * (input).denominator);
"""
)
end
@doc section: :migrations_functions
@doc """
Creates `ROUND(rational)` function that returns the rational input, rounded to the
nearest :bigint.
"""
@spec create_func_round() :: migration_info()
def create_func_round do
PgTypeMigration.create_plpgsql_function(
"ROUND",
args: [input: :rational],
returns: :bigint,
body: """
CASE
WHEN (input).numerator < 0 THEN
input := -input;
RETURN ROUND(input) * -1;
WHEN (((input).numerator % (input).denominator) * 2) < (input).denominator THEN
RETURN (input).numerator / (input).denominator;
ELSE
RETURN ((input).numerator / (input).denominator) + 1;
END CASE;
"""
)
end
@doc section: :migrations_functions
@doc """
Creates `FLOOR(rational)` function that returns the rational input as a `bigint`,
rounded towards zero, to match Postgres `FLOOR(real)` behavior.
"""
@spec create_func_floor() :: migration_info()
def create_func_floor do
PgTypeMigration.create_plpgsql_function(
"FLOOR",
args: [input: :rational],
returns: :bigint,
body: """
RETURN ((input).numerator / (input).denominator);
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates a native CAST from `rational` to `double precision`.
"""
@spec create_func_cast_to_double_precision() :: migration_info()
def create_func_cast_to_double_precision do
PgTypeMigration.create_plpgsql_function(
private_function(:cast_to_double, Migration.repo()),
args: [value: :rational],
returns: :"double precision",
body: """
RETURN CAST ((value).numerator AS double precision) / CAST ((value).denominator AS double precision);
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates a native CAST from `bigint` to `rational`.
"""
@spec create_func_cast_bigint_to_rational() :: migration_info()
def create_func_cast_bigint_to_rational do
PgTypeMigration.create_plpgsql_function(
private_function(:cast_bigint_to_rational, Migration.repo()),
args: [value: :bigint],
returns: :rational,
body: """
RETURN (value, 1)::rational;
"""
)
end
## ARITHMETIC BACKING FUNCS
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__add(a, b)` backing function for the `+` operator
between two rationals.
"""
@spec create_func_add() :: migration_info()
def create_func_add do
PgTypeMigration.create_plpgsql_function(
private_function(:add, Migration.repo()),
args: [a: :rational, b: :rational],
declares: [
numerator: {:bigint, "((a).numerator * (b).denominator) + ((b).numerator * (a).denominator)"},
denominator: {:bigint, "(a).denominator * (b).denominator"},
greatest_denom: {:bigint, "GCD(numerator, denominator)"}
],
returns: :rational,
body: """
#{Fragments.sql_inline_simplify(:numerator, :denominator, :greatest_denom)}
RETURN (numerator, denominator);
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__sub(a, b)` backing function for the `-` operator
between two rationals.
"""
@spec create_func_sub() :: migration_info()
def create_func_sub do
PgTypeMigration.create_plpgsql_function(
private_function(:sub, Migration.repo()),
args: [a: :rational, b: :rational],
declares: [
numerator: {:bigint, "((a).numerator * (b).denominator) - ((b).numerator * (a).denominator)"},
denominator: {:bigint, "(a).denominator * (b).denominator"},
greatest_denom: {:bigint, "GCD(numerator, denominator)"}
],
returns: :rational,
body: """
#{Fragments.sql_inline_simplify(:numerator, :denominator, :greatest_denom)}
RETURN (numerator, denominator);
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__mult(a, b)` backing function for the `*` operator
between two rationals.
"""
@spec create_func_mult() :: migration_info()
def create_func_mult do
PgTypeMigration.create_plpgsql_function(
private_function(:mult, Migration.repo()),
args: [a: :rational, b: :rational],
declares: [
numerator: {:bigint, "(a).numerator * (b).numerator"},
denominator: {:bigint, "(a).denominator * (b).denominator"},
greatest_denom: {:bigint, "GCD(numerator, denominator)"}
],
returns: :rational,
body: """
#{Fragments.sql_inline_simplify(:numerator, :denominator, :greatest_denom)}
RETURN (numerator, denominator);
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__div(a, b)` backing function for the `/` operator
between two rationals.
"""
@spec create_func_div() :: migration_info()
def create_func_div do
PgTypeMigration.create_plpgsql_function(
private_function(:div, Migration.repo()),
args: [a: :rational, b: :rational],
declares: [
numerator: {:bigint, "(a).numerator * (b).denominator"},
denominator: {:bigint, "(a).denominator * (b).numerator"},
greatest_denom: {:bigint, "GCD(numerator, denominator)"}
],
returns: :rational,
body: """
#{Fragments.sql_inline_simplify(:numerator, :denominator, :greatest_denom)}
RETURN (numerator, denominator);
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `DIV(a, b)` function, which executed integer floor division on rational
values.
Just like `DIV(real, real)`, `DIV(rational, rational)` floors towards zero.
"""
@spec create_func_floor_div() :: migration_info()
def create_func_floor_div do
PgTypeMigration.create_plpgsql_function(
"DIV",
args: [a: :rational, b: :rational],
declares: [
numerator: {:bigint, "(a).numerator * (b).denominator"},
denominator: {:bigint, "(a).denominator * (b).numerator"}
],
returns: :bigint,
body: """
RETURN numerator / denominator;
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__modulo(a, b)` backing function for the `%` operator
between two rationals.
"""
@spec create_func_modulo() :: migration_info()
def create_func_modulo do
PgTypeMigration.create_plpgsql_function(
private_function(:modulo, Migration.repo()),
args: [dividend: :rational, divisor: :rational],
declares: [
numerator:
{:bigint,
"""
((dividend).numerator * (divisor).denominator)
% ((divisor).numerator * (dividend).denominator)
"""},
denominator: {:bigint, "(dividend).denominator * (divisor).denominator"},
greatest_denom: {:bigint, "GCD(numerator, denominator)"}
],
returns: :rational,
body: """
#{Fragments.sql_inline_simplify(:numerator, :denominator, :greatest_denom)}
RETURN (numerator, denominator);
"""
)
end
## COMPARISON BACKING FUNCS
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__cmp(a, b)` that returns:
- `1` if a > b
- `0` if a == b
- `-1` if a < b
Used to back equality operators.
"""
@spec create_func_cmp() :: migration_info()
def create_func_cmp do
PgTypeMigration.create_plpgsql_function(
private_function(:cmp, Migration.repo()),
args: [a: :rational, b: :rational],
declares: [
a_cmp: {:bigint, "((a).numerator * (b).denominator)"},
b_cmp: {:bigint, "((b).numerator * (a).denominator)"}
],
returns: :integer,
body: """
RETURN SIGN(a_cmp - b_cmp);
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__eq(a, b)` that backs the `=` operator.
"""
@spec create_func_eq() :: migration_info()
def create_func_eq do
PgTypeMigration.create_plpgsql_function(
private_function(:eq, Migration.repo()),
args: [a: :rational, b: :rational],
declares: compare_declarations(),
returns: :boolean,
body: """
RETURN cmp_sign = 0;
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__neq(a, b)` that backs the `<>` operator.
"""
@spec create_func_neq() :: migration_info()
def create_func_neq do
PgTypeMigration.create_plpgsql_function(
private_function(:neq, Migration.repo()),
args: [a: :rational, b: :rational],
declares: compare_declarations(),
returns: :boolean,
body: """
RETURN cmp_sign != 0;
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__lt(a, b)` that backs the `<` operator.
"""
@spec create_func_lt() :: migration_info()
def create_func_lt do
PgTypeMigration.create_plpgsql_function(
private_function(:lt, Migration.repo()),
args: [a: :rational, b: :rational],
declares: compare_declarations(),
returns: :boolean,
body: """
RETURN cmp_sign = -1;
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__lte(a, b)` that backs the `<=` operator.
"""
@spec create_func_lte() :: migration_info()
def create_func_lte do
PgTypeMigration.create_plpgsql_function(
private_function(:lte, Migration.repo()),
args: [a: :rational, b: :rational],
declares: compare_declarations(),
returns: :boolean,
body: """
RETURN cmp_sign = -1 or cmp_sign = 0;
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__gt(a, b)` that backs the `>` operator.
"""
@spec create_func_gt() :: migration_info()
def create_func_gt do
PgTypeMigration.create_plpgsql_function(
private_function(:gt, Migration.repo()),
args: [a: :rational, b: :rational],
declares: compare_declarations(),
returns: :boolean,
body: """
RETURN cmp_sign = 1;
"""
)
end
@doc section: :migrations_private_functions
@doc """
Creates `rational.__private__gte(a, b)` that backs the `>=` operator.
"""
@spec create_func_gte() :: migration_info()
def create_func_gte do
PgTypeMigration.create_plpgsql_function(
private_function(:gte, Migration.repo()),
args: [a: :rational, b: :rational],
declares: compare_declarations(),
returns: :boolean,
body: """
RETURN cmp_sign = 1 or cmp_sign = 0;
"""
)
end
## ARITHMETIC OPS
@doc section: :migrations_operators
@doc """
Creates a custom unary :rational `@` unary operator.
Returns the absolute value of the input.
"""
@spec create_op_abs() :: migration_info()
def create_op_abs do
PgTypeMigration.create_operator(
:@,
nil,
:rational,
"ABS"
)
end
@doc section: :migrations_operators
@doc """
Creates a custom unary :rational `-` operator.
Flips the sign of `value`. Equivalent to `value * -1`.
"""
@spec create_op_minus() :: migration_info()
def create_op_minus do
PgTypeMigration.create_operator(
:-,
nil,
:rational,
private_function(:minus, Migration.repo())
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `+` operator.
"""
@spec create_op_add() :: migration_info()
def create_op_add do
PgTypeMigration.create_operator(
:+,
:rational,
:rational,
private_function(:add, Migration.repo()),
commutator: :+
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `-` operator.
"""
@spec create_op_sub() :: migration_info()
def create_op_sub do
PgTypeMigration.create_operator(
:-,
:rational,
:rational,
private_function(:sub, Migration.repo())
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `*` operator.
"""
@spec create_op_mult() :: migration_info()
def create_op_mult do
PgTypeMigration.create_operator(
:*,
:rational,
:rational,
private_function(:mult, Migration.repo()),
commutator: :*
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `/` operator.
"""
@spec create_op_div() :: migration_info()
def create_op_div do
PgTypeMigration.create_operator(
:/,
:rational,
:rational,
private_function(:div, Migration.repo())
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `%` operator.
"""
@spec create_op_modulo() :: migration_info()
def create_op_modulo do
PgTypeMigration.create_operator(
:%,
:rational,
:rational,
private_function(:modulo, Migration.repo())
)
end
## COMPARISON OPS
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `=` operator.
"""
@spec create_op_eq() :: migration_info()
def create_op_eq do
PgTypeMigration.create_operator(
:=,
:rational,
:rational,
private_function(:eq, Migration.repo()),
commutator: :=,
negator: :<>
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `<>` operator.
"""
@spec create_op_neq() :: migration_info()
def create_op_neq do
PgTypeMigration.create_operator(
:<>,
:rational,
:rational,
private_function(:neq, Migration.repo()),
commutator: :<>,
negator: :=
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `<` operator.
"""
@spec create_op_lt() :: migration_info()
def create_op_lt do
PgTypeMigration.create_operator(
:<,
:rational,
:rational,
private_function(:lt, Migration.repo()),
commutator: :>,
negator: :>=
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `<` operator.
"""
@spec create_op_lte() :: migration_info()
def create_op_lte do
PgTypeMigration.create_operator(
:<=,
:rational,
:rational,
private_function(:lte, Migration.repo()),
commutator: :>=,
negator: :>
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `<` operator.
"""
@spec create_op_gt() :: migration_info()
def create_op_gt do
PgTypeMigration.create_operator(
:>,
:rational,
:rational,
private_function(:gt, Migration.repo()),
commutator: :<,
negator: :<=
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :rational, :rational `<` operator.
"""
@spec create_op_gte() :: migration_info()
def create_op_gte do
PgTypeMigration.create_operator(
:>=,
:rational,
:rational,
private_function(:gte, Migration.repo()),
commutator: :<=,
negator: :<
)
end
## OPERATOR CLASSES
@doc section: :migrations_operator_classes
@doc """
Creates a B-tree operator class to support indexing on comparison operations.
"""
@spec create_op_class_btree() :: migration_info()
def create_op_class_btree do
PgTypeMigration.create_operator_class(
:rational_ops_btree,
:rational,
:btree,
[
<: 1,
<=: 2,
=: 3,
>=: 4,
>: 5
],
[
{private_function(:cmp, Migration.repo()), 1}
]
)
end
## CASTS
@doc section: :migrations_casts
@doc """
Creates a native cast for:
```sql
rational AS double precision
```
"""
@spec create_cast_double_precision() :: migration_info()
def create_cast_double_precision do
PgTypeMigration.create_cast(
:rational,
:"double precision",
private_function(:cast_to_double, Migration.repo())
)
end
@doc section: :migrations_casts
@doc """
Creates a native cast for:
```sql
bigint AS rational
```
"""
@spec create_cast_bigint_to_rational() :: migration_info()
def create_cast_bigint_to_rational do
PgTypeMigration.create_cast(
:bigint,
:rational,
private_function(:cast_bigint_to_rational, Migration.repo()),
implicit: true
)
end
@doc section: :migrations_constraints
@doc """
Creates basic constraints for a `PgRational` database field.
## Constraints created:
- `{field_name}_denominator_positive`: Checks that the denominator of the field is
positive.
## Examples
```elixir
create table("rationals", primary_key: false) do
add(:id, :uuid, primary_key: true, null: false)
add(:a, PgRational.type())
add(:b, PgRational.type())
end
PgRational.migration_add_field_constraints(:rationals, :a)
PgRational.migration_add_field_constraints(:rationals, :b)
```
"""
@spec create_constraints(atom(), atom()) :: :ok
def create_constraints(table, field_name) do
sql_field = "#{table}.#{field_name}"
constraint =
Migration.constraint(
table,
"#{field_name}_denominator_positive",
check: """
(#{sql_field}).denominator > 0
"""
)
%Constraint{} = Migration.create(constraint)
:ok
end
# Returns declaration list for comparison operators.
@spec compare_declarations() :: PgTypeMigration.function_declarations()
defp compare_declarations do
[
a_cmp: {:bigint, "((a).numerator * (b).denominator)"},
b_cmp: {:bigint, "((b).numerator * (a).denominator)"},
cmp_sign: {:bigint, "SIGN(a_cmp - b_cmp)"}
]
end
end