View Source Ecto Quickstart
Vtc ships with a number of Ecto custom types designed to work with Postgres databases connected by Postgrex.
ecto-types
Ecto types
The custom Ecto & Postgres types provided by Vtc are:
PgRational
- Migrations Schema
- Not re-exported,
Ratio
is not owned by Vtc.
PgFramerate
- Migrations Schema
- Re-exported by Framerate
PgFramestamp
- Migrations Schema
- Re-exported by Framestamp
PgFramestamp.Range
- Migrations Schema
- Re-exported by Framestamp.Range
configuration
Configuration
Additional configuration is done at the Repo level:
config :vtc, Vtc.Test.Support.Repo,
adapter: Ecto.Adapters.Postgres,
vtc: [
rational: [
functions_schema: :rational,
functions_prefix: ""
],
framerate: [
functions_schema: :framerate,
functions_prefix: ""
],
framestamp: [
functions_schema: :framestamp,
functions_prefix: ""
],
framestamp_range: [
functions_schema: :framestamp_range,
functions_prefix: ""
]
]
config :vtc,
ecto_repos: [Vtc.Test.Support.Repo]
Each type can be configured to store it's support functions on a schema of your choice as well as have a custom, per-type function name prefix.
By default, all functions are added to the public
schema, and are prefaced by their
type name. For example, the PgFramestamp
frames function
is created in the public
schema, and named framestamp_frames
.
initial-migration
Initial Migration
To create all the Postgres types, functions, operators, casts, and operator classes that Vtc provides, run the following migration:
defmodule Vtc.Test.Support.Repo.Migrations.AddPostgresTypes do
@moduledoc false
use Ecto.Migration
def change do
Vtc.Ecto.Postgres.Migrations.run()
end
end
This migration is safe to run multiple times, and can be run again when new
functionality is added to Vtc. All migrations implement both up
and down
functionality, and support rollbacks out of the box.
All objects created by the migration come complete with in-database documentation with links to the relevant Vtc docs. Using DataGrip:
ecto-api
Ecto API
Vtc's custom Ecto types are each implemented in a dedicated module. For instance, the
framestamp
type is implemented by PgFramestamp.
However, each ecto type's functions are re-exported by the Vtc
elixir type it is
designed to work with, allowing you to mostly ignore the dedicated ecto module when
creating migrations and schemas:
defmodule MyApp.MySchema do
@moduledoc false
use Ecto.Migration
alias Vtc.Ecto.Postgres.PgFramestamp
alias Vtc.Framestamp
def change do
create table("my_table", primary_key: false) do
add(:id, :uuid, primary_key: true, null: false)
add(:a, Framestamp.type())
add(:b, Framestamp.type())
end
end
end
Each type ships with a set of custom constraints that can be used for data integrity validation in migrations:
Framestamp constraint docs
def change do
create table("my_table", primary_key: false) do
add(:b, Framestamp.type())
end
PgFramestamp.Migrations.create_constraints("my_table", :b)
end
Vtc modules can be used directly in schemas:
Framestamp changeset casting docs
defmodule Vtc.Test.Support.FramestampSchema01 do
@moduledoc false
use Ecto.Schema
alias Ecto.Changeset
alias Vtc.Framestamp
@type t() :: %__MODULE__{
id: Ecto.UUID.t(),
a: Framestamp.t(),
b: Framestamp.t()
}
@primary_key {:id, Ecto.UUID, autogenerate: true}
schema "my_table" do
field(:a, Framestamp)
field(:b, Framestamp)
end
@spec changeset(%__MODULE__{}, %{atom() => any()}) :: Changeset.t(%__MODULE__{})
def changeset(schema, attrs), do: Changeset.cast(schema, attrs, [:id, :a, :b])
end
Values can be used in Ecto queries using the type/2 function. Vtc registers native operators for each type, so you can write queries like you would expect to with any other numeric type:
query value
iex> one_hour = Framestamp.with_frames("01:00:00:00", Rates.f23_98())
iex>
iex> EdlEvents
iex> |> where([event], event.start > type(^one_hour, Framestamp))
iex> |> select([event], {event, event.end - event.in_framestamp})
The above query finds all events with a start time greater than 01:00:00:00
and
returns the record AND its calculated duration.
private-functions
Private functions
Many of Vtc's postgres functions are prefaced with __private__
, for instance, the
framestamp__private__add
function is used to back the addition +
operator.
These functions have no API stability guarantee, and callers should avoid calling them directly.
framestamp-postgres-functions
Framestamp Postgres functions
with_seconds-2
with_seconds/2
Elixir
iex> seconds = Ratio.new(3600)
iex> rate = Rates.f23_98()
iex>
iex> query =
iex> Query.from(
iex> f in fragment(
iex> "SELECT framestamp.with_seconds(?, ?) as r",
iex> type(^seconds, PgRational),
iex> type(^rate, Framerate)
iex> ),
iex> select: f.r
iex> )
iex>
iex> query |> Repo.one!() |> Framestamp.load() |> inspect()
"{:ok, <00:59:56:10 <23.98 NTSC>>}"
SQL
SELECT framestamp.with_seconds(
(3600, 1)::rational,
((24000, 1001), '{non_drop}')::framerate
);
Output:
with_seconds
------------------------------------------------------
(43200157,12000,24000,1001,{non_drop})
(1 row)
Notice that just like Framestamp.with_seconds/3, the seconds value is rounded to the nearest whole-frame on construction.
with_frames-2
with_frames/2
Elixir
iex> frames = 24
iex> rate = Rates.f23_98()
iex>
iex> query =
iex> Query.from(
iex> f in fragment(
iex> "SELECT framestamp.with_frames(?, ?) as r",
iex> ^frames,
iex> type(^rate, Framerate)
iex> ),
iex> select: f.r
iex> )
iex>
iex> query |> Repo.one!() |> Framestamp.load() |> inspect()
"{:ok, <00:00:01:00 <23.98 NTSC>>}"
SQL
SELECT framestamp.with_frames(
24,
((24000, 1001), '{non_drop}')::framerate
);
Output:
with_frames
-------------------------------------------------
(1001,1000,24000,1001,{non_drop})
(1 row)
frame-count
frame count
Elixir
iex> stamp =
iex> Framestamp.with_frames!(
iex> "01:00:00:00",
iex> Rates.f23_98()
iex> )
iex>
iex> query =
iex> Query.from(
iex> f in fragment(
iex> "SELECT framestamp.frames(?) as r",
iex> type(^stamp, Framestamp)
iex> ),
iex> select: f.r
iex> )
iex>
iex> Repo.one!(query)
86400
SQL
SELECT framestamp.frames(
(18018, 5, 24000, 1001, '{non_drop}')
);
Output:
frames
--------
86400
(1 row)
framestamp-postgres-operators
Framestamp Postgres operators
Available operators
name | type | args | result | description |
---|---|---|---|---|
= | operator | framestamp , framestamp | boolean | true if a seconds == b seconds |
< | operator | framestamp , framestamp | boolean | true if a seconds < b seconds |
<= | operator | framestamp , framestamp | boolean | true if a seconds <= b seconds |
> | operator | framestamp , framestamp | boolean | true if a seconds > b seconds |
>= | operator | framestamp , framestamp | boolean | true if a seconds >= b seconds |
ABS | function | framestamp | framestamp | absolute value |
SIGN | function | framestamp | integer | returns -1, 0, or 1 based on comparison to 0 |
@ | operator | framestamp | framestamp | absolute value |
- | function | framestamp | framestamp | negate. flips input's sign |
+ | operator | framestamp , framestamp | framestamp | addition. raises if framerates do not match |
@+ | operator | framestamp , framestamp | framestamp | addition. inherit framerate from left |
+@ | operator | framestamp , framestamp | framestamp | addition. inherit framerate from right |
- | operator | framestamp , framestamp | framestamp | subtraction. raises if framerates do not match |
@- | operator | framestamp , framestamp | framestamp | subtraction. inherit framerate from left |
-@ | operator | framestamp , framestamp | framestamp | subtraction. inherit framerate from right |
* | operator | framestamp , rational | framestamp | multiplication. rounds to nearest whole-frame |
/ | operator | framestamp , rational | framestamp | division. rounds to nearest frame |
DIV | function | framestamp , rational | framestamp | floor-division. rounds down to nearest frame |
% | operator | framestamp , rational | framestamp | remainder. returns leftover frames from DIV |
.
mixed-rate-arithmetic
Mixed rate arithmetic
Elixir
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:00:00:00", Rates.f47_95())
iex>
iex> query =
iex> Query.from(
iex> f in fragment(
iex> "SELECT "? +@ ? as r",
iex> type(^a, Framestamp),
iex> type(^b, Framestamp)
iex> ),
iex> select: f.r
iex> )
iex>
iex> Repo.one!(query) |> Framestamp.load() |> inspect()
"{:ok, <02:00:00:00 <47.95 NTSC>>}"
SQL
SELECT
(18018, 5, 24000, 1001, '{non_drop}')::framestamp
+@ (18018, 5, 48000, 1001, '{non_drop}')::framestamp;
Output:
?column?
-----------------------------------------------
("(36036,5)","(""(48000,1001)"",{non_drop})")
(1 row)
rational-postgres-operators
Rational Postgres operators
Available operators
name | type | args | result | description |
---|---|---|---|---|
= | operator | rational , rational | boolean | true if a == b |
< | operator | rational , rational | boolean | true if a < b |
<= | operator | rational , rational | boolean | true if a <= b |
> | operator | rational , rational | boolean | true if a > b |
>= | operator | rational , rational | boolean | true if a >= b |
ABS | function | rational | rational | absolute value |
SIGN | function | rational | integer | returns -1 , 0 , or 1 based on comparison to 0 |
@ | operator | rational | rational | absolute value |
- | function | rational | rational | negate. flips input's sign |
+ | operator | rational , rational | rational | addition |
- | operator | rational , rational | rational | subtraction |
* | operator | rational , rational | rational | multiplication |
/ | operator | rational , rational | rational | division. rounds to the nearest integer |
DIV | function | rational , rational | rational | floor-division. rounds down to nearest integer |
% | operator | rational , rational | rational | remainder based on DIV |