Current section

Files

Jump to
spawn_statestores_postgres lib statestores adapters postgres_snapshot_adapter.ex
Raw

lib/statestores/adapters/postgres_snapshot_adapter.ex

defmodule Statestores.Adapters.PostgresSnapshotAdapter do
@moduledoc """
Implements the behavior defined in `Statestores.Adapters.SnapshotBehaviour` for Postgres databases.
"""
use Statestores.Adapters.SnapshotBehaviour
use Ecto.Repo,
otp_app: :spawn_statestores,
adapter: Ecto.Adapters.Postgres
alias Statestores.Schemas.{Snapshot, HistoricalSnapshot}
def get_by_key(id), do: get_by(Snapshot, id: id)
def get_by_key_and_revision(id, revision) do
query = """
SELECT *
FROM historical_snapshots
WHERE actor_id = #{id}
AND revision = #{revision}
ORDER BY inserted_at DESC, updated_at DESC
"""
%Postgrex.Result{rows: rows} =
Ecto.Adapters.SQL.query!(Statestores.Adapters.PostgresSnapshotAdapter, query)
List.first(rows)
|> to_snapshot(:historical)
|> case do
nil -> get_by_key(id)
response -> response
end
end
def get_all_snapshots_by_key(id) do
query = "SELECT * FROM historical_snapshots WHERE actor_id = #{id}"
%Postgrex.Result{rows: rows} =
Ecto.Adapters.SQL.query!(Statestores.Adapters.PostgresSnapshotAdapter, query)
rows
|> Enum.map(&to_snapshot(&1, :historical))
end
def get_snapshots_by_interval(id, time_start, time_end) do
query = """
SELECT *
FROM historical_snapshots
WHERE actor_id = #{id}
AND valid_from <= '#{time_end}'
AND valid_to >= '#{time_start}'
ORDER BY inserted_at ASC, updated_at ASC
"""
%Postgrex.Result{rows: rows} =
Ecto.Adapters.SQL.query!(Statestores.Adapters.PostgresSnapshotAdapter, query)
rows
|> Enum.map(&to_snapshot(:historical, &1))
end
def save(%Snapshot{} = actual_snapshot) do
__MODULE__.transaction(fn ->
case get_by_key(actual_snapshot.id) do
nil ->
# Insert the new snapshot as there's no previous one
insert_new_snapshot(actual_snapshot)
previous_snapshot ->
# Move the previous snapshot to historical_snapshots
move_to_historical_snapshots(previous_snapshot)
# Update the current snapshot with the new data
update_snapshot(previous_snapshot, actual_snapshot)
end
end)
end
defp insert_new_snapshot(%Snapshot{} = snapshot) do
snapshot
|> Map.put(:revision, 1)
|> __MODULE__.insert()
end
defp move_to_historical_snapshots(%Snapshot{} = previous_snapshot) do
%HistoricalSnapshot{
actor_id: previous_snapshot.id,
actor: previous_snapshot.actor,
system: previous_snapshot.system,
status: previous_snapshot.status,
node: previous_snapshot.node,
revision: previous_snapshot.revision,
tags: previous_snapshot.tags,
data_type: previous_snapshot.data_type,
data: previous_snapshot.data,
valid_from: previous_snapshot.inserted_at,
valid_to: previous_snapshot.updated_at
}
|> __MODULE__.insert()
end
defp update_snapshot(%Snapshot{} = previous_snapshot, %Snapshot{} = actual_snapshot) do
changeset =
actual_snapshot
|> Map.put(:revision, previous_snapshot.revision + 1)
|> build_snapshot_changeset(previous_snapshot)
__MODULE__.update(changeset)
end
def default_port, do: "5432"
defp build_snapshot_changeset(%Snapshot{} = snapshot, %Snapshot{} = previous_snapshot) do
previous_snapshot
|> Snapshot.changeset(%{
id: snapshot.id,
actor: snapshot.actor,
system: snapshot.system,
status: snapshot.status,
node: snapshot.node,
revision: snapshot.revision || 0,
tags: snapshot.tags,
data_type: snapshot.data_type,
data: snapshot.data
})
end
defp to_snapshot(row, :current) do
data = Statestores.Vault.decrypt!(Enum.at(row, 8))
%Snapshot{
id: Enum.at(row, 0),
actor: Enum.at(row, 1),
system: Enum.at(row, 2),
status: Enum.at(row, 3),
node: Enum.at(row, 4),
revision: Enum.at(row, 5),
tags: Enum.at(row, 6),
data_type: Enum.at(row, 7),
data: data,
inserted_at: Enum.at(row, 9),
updated_at: Enum.at(row, 10)
}
end
defp to_snapshot(row, :historical) do
data = Statestores.Vault.decrypt!(Enum.at(row, 9))
%Snapshot{
id: Enum.at(row, 1),
actor: Enum.at(row, 2),
system: Enum.at(row, 3),
status: Enum.at(row, 4),
node: Enum.at(row, 5),
revision: Enum.at(row, 6),
tags: Enum.at(row, 7),
data_type: Enum.at(row, 8),
data: data,
inserted_at: Enum.at(row, 12),
updated_at: Enum.at(row, 13)
}
end
end