The following notes are complementary to this ETS lesson with a focus on "ets.select / match_spec" and the Ex2ms
library to use with ETS
, the Erlang built-in in-memory database.
Suppose we have an ETS table that saves messages exchanged between two users. The records will be in the form:
{timestamp, user, receiver, msg}
We will use match_spec
for .select
based queries.
Remove "old" messages
We want to run periodical cleanups to limit the size of the table. We use the Erlang-ETS function :ets.select_delete/2
that takes the table name (we named it in the options) and a match_spec
.
A record to be deleted should have a "match_spec" that returns true
whenever this record matches. A match here is when the first element of the record - the timestamp - is lower than a given constant.
For example, if the constant is 1, a "match_spec" to be used in the function :ets.select_delete
is:
ms = [{ {:"$1", :"$2", :"$3", :"$4"}, [ {:<, :"$1", {:const, 1}} ], [true] }]
and use it: :ets.select_delete(:table, ms)
.
We can test this against the table with the function :ets.test_ms/2
. It will detect errors if any.
We can advantageously use the package Ex2ms
to rewrite this into a function form with Ex2ms.fun
. It is more readable, but also allows you to use variables in the scope. Add the package to the MixProject
.
With this package, the "match_spec" is built by passing a function to Ex2ms.fun
that returns the same "match_spec":
ms = Ex2ms.fun do {t, _e, _u, _r, _m} when t < 1 -> true end
you can use the built-in function
fm2ms
when you use constants, but when you want to use variables in scope, then you should use theEx2ms
package.
We can use a variable in scope and use this "match_spec" in the :ets.select_delete
function:
# module MyApp.ChatCache import Ex2ms, only: [fun: 1] def clean_up(delay) do some_time_ago = System.os_time(:second) - delay * 60 ms = fun do {t, _e, _u, _r, _m} when t < ^some_time_ago -> true end :ets.select_delete(:chat, ms) end
This function returns the number of records deleted.
Another example
We want to :ets.select
from our table all the messages exchanged between a couple of users. In other words, we want the records where the user and receiver - the second and third element (of the record) - are equal to some values, regardless of the order.
Given two constants values a
and b
that represent our couple of users, our constraint is:
($2 == "a" and $3 == "b") or ($2 == "b" and $3 == "a")
Such a "match_spec" would be written (carefully!) in Erlang for our table:
[{ {:"$1", :"$2", :"$3", :"$4"}, [{ :orelse, {:andalso, {:==, :"$2", {:const, "a"}}, {:==, :"$3", {:const, "b"}} }, {:andalso, {:==, :"$2", {:const, "b"}}, {:==, :"$3", {:const, "a"}} } }], [{ {:"$1", :"$2", :"$3", :"$4"} }] }]
The list of Erlang's allowed function descriptions is here. Not so easy? Ex2ms
makes it easy:
import Ex2ms, only: [fun: 1] def get_messages_by_channel(val_e, val_r) do ms = fun do {t, e, r, m} when (e == ^val_e and r == ^val_r) or (e == ^val_r and r == ^val_e) -> {t, e, r, m} :ets.select(:chat, ms) end
Top comments (0)