Current section

Files

Jump to
nuamnesia lib amnesia table.ex
Raw

lib/amnesia/table.ex

# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# Version 2, December 2004
#
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
# 0. You just DO WHAT THE FUCK YOU WANT TO.
defmodule Amnesia.Table do
@type cv :: :disk | :disk! | :memory
@type c :: [{ cv, [node] }]
@type o :: :ok | { :error, any }
alias Amnesia.Selection
alias Amnesia.Table.Select
alias Amnesia.Table.Match
alias Amnesia.Helper.Options
@doc """
Wait for the passed tables for the given timeout, see `mnesia:wait_for_tables`.
"""
@spec wait([atom]) :: :ok | { :timeout, [atom] } | { :error, atom }
@spec wait([atom], integer | :infinity) :: :ok | { :timeout, [atom] } | { :error, atom }
def wait(names, timeout \\ :infinity) do
:mnesia.wait_for_tables(names, timeout)
end
@doc """
Force the loading of the given table, see `mnesia:force_load_table`.
"""
@spec force(atom) :: :yes | { :error, any }
def force(name) do
:mnesia.force_load_table(name)
end
@doc """
Checks if a table exists or not.
"""
@spec exists?(atom) :: boolean
def exists?(name) do
:mnesia.system_info(:tables) |> Enum.member?(name)
end
@doc """
Create a table with the given name and definition, see `mnesia:create_table`.
The definition is a keyword list of options which have a correspondence with
`mnesia` options, to know what they do check the `mnesia` documentation.
* `:record` => `:record_name`
* `:attributes`
* `:mode` => `:access_mode`
+ `:both` => `:read_write`
+ `:read!` => `:read_only`
* `:type`
* `:index`
* `:majority`
* `:priority` => `:load_order`
* `:user` => `:user_properties`
* `:local` => `:local_content`
* `:copying` _(a keyword list composed of)_
+ `:memory` => `:ram_copies`
+ `:disk` => `:disc_copies`
+ `:disk!` => `:disc_only_copies`
+ `:rock!` => `:rocksdb_copies` -- [Will be moved to a formal plug-in, extension system - noizu]
* `:fragmentation` => `:frag_properties` _(a keyword list composed of)_
+ `:number` => `:n_fragments`
+ `:nodes` => `:node_pool`
+ `:foreign` => `:foreign_key`
+ `:hash` _(a keyword list composed of)_
+ `:module` => `:hash_module`
+ `:state` => `:hash_state`
+ `:copying` _(a keyword list composed of)_
- `:memory` => `:n_ram_copies`
- `:disk` => `:n_disc_copies`
- `:disk!` => `:n_disc_only_copies`
- `:rock!` => `:n_rocksdb_copies`
[@see storage options](http://erlang.org/doc/man/mnesia.html#create_table "storage_settings options")
[@see ets options](http://erlang.org/doc/man/ets.html#new-2 "ets options")
[@see dets options](http://erlang.org/doc/man/dets.html#init_table-3 "dets options")
+ :ets_options => `:compressed`
+ :dets_options => `{:auto_save, 5000}`
+ :compressed => `true`
[@see rock options](https://github.com/aeternity/mnesia_rocksdb/blob/g3553-refactor-plugin-migration-tmp-220318/doc/README.md#Customization "rocksdb options")
+ :rock_options => `plugin specific` -- [Will be moved to a formal plug-in, extension system, each plugin should expose it's own option set even if folded into user_props internalls - noizu]
"""
@spec create(atom) :: o
@spec create(atom, c) :: o
def create(name, definition \\ []) do
args = Keyword.new
|> Options.update(:record_name, Keyword.get(definition, :record, name))
|> Options.update(:attributes, definition[:attributes])
|> Options.update(:type, definition[:type])
|> Options.update(:index, definition[:index])
|> Options.update(:majority, definition[:majority])
|> Options.update(:load_order, definition[:priority])
|> Options.update(:user_properties, definition[:user])
|> Options.update(:local_content, definition[:local])
|> Options.update(:ram_copies, definition[:copying][:memory])
|> Options.update(:disc_copies, definition[:copying][:disk])
|> Options.update(:disc_only_copies, definition[:copying][:disk!])
# Will be moved to plugin system -- noizu
|> Options.update(:rocksdb_copies, definition[:copying][:rock!])
# rocksdb plugin - noizu
args = case definition[:rock_options] do
rock_options when is_list(rock_options) ->
args
|> update_in([:user_properties], &(&1 || []))
|> update_in([:user_properties, :rocksdb_opts], rock_options)
_ -> args
end
ets_options = cond do
definition[:ets_options] ->
ets_options = is_list(definition[:ets_options]) && definition[:ets_options] || [definition[:ets_options]]
cond do
definition[:compressed] && !Enum.member?(ets_options, :compressed) -> ets_options ++ [:compressed]
true -> ets_options
end
definition[:compressed] -> [:compressed]
true -> nil
end
dets_options = cond do
definition[:dets_options] -> is_list(definition[:dets_options]) && definition[:dets_options] || [definition[:dets_options]]
true -> nil
end
args = case (ets_options && [{:ets, ets_options}] || []) ++ (dets_options && [{:dets, dets_options}] || []) do
[] -> args
storage_properties -> Options.update(args, :storage_properties, storage_properties)
end
args = if fragmentation = definition[:fragmentation] do
properties = Keyword.new
|> Options.update(:n_fragments, fragmentation[:number])
|> Options.update(:node_pool, fragmentation[:nodes])
|> Options.update(:n_ram_copies, fragmentation[:copying][:memory])
|> Options.update(:n_disc_copies, fragmentation[:copying][:disk])
|> Options.update(:n_rocksdb_copies, fragmentation[:copying][:rock!])
|> Options.update(:n_disc_only_copies, fragmentation[:copying][:disk!])
|> Options.update(:foreign_key, fragmentation[:foreign][:key])
|> Options.update(:hash_module, fragmentation[:hash][:module])
|> Options.update(:hash_state, fragmentation[:hash][:state])
Keyword.put(args, :frag_properties, properties)
else
args
end
args = Options.update(args, :access_mode,
if mode = definition[:mode] || :both do
case mode do
:both -> :read_write
:read! -> :read_only
end
end)
:mnesia.create_table(name, args) |> result
end
@doc """
Create a table with the given name and definition, see `mnesia:create_table`,
raises in case of error.
"""
@spec create!(atom) :: :ok | no_return
@spec create!(atom, c) :: :ok | no_return
def create!(name, definition \\ []) do
case create(name, definition) do
:ok ->
:ok
{ :error, { :already_exists, _ } } ->
raise Amnesia.TableExistsError, name: name
end
end
@doc """
Transform a table, useful to change tables in a running instance, see
`mnesia:transform_table`.
"""
@spec transform(atom, [atom]) :: o
def transform(name, attributes) do
:mnesia.transform_table(name, :ignore, attributes) |> result
end
@doc """
Transform a table, useful to change tables in a running instance, see
`mnesia:transform_table`.
"""
@spec transform(atom, [atom] | atom, (tuple -> tuple) | [atom]) :: o
def transform(name, attributes, fun) when is_function(fun) do
:mnesia.transform_table(name, fun, attributes) |> result
end
def transform(name, new_name, attributes) do
:mnesia.transform_table(name, :ignore, attributes, new_name) |> result
end
@doc """
Transform a table, renaming it, useful to change tables in a running
instance, see `mnesia:transform_table`.
"""
@spec transform(atom, atom, [atom], (tuple -> tuple)) :: o
def transform(name, new_name, attributes, fun) do
:mnesia.transform_table(name, fun, attributes, new_name) |> result
end
@doc """
Get information about a given table, see `mnesia:table_info`.
"""
@spec info(atom, atom) :: any
def info(name, key) do
:mnesia.table_info(name, key)
end
@doc """
Return properties of the given table.
"""
@spec properties(atom) :: Keyword.t
def properties(name) do
props = info(name, :all)
[ version: props[:version],
type: props[:type],
mode: props[:access_mode],
attributes: props[:attributes],
record: props[:record_name],
arity: props[:arity],
checkpoints: props[:checkpoints],
cookie: props[:cookie],
user: props[:user_properties],
storage: case props[:storage_type] do
:ram_copies -> :memory
:disc_copies -> :disk
:disc_only_copies -> :disk!
#plugin
:rocksdb_copies -> :rock!
:unknown -> :remote
end,
master_nodes: props[:master_nodes],
where: [
read: props[:where_to_read],
write: props[:where_to_write]
],
load: [
node: props[:load_node],
order: props[:load_order],
reason: props[:load_reason]
],
copying: [
memory: props[:ram_copies],
disk: props[:disc_copies],
disk!: props[:disc_only_copies]
],
size: props[:size],
memory: props[:memory] ]
end
@doc """
Return the type of the given table.
"""
@spec type(atom) :: :set | :ordered_set | :bag
def type(name) do
info(name, :type)
end
@doc """
Check if the given table is a bag.
"""
@spec bag?(atom) :: boolean
def bag?(name) do
type(name) == :bag
end
@doc """
Check if the given table is a set.
"""
@spec set?(atom) :: boolean
def set?(name) do
type(name) == :set
end
@doc """
Check if the given table is an ordered set.
"""
@spec ordered_set?(atom) :: boolean
def ordered_set?(name) do
type(name) == :ordered_set
end
@doc """
Change the access mode of the given table, see `mnesia:change_table_access_mode`.
## Modes
* `:both` sets read and write mode, it's the default.
* `:read!` sets read-only mode.
"""
@spec mode(atom, :both | :read!) :: o
def mode(name, value) do
:mnesia.change_table_access_mode(name, case value do
:both -> :read_write
:read! -> :read_only
end) |> result
end
@doc """
Change the copying mode of the given table on the given node, see
`mnesia:change_table_copy_type`.
## Modes
* `:disk` sets `:disc_copies` mode
* `:disk!` sets `:disc_only_copies` mode
* `:memory` sets `:ram_copies` mode
"""
@spec copying(atom, node, cv) :: o
def copying(name, node, to) do
:mnesia.change_table_copy_type(name, node, case to do
:disk -> :disc_copies
:disk! -> :disc_only_copies
:memory -> :ram_copies
# plugin
:rock! -> :rocksdb_copies
end) |> result
end
@doc """
Change the given table loading priority.
"""
@spec priority(atom, integer) :: o
def priority(name, value) do
:mnesia.change_table_load_order(name, value) |> result
end
@doc """
Change the given table majority, see `mnesia:change_table_majority`.
"""
@spec majority(atom, boolean) :: o
def majority(name, value) do
:mnesia.change_table_majority(name, value) |> result
end
@doc """
Add a copy of the table to the given node with the given mode, see
`mnesia:add_table_copy`.
* `:disk` sets `:disc_copies` mode
* `:disk!` sets `:disc_only_copies` mode
* `:memory` sets `:ram_copies` mode
"""
@spec add_copy(atom, node) :: o
@spec add_copy(atom, node, cv) :: o
def add_copy(name, node, type \\ :disk) do
:mnesia.add_table_copy(name, node, case type do
:disk -> :disc_copies
:disk! -> :disc_only_copies
:memory -> :ram_copies
:rock! -> :rocksdb_copies
end) |> result
end
@doc """
Move the copy of the given table from the given node to another given
node, see `mnesia:move_table_copy`.
"""
@spec move_copy(atom, node, node) :: o
def move_copy(name, from, to) do
:mnesia.move_table_copy(name, from, to) |> result
end
@doc """
Delete a copy of the table on the given node, see `mnesia:del_table_copy`.
"""
@spec delete_copy(atom, node) :: o
def delete_copy(name, node) do
:mnesia.del_table_copy(name, node) |> result
end
@doc """
Add an index to the given table for the given attribute, see
`mnesia:add_table_index`.
"""
@spec add_index(atom, atom) :: o
def add_index(name, attribute) do
:mnesia.add_table_index(name, attribute) |> result
end
@doc """
Delete an index on the given table for the given attribute, see
`mnesia:del_table_index`.
"""
@spec delete_index(atom, atom) :: o
def delete_index(name, attribute) do
:mnesia.del_table_index(name, attribute) |> result
end
@doc """
Set master nodes for the given table, see `mnesia:set_master_nodes`.
"""
@spec master_nodes(atom, [node]) :: :ok | { :error, any }
def master_nodes(name, nodes) do
:mnesia.set_master_nodes(name, nodes)
end
@doc """
Lock the given table for the given kind of lock, see `mnesia:lock`.
## Locks
* `:write` sets a `:write` lock
* `:write!` sets a `:sticky_write` lock
* `:read` sets a `:read` lock
"""
@spec lock(atom, :write | :write! | :read) :: [node] | :ok | no_return
def lock(name, mode) do
:mnesia.lock({ :table, name }, case mode do
:write -> :write
:write! -> :sticky_write
:read -> :read
end)
end
@doc """
Destroy the given table, see `mnesia:delete_table`.
"""
@spec destroy(atom) :: o
def destroy(name) do
:mnesia.delete_table(name) |> result
end
@doc """
Destroy the given table, see `mnesia:delete_table`, raising in case of error.
"""
@spec destroy!(atom) :: :ok | no_return
def destroy!(name) do
case :mnesia.delete_table(name) do
{ :atomic, :ok } ->
:ok
{ :aborted, { :no_exists, _ } } ->
raise Amnesia.TableMissingError, name: name
end
end
@doc """
Clear the given table, see `mnesia:clear_table`.
"""
@spec clear(atom) :: o
def clear(name) do
:mnesia.clear_table(name) |> result
end
@doc """
Check if the key is present in the given table.
"""
@spec member?(atom, any) :: boolean
def member?(name, key) do
case :mnesia.dirty_read(name, key) do
[] -> false
_ -> true
end
end
@doc """
Get the number of records in the given table.
"""
@spec count(atom) :: non_neg_integer
def count(name) do
info(name, :size)
end
@doc """
Read records from the given table with the given key, locking in the given
mode, see `mnesia:read`.
* `:write` sets a `:write` lock
* `:write!` sets a `:sticky_write` lock
* `:read` sets a `:read` lock
"""
@spec read(atom, any) :: [tuple] | no_return
@spec read(atom, any, :read | :write | :write!) :: [tuple] | nil | no_return
def read(name, key, lock \\ :read) do
case :mnesia.read(name, key, case lock do
:read -> :read
:write -> :write
:write! -> :sticky_write
end) do
[] -> nil
r -> r
end
end
@doc """
Read records from the given table with the given key, see `mnesia:dirty_read`.
"""
@spec read!(atom, any) :: [tuple] | nil | no_return
def read!(name, key) do
case :mnesia.dirty_read(name, key) do
[] -> nil
r -> r
end
end
@doc """
Read records on the given table based on a secondary index given as position,
see `mnesia:index_read`.
"""
@spec read_at(atom, any, integer | atom) :: [tuple] | nil | no_return
def read_at(name, key, position) do
case :mnesia.index_read(name, key, position) do
[] -> nil
r -> r
end
end
@doc """
Read records on the given table based on a secondary index given as position,
see `mnesia:dirty_index_read`.
"""
@spec read_at!(atom, any, integer | atom) :: [tuple] | nil | no_return
def read_at!(name, key, position) do
case :mnesia.dirty_index_read(name, key, position) do
[] -> nil
r -> r
end
end
@doc """
Read all keys in the given table, see `mnesia:all_keys`.
"""
@spec keys(atom) :: list | no_return
def keys(name) do
:mnesia.all_keys(name)
end
@doc """
Read all keys in the given table, see `mnesia:dirty_all_keys`.
"""
@spec keys!(atom) :: list | no_return
def keys!(name) do
:mnesia.dirty_all_keys(name)
end
@doc """
Read a slot from the given table, see `mnesia:dirty_slot`.
"""
@spec at!(atom, integer) :: tuple | nil | no_return
def at!(name, position) do
case :mnesia.dirty_slot(name, position) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Get the first key in the table, see `mnesia:first`.
"""
@spec first(atom) :: any | nil | no_return
def first(name) do
case :mnesia.first(name) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Get the first key in the table, see `mnesia:dirty_read`.
"""
@spec first!(atom) :: any | nil | no_return
def first!(name) do
case :mnesia.dirty_first(name) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Get the next key in the table starting from the given key, see `mnesia:next`.
"""
@spec next(atom, any) :: any | nil | no_return
def next(name, key) do
case :mnesia.next(name, key) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Get the next key in the table starting from the given key, see
`mnesia:dirty_next`.
"""
@spec next!(atom, any) :: any | nil | no_return
def next!(name, key) do
case :mnesia.dirty_next(name, key) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Get the previous key in the table starting from the given key, see
`mnesia:prev`.
"""
@spec prev(atom, any) :: any | nil | no_return
def prev(name, key) do
case :mnesia.prev(name, key) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Get the previous key in the table starting from the given key, see
`mnesia:dirty_prev`.
"""
@spec prev!(atom, any) :: any | nil | no_return
def prev!(name, key) do
case :mnesia.dirty_prev(name, key) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Get the last key in the table, see `mnesia:last`.
"""
@spec last(atom) :: any | nil | no_return
def last(name) do
case :mnesia.last(name) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Get the last key in the table, see `mnesia:dirty_last`.
"""
@spec last!(atom) :: any | nil | no_return
def last!(name) do
case :mnesia.dirty_last(name) do
:"$end_of_table" -> nil
value -> value
end
end
@doc """
Select records in the given table using a match_spec, see `mnesia:select`.
"""
@spec select(atom, any) :: Selection.t | nil | no_return
def select(name, spec) do
Select.new(:mnesia.select(name, spec))
end
@doc """
Select records in the given table using a match_spec passing a limit or a
lock kind, see `mnesia:select`.
"""
@spec select(atom, integer | :read | :write, any) :: Selection.t | nil | no_return
def select(name, limit, spec) when is_integer limit do
Select.new(:mnesia.select(name, spec, limit, :read))
end
def select(name, lock, spec) when lock in [:read, :write] do
Select.new(:mnesia.select(name, spec, lock))
end
@doc """
Select records in the given table using a match_spec passing a limit and a
lock kind, see `mnesia:select`.
"""
@spec select(atom, integer | :read | :write, integer | :read | :write, integer) :: Selection.t | nil | no_return
def select(name, lock, limit, spec) when lock in [:read, :write] and is_integer limit do
Select.new(:mnesia.select(name, spec, limit, lock))
end
def select(name, limit, lock, spec) when lock in [:read, :write] and is_integer limit do
Select.new(:mnesia.select(name, spec, limit, lock))
end
@doc """
Select records in the given table using a match_spec, see
`mnesia:dirty_select`.
"""
@spec select!(atom, any) :: Selection.t | nil | no_return
def select!(name, spec) do
Select.new(:mnesia.dirty_select(name, spec))
end
@doc """
Select records in the given table using simple don't care values, see
`mnesia:match_object`.
"""
@spec match(atom, :read | :write, any) :: [tuple] | nil | no_return
def match(name, lock \\ :read, pattern) do
Match.new(:mnesia.match_object(name, pattern, lock))
end
@doc """
Select records in the given table using simple don't care values, see
`mnesia:dirty_match_object`.
"""
@spec match(atom, any) :: [tuple] | nil | no_return
def match!(name, pattern) do
Match.new(:mnesia.dirty_match_object(name, pattern))
end
@doc """
Fold the whole given table from the left, see `mnesia:foldl`.
"""
@spec foldl(atom, any, (tuple, any -> any)) :: any | no_return
def foldl(name, acc, fun) do
:mnesia.foldl(fun, acc, name)
end
@doc """
Fold the whole given table from the right, see `mnesia:foldr`.
"""
@spec foldl(atom, any, (tuple, any -> any)) :: any | no_return
def foldr(name, acc, fun) do
:mnesia.foldr(fun, acc, name)
end
def stream(name, lock \\ :read) do
Amnesia.Table.Stream.new(name, type(name), lock: lock)
end
def stream!(name) do
Amnesia.Table.Stream.new(name, type(name), dirty: true)
end
@doc """
Delete the given record in the given table, see `mnesia:delete`.
## Locks
* `:write` sets a `:write` lock
* `:write!` sets a `:sticky_write` lock
"""
@spec delete(atom, any) :: :ok | no_return
@spec delete(atom, any, :write | :write!) :: :ok | no_return
def delete(name, key, lock \\ :write) do
:mnesia.delete(name, key, case lock do
:write -> :write
:write! -> :sticky_write
end)
end
@doc """
Delete the given record in the given table, see `mnesia:dirty_delete`.
"""
@spec delete!(atom, any) :: :ok | no_return
def delete!(name, key) do
:mnesia.dirty_delete(name, key)
end
@doc """
Write the given record in the given table, using the given lock, see
`mnesia:write`.
## Locks
* `:write` sets a `:write` lock
* `:write!` sets a `:sticky_write` lock
"""
@spec write(atom, tuple) :: :ok | no_return
@spec write(atom, tuple, :write | :write!) :: :ok | no_return
def write(name, data, lock \\ :write) do
:mnesia.write(name, data, case lock do
:write -> :write
:write! -> :sticky_write
end)
end
@doc """
Write the given record in the given table, see `mnesia:dirty_write`.
"""
@spec write!(atom, tuple) :: :ok | no_return
def write!(name, data) do
:mnesia.dirty_write(name, data)
end
@doc false
def result({ :atomic, :ok }) do
:ok
end
def result({ :aborted, reason }) do
{ :error, reason }
end
end