Skip to content

Commit

Permalink
v0.2.0. Add credo. Set insert/lookup to put/get.
Browse files Browse the repository at this point in the history
  • Loading branch information
TheFirstAvenger committed Dec 9, 2018
1 parent 932fbaf commit 2e25eb7
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 703 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -8,9 +8,9 @@ env:
sudo: false
before_script:
- mix deps.get
- nvm install 6.2 && nvm use 6.2
- mix format --check-formatted
- mix compile --warnings-as-errors
- mix credo --strict
script:
- mix test
after_script:
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,7 @@
# Changelog

## 0.2.0

* Redesign from ground up to use module/struct based approach.
* Implemented `Ets.Set` and `Ets.Base`.
* Set up CI and Readme badges.
60 changes: 46 additions & 14 deletions README.md
Expand Up @@ -3,6 +3,10 @@
`:ets`, the Elixir way

[![Build Status](https://travis-ci.com/TheFirstAvenger/ets.svg?branch=master)](https://travis-ci.com/TheFirstAvenger/ets)
[![Coverage Status](https://coveralls.io/repos/github/TheFirstAvenger/ets/badge.svg?branch=master)](https://coveralls.io/github/TheFirstAvenger/ets?branch=master)
[![Project license](https://img.shields.io/hexpm/l/ets.svg)](https://unlicense.org/)
[![Hex.pm package](https://img.shields.io/hexpm/v/ets.svg)](https://hex.pm/packages/ets)
[![Hex.pm downloads](https://img.shields.io/hexpm/dt/ets.svg)](https://hex.pm/packages/ets)

Ets is a set of Elixir modules that wrap Erlang Term Storage (`:ets`). The purposes of this package is to improve the developer experience when both learning and interacting with Erlang Term Storage.

Expand All @@ -11,21 +15,52 @@ This will be accomplished by:
* Conforming to Elixir standards:
* Two versions of all functions:
* Main function (e.g. `get` returns `{:ok, return}`/`{:error, reason}` tuples.
* Bang function (e.g. `get!`) returns value or raises on :error.
* Bang function (e.g. `get!`) returns unwrapped value or raises on :error.
* All options specified via keyword list.
* Providing Elixir friendly documentation
* Providng `Set` and `Bag` modules with appropriate function signatures and error handling
* `Set.get` returns a single item or nil (instead of list) as duplicates are not allowed
* Providing Elixir friendly documentation.
* Providng `Ets.Set` and `Ets.Bag` modules with appropriate function signatures and error handling.
* `Ets.Set.get` returns a single item (or nil/provided default) instead of list as sets never have multiple records for a key.
* Wrapping unhelpful `ArgumentError`'s with appropriate error returns.
* Handle `$end_of_table`
* Appropriate error returns/raises when encountered.
* Prevent insertion of `$end_of_table` where possible without affecting performance.
* Appropriate error returns/raises when encountering `$end_of_table`.

## Usage

### Creating Tables
### Creating Ets Tables

Tables can be created using the `new` function of the appropriate module, either `Ets.Set` (for ordered and unordered sets) or `Ets.Bag` (for duplicate or non-duplicate bags) (`Ets.Bag` coming soon). See module documentation for more examples and documentation.
Ets Tables can be created using the `new` function of the appropriate module, either `Ets.Set` (for ordered and unordered sets) or `Ets.Bag` (for duplicate or non-duplicate bags) (`Ets.Bag` coming soon). See module documentation for more examples and documentation.

#### Create 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

### Adding/Updating/Retrieving records

To add records to an Ets table, use `put` and its variations `put_new`, `put_multi`, and `put_multi_new`. `put` and `put_multi`
will overwrite existing records with the same key. The `_new` variations will return an error and not insert if the key
already exists. The `_multi` variations will insert all records in an atomic and isolated manner (or insert no records if at least one is found with `put_multi_new`)

#### Record Examples

iex> Set.new(ordered: true)
iex> |> Set.put!({:a, :b})
iex> |> Set.put!({:a, :c})
iex> |> Set.put!({:c, :d})
iex> |> Set.to_list!()
[{:a, :c}, {:c, :d}]

iex> Set.new(ordered: true)
iex> |> Set.put!({:a, :b})
iex> |> Set.put({:a, :c})
{:error, :key_already_exists}

## Current Progress

* [X] `Ets`
* [X] All
Expand All @@ -48,8 +83,7 @@ Tables can be created using the `new` function of the appropriate module, either

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `ets` to your list of dependencies in `mix.exs`:
`Ets` can be installed by adding `ets` to your list of dependencies in `mix.exs`:

```elixir
def deps do
Expand All @@ -59,9 +93,7 @@ def deps do
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/ets](https://hexdocs.pm/ets).
Docs can be found at [https://hexdocs.pm/ets](https://hexdocs.pm/ets).

## Contributing

Expand Down
7 changes: 7 additions & 0 deletions all_checks.sh
@@ -0,0 +1,7 @@
#!/bin/bash

rm -rf _build/test/lib/ets
MIX_ENV=test mix compile --warnings-as-errors
MIX_ENV=test mix test
MIX_ENV=test mix format --check-formatted
MIX_ENV=test mix credo --strict
4 changes: 2 additions & 2 deletions lib/ets.ex
Expand Up @@ -30,8 +30,8 @@ defmodule Ets do
true
"""
@spec all() :: {:ok, [Ets.table_identifier()]} | {:error, any()}
def all() do
@spec all :: {:ok, [Ets.table_identifier()]} | {:error, any()}
def all do
catch_error do
all =
:ets.all()
Expand Down
3 changes: 2 additions & 1 deletion lib/ets/base.ex
Expand Up @@ -35,7 +35,8 @@ defmodule Ets.Base do
case parse_opts(starting_opts, opts) do
{:ok, parsed_opts} ->
info =
:ets.new(name, parsed_opts)
name
|> :ets.new(parsed_opts)
|> :ets.info()

ref = info[:id]
Expand Down
104 changes: 61 additions & 43 deletions lib/ets/set.ex
Expand Up @@ -2,17 +2,35 @@ defmodule Ets.Set do
@moduledoc """
Module for creating and interacting with :ets tables of the type `:set` and `:ordered_set`.
Sets contain tuple records. One element of the tuple is the key of the tuple, and is
specified when the Set is created with the `keypos: 1` option. If not specified, the default
is 1. When a record is added to the table with `put`, it will overwrite an existing record
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!(set, {:a, :b, :c})
iex> Set.lookup!(set, :a)
{:a, :b, :c}
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})
{:error, :key_already_exists}
iex> Set.to_list!(set)
[{:a, :b, :c}, {:d, :e, :f}]
To insert multiple records in an atomic an isolated manner, use `put_multi` or `put_multi_new`.
"""
use Ets.Utils
Expand Down Expand Up @@ -144,22 +162,22 @@ defmodule Ets.Set do
## Examples
iex> {:ok, set} = Set.new(ordered: true)
iex> {:ok, _} = Set.insert_new(set, {:a, :b, :c})
iex> {:ok, _} = Set.insert_new(set, {:d, :e, :f})
iex> Set.insert_new(set, {:d, :e, :f})
iex> {:ok, _} = Set.put_new(set, {:a, :b, :c})
iex> {:ok, _} = Set.put_new(set, {:d, :e, :f})
iex> Set.put_new(set, {:d, :e, :f})
{:error, :key_already_exists}
"""
@spec insert_new(Set.t(), tuple()) :: {:ok, Set.t()} | {:error, any()}
def insert_new(%Set{table: table} = set, record) when is_tuple(record),
@spec put_new(Set.t(), 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)

@doc """
Same as `insert_new/2` but unwraps or raises on error.
Same as `put_new/2` but unwraps or raises on error.
"""
@spec insert_new!(Set.t(), tuple()) :: Set.t()
def insert_new!(%Set{} = set, record) when is_tuple(record),
do: unwrap_or_raise(insert_new(set, record))
@spec put_new!(Set.t(), tuple()) :: Set.t()
def put_new!(%Set{} = set, record) when is_tuple(record),
do: unwrap_or_raise(put_new(set, record))

@doc """
Inserts multiple records in an [atomic and isolated](http://erlang.org/doc/man/ets.html#concurrency) manner.
Expand All @@ -168,70 +186,70 @@ defmodule Ets.Set do
## Examples
iex> {:ok, set} = Set.new(ordered: true)
iex> {:ok, _} = Set.insert_multi(set, [{:a, :b, :c}, {:d, :e, :f}, {:d, :e, :f}])
iex> {:ok, _} = Set.put_multi(set, [{:a, :b, :c}, {:d, :e, :f}, {:d, :e, :f}])
iex> Set.to_list(set)
{:ok, [{:a, :b, :c}, {:d, :e, :f}]}
"""
@spec insert_multi(Set.t(), list(tuple())) :: {:ok, Set.t()} | {:error, any()}
def insert_multi(%Set{table: table} = set, records) when is_list(records),
@spec put_multi(Set.t(), list(tuple())) :: {:ok, Set.t()} | {:error, any()}
def put_multi(%Set{table: table} = set, records) when is_list(records),
do: Base.insert_multi(table, records, set)

@doc """
Same as `insert_multi/2` but unwraps or raises on error.
Same as `put_multi/2` but unwraps or raises on error.
"""
@spec insert_multi!(Set.t(), list(tuple())) :: Set.t()
def insert_multi!(%Set{} = set, records) when is_list(records),
do: unwrap_or_raise(insert_multi(set, records))
@spec put_multi!(Set.t(), list(tuple())) :: Set.t()
def put_multi!(%Set{} = set, records) when is_list(records),
do: unwrap_or_raise(put_multi(set, records))

@doc """
Same as `insert_multi/2` but returns error and doesn't insert if one of the specified keys already exists.
Same as `put_multi/2` but returns error and doesn't insert if one of the specified keys already exists.
## Examples
iex> {:ok, set} = Set.new(ordered: true)
iex> {:ok, _} = Set.insert_multi_new(set, [{:a, :b, :c}, {:d, :e, :f}, {:d, :e, :f}])
iex> {:error, :key_already_exists} = Set.insert_multi_new(set, [{:a, :b, :c}, {:d, :e, :f}, {:d, :e, :f}])
iex> {:ok, _} = Set.put_multi_new(set, [{:a, :b, :c}, {:d, :e, :f}, {:d, :e, :f}])
iex> {:error, :key_already_exists} = Set.put_multi_new(set, [{:a, :b, :c}, {:d, :e, :f}, {:d, :e, :f}])
iex> Set.to_list(set)
{:ok, [{:a, :b, :c}, {:d, :e, :f}]}
"""
@spec insert_multi_new(Set.t(), list(tuple())) :: {:ok, Set.t()} | {:error, any()}
def insert_multi_new(%Set{table: table} = set, records) when is_list(records),
@spec put_multi_new(Set.t(), list(tuple())) :: {:ok, Set.t()} | {:error, any()}
def put_multi_new(%Set{table: table} = set, records) when is_list(records),
do: Base.insert_multi_new(table, records, set)

@doc """
Same as `insert_multi_new/2` but unwraps or raises on error.
Same as `put_multi_new/2` but unwraps or raises on error.
"""
@spec insert_multi_new!(Set.t(), list(tuple())) :: Set.t()
def insert_multi_new!(%Set{} = set, records) when is_list(records),
do: unwrap_or_raise(insert_multi_new(set, records))
@spec put_multi_new!(Set.t(), list(tuple())) :: Set.t()
def put_multi_new!(%Set{} = set, records) when is_list(records),
do: unwrap_or_raise(put_multi_new(set, records))

@doc """
Returns record with specified key or nil if no record found.
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.lookup(:d)
iex> |> Set.get(:d)
{:ok, {:d, :e, :f}}
"""
@spec lookup(Set.t(), any()) :: {:ok, tuple()} | {:error, any()}
def lookup(%Set{table: table}, key) do
@spec get(Set.t(), any(), any()) :: {:ok, tuple()} | {:error, any()}
def get(%Set{table: table}, key, default \\ nil) do
case Base.lookup(table, key) do
{:ok, []} -> {:ok, nil}
{:ok, []} -> {:ok, default}
{:ok, [x | []]} -> {:ok, x}
{:ok, _} -> {:error, :invalid_set}
{:error, reason} -> {:error, reason}
end
end

@doc """
Same as `lookup/2` but unwraps or raises on error.
Same as `get/3` but unwraps or raises on error.
"""
@spec lookup!(Set.t(), any()) :: tuple()
def lookup!(%Set{} = set, key), do: unwrap_or_raise(lookup(set, key))
@spec get!(Set.t(), any(), any()) :: tuple()
def get!(%Set{} = set, key, default \\ nil), do: unwrap_or_raise(get(set, key, default))

@doc """
Returns records in the specified Set that match the specified pattern.
Expand All @@ -241,7 +259,7 @@ defmodule Ets.Set do
## Examples
iex> Set.new!(ordered: true)
iex> |> Set.insert_multi!([{:a, :b, :c, :d}, {:e, :c, :f, :g}, {:h, :b, :i, :j}])
iex> |> Set.put_multi!([{:a, :b, :c, :d}, {:e, :c, :f, :g}, {:h, :b, :i, :j}])
iex> |> Set.match({:"$1", :b, :"$2", :_})
{:ok, [[:a, :c], [:h, :i]]}
Expand All @@ -263,7 +281,7 @@ defmodule Ets.Set do
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.insert_multi!(set, [{:a, :b, :c, :d}, {:e, :b, :f, :g}, {:h, :b, :i, :j}])
iex> Set.put_multi!(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]]
Expand All @@ -286,7 +304,7 @@ defmodule Ets.Set do
## Examples
iex> set = Set.new!(ordered: true)
iex> Set.insert_multi!(set, [{:a, :b, :c, :d}, {:e, :b, :f, :g}, {:h, :b, :i, :j}])
iex> Set.put_multi!(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]]
Expand Down Expand Up @@ -506,7 +524,7 @@ defmodule Ets.Set do
iex> set = Set.new!()
iex> Set.put(set, {:a, :b, :c})
iex> Set.delete(set, :a)
iex> Set.lookup!(set, :a)
iex> Set.get!(set, :a)
nil
"""
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Expand Up @@ -4,11 +4,12 @@ defmodule Ets.MixProject do
def project do
[
app: :ets,
version: "0.1.2",
version: "0.2.0",
elixir: "~> 1.7",
start_permanent: Mix.env() == :prod,
deps: deps(),
description: description(),
docs: [main: "Ets", extras: ["README.md"]],
package: package(),
source_url: "https://github.com/TheFirstAvenger/ets",
test_coverage: [tool: ExCoveralls],
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Expand Up @@ -3,7 +3,7 @@
"certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"credo": {:hex, :credo, "1.0.0", "aaa40fdd0543a0cf8080e8c5949d8c25f0a24e4fc8c1d83d06c388f5e5e0ea42", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.3.0", "17f0c38eaafb4800f746b457313af4b2442a8c2405b49c645768680f900be603", [:mix], [], "hexpm"},
"erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_unit_notifier": {:hex, :ex_unit_notifier, "0.1.4", "36a2dcab829f506e01bf17816590680dd1474407926d43e64c1263e627c364b8", [:mix], [], "hexpm"},
Expand Down

0 comments on commit 2e25eb7

Please sign in to comment.