Current section

Files

Jump to
dbkv lib dvkv.ex
Raw

lib/dvkv.ex

defmodule DBKV do
@moduledoc """
A disk-based key-value store built on top of [`:dets`](https://erlang.org/doc/man/dets.html).
Inspired by [CubDB](https://github.com/lucaong/cubdb)'s intuitive API.
"""
alias DBKV.BooleanMatchSpec
alias DBKV.FinderMatchSpec
#
# Table
#
@spec open(keyword) :: {:ok, atom} | {:error, any}
def open(opts \\ []) do
name = Keyword.fetch!(opts, :name)
data_dir = opts[:data_dir] || "tmp"
File.mkdir_p!(data_dir)
:dets.open_file(dets_name(name), file: dets_file(data_dir, name), type: :set)
end
@deprecated "Use open/1 instead"
def create_table(opts \\ []) do
case open(opts) do
{:ok, _} -> :ok
error -> error
end
end
defp dets_name(name) when is_atom(name), do: name
defp dets_file(data_dir, name), do: :binary.bin_to_list("#{data_dir}/#{name}.db")
@spec close(atom) :: :ok | {:error, any}
def close(table_name) do
:dets.close(table_name)
end
@deprecated "Use close/1 instead"
def delete_table(table_name), do: delete_table(table_name)
@spec info(atom) :: map | :undefined
def info(table_name) when is_atom(table_name) do
case :dets.info(table_name) do
:undefined -> :undefined
info_list -> Enum.into(info_list, %{})
end
end
@deprecated "Use info/1 instead"
def describe_table(table_name), do: info(table_name)
@spec exist?(atom) :: boolean
def exist?(table_name) when is_atom(table_name) do
table_name in :dets.all()
end
@spec size(atom) :: integer | :undefined
def size(table_name) when is_atom(table_name) do
case :dets.info(table_name, :size) do
:undefined -> :undefined
size -> size
end
end
#
# CRUD
#
@spec has_key?(atom, any) :: boolean
def has_key?(table_name, key) when is_atom(table_name) do
case :dets.member(table_name, key) do
true -> true
_ -> false
end
end
@spec get(atom, any, any) :: any
def get(table_name, key, default \\ nil) when is_atom(table_name) do
case :dets.lookup(table_name, key) do
[] -> default
[{_key, value} | _rest] -> value
end
end
@spec put(atom, any, any) :: :ok | {:error, any}
def put(table_name, key, value) when is_atom(table_name) do
:dets.insert(table_name, [{key, value}])
end
@spec put_new(atom, any, any) :: :ok | {:error, any}
def put_new(table_name, key, value) when is_atom(table_name) do
case :dets.insert_new(table_name, [{key, value}]) do
false -> {:error, :exists}
true -> :ok
error -> error
end
end
@spec update(atom, any, any, (any -> any)) :: :ok | {:error, any}
def update(table_name, key, default, fun) when is_atom(table_name) and is_function(fun) do
case get(table_name, key) do
nil -> put(table_name, key, default)
value -> put(table_name, key, fun.(value))
end
end
@spec delete(atom, any) :: :ok | {:error, any}
def delete(table_name, key) when is_atom(table_name) do
:dets.delete(table_name, key)
end
@spec delete_all(atom) :: :ok | {:error, any}
def delete_all(table_name) when is_atom(table_name) do
:dets.delete_all_objects(table_name)
end
#
# Counter
#
@spec increment(atom, any, number) :: number
def increment(table_name, key, by) do
:dets.update_counter(table_name, key, by)
end
@spec decrement(atom, any, number) :: number
def decrement(table_name, key, by) do
:dets.update_counter(table_name, key, -by)
end
#
# Select
#
@spec all(atom) :: list
def all(table_name) do
match_spec = FinderMatchSpec.all()
select_by_match_spec(table_name, match_spec)
end
@spec keys(atom) :: list
def keys(table_name) do
match_spec = FinderMatchSpec.keys()
select_by_match_spec(table_name, match_spec)
end
@spec values(atom) :: list
def values(table_name) do
match_spec = FinderMatchSpec.values()
select_by_match_spec(table_name, match_spec)
end
@spec select_by_match_spec(atom, list) :: list
def select_by_match_spec(table_name, match_spec) do
:dets.select(table_name, match_spec)
end
@spec select_by_key_range(atom, any, any, list) :: list
def select_by_key_range(table_name, min_key, max_key, opts \\ []) do
match_spec = FinderMatchSpec.key_range(min_key, max_key, opts)
select_by_match_spec(table_name, match_spec)
end
@spec select_by_min_key(atom, any) :: list
def select_by_min_key(table_name, min_key) do
match_spec = FinderMatchSpec.min_key(min_key)
select_by_match_spec(table_name, match_spec)
end
@spec select_by_max_key(atom, any, list) :: list
def select_by_max_key(table_name, max_key, opts \\ []) do
match_spec = FinderMatchSpec.max_key(max_key, opts)
select_by_match_spec(table_name, match_spec)
end
@spec select_by_value_range(atom, any, any, list) :: list
def select_by_value_range(table_name, min_value, max_value, opts \\ []) do
match_spec = FinderMatchSpec.value_range(min_value, max_value, opts)
select_by_match_spec(table_name, match_spec)
end
@spec select_by_min_value(atom, any) :: list
def select_by_min_value(table_name, min_value) do
match_spec = FinderMatchSpec.min_value(min_value)
select_by_match_spec(table_name, match_spec)
end
@spec select_by_max_value(atom, any, list) :: list
def select_by_max_value(table_name, max_value, opts \\ []) do
match_spec = FinderMatchSpec.max_value(max_value, opts)
select_by_match_spec(table_name, match_spec)
end
#
# Select delete
#
@spec delete_by_match_spec(atom, list) :: integer | {:error, any}
def delete_by_match_spec(table_name, match_spec) do
:dets.select_delete(table_name, match_spec)
end
@spec delete_by_key_range(atom, any, any, list) :: integer | {:error, any}
def delete_by_key_range(table_name, min_key, max_key, opts \\ []) do
match_spec = BooleanMatchSpec.key_range(min_key, max_key, opts)
delete_by_match_spec(table_name, match_spec)
end
@spec delete_by_min_key(atom, any) :: integer | {:error, any}
def delete_by_min_key(table_name, min_key) do
match_spec = BooleanMatchSpec.min_key(min_key)
delete_by_match_spec(table_name, match_spec)
end
@spec delete_by_max_key(atom, any, list) :: integer | {:error, any}
def delete_by_max_key(table_name, max_key, opts \\ []) do
match_spec = BooleanMatchSpec.max_key(max_key, opts)
delete_by_match_spec(table_name, match_spec)
end
@spec delete_by_value_range(atom, any, any, list) :: integer | {:error, any}
def delete_by_value_range(table_name, min_value, max_value, opts \\ []) do
match_spec = BooleanMatchSpec.value_range(min_value, max_value, opts)
delete_by_match_spec(table_name, match_spec)
end
@spec delete_by_min_value(atom, any) :: integer | {:error, any}
def delete_by_min_value(table_name, min_value) do
match_spec = BooleanMatchSpec.min_value(min_value)
delete_by_match_spec(table_name, match_spec)
end
@spec delete_by_max_value(atom, any, list) :: integer | {:error, any}
def delete_by_max_value(table_name, max_value, opts \\ []) do
match_spec = BooleanMatchSpec.max_value(max_value, opts)
delete_by_match_spec(table_name, match_spec)
end
end