View Source Vtc.Ecto.Postgres.PgFramestamp.Range (vtc v0.17.5)

Defines a custom Range type for dealing with Framestamp ranges.

The new range types are defined as follows:

CREATE TYPE framestamp_range AS RANGE (
  subtype = framestamp,
  subtype_diff = framestamp_range_private.subtype_diff
  canonical = framestamp_range_private.canonical
);

Framestamp ranges can be created in SQL expressions like so:

SELECT framestamp_range(stamp_1, stamp_2, '[)')

Framestamp fastranges can be created in SQL expressions like so:

SELECT framestamp_fastrange(stamp_1, stamp_2)

Indexing

framestamp_range is currently VERY slow when using a GiST index, consider using a framestamp_fastrange instead.

canonicalization

Canonicalization

Postgres framestamp_range values are ALWAYS coerced to exclusive out ranges. That means that even if a Framestamp.Range has :out_type set to :inclusive when it is sent to the database, it will come back from the database with :out_type set to :exclusive, and the :out field will be adjusted accordingly.

Further, when a Range operation, like a union, would result in an in-and-out point with different framerates, the higher rate will always be selected.

This unlike the application behavior of Vtc.Framestamp.Range, which always inherits the rate of the value that appears on the left side. This behavior may be updated to match Vtc's application behavior in the future.

framestamp-fast-range

Framestamp Fast Range

In addition to framestamp_range, a framestamp_fastrange type is defined as well:

CREATE TYPE framestamp_fastrange AS RANGE (
  subtype = double precision,
  subtype_diff = float8mi
);

Fast ranges are meant to support GiST indexing, as in most cases, framestamp_range will be VERY slow to index.

Frame-accurate

Vtc's position is that rational values are necessary for frame-accurate timecode manipulation, so why does it put forth a float-based range type?

The main risk of using floats is unpredictable floating-point errors stacking up during arithmetic operations so that when you go to compare two values that SHOULD be equal, they aren't.

However, if you do all calculations with rational values -- up to the point where you need to compare them -- it is safe to cast to floats for the comparison operation, as equal rational values will always cast to the same float value.

FastRanges should ONLY be used for comparisons, and should NEVER be adjusted once built.

For more on FastRanges, see PgFramestamp.FastRange, which can be used to help serialize and deserialize the type.

field-migrations

Field migrations

You can create framestamp_range fields during a migration like so:

alias Vtc.Framerate

create table("framestamp_ranges") do
  add(:a, Framestamp.Range.type())
  add(:b, Framestamp.Range.type())
end

Framestamp.Range re-exports the Ecto.Type implementation of this module, and can be used any place this module would be used.

schema-fields

Schema fields

Then in your schema module:

defmodule MyApp.FramestampRanges do
@moduledoc false
use Ecto.Schema

alias Vtc.Framestamp

@type t() :: %__MODULE__{
        a: Framestamp.Range.t(),
        b: Framestamp.Range.t()
      }

schema "rationals_01" do
  field(:a, Framestamp.Range)
  field(:b, Framestamp.Range)
end

changesets

Changesets

With the above setup, changesets should just work:

def changeset(schema, attrs) do
  schema
  |> Changeset.cast(attrs, [:a, :b])
  |> Changeset.validate_required([:a, :b])
end

Framestamp.Range values can be cast from the following values in changesets:

fragments

Fragments

Framestamp.Range values must be explicitly cast using type/2:

stamp_in = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
stamp_out = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
stamp_range = Framestamp.Range.new!(stamp_in, stamp_out)

query = Query.from(
  f in fragment("SELECT ? as r", type(^stamp_range, Framerate.Range)), select: f.r
)

Link to this section Summary

Types

Type of the raw composite value that will be sent to / received from the database.

Ecto Migrations

The database type for PgFramerate.

Functions

Callback implementation for Ecto.Type.embed_as/1.

Callback implementation for Ecto.Type.equal?/2.

Link to this section Types

@type db_record() :: %Postgrex.Range{
  lower: Vtc.Ecto.Postgres.PgFramestamp.db_record(),
  lower_inclusive: boolean(),
  upper: Vtc.Ecto.Postgres.PgFramestamp.db_record(),
  upper_inclusive: boolean()
}

Type of the raw composite value that will be sent to / received from the database.

Link to this section Ecto Migrations

@spec type() :: atom()

The database type for PgFramerate.

Can be used in migrations as the fields type.

Link to this section Functions

Callback implementation for Ecto.Type.embed_as/1.

Callback implementation for Ecto.Type.equal?/2.