29 changes: 1 addition & 28 deletions lib/ecto/postgres/pg_rational_migrations.ex
Expand Up @@ -182,34 +182,7 @@ defpgmodule Vtc.Ecto.Postgres.PgRational.Migrations do
section above.
"""
@spec create_function_schemas() :: :ok
def create_function_schemas do
functions_schema = Postgres.Utils.get_type_config(Migration.repo(), :pg_rational, :functions_schema, :public)

if functions_schema != :public do
Migration.execute("""
DO $$ BEGIN
CREATE SCHEMA #{functions_schema};
EXCEPTION WHEN duplicate_schema
THEN null;
END $$;
""")
end

functions_private_schema =
Postgres.Utils.get_type_config(Migration.repo(), :pg_rational, :functions_private_schema, :public)

if functions_private_schema != :public do
Migration.execute("""
DO $$ BEGIN
CREATE SCHEMA #{functions_private_schema};
EXCEPTION WHEN duplicate_schema
THEN null;
END $$;
""")
end

:ok
end
def create_function_schemas, do: Postgres.Utils.create_type_schemas(:pg_rational)

@doc section: :migrations_private_functions
@doc """
Expand Down
34 changes: 34 additions & 0 deletions lib/ecto/postgres/pg_utils.ex
@@ -1,6 +1,8 @@
defmodule Vtc.Ecto.Postgres.Utils do
@moduledoc false

alias Ecto.Migration

## Exposes a macro for defining modules that will only be compiled if the caller
## has set `:vtc, Postrgrex, :include?` to `true` in their application config.

Expand Down Expand Up @@ -224,6 +226,38 @@ defmodule Vtc.Ecto.Postgres.Utils do
"""
end

@doc """
Creates a public and private schema for a type based on the repo's confguration.
"""
@spec create_type_schemas(atom()) :: :ok
def create_type_schemas(type_name) do
functions_schema = get_type_config(Migration.repo(), type_name, :functions_schema, :public)

if functions_schema != :public do
Migration.execute("""
DO $$ BEGIN
CREATE SCHEMA #{functions_schema};
EXCEPTION WHEN duplicate_schema
THEN null;
END $$;
""")
end

functions_private_schema = get_type_config(Migration.repo(), type_name, :functions_private_schema, :public)

if functions_private_schema != :public do
Migration.execute("""
DO $$ BEGIN
CREATE SCHEMA #{functions_private_schema};
EXCEPTION WHEN duplicate_schema
THEN null;
END $$;
""")
end

:ok
end

@doc """
Returns a configuration option for a specific vtc Postgres type and Repo.
"""
Expand Down
22 changes: 22 additions & 0 deletions lib/framestamp_range.ex
Expand Up @@ -20,7 +20,9 @@ defmodule Vtc.Framestamp.Range do
In mathematical notation, inclusive ranges are `[in, out]`, while exclusive ranges are
`[in, out)`.
"""
use Vtc.Ecto.Postgres.Utils

alias Vtc.Ecto.Postgres.PgFramestamp
alias Vtc.Framestamp
alias Vtc.Source.Frames

Expand Down Expand Up @@ -561,6 +563,26 @@ defmodule Vtc.Framestamp.Range do
value -> value
end)
end

when_pg_enabled do
use Ecto.Type

@impl Ecto.Type
@spec type() :: atom()
defdelegate type, to: PgFramestamp.Range

@impl Ecto.Type
@spec cast(t() | %{String.t() => any()} | %{atom() => any()}) :: {:ok, t()} | :error
defdelegate cast(value), to: PgFramestamp.Range

@impl Ecto.Type
@spec load(PgFramestamp.Range.db_record()) :: {:ok, t()} | :error
defdelegate load(value), to: PgFramestamp.Range

@impl Ecto.Type
@spec dump(t()) :: {:ok, PgFramestamp.Range.db_record()} | :error
defdelegate dump(value), to: PgFramestamp.Range
end
end

defimpl Inspect, for: Vtc.Framestamp.Range do
Expand Down
11 changes: 11 additions & 0 deletions priv/repo/migrations/20230706223647_add_framestamp_range_type.exs
@@ -0,0 +1,11 @@
defmodule Vtc.Test.Support.Repo.Migrations.AddFramestampRangeType do
@moduledoc false
use Ecto.Migration

alias Vtc.Ecto.Postgres.PgFramestamp

def change do
:ok = PgFramestamp.Range.Migrations.create_all()
:ok = PgFramestamp.Range.Migrations.create_all()
end
end
72 changes: 72 additions & 0 deletions test/ecto/postgres/pg_framestamp_range_test.exs
@@ -0,0 +1,72 @@
defmodule Vtc.Ecto.Postgres.PgFramestampRangeTest do
use Vtc.Test.Support.EctoCase, async: true
use ExUnitProperties

alias Ecto.Query
alias Vtc.Framestamp
alias Vtc.Rates

require Ecto.Query

describe "#SELECT" do
test "can construct exclusive range using type/2 fragment" do
in_stamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
out_stamp = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
stamp_range = Framestamp.Range.new!(in_stamp, out_stamp)

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

assert %Postgrex.Range{} = result = Repo.one!(query)
assert {:ok, ^stamp_range} = Framestamp.Range.load(result)
end

test "can construct inclusive range using type/2 fragment" do
in_stamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
out_stamp = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
stamp_range = Framestamp.Range.new!(in_stamp, out_stamp, out_type: :inclusive)

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

assert %Postgrex.Range{} = result = Repo.one!(query)
assert {:ok, ^stamp_range} = Framestamp.Range.load(result)
end

test "can construct exclusive range using native cast" do
in_stamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
out_stamp = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
stamp_range = Framestamp.Range.new!(in_stamp, out_stamp)

query =
Query.from(
f in fragment(
"SELECT framestamp_range(?, ?, '[)') as r",
type(^in_stamp, Framestamp),
type(^out_stamp, Framestamp)
),
select: f.r
)

assert %Postgrex.Range{} = result = Repo.one!(query)
assert {:ok, ^stamp_range} = Framestamp.Range.load(result)
end

test "can construct inclusive range using native cast" do
in_stamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
out_stamp = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
stamp_range = Framestamp.Range.new!(in_stamp, out_stamp, out_type: :inclusive)

query =
Query.from(
f in fragment(
"SELECT framestamp_range(?, ?, '[]') as r",
type(^in_stamp, Framestamp),
type(^out_stamp, Framestamp)
),
select: f.r
)

assert %Postgrex.Range{} = result = Repo.one!(query)
assert {:ok, ^stamp_range} = Framestamp.Range.load(result)
end
end
end