Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

ETS

livebook-with-otp/ets.livemd

ETS

Mix.install([
  {:kino, "~> 0.6.2"},
  {:factory, path: "./factory"}
])

Navigation

ETS Tables

Again, we suffer from issues with named :ets tables (evaluate the cell below twice.)

:ets.new(:my_table, [:named_table])

For the most part, if we start the table in it’s own cell this isn’t a problem.

Visualizing ETS tables

Kino.ETS provides a new/1 function for visualizing tables.

Kino.ETS.new(:my_table)

This table can be manually updated to view the :ets table when we insert values into it.

:ets.insert(:my_table, {:key, "value"})

Resetting :ets Tables

If you want to reset the table, the best way I’ve found so far is to use delete_all_objects.

case :ets.whereis(:resetting_table) do
  :undefined -> :ets.new(:resetting_table, [:named_table])
  _ -> :ets.delete_all_objects(:resetting_table)
end

Kino.ETS.new(:resetting_table)
:ets.insert(:resetting_table, {:key, "value"})

ETS Configuration

Lets use this opportunity to explore ETS table configuration.

:ets Tables are configured with a Table Type and Access Control.

Table Types

  • :set (default). One value per unique key.
  • :ordered_set One value per unique key, ordered by Elixir terms.
  • :bag Many values per key, but only one instance of each value per key.
  • :duplicate_bag Many values per key with duplicates allowed.

Access Control

  • :protected (default) Read from all process. Write allowed only for the parent process.
  • :public Read/Write available from all processes.
  • :private Read/Write allowed only for the parent process.

By default, :ets tables use the :set and :protected configuration values. So we may include or exclude them when starting an :ets table, and it does not have any effect.

:set

case :ets.whereis(:set_table) do
  :undefined -> :ets.new(:set_table, [:named_table, :set])
  _ -> :ets.delete_all_objects(:set_table)
end

Kino.ETS.new(:set_table)
:ets.insert(:set_table, {:duplicate, "1"})
:ets.insert(:set_table, {:duplicate, "2"})
:ets.insert(:set_table, {:duplicate, "3"})
# sets only store one value per key, so the table contains the last inserted value only.
:ets.insert(:set_table, {:duplicate, "4"})

:ets.insert(:set_table, {1, "1"})
:ets.insert(:set_table, {2, "2"})
:ets.insert(:set_table, {3, "3"})
:ets.insert(:set_table, {4, "4"})

# sets do not guarantee term ordering.
:ets.insert(:set_table, {:atom, ""})

:bag

case :ets.whereis(:bag_table) do
  :undefined -> :ets.new(:bag_table, [:named_table, :bag])
  _ -> :ets.delete_all_objects(:bag_table)
end

Kino.ETS.new(:bag_table)
# :bag allows multiple values under the same key
:ets.insert(:bag_table, {:key, "1"})
:ets.insert(:bag_table, {:key, "2"})
:ets.insert(:bag_table, {:key, "3"})
:ets.insert(:bag_table, {:key, "4"})

# However, values must be unique per key, so only one of the following will be stored.
:ets.insert(:bag_table, {:key, "duplicate"})
:ets.insert(:bag_table, {:key, "duplicate"})
:ets.insert(:bag_table, {:key, "duplicate"})
:ets.insert(:bag_table, {:key, "duplicate"})

:duplicate_bag

case :ets.whereis(:duplicate_bag_table) do
  :undefined -> :ets.new(:duplicate_bag_table, [:named_table, :duplicate_bag])
  _ -> :ets.delete_all_objects(:duplicate_bag_table)
end

Kino.ETS.new(:duplicate_bag_table)
# :duplicate_bag allows multiple values under the same key
:ets.insert(:duplicate_bag_table, {:key, "1"})
:ets.insert(:duplicate_bag_table, {:key, "2"})
:ets.insert(:duplicate_bag_table, {:key, "3"})
:ets.insert(:duplicate_bag_table, {:key, "4"})

# :duplicate_bag also allows duplicate values.
:ets.insert(:duplicate_bag_table, {:key, "duplicate"})
:ets.insert(:duplicate_bag_table, {:key, "duplicate"})
:ets.insert(:duplicate_bag_table, {:key, "duplicate"})
:ets.insert(:duplicate_bag_table, {:key, "duplicate"})

:ordered_set

Elixir terms are sorted according to order.

number < atom < reference < fun < port < pid < tuple < list < bit-string

case :ets.whereis(:ordered_set_table) do
  :undefined -> :ets.new(:ordered_set_table, [:named_table, :ordered_set])
  _ -> :ets.delete_all_objects(:ordered_set_table)
end

Kino.ETS.new(:ordered_set_table)
:ets.insert(:ordered_set_table, {:first_atom, ""})

:ets.insert(:ordered_set_table, {1, "1"})
:ets.insert(:ordered_set_table, {2, "2"})
:ets.insert(:ordered_set_table, {3, "3"})
:ets.insert(:ordered_set_table, {4, "4"})

:ets.insert(:ordered_set_table, {:second_atom, ""})
:ets.first(:ordered_set_table)
:ets.last(:ordered_set_table)

:private

Since Read/Write on :private tables is only allowed for the parent process, we cannot display a private table with Kino.

case :ets.whereis(:private_table) do
  :undefined -> :ets.new(:private_table, [:named_table, :private])
  _ -> :ets.delete_all_objects(:private_table)
end

Kino.ETS.new(:private_table)
:ets.insert(:private_table, {:key, "value"})
:ets.lookup(:private_table, :key)
# Task.start(fn ->
#   :ets.lookup(:private_table, :key)
# end)
# Task.start(fn ->
#   :ets.insert(:private_table, {:key, "value"})
# end)

:public

case :ets.whereis(:public_table) do
  :undefined -> :ets.new(:public_table, [:named_table, :public])
  _ -> :ets.delete_all_objects(:public_table)
end

Kino.ETS.new(:public_table)
:ets.insert(:public_table, {:key, "value"})
task =
  Task.async(fn ->
    :ets.insert(:public_table, {:key, "value"})
  end)

Task.await(task)

:protected

Writes only allowed from parent process. Reads allowed from all processes.

case :ets.whereis(:protected_table) do
  :undefined -> :ets.new(:protected_table, [:named_table, :protected])
  _ -> :ets.delete_all_objects(:protected_table)
end

Kino.ETS.new(:protected_table)
:ets.insert(:protected_table, {:key, "value"})
task =
  Task.async(fn ->
    :ets.lookup(:protected_table, :key)
  end)

Task.await(task)
# Task.start(fn ->
#   :ets.insert(:protected_table, {:key, "value"})
# end)

Up Next