ETS 1 to n lookup; updating ETS entry with update_element

To allow data lookup, I would like to use an ETS table.

I have a 1 to n relation that I want to store, where I usually have the 1 and want to find the n.

My first thought was to use a :set with key->list() lookup and, if more options are added to the key, append the option to the list.

:ets.update_element sounds like a good thing to use here, but I think I do not understand the syntax.

What I can do is:

:ets.new(:table, [:set, :protected, :named_table])
:ets.insert(:table, {:a, 10})
:ets.insert(:table, {:b, 11})
:ets.insert(:table, [c: 12, d: 13, e: 14])
:ets.lookup(:table, :d) # <- returns [d: 14]

Here, numbers are used for simplicity.

But I cannot figure out how to update a value, for example turn d: 14 into d: rem(14,3).

I managed to “update” the value using

:ets.update_element(:table, :d, {2, rem(14,3})

“update” is in quotation marks, because the table entry is updated, but the value is simply set without regard of its previous value. Is there a way to alter the value based on the previous value? Maybe something like

:ets.update_element(:table, :d, {2, &(rem(&1, 3))}) # actually stores the _function_, not the result

Or is the approach of using a :set and storing the list with n elements for 1 given key not a good fit in this scenario?

Afaik the only atomic updates based on a previous valus is with :ets.update_counter, which only supports a limited set of operations. Otherwise you need to lookup and insert, which for a protected table should not be a problem given there’s no write concurrency.

2 Likes

This sounds like a good place to use :duplicate_bag:

:ets.new(:table, [:duplicate_bag, :named_table])
:ets.insert(:table, {:b, 11})
:ets.insert(:table, {:b, 12})
:ets.insert(:table, [b: 12, c: 14, a: 10])

:ets.lookup(:table, :b) |> IO.inspect(label: "result for :b")

:ets.tab2list(:table) |> IO.inspect(label: "whole table")

prints out:

result for :b: [b: 11, b: 12, b: 12]
whole table: [c: 14, b: 11, b: 12, b: 12, a: 10]