Packages

Simple library for working with data in DynamoDB.

Current section

Files

Jump to
exddb lib repo.ex
Raw

lib/repo.ex

defmodule Exddb.Repo do
@moduledoc ~S"""
This module acts as a wrapper for the database, routing calls to correct backend implementations.
Currently `:exddb` supports DynamoDB through `erlcloud` and usig the local DynamoDB implementation.
You are expected to implement a `Repo` module for your own app, for example:
defmodule ShopApp.Repo do
use Exddb.Repo, adapter: Exddb.Adapters.DynamoDB,
table_name_prefix: "myshopapp_#{to_string(Mix.env)}_"
end
You can then use your apps `Repo` module to make calls to the database:
iex> ShopApp.Repo.find(ShopApp.ReceiptModel, "123-456-789")
{:ok, %ShopApp.ReceiptModel{receipt_id: "123-456-789"", ...}}
"""
use Exddb.ConditionalOperation
@type t :: module
@type return_ok_item :: {:ok, Exddb.Model.t} | {:error, :any}
@callback create_table(model :: Exddb.Repo.t, write_units :: integer, read_units :: integer) :: :ok | :any
@callback delete_table(model :: Exddb.Repo.t) :: :ok | :any
@callback list_tables(options :: []) :: :any
@callback insert(record :: Exddb.Model.t) :: return_ok_item
@callback update(record :: Exddb.Model.t) :: return_ok_item
@callback delete(record :: Exddb.Model.t) :: return_ok_item
@callback find(model :: Exddb.Model.t, item_id :: String.t) :: {:ok, Exddb.Model.t} | :not_found | {:error, :any}
defmacro __using__(opts) do
quote do
alias Exddb.Adapter
import Exddb.Repo
@behaviour Exddb.Repo
@table_name_prefix unquote(Keyword.get(opts, :table_name_prefix)) || ""
@adapter unquote(Keyword.get(opts, :adapter) || Exddb.Adapters.DynamoDB)
def create_table(model, write_units \\ 1, read_units \\ 1) do
create_table(@adapter, model, table_name(model), write_units, read_units)
end
def delete_table(model) do
case @adapter.delete_table(table_name(model)) do
{:ok, _result} -> :ok
error -> error
end
end
def list_tables(options \\ []), do: @adapter.list_tables(options)
def insert(record, conditional_op \\ nil), do: insert(@adapter, table_name(record), record, conditional_op)
def update(record, conditional_op \\ nil), do: update(@adapter, table_name(record), record, conditional_op)
def delete(record, conditional_op \\ nil), do: delete(@adapter, table_name(record), record, conditional_op)
def find(model, record_id), do: find(@adapter, table_name(model), model, record_id)
def query(%Exddb.Query.QueryObject{model: module} = query) do
query(@adapter, table_name(module), query)
end
defp table_name(%{} = record), do: table_name(record.__struct__)
defp table_name(model), do: @table_name_prefix <> model.schema(:table_name)
end
end
@spec create_table(adapter :: Exddb.Adapter.t, model :: Exddb.Model.t, table_name :: String.t, write_units :: integer, read_units :: integer) :: :ok | {:error, :any}
def create_table(adapter, model, table_name, write_units, read_units) do
key = model.schema(:key)
create_table(adapter, model, table_name, key, write_units, read_units)
end
@doc ~S"""
Create table with hash and range key
"""
def create_table(adapter, model, table_name, {key, range}, write_units, read_units) do
key_type = model.schema(:field, key) |> Exddb.Type.dynamo_type
range_type = model.schema(:field, range) |> Exddb.Type.dynamo_type
case adapter.create_table(table_name, [{key, key_type}, {range, range_type}], {key, range}, write_units, read_units) do
{:ok, _result} -> :ok
error -> error
end
end
@doc ~S"""
Create table with hash key
"""
def create_table(adapter, model, table_name, key, write_units, read_units) when is_atom(key) do
key_type = model.schema(:field, key) |> Exddb.Type.dynamo_type
case adapter.create_table(table_name, {key, key_type}, key, write_units, read_units) do
{:ok, _result} -> :ok
error -> error
end
end
@spec insert(adapter :: Exddb.Adapter.t, table_name :: String.t, record :: Exddb.Model.t) :: return_ok_item
def insert(adapter, table_name, record, conditional_op \\ nil) do
if conditional_op == nil, do: conditional_op = conditional_op(not_exist: record)
{model, key} = metadata(record)
case model.__validate__(record) do
:ok ->
value = Map.get(record, key)
case adapter.put_item(table_name, {key, value}, Exddb.Type.dump(record), conditional_op) do
{:ok, []} -> {:ok, record}
{:error, error} -> {:error, error}
end
error -> {:error, error}
end
end
@spec update(adapter :: Exddb.Adapter.t, table_name :: String.t, record :: Exddb.Model.t) :: return_ok_item
def update(adapter, table_name, record, conditional_op \\ nil) do
if conditional_op == nil, do: conditional_op = conditional_op(exist: record)
{model, key} = metadata(record)
case model.__validate__(record) do
:ok ->
value = Map.get(record, key)
case adapter.put_item(table_name, {key, value}, Exddb.Type.dump(record), conditional_op) do
{:ok, []} -> {:ok, record}
{:error, error} -> {:error, error}
end
error -> {:error, error}
end
end
@spec delete(adapter :: Exddb.Adapter.t, table_name :: String.t, record :: Exddb.Model.t) :: return_ok_item
def delete(adapter, table_name, record, conditional_op \\ nil) do
if conditional_op == nil, do: conditional_op = conditional_op(exist: record)
{model, key} = metadata(record)
delete(adapter, table_name, model, key, record, conditional_op)
end
def delete(adapter, table_name, model, {key, range}, record, conditional_op) do
key_val = model.schema(:field, key) |> Exddb.Type.dump(Map.get(record, key))
range_val = model.schema(:field, range) |> Exddb.Type.dump(Map.get(record, range))
adapter.delete_item(table_name, [{to_string(key), key_val}, {to_string(range), range_val}], conditional_op)
|> parse_delete_resp(record)
end
def delete(adapter, table_name, model, key, record, conditional_op) do
key_type = model.schema(:field, key)
value = Map.get(record, key)
encoded_id = Exddb.Type.dump(key_type, value)
adapter.delete_item(table_name, {to_string(key), encoded_id}, conditional_op)
|> parse_delete_resp(record)
end
def parse_delete_resp(res, record) do
case res do
{:ok, nil} -> {:ok, record}
{:ok, []} -> {:ok, record}
{:error, error} -> {:error, error}
error -> {:error, error}
end
end
@spec find(adapter :: Exddb.Adapter.t, table_name :: String.t, model :: Exddb.Model.t, record_id :: :any) :: {:ok, Exddb.Model.t} | :not_found | {:error, :any}
def find(adapter, table_name, model, record_id) do
key = model.schema(:key)
find(adapter, table_name, model, key, record_id)
end
def find(adapter, table_name, model, {hash, range}, {hash_id, range_val}) do
encoded_hash = model.schema(:field, hash) |> Exddb.Type.dump(hash_id)
encoded_range = model.schema(:field, range) |> Exddb.Type.dump(range_val)
case adapter.get_item(table_name, [{to_string(hash), encoded_hash}, {to_string(range), encoded_range}]) do
{:ok, []} -> :not_found
{:ok, item} -> {:ok, Exddb.Type.parse(item, model.new)}
end
end
def find(adapter, table_name, model, key, record_id) when is_atom(key) do
key_type = model.schema(:field, key)
encoded_id = Exddb.Type.dump(key_type, record_id)
case adapter.get_item(table_name, {to_string(key), encoded_id}) do
{:ok, []} -> :not_found
{:ok, item} -> {:ok, Exddb.Type.parse(item, model.new)}
end
end
@spec query(adapter :: Exddb.Adapter.t, table_name :: String.t, query :: map) :: {:ok, :any} | {:error, :any}
def query(adapter, table_name, query) do
options = Keyword.put_new(query.options, :select, :all_attributes)
options = Keyword.put_new(options, :consistent_read, :true)
model = query.model
case adapter.query(table_name, query.query, options) do
{:ok, data} -> {:ok, data |> Stream.map(&model.parse(&1))}
{:error, error} -> {:error, error}
end
end
defp metadata(record) do
model = record.__struct__
key = model.schema(:key)
{model, key}
end
end