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,
Ratiois 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
endThis 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
endEach 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)
endVtc 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])
endValues 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)
86400SQL
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 |