Packages
vtc
0.16.5
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_framerate_migrations.ex
use Vtc.Ecto.Postgres.Utils
defpgmodule Vtc.Ecto.Postgres.PgFramerate.Migrations do
@moduledoc """
Migrations for adding framerate types, functions and constraints to a
Postgres database.
"""
use Vtc.Ecto.Postgres.PgTypeMigration
alias Ecto.Migration
alias Vtc.Ecto.Postgres.PgFramerate
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 included all migrations under the
[Pg Types](Vtc.Ecto.Postgres.PgFramerate.Migrations.html#pg-types),
[Pg Operators](Vtc.Ecto.Postgres.PgFramerate.Migrations.html#pg-operators),
[Pg Functions](Vtc.Ecto.Postgres.PgFramerate.Migrations.html#pg-functions), and
[Pg Private Functions](Vtc.Ecto.Postgres.PgFramerate.Migrations.html#pg-private-functions),
headings.
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 framerate_tags AS ENUM (
'drop',
'non_drop'
);
```
```sql
CREATE TYPE framerate AS (
playback rational,
tags framerate_tags[]
);
```
## Schemas Created
Up to two schemas are created as detailed by the
[Configuring Database Objects](Vtc.Ecto.Postgres.PgFramerate.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: [
framerate: [
functions_schema: :framerate,
functions_prefix: "framerate"
]
]
```
Option definitions are as follows:
- `functions_schema`: The postgres schema to store framerate-related custom functions.
- `functions_prefix`: A prefix to add before all functions. Defaults to "framestamp"
for any function created in the `:public` schema, and "" otherwise.
## Private Functions
Some custom function names are prefaced with `__private__`. These functions should
not be called by end-users, as they are not subject to *any* API stability guarantees.
## Examples
```elixir
defmodule MyMigration do
use Ecto.Migration
alias Vtc.Ecto.Postgres.PgFramerate
require PgFramerate.Migrations
def change do
PgFramerate.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: PgFramerate
@doc false
@impl PgTypeMigration
def migrations_list do
[
&create_type_framerate_tags/0,
&create_type_framerate/0,
&create_function_schemas/0,
&create_func_is_ntsc/0,
&create_func_strict_eq/0,
&create_func_strict_neq/0,
&create_op_strict_eq/0,
&create_op_strict_neq/0
]
end
@doc section: :migrations_types
@doc """
Adds `framerate_tgs` enum type.
"""
@spec create_type_framerate_tags() :: migration_info()
def create_type_framerate_tags, do: PgTypeMigration.create_type(:framerate_tags, :enum, [:drop, :non_drop])
@doc section: :migrations_types
@doc """
Adds `framerate` composite type.
"""
@spec create_type_framerate() :: migration_info()
def create_type_framerate do
PgTypeMigration.create_type(:framerate,
playback: :rational,
tags: "framerate_tags[]"
)
end
## FUNCTIONS
@doc section: :migrations_types
@doc """
Creates function schema as described by the
[Configuring Database Objects](Vtc.Ecto.Postgres.PgFramerate.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(:framerate)
@doc section: :migrations_functions
@doc """
Creates `framerate.is_ntsc(rat)` function that returns true if the framerate
is an NTSC drop or non-drop rate.
"""
@spec create_func_is_ntsc() :: migration_info()
def create_func_is_ntsc do
PgTypeMigration.create_plpgsql_function(
function(:is_ntsc, Migration.repo()),
args: [input: :framerate],
returns: :boolean,
body: """
RETURN (
(input).tags @> '{drop}'::framerate_tags[]
OR (input).tags @> '{non_drop}'::framerate_tags[]
);
"""
)
end
@doc section: :migrations_functions
@doc """
Creates `framerate.__private__strict_eq(a, b)` that backs the `===` operator.
"""
@spec create_func_strict_eq() :: migration_info()
def create_func_strict_eq do
PgTypeMigration.create_plpgsql_function(
private_function(:strict_eq, Migration.repo()),
args: [a: :framerate, b: :framerate],
returns: :boolean,
body: """
RETURN (a).playback = (b).playback
AND (a).tags <@ (b).tags
AND (a).tags @> (b).tags;
"""
)
end
@doc section: :migrations_functions
@doc """
Creates `framerate.__private__strict_eq(a, b)` that backs the `===` operator.
"""
@spec create_func_strict_neq() :: migration_info()
def create_func_strict_neq do
PgTypeMigration.create_plpgsql_function(
private_function(:strict_neq, Migration.repo()),
args: [a: :framerate, b: :framerate],
returns: :boolean,
body: """
RETURN (a).playback != (b).playback
OR NOT (a).tags <@ (b).tags
OR NOT (a).tags @> (b).tags;
"""
)
end
## OPERATORS
@doc section: :migrations_operators
@doc """
Creates a custom :framerate, :framerate `===` operator that returns true if *both*
the playback rate AND tags of a framerate are equal.
"""
@spec create_op_strict_eq() :: migration_info()
def create_op_strict_eq do
PgTypeMigration.create_operator(
:===,
:framerate,
:framerate,
private_function(:strict_eq, Migration.repo()),
commutator: :===,
negator: :"!==="
)
end
@doc section: :migrations_operators
@doc """
Creates a custom :framerate, :framerate `===` operator that returns true if *both*
the playback rate AND tags of a framerate are equal.
"""
@spec create_op_strict_neq() :: migration_info()
def create_op_strict_neq do
PgTypeMigration.create_operator(
:"!===",
:framerate,
:framerate,
private_function(:strict_neq, Migration.repo()),
commutator: :"!===",
negator: :===
)
end
## CONSTRAINTS
@typedoc """
SQL value that can be passed as an atom or a string.
"""
@type sql_value() :: String.t() | atom()
@typedoc """
Opts for `create_constraints/3`.
"""
@type constraint_opt() ::
{:check_value, sql_value()}
| {:create_positive?, boolean()}
| {:create_ntsc_tags?, boolean()}
| {:create_ntsc_valid?, boolean()}
| {:create_ntsc_drop_valid?, boolean()}
@doc section: :migrations_constraints
@doc """
Creates basic constraints for a [PgFramerate](`Vtc.Ecto.Postgres.PgFramerate`) /
[Framerate](`Vtc.Framerate`) database field.
## Arguments
- `table`: The table to make the constraint on.
- `field`: The name of the field being validated.
## Options
- `check_value`: The target value to check. If not set, `table.field` will be used.
- `create_positive?`: `boolean` Add `{field}_positive` constraint (see below).
Default: `true`.
- `create_ntsc_tags?`: `boolean` Add `{field}_ntsc_tags` constraint (see below).
Default: `true`.
- `create_ntsc_valid?`: `boolean` Add `{field})_ntsc_valid` constraint (see below).
Default: `true`.
- `create_ntsc_drop_valid?`: `boolean` Add `{field})_ntsc_drop_valid` constraint
(see below). Default: `true`.
## Constraints created:
- `{field}_positive`: Checks that the playback speed is positive.
- `{field}_ntsc_tags`: Checks that both `drop` and `non_drop` are not set at the same
time.
- `{field}_ntsc_valid`: Checks that NTSC framerates are mathematically sound, i.e.,
that the rate is equal to `(round(rate.playback) * 1000) / 1001`.
- `{field}_ntsc_drop_valid`: Checks that NTSC, drop-frame framerates are valid, i.e,
are cleanly divisible by `30_000/1001`.
## Examples
```elixir
create table("my_table", primary_key: false) do
add(:id, :uuid, primary_key: true, null: false)
add(:a, Framerate.type())
add(:b, Framerate.type())
end
PgRational.migration_add_field_constraints(:my_table, :a)
PgRational.migration_add_field_constraints(:my_table, :b)
```
"""
@spec create_constraints(sql_value(), sql_value(), [constraint_opt()]) :: :ok
def create_constraints(table, field_name, opts \\ []) do
table
|> build_constraint_list(field_name, opts)
|> Enum.each(&Migration.create(&1))
:ok
end
# Compiles the constraint structs to be created in the database.
@doc false
@spec build_constraint_list(sql_value(), sql_value(), [constraint_opt()]) :: [Migration.Constraint.t()]
def build_constraint_list(table, field_name, opts) do
check_value = Keyword.get(opts, :check_value, nil)
{field_name, check_value} =
if is_nil(check_value) do
{field_name, "#{table}.#{field_name}"}
else
{field_name, check_value}
end
constraints = [
{
:create_positive?,
fn ->
Migration.constraint(
table,
"#{field_name}_rate_positive",
check: """
(#{check_value}).playback.denominator > 0
AND (#{check_value}).playback.numerator > 0
"""
)
end
},
{
:create_ntsc_tags?,
fn ->
Migration.constraint(
table,
"#{field_name}_ntsc_tags",
check: """
NOT (
((#{check_value}).tags) @> '{drop}'::framerate_tags[]
AND ((#{check_value}).tags) @> '{non_drop}'::framerate_tags[]
)
"""
)
end
},
{
:create_ntsc_valid?,
fn ->
Migration.constraint(
table,
"#{field_name}_ntsc_valid",
check: """
NOT (
((#{check_value}).tags) @> '{drop}'::framerate_tags[]
OR ((#{check_value}).tags) @> '{non_drop}'::framerate_tags[]
)
OR (
(
ROUND((#{check_value}).playback) * 1000,
1001
)::rational
= (#{check_value}).playback
)
"""
)
end
},
{
:create_drop_valid?,
fn ->
Migration.constraint(
table,
"#{field_name}_ntsc_drop_valid",
check: """
NOT (#{check_value}).tags @> '{drop}'::framerate_tags[]
OR (#{check_value}).playback % (30000, 1001)::rational = (0, 1)::rational
"""
)
end
}
]
constraints
|> Enum.filter(fn {opt, _} -> Keyword.get(opts, opt, true) end)
|> Enum.map(fn {_, constraint_builder} -> constraint_builder.() end)
end
end