defmodule ExDbmigrate.Table.Server do
use GenServer
require Logger
@registry_name :tables
@assoc_types [:belongs_to, :many_to_many, :has_one, :has_many]
defstruct table: nil,
links: [],
incoming_links: [],
assoc_type: nil,
schema: %{},
timestamps: false
def child_spec(args) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [args]},
type: :worker
}
end
def schema(arg, data) do
name = via_tuple(arg)
GenServer.call(name, {:schema, data})
end
def links(arg, data) do
name = via_tuple(arg)
GenServer.call(name, {:links, data})
end
def send_incoming_links(arg) do
name = via_tuple(arg)
GenServer.call(name, :send_incoming_links)
end
def assoc_type(arg) do
name = via_tuple(arg)
GenServer.call(name, :assoc_type)
end
def init(init_arg) do
data = ExDbmigrate.list_foreign_keys(init_arg)
ref_type = ExDbmigrate.Config.key_type()
timestamp_fields =
Application.get_env(:ex_dbmigrate, :timestamp_fields, [:updated_at, :inserted_at])
link_data =
Enum.map(data, fn data ->
%{
column_name: data.column_name,
references: %{
ref_table: data.foreign_table_name,
ref_column: data.foreign_column_name,
type: ref_type
}
}
end)
assoc_type =
case Enum.count(link_data) do
x when x > 1 -> :many_to_many
1 -> :belongs_to
0 -> nil
end
data = ExDbmigrate.fetch_table_data(init_arg)
column_names =
Enum.map(link_data, fn data ->
data.column_name
end)
schema_data =
Enum.map(data.rows, fn [id, _is_null, type, _position, _max_length] ->
unless id == "id" || Enum.member?(column_names, id) do
type = String.to_atom(ExDbmigrate.type_select(type))
{String.to_atom(id), type}
end
end)
|> Enum.reject(fn x -> is_nil(x) end)
column_names =
Enum.map(schema_data, fn data ->
{id, _d} = data
id
end)
timestamps =
Enum.map(column_names, fn x ->
Enum.member?(timestamp_fields, x)
end)
|> Enum.reject(fn x -> x != true end)
|> Enum.count() > 0
schema_data = Keyword.drop(schema_data, timestamp_fields)
{:ok,
%__MODULE__{
table: init_arg,
links: link_data,
schema: schema_data,
timestamps: timestamps,
assoc_type: assoc_type
}}
end
def start_link([arg]) do
name = via_tuple(arg)
GenServer.start_link(__MODULE__, arg, name: name)
end
def shutdown() do
GenServer.call(__MODULE__, :shutdown)
end
def show(name) do
name = via_tuple(name)
GenServer.call(name, :show)
end
@impl true
def handle_call(
:send_incoming_links,
_from,
state
) do
Enum.map(state.links, fn x ->
name = via_tuple(x.references.ref_table)
references = %{x.references | ref_table: state.table}
x = %{x | references: references}
GenServer.call(name, {:incoming_link, x})
end)
{:reply, state, state}
end
@impl true
def handle_call(
:assoc_type,
_from,
state
) do
ref_type = ExDbmigrate.Config.key_type()
assoc_type =
case Enum.count(state.links) do
x when x > 1 -> :many_to_many
1 -> :belongs_to
0 -> nil
end
state = %{state | assoc_type: assoc_type}
{:reply, state, state}
end
@impl true
def handle_call(
{:links, data},
_from,
state
) do
link_data =
Enum.map(data, fn x ->
ref_type = ExDbmigrate.Config.key_type()
%{
column_name: x.column_name,
references: %{ref_table: x.foreign_table_name, type: ref_type}
}
end)
state = %{state | links: link_data}
{:reply, state, state}
end
def incoming_link(name, data) do
name = via_tuple(name)
GenServer.call(name, {:incoming_link, data})
end
@impl true
def handle_call(
{:incoming_link, data},
_from,
state
) do
link_data = state.incoming_links ++ [data]
state = %{state | incoming_links: link_data}
{:reply, state, state}
end
def handle_call(
{:schema, data},
_from,
state
) do
state = %{state | schema: data}
{:reply, state, state}
end
def handle_call(
:shutdown,
_from,
state
) do
{:stop, {:ok, "Normal Shutdown"}, state}
end
def handle_call(
:show,
_from,
state
) do
{:reply, state, state}
end
def handle_cast(
:shutdown,
state
) do
{:stop, :normal, state}
end
@doc false
def via_tuple(data, registry \\ @registry_name) do
{:via, Registry, {registry, data}}
end
@impl true
def handle_info({:DOWN, ref, :process, _pid, _reason}, {names, refs}) do
{:noreply, {names, refs}}
end
@impl true
def handle_info(_msg, state) do
{:noreply, state}
end
end