defmodule ETS.Set do
@moduledoc """
Module for creating and interacting with :ets tables of the type `:set` and `:ordered_set`.
Sets contain "records" which are tuples. Sets are configured with a key position via the `keypos: integer` option.
If not specified, the default key position is 1. The element of the tuple record at the key position is that records key.
For example, setting the `keypos` to 2 means the key of an inserted record `{:a, :b}` is `:b`:
iex> {:ok, set} = Set.new(keypos: 2)
iex> Set.put!(set, {:a, :b})
iex> Set.get(set, :a)
{:ok, nil}
iex> Set.get(set, :b)
{:ok, {:a, :b}}
When a record is added to the table with `put`, it will overwrite an existing record
with the same key. `put_new` will only put the record if a matching key doesn't already exist.
## Examples
iex> {:ok, set} = Set.new(ordered: true)
iex> Set.put_new!(set, {:a, :b, :c})
iex> Set.to_list!(set)
[{:a, :b, :c}]
iex> Set.put_new!(set, {:d, :e, :f})
iex> Set.to_list!(set)
[{:a, :b, :c}, {:d, :e, :f}]
iex> Set.put_new!(set, {:a, :g, :h})
iex> Set.to_list!(set)
[{:a, :b, :c}, {:d, :e, :f}]
`put` and `put_new` take either a single tuple or a list of tuples. When inserting multiple records,
they are inserted in an atomic an isolated manner. `put_new` doesn't insert any records if any of
the new keys already exist in the set.
To make your set ordered (which maps to the `:ets` table type `:ordered_set`), specify `ordered: true`
in the options list. An ordered set will store records in term order of the key of the record. This is
helpful when using things like `first`, `last`, `previous`, `next`, and `to_list`, but comes with the penalty of
log(n) insert time vs consistent insert time of an unordered set.
## Working with named tables
The functions on `ETS.Set` require that you pass in an `ETS.Set` as the first argument. In some design patterns,
you may have the table name but an instance of an `ETS.Set` may not be available to you. If this is the case,
you should use `wrap_existing/1` to turn your table name atom into an `ETS.Set`. For example, a `GenServer` that
handles writes within the server, but reads in the client process would be implemented like this:
```
defmodule MyExampleGenServer do
use GenServer
# Client Functions
def get_token_for_user(user_id) do
:my_token_table
|> ETS.Set.wrap_existing!()
|> ETS.Set.get!(user_id)
|> elem(1)
end
def set_token_for_user(user_id, token) do
GenServer.call(__MODULE__, {:set_token_for_user, user_id, token})
end
# Server Functions
def init(_) do
{:ok, %{set: ETS.Set.new!(name: :my_token_table)}}
end
def handle_call({:set_token_for_user, user_id, token}, _from, %{set: set}) do
ETS.Set.put(set, user_id, token)
end
end
```
"""
use ETS.Utils
alias ETS.Base
alias ETS.Set
@type t :: %__MODULE__{
info: keyword(),
ordered: boolean(),
table: ETS.table_reference()
}
@type set_options :: [ETS.Base.option() | {:ordered, boolean()}]
defstruct table: nil, info: nil, ordered: nil
@doc """
Creates new set module with the specified options.
Note that the underlying :ets table will be attached to the process that calls `new` and will be destroyed
if that process dies.
Possible options:
* `name:` when specified, creates a named table with the specified name
* `ordered:` when true, records in set are ordered (default false)
* `protection:` :private, :protected, :public (default :protected)
* `heir:` :none | {heir_pid, heir_data} (default :none)
* `keypos:` integer (default 1)
* `read_concurrency:` boolean (default false)
* `write_concurrency:` boolean (default false)
* `compressed:` boolean (default false)
## Examples
iex> {:ok, set} = Set.new(ordered: true, keypos: 3, read_concurrency: true, compressed: false)
iex> Set.info!(set)[:read_concurrency]
true
# Named :ets tables via the name keyword
iex> {:ok, set} = Set.new(name: :my_ets_table)
iex> Set.info!(set)[:name]
:my_ets_table
"""
@spec new(set_options) :: {:error, any()} | {:ok, Set.t()}
def new(opts \\ []) when is_list(opts) do
{opts, ordered} = take_opt(opts, :ordered, false)
if is_boolean(ordered) do
case Base.new_table(type(ordered), opts) do
{:error, reason} -> {:error, reason}
{:ok, {table, info}} -> {:ok, %Set{table: table, info: info, ordered: ordered}}
end
else
{:error, {:invalid_option, {:ordered, ordered}}}
end
end
@doc """
Same as `new/1` but unwraps or raises on error.
"""
@spec new!(set_options) :: Set.t()
def new!(opts \\ []), do: unwrap_or_raise(new(opts))
defp type(true), do: :ordered_set
defp type(false), do: :set
@doc """
Returns information on the set.
Second parameter forces updated information from ets, default (false) uses in-struct cached information.
Force should be used when requesting size and memory.
## Examples
iex> {:ok, set} = Set.new(ordered: true, keypos: 3, read_concurrency: true, compressed: false)
iex> {:ok, info} = Set.info(set)
iex> info[:read_concurrency]
true
iex> {:ok, _} = Set.put(set, {:a, :b, :c})
iex> {:ok, info} = Set.info(set)
iex> info[:size]
0
iex> {:ok, info} = Set.info(set, true)
iex> info[:size]
1
"""
@spec info(Set.t(), boolean()) :: {:ok, keyword()} | {:error, any()}
def info(set, force_update \\ false)
def info(%Set{table: table}, true), do: Base.info(table)
def info(%Set{info: info}, false), do: {:ok, info}
@doc """
Same as `info/1` but unwraps or raises on error.
"""
@spec info!(Set.t(), boolean()) :: keyword()
def info!(%Set{} = set, force_update \\ false) when is_boolean(force_update),
do: unwrap_or_raise(info(set, force_update))
@doc """
Returns underlying `:ets` table reference.
For use in functions that are not yet implemented. If you find yourself using this, please consider
submitting a PR to add the necessary function to `ETS`.
## Examples
iex> set = Set.new!(name: :my_ets_table)
iex> {:ok, table} = Set.get_table(set)
iex> info = :ets.info(table)
iex> info[:name]
:my_ets_table
"""
@spec get_table(Set.t()) :: {:ok, ETS.table_reference()}
def get_table(%Set{table: table}), do: {:ok, table}
@doc """
Same as `get_table/1` but unwraps or raises on error
"""
@spec get_table!(Set.t()) :: ETS.table_reference()
def get_table!(%Set{} = set), do: unwrap(get_table(set))
@doc """
Puts tuple record or list of tuple records into table. Overwrites records for existing key(s).
Inserts multiple records in an [atomic and isolated](http://erlang.org/doc/man/ets.html#concurrency) manner.
## Examples
iex> {:ok, set} = Set.new(ordered: true)
iex> {:ok, _} = Set.put(set, [{:a, :b, :c}, {:d, :e, :f}])
iex> {:ok, _} = Set.put(set, {:g, :h, :i})
iex> {:ok, _} = Set.put(set, {:d, :x, :y})
iex> Set.to_list(set)
{:ok, [{:a, :b, :c}, {:d, :x, :y}, {:g, :h, :i}]}
"""
@spec put(Set.t(), tuple() | list(tuple())) :: {:ok, Set.t()} | {:error, any()}
def put(%Set{table: table} = set, record) when is_tuple(record),
do: Base.insert(table, record, set)
def put(%Set{table: table} = set, records) when is_list(records),
do: Base.insert_multi(table, records, set)
@doc """
Same as `put/2` but unwraps or raises on error.
"""
@spec put!(Set.t(), tuple() | list(tuple())) :: Set.t()
def put!(%Set{} = set, record_or_records)
when is_tuple(record_or_records) or is_list(record_or_records),
do: unwrap_or_raise(put(set, record_or_records))
@doc """
Same as `put/2` but doesn't put any records if one of the given keys already exists.
## Examples
iex> set = Set.new!(ordered: true)
iex> {:ok, _} = Set.put_new(set, [{:a, :b, :c}, {:d, :e, :f}])
iex> {:ok, _} = Set.put_new(set, [{:a, :x, :y}, {:g, :h, :i}]) # skips due to duplicate :a key
iex> {:ok, _} = Set.put_new(set, {:d, :z, :zz}) # skips due to duplicate :d key
iex> Set.to_list!(set)
[{:a, :b, :c}, {:d, :e, :f}]
"""
@spec put_new(Set.t(), tuple() | list(tuple())) :: {:ok, Set.t()} | {:error, any()}
def put_new(%Set{table: table} = set, record) when is_tuple(record),
do: Base.insert_new(table, record, set)
def put_new(%Set{table: table} = set, records) when is_list(records),
do: Base.insert_multi_new(table, records, set)
@doc """
Same as `put_new/2` but unwraps or raises on error.
"""
@spec put_new!(Set.t(), tuple() | list(tuple())) :: Set.t()
def put_new!(%Set{} = set, record_or_records)
when is_tuple(record_or_records) or is_list(record_or_records),
do: unwrap_or_raise(put_new(set, record_or_records))
@doc """
Returns record with specified key or an error if no record found.
## Examples
iex> Set.new!()
iex> |> Set.put!({:a, :b, :c})
iex> |> Set.put!({:d, :e, :f})
iex> |> Set.fetch(:d)
{:ok, {:d, :e, :f}}
iex> Set.new!()
iex> |> Set.put!({:a, :b, :c})
iex> |> Set.put!({:d, :e, :f})
iex> |> Set.fetch(:g)
{:error, :key_not_found}
"""
@spec fetch(Set.t(), any()) :: {:ok, tuple() | nil} | {:error, any()}
def fetch(%Set{table: table}, key) do
case Base.lookup(table, key) do
{:ok, []} -> {:error, :key_not_found}
{:ok, [x | []]} -> {:ok, x}
{:ok, _} -> {:error, :invalid_set}
{:error, reason} -> {:error, reason}
end
end
@doc """
Returns record with specified key or the provided default (nil if not specified) if no record found.
## Examples
iex> Set.new!()
iex> |> Set.put!({:a, :b, :c})
iex> |> Set.put!({:d, :e, :f})
iex> |> Set.get(:d)
{:ok, {:d, :e, :f}}
"""
@spec get(Set.t(), any(), any()) :: {:ok, tuple() | nil} | {:error, any()}
def get(%Set{table: table}, key, default \\ nil) do
case Base.lookup(table, key) do
{:ok, []} -> {:ok, default}
{:ok, [x | []]} -> {:ok, x}
{:ok, _} -> {:error, :invalid_set}
{:error, reason} -> {:error, reason}
end
end
@doc """
Same as `get/3` but unwraps or raises on error.
"""
@spec get!(Set.t(), any(), any()) :: tuple() | nil
def get!(%Set{} = set, key, default \\ nil), do: unwrap_or_raise(get(set, key, default))
@doc """
Returns element in specified position of record with specified key.
## Examples
iex> Set.new!()
iex> |> Set.put!({:a, :b, :c})
iex> |> Set.put!({:d, :e, :f})
iex> |> Set.get_element(:d, 2)
{:ok, :e}
"""
@spec get_element(Set.t(), any(), non_neg_integer()) :: {:ok, any()} | {:error, any()}
def get_element(%Set{table: table}, key, pos), do: Base.lookup_element(table, key, pos)
@doc """
Same as `get_element/3` but unwraps or raises on error.
"""
@spec get_element!(Set.t(), any(), non_neg_integer()) :: any()
def get_element!(%Set{} = set, key, pos), do: unwrap_or_raise(get_element(set, key, pos))
@doc """
Returns records in the specified Set that match the specified pattern.
For more information on the match pattern, see the [erlang documentation](http://erlang.org/doc/man/ets.html#match-2)
## Examples
iex> Set.new!(ordered: true)
iex> |> Set.put!([{:a, :b, :c, :d}, {:e, :c, :f, :g}, {:h, :b, :i, :j}])
iex> |> Set.match({:"$1", :b, :"$2", :_})
{:ok, [[:a, :c], [:h, :i]]}
"""
@spec match(Set.t(), ETS.match_pattern()) :: {:ok, [tuple()]} | {:error, any()}
def match(%Set{table: table}, pattern) when is_atom(pattern) or is_tuple(pattern),
do: Base.match(table, pattern)
@doc """
Same as `match/2` but unwraps or raises on error.
"""
@spec match!(Set.t(), ETS.match_pattern()) :: [tuple()]
def match!(%Set{} = set, pattern) when is_atom(pattern) or is_tuple(pattern),
do: unwrap_or_raise(match(set, pattern))
@doc """
Same as `match/2` but limits number of results to the specified limit.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.put!(set, [{:a, :b, :c, :d}, {:e, :b, :f, :g}, {:h, :b, :i, :j}])
iex> {:ok, {results, _continuation}} = Set.match(set, {:"$1", :b, :"$2", :_}, 2)
iex> results
[[:a, :c], [:e, :f]]
"""
@spec match(Set.t(), ETS.match_pattern(), non_neg_integer()) ::
{:ok, {[tuple()], any() | :end_of_table}} | {:error, any()}
def match(%Set{table: table}, pattern, limit), do: Base.match(table, pattern, limit)
@doc """
Same as `match/3` but unwraps or raises on error.
"""
@spec match!(Set.t(), ETS.match_pattern(), non_neg_integer()) ::
{[tuple()], any() | :end_of_table}
def match!(%Set{} = set, pattern, limit), do: unwrap_or_raise(match(set, pattern, limit))
@doc """
Matches next set of records from a match/3 or match/1 continuation.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.put!(set, [{:a, :b, :c, :d}, {:e, :b, :f, :g}, {:h, :b, :i, :j}])
iex> {:ok, {results, continuation}} = Set.match(set, {:"$1", :b, :"$2", :_}, 2)
iex> results
[[:a, :c], [:e, :f]]
iex> {:ok, {records2, continuation2}} = Set.match(continuation)
iex> records2
[[:h, :i]]
iex> continuation2
:end_of_table
"""
@spec match(any()) :: {:ok, {[tuple()], any() | :end_of_table}} | {:error, any()}
def match(continuation), do: Base.match(continuation)
@doc """
Same as `match/1` but unwraps or raises on error.
"""
@spec match!(any()) :: {[tuple()], any() | :end_of_table}
def match!(continuation), do: unwrap_or_raise(match(continuation))
@doc """
Deletes all records that match the specified pattern.
Always returns `:ok`, regardless of whether anything was deleted or not.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.put!(set, [{:a, :b, :c, :d}, {:e, :b, :f, :g}, {:h, :i, :j, :k}])
iex> Set.match_delete(set, {:_, :b, :_, :_})
{:ok, set}
iex> Set.to_list!(set)
[{:h, :i, :j, :k}]
"""
@spec match_delete(Set.t(), ETS.match_pattern()) :: {:ok, Set.t()} | {:error, any()}
def match_delete(%Set{table: table} = set, pattern)
when is_atom(pattern) or is_tuple(pattern) do
with :ok <- Base.match_delete(table, pattern) do
{:ok, set}
end
end
@doc """
Same as `match_delete/2` but unwraps or raises on error.
"""
@spec match_delete!(Set.t(), ETS.match_pattern()) :: Set.t()
def match_delete!(%Set{} = set, pattern) when is_atom(pattern) or is_tuple(pattern),
do: unwrap_or_raise(match_delete(set, pattern))
@doc """
Returns records in the specified Set that match the specified pattern.
For more information on the match pattern, see the [erlang documentation](http://erlang.org/doc/man/ets.html#match-2)
## Examples
iex> Set.new!(ordered: true)
iex> |> Set.put!([{:a, :b, :c, :d}, {:e, :c, :f, :g}, {:h, :b, :i, :j}])
iex> |> Set.match_object({:"$1", :b, :"$2", :_})
{:ok, [{:a, :b, :c, :d}, {:h, :b, :i, :j}]}
"""
@spec match_object(Set.t(), ETS.match_pattern()) :: {:ok, [tuple()]} | {:error, any()}
def match_object(%Set{table: table}, pattern) when is_atom(pattern) or is_tuple(pattern),
do: Base.match_object(table, pattern)
@doc """
Same as `match_object/2` but unwraps or raises on error.
"""
@spec match_object!(Set.t(), ETS.match_pattern()) :: [tuple()]
def match_object!(%Set{} = set, pattern) when is_atom(pattern) or is_tuple(pattern),
do: unwrap_or_raise(match_object(set, pattern))
@doc """
Same as `match_object/2` but limits number of results to the specified limit.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.put!(set, [{:a, :b, :c, :d}, {:e, :b, :f, :g}, {:h, :b, :i, :j}])
iex> {:ok, {results, _continuation}} = Set.match_object(set, {:"$1", :b, :"$2", :_}, 2)
iex> results
[{:a, :b, :c, :d}, {:e, :b, :f, :g}]
"""
@spec match_object(Set.t(), ETS.match_pattern(), non_neg_integer()) ::
{:ok, {[tuple()], any() | :end_of_table}} | {:error, any()}
def match_object(%Set{table: table}, pattern, limit),
do: Base.match_object(table, pattern, limit)
@doc """
Same as `match_object/3` but unwraps or raises on error.
"""
@spec match_object!(Set.t(), ETS.match_pattern(), non_neg_integer()) ::
{[tuple()], any() | :end_of_table}
def match_object!(%Set{} = set, pattern, limit),
do: unwrap_or_raise(match_object(set, pattern, limit))
@doc """
Matches next records from a match_object/3 or match_object/1 continuation.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.put!(set, [{:a, :b, :c}, {:d, :b, :e}, {:f, :b, :g}, {:h, :b, :i}])
iex> {:ok, {results, continuation}} = Set.match_object(set, {:"$1", :b, :_}, 2)
iex> results
[{:a, :b, :c}, {:d, :b, :e}]
iex> {:ok, {results2, continuation2}} = Set.match_object(continuation)
iex> results2
[{:f, :b, :g}, {:h, :b, :i}]
iex> {:ok, {[], :end_of_table}} = Set.match_object(continuation2)
"""
@spec match_object(any()) :: {:ok, {[tuple()], any() | :end_of_table}} | {:error, any()}
def match_object(continuation), do: Base.match_object(continuation)
@doc """
Same as `match_object/1` but unwraps or raises on error.
"""
@spec match_object!(any()) :: {[tuple()], any() | :end_of_table}
def match_object!(continuation), do: unwrap_or_raise(match_object(continuation))
@spec select(ETS.continuation()) ::
{:ok, {[tuple()], ETS.continuation()} | ETS.end_of_table()} | {:error, any()}
def select(continuation), do: Base.select(continuation)
@spec select!(ETS.continuation()) :: {[tuple()], ETS.continuation()} | ETS.end_of_table()
def select!(continuation) do
unwrap_or_raise(select(continuation))
end
@doc """
Returns records in the specified Set that match the specified match specification.
For more information on the match specification, see the [erlang documentation](http://erlang.org/doc/man/ets.html#select-2)
## Examples
iex> Set.new!(ordered: true)
iex> |> Set.put!([{:a, :b, :c, :d}, {:e, :c, :f, :g}, {:h, :b, :i, :j}])
iex> |> Set.select([{{:"$1", :b, :"$2", :_},[],[:"$$"]}])
{:ok, [[:a, :c], [:h, :i]]}
"""
@spec select(Set.t(), ETS.match_spec()) :: {:ok, [tuple()]} | {:error, any()}
def select(%Set{table: table}, spec) when is_list(spec),
do: Base.select(table, spec)
@doc """
Same as `select/2` but unwraps or raises on error.
"""
@spec select!(Set.t(), ETS.match_spec()) :: [tuple()]
def select!(%Set{} = set, spec) when is_list(spec),
do: unwrap_or_raise(select(set, spec))
@doc """
Same as `select/2` but limits the number of results returned.
"""
@spec select(Set.t(), ETS.match_spec(), limit :: integer) ::
{:ok, {[tuple()], ETS.continuation()} | ETS.end_of_table()} | {:error, any()}
def select(%Set{table: table}, spec, limit) when is_list(spec),
do: Base.select(table, spec, limit)
@doc """
Same as `select/3` but unwraps or raises on error.
"""
@spec select!(Set.t(), ETS.match_spec(), limit :: integer) ::
{[tuple()], ETS.continuation()} | ETS.end_of_table()
def select!(%Set{} = set, spec, limit) when is_list(spec),
do: unwrap_or_raise(select(set, spec, limit))
@doc """
Deletes records in the specified Set that match the specified match specification.
For more information on the match specification, see the [erlang documentation](http://erlang.org/doc/man/ets.html#select_delete-2)
## Examples
iex> set = Set.new!(ordered: true)
iex> set
iex> |> Set.put!([{:a, :b, :c, :d}, {:e, :c, :f, :g}, {:h, :b, :c, :h}])
iex> |> Set.select_delete([{{:"$1", :b, :"$2", :_},[{:"==", :"$2", :c}],[true]}])
{:ok, 2}
iex> Set.to_list!(set)
[{:e, :c, :f, :g}]
"""
@spec select_delete(Set.t(), ETS.match_spec()) :: {:ok, non_neg_integer()} | {:error, any()}
def select_delete(%Set{table: table}, spec) when is_list(spec),
do: Base.select_delete(table, spec)
@doc """
Same as `select_delete/2` but unwraps or raises on error.
"""
@spec select_delete!(Set.t(), ETS.match_spec()) :: non_neg_integer()
def select_delete!(%Set{} = set, spec) when is_list(spec),
do: unwrap_or_raise(select_delete(set, spec))
@doc """
Determines if specified key exists in specified set.
## Examples
iex> set = Set.new!()
iex> Set.has_key(set, :key)
{:ok, false}
iex> Set.put(set, {:key, :value})
iex> Set.has_key(set, :key)
{:ok, true}
"""
@spec has_key(Set.t(), any()) :: {:ok, boolean()} | {:error, any()}
def has_key(%Set{table: table}, key), do: Base.has_key(table, key)
@doc """
Same as `has_key/2` but unwraps or raises on error.
"""
@spec has_key!(Set.t(), any()) :: boolean()
def has_key!(set, key), do: unwrap_or_raise(has_key(set, key))
@doc """
Returns the first key in the specified Set. Set must be ordered or error is returned.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.first(set)
{:error, :empty_table}
iex> Set.put!(set, {:key1, :val})
iex> Set.put!(set, {:key2, :val})
iex> Set.first(set)
{:ok, :key1}
"""
@spec first(Set.t()) :: {:ok, any()} | {:error, any()}
def first(%Set{ordered: false}), do: {:error, :set_not_ordered}
def first(%Set{table: table}), do: Base.first(table)
@doc """
Same as `first/1` but unwraps or raises on error
"""
@spec first!(Set.t()) :: any()
def first!(%Set{} = set), do: unwrap_or_raise(first(set))
@doc """
Returns the last key in the specified Set. Set must be ordered or error is returned.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.last(set)
{:error, :empty_table}
iex> Set.put!(set, {:key1, :val})
iex> Set.put!(set, {:key2, :val})
iex> Set.last(set)
{:ok, :key2}
"""
@spec last(Set.t()) :: {:ok, any()} | {:error, any()}
def last(%Set{ordered: false}), do: {:error, :set_not_ordered}
def last(%Set{table: table}), do: Base.last(table)
@doc """
Same as `last/1` but unwraps or raises on error
"""
@spec last!(Set.t()) :: any()
def last!(set), do: unwrap_or_raise(last(set))
@doc """
Returns the next key in the specified Set.
The given key does not need to exist in the set. The key returned will be the first key that exists in the
set which is subsequent in term order to the key given.
Set must be ordered or error is returned.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.put!(set, {:key1, :val})
iex> Set.put!(set, {:key2, :val})
iex> Set.put!(set, {:key3, :val})
iex> Set.first(set)
{:ok, :key1}
iex> Set.next(set, :key1)
{:ok, :key2}
iex> Set.next(set, :key2)
{:ok, :key3}
iex> Set.next(set, :key3)
{:error, :end_of_table}
iex> Set.next(set, :a)
{:ok, :key1}
iex> Set.next(set, :z)
{:error, :end_of_table}
"""
@spec next(Set.t(), any()) :: {:ok, any()} | {:error, any()}
def next(%Set{ordered: false}, _key), do: {:error, :set_not_ordered}
def next(%Set{table: table}, key), do: Base.next(table, key)
@doc """
Same as `next/1` but unwraps or raises on error
"""
@spec next!(Set.t(), any()) :: any()
def next!(set, key), do: unwrap_or_raise(next(set, key))
@doc """
Returns the previous key in the specified Set.
The given key does not need to exist in the set. The key returned will be the first key that exists in the
set which is previous in term order to the key given.
Set must be ordered or error is returned.
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.put!(set, {:key1, :val})
iex> Set.put!(set, {:key2, :val})
iex> Set.put!(set, {:key3, :val})
iex> Set.last(set)
{:ok, :key3}
iex> Set.previous(set, :key3)
{:ok, :key2}
iex> Set.previous(set, :key2)
{:ok, :key1}
iex> Set.previous(set, :key1)
{:error, :start_of_table}
iex> Set.previous(set, :a)
{:error, :start_of_table}
iex> Set.previous(set, :z)
{:ok, :key3}
"""
@spec previous(Set.t(), any()) :: {:ok, any()} | {:error, any()}
def previous(%Set{ordered: false}, _key), do: {:error, :set_not_ordered}
def previous(%Set{table: table}, key), do: Base.previous(table, key)
@doc """
Same as `previous/1` but raises on :error
Returns previous key in table.
"""
@spec previous!(Set.t(), any()) :: any()
def previous!(%Set{} = set, key), do: unwrap_or_raise(previous(set, key))
@doc """
Returns contents of table as a list.
## Examples
iex> Set.new!(ordered: true)
iex> |> Set.put!({:a, :b, :c})
iex> |> Set.put!({:d, :e, :f})
iex> |> Set.put!({:d, :e, :f})
iex> |> Set.to_list()
{:ok, [{:a, :b, :c}, {:d, :e, :f}]}
"""
@spec to_list(Set.t()) :: {:ok, [tuple()]} | {:error, any()}
def to_list(%Set{table: table}), do: Base.to_list(table)
@doc """
Same as `to_list/1` but unwraps or raises on error.
"""
@spec to_list!(Set.t()) :: [tuple()]
def to_list!(%Set{} = set), do: unwrap_or_raise(to_list(set))
@doc """
Deletes specified Set.
## Examples
iex> {:ok, set} = Set.new()
iex> {:ok, _} = Set.info(set, true)
iex> {:ok, _} = Set.delete(set)
iex> Set.info(set, true)
{:error, :table_not_found}
"""
@spec delete(Set.t()) :: {:ok, Set.t()} | {:error, any()}
def delete(%Set{table: table} = set), do: Base.delete(table, set)
@doc """
Same as `delete/1` but unwraps or raises on error.
"""
@spec delete!(Set.t()) :: Set.t()
def delete!(%Set{} = set), do: unwrap_or_raise(delete(set))
@doc """
Deletes record with specified key in specified Set.
## Examples
iex> set = Set.new!()
iex> Set.put(set, {:a, :b, :c})
iex> Set.delete(set, :a)
iex> Set.get!(set, :a)
nil
"""
@spec delete(Set.t(), any()) :: {:ok, Set.t()} | {:error, any()}
def delete(%Set{table: table} = set, key), do: Base.delete_records(table, key, set)
@doc """
Same as `delete/2` but unwraps or raises on error.
"""
@spec delete!(Set.t(), any()) :: Set.t()
def delete!(%Set{} = set, key), do: unwrap_or_raise(delete(set, key))
@doc """
Deletes all records in specified Set.
## Examples
iex> set = Set.new!()
iex> set
iex> |> Set.put!({:a, :b, :c})
iex> |> Set.put!({:b, :b, :c})
iex> |> Set.put!({:c, :b, :c})
iex> |> Set.to_list!()
[{:c, :b, :c}, {:b, :b, :c}, {:a, :b, :c}]
iex> Set.delete_all(set)
iex> Set.to_list!(set)
[]
"""
@spec delete_all(Set.t()) :: {:ok, Set.t()} | {:error, any()}
def delete_all(%Set{table: table} = set), do: Base.delete_all_records(table, set)
@doc """
Same as `delete_all/1` but unwraps or raises on error.
"""
@spec delete_all!(Set.t()) :: Set.t()
def delete_all!(%Set{} = set), do: unwrap_or_raise(delete_all(set))
@doc """
Updates one or more elements within the record with the given `key`. The element_spec is
a tuple (or list of tuples) of the form `{position, value}`, which will update the element
at `position` (1-indexed) to have the given `value`. When a list is given, multiple elements
can be updated within the same record. If the same position occurs more than once in the list,
the last value in the list is written. If the list is empty or the function fails, no updates
are done. The function is also atomic in the sense that other processes can never see any
intermediate results.
Returns `{:ok, set}` if an object with the given key is found, otherwise returns
`{:error, :key_not_found}`.
## Examples
iex> set = Set.new!()
iex> Set.put!(set, {:a, :b, :c})
iex> Set.update_element(set, :a, {2, :d})
{:ok, set}
iex> Set.to_list!(set)
[{:a, :d, :c}]
iex> Set.update_element(set, :a, [{2, :x}, {3, :y}])
{:ok, set}
iex> Set.to_list!(set)
[{:a, :x, :y}]
"""
@spec update_element(Set.t(), any(), tuple() | [tuple()]) :: {:ok, Set.t()} | {:error, any()}
def update_element(%Set{table: table} = set, key, element_spec) do
case Base.update_element(table, key, element_spec) do
true -> {:ok, set}
false -> {:error, :key_not_found}
error -> error
end
end
@doc """
Same as `update_element/3` but unwraps or raises on error.
"""
@spec update_element!(Set.t(), any(), tuple() | [tuple()]) :: Set.t()
def update_element!(%Set{} = set, key, element_spec),
do: unwrap_or_raise(update_element(set, key, element_spec))
@doc """
Wraps an existing :ets :set or :ordered_set in a Set struct.
## Examples
iex> :ets.new(:my_ets_table, [:set, :named_table])
iex> {:ok, set} = Set.wrap_existing(:my_ets_table)
iex> Set.info!(set)[:name]
:my_ets_table
"""
@spec wrap_existing(ETS.table_identifier()) :: {:ok, Set.t()} | {:error, any()}
def wrap_existing(table_identifier) do
case Base.wrap_existing(table_identifier, [:set, :ordered_set]) do
{:ok, {table, info}} ->
{:ok, %Set{table: table, info: info, ordered: info[:type] == :ordered_set}}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Same as `wrap_existing/1` but unwraps or raises on error.
"""
@spec wrap_existing!(ETS.table_identifier()) :: Set.t()
def wrap_existing!(table_identifier), do: unwrap_or_raise(wrap_existing(table_identifier))
@doc """
Transfers ownership of a Set to another process.
## Examples
iex> set = Set.new!()
iex> receiver_pid = spawn(fn -> Set.accept() end)
iex> Set.give_away(set, receiver_pid)
{:ok, set}
iex> set = Set.new!()
iex> dead_pid = ETS.TestUtils.dead_pid()
iex> Set.give_away(set, dead_pid)
{:error, :recipient_not_alive}
"""
@spec give_away(Set.t(), pid(), any()) :: {:ok, Set.t()} | {:error, any()}
def give_away(%Set{table: table} = set, pid, gift \\ []),
do: Base.give_away(table, pid, gift, set)
@doc """
Same as `give_away/3` but unwraps or raises on error.
"""
@spec give_away!(Set.t(), pid(), any()) :: Set.t()
def give_away!(%Set{} = set, pid, gift \\ []),
do: unwrap_or_raise(give_away(set, pid, gift))
@doc """
Waits to accept ownership of a table after it is given away. Successful receipt will
return `{:ok, %{set: set, from: from, gift: gift}}` where `from` is the pid of the previous
owner, and `gift` is any additional metadata sent with the table.
A timeout may be given in milliseconds, which will return `{:error, :timeout}` if reached.
See `give_away/3` for more information.
"""
@spec accept() :: {:ok, Set.t(), pid(), any()} | {:error, any()}
def accept(timeout \\ :infinity) do
with {:ok, table, from, gift} <- Base.accept(timeout),
{:ok, set} <- Set.wrap_existing(table) do
{:ok, %{set: set, from: from, gift: gift}}
end
end
@doc """
For processes which may receive ownership of a Set unexpectedly - either via `give_away/3` or
by being named the Set's heir (see `new/1`) - the module should include at least one `accept`
clause. For example, if we want a server to inherit Sets after their previous owner dies:
```
defmodule Receiver do
use GenServer
alias ETS.Set
require ETS.Set
...
Set.accept :owner_crashed, set, _from, state do
new_state = Map.update!(state, :crashed_sets, &[set | &1])
{:noreply, new_state}
end
```
The first argument is a unique identifier which should match either the "heir_data"
in `new/1`, or the "gift" in `give_away/3`.
The other arguments declare the variables which may be used in the `do` block:
the received Set, the pid of the previous owner, and the current state of the process.
The return value should be in the form {:noreply, new_state}, or one of the similar
returns expected by `handle_info`/`handle_cast`.
"""
defmacro accept(id, table, from, state, do: contents) do
quote do
require Base
Base.accept unquote(id), unquote(table), unquote(from), unquote(state) do
var!(unquote(table)) = Set.wrap_existing!(unquote(table))
unquote(contents)
end
end
end
end