View Source Memento.Query (memento v0.4.1)
Module to read/write from Memento Tables.
This module provides the most important transactional operations
that can be executed on Memento Tables. Mnesia's "dirty" methods
are left out on purpose. In almost all circumstances, these
methods would be enough for interacting with Memento Tables, but
for very special situations, it is better to directly use the
API provided by the Erlang :mnesia
module.
Transaction Only
All the methods exported by this module can only be executed
within the context of a Memento.Transaction
. Outside the
transaction (synchronous or not), these methods will raise an
error, even though they will be ignored in all examples moving
forward.
# Will raise an error
Memento.Query.read(Blog.Post, :some_id)
# Will work fine
Memento.transaction fn ->
Memento.Query.read(Blog.Post, :some_id)
end
Basic Operations
# Get all records in a Table
Memento.Query.all(User)
# Get a specific record by its primary key
Memento.Query.read(User, id)
# Write a record
Memento.Query.write(%User{id: 3, name: "Some User"})
# Delete a record by primary key
Memento.Query.delete(User, id)
# Delete a record by passing the full object
Memento.Query.delete_record(%User{id: 4, name: "Another User"})
Complex Queries
Memento provides 3 ways of querying records based on some passed conditions:
Each method uses a different way of querying records, which is
explained in detail for each of them in their method docs. But
the recommended method of performing queries is using the
select/3
method, which makes working with Erlang MatchSpec a
lot easier.
# Get all Movies
Memento.Query.select(Movie, [])
# Get all Movies named "Rush"
Memento.Query.select(Movie, {:==, :title, "Rush"})
# Get all Movies directed by Tarantino before the year 2000
guards = [
{:==, :director, "Quentin Tarantino"},
{:<, :year, 2000},
]
Memento.Query.select(Movie, guards)
Summary
Functions
Returns all records of a Table.
Delete a Record in the given table for the specified key.
Delete the given Memento record object.
Returns all records in a table that match the specified pattern.
Finds the Memento record for the given id in the specified table.
Return all records matching the given query.
Returns all records in the given table according to the full Erlang
match_spec
.
Writes a Memento record to its Mnesia table.
Types
@type lock() :: :read | :write | :sticky_write
Types of locks that can be acquired.
There are, in total, 3 types of locks that can be aqcuired, but
some operations don't support all of them. The write/2
method,
for example, can only accept :write
or :sticky_write
locks.
Conflicting lock requests are automatically queued if there is
no risk of deadlock. Otherwise, the transaction must be
terminated and executed again. Memento does this automatically
as long as the upper limit of retries
is not reached in a
transaction.
Types
:write
locks are exclusive. That means, if one transaction acquires a write lock, no other transaction can acquire any kind of lock on the same item.:read
locks can be shared, meaning if one transaction has a read lock on an item, other transactions can also acquire a read lock on the same item. However, no one else can acquire a write lock on that item while the read lock is in effect.:sticky_write
locks are used for optimizing write lock acquisitions, by informing other nodes which node is locked. New sticky lock requests from the same node are performed as local operations.
For more details, see :mnesia.lock/2
.
@type options() :: [lock: lock(), limit: non_neg_integer(), coerce: boolean()]
Option Keyword that can be passed to some methods.
These are all the possible options that can be set in the given keyword list, although it mostly depends on the method which options it actually uses.
Options
lock
- What kind of lock to acquire on the item in that transaction. This is the most common option, that almost all methods accept, and usually has some default value depending on the method. Seelock/0
for more details.limit
- The maximum number of items to return in a query. This is used only read queries likematch/3
orselect/3
, and is of the typenon_neg_integer/0
. Defaults tonil
, resulting in no limit and returning all records.coerce
- Records in Mnesia are stored in the form of a Tuple. This converts them into simple Memento struct records of typeMemento.Table.record/0
. This is equivalent to callingMemento.Query.Data.load/1
on the returned records. This option is only available to some read methods likeselect/3
&match/3
, and its value defaults totrue
.
Functions
@spec all(Memento.Table.name(), options()) :: [Memento.Table.record()]
Returns all records of a Table.
This is equivalent to calling match/3
with the catch-all pattern.
This also accepts an optional lock
option to acquire that kind of
lock in the transaction (defaults to :read
). See lock/0
for
more details about lock types.
# Both are equivalent
Memento.Query.all(Movie)
Memento.Query.match(Movie, {:_, :_, :_, :_})
@spec delete(Memento.Table.name(), term(), options()) :: :ok
Delete a Record in the given table for the specified key.
This method takes a Memento.Table
name and a key, and deletes all
records with that key (There can be more than one for table type
of bag
). Options default to [lock: :write]
.
If you want to delete a record, by passing the record itself as
the argument, see delete_record/2
.
Examples
# Delete a Blog Post record with the id `10` (primary key)
Memento.Query.delete(Blog.Post, 10)
@spec delete_record(Memento.Table.record(), options()) :: :ok
Delete the given Memento record object.
This method accepts a Memento.Table.record/0
object and deletes
that from its table. A complete record object needs to be specified
for this to work. Options default to [lock: :write]
.
This method is especially useful in Tables of type bag
where
multiple records can have the same key. Also see delete/3
.
Examples
Consider an Email
table of type bag
, with two attributes;
user_id
and email
, where user_id
is the primary key. The Table
contains all email addresses for a given user.
# Calling `delete` will delete all emails for a `user_id`:
Memento.Query.delete(Email, user_id)
# To delete a specific record, you have to pass the entire object:
email_record = %Email{user_id: 5, email: "a.specific@email.addr"}
Memento.Query.delete_record(email_record)
@spec match(Memento.Table.name(), tuple(), options()) :: [Memento.Table.record()] | no_return()
Returns all records in a table that match the specified pattern.
This method takes the name of a Memento.Table
and a tuple pattern
representing the values of those attributes, and returns all
records that match it. It uses :_
to represent attributes that
should be ignored. The tuple passed should be of the same length as
the number of attributes in that table, otherwise it will throw an
exception.
It's recommended to use the select/3
method as it is more
user-friendly, can let you make complex selections.
Also accepts an optional argument :lock
to acquire the kind of
lock specified in that transaction (defaults to :read
). See
lock/0
for more details. Also see :mnesia.match_object/3
.
Examples
Suppose a Movie
Table with these attributes: id
, title
, year
,
and director
. So the tuple passed in the match query should have
4 elements.
# Get all movies from the Table
Memento.Query.match(Movie, {:_, :_, :_, :_})
# Get all movies named 'Rush', with a write lock on the item
Memento.Query.match(Movie, {:_, "Rush", :_, :_}, lock: :write)
# Get all movies directed by Tarantino
Memento.Query.match(Movie, {:_, :_, :_, "Quentin Tarantino"})
# Get all movies directed by Spielberg, in the year 1993
Memento.Query.match(Movie, {:_, :_, 1993, "Steven Spielberg"})
# Will raise exceptions
Memento.Query.match(Movie, {:_, :_})
Memento.Query.match(Movie, {:_, :_, :_})
Memento.Query.match(Movie, {:_, :_, :_, :_, :_})
@spec read(Memento.Table.name(), any(), options()) :: Memento.Table.record() | nil
Finds the Memento record for the given id in the specified table.
If no record is found, nil
is returned. You can also pass an
optional keyword list as the 3rd argument. The only option currently
supported is :lock
, which acquires a lock of specified type on the
operation (defaults to :read
). See lock/0
for more details.
This method works a bit differently from the original :mnesia.read/3
when the table type is :bag
. Since a bag can have many records
with the same key, this returns only the first one. If you want to
fetch all records with the given key, use match/3
or select/3
.
Example
Memento.Query.read(Blog.Post, 1)
# => %Blog.Post{id: 1, ... }
Memento.Query.read(Blog.Post, 2, lock: :write)
# => %Blog.Post{id: 2, ... }
Memento.Query.read(Blog.Post, :unknown_id)
# => nil
@spec select(Memento.Table.name(), [tuple()] | tuple(), options()) :: [ Memento.Table.record() ]
Return all records matching the given query.
This method takes a table name, and a simplified version of the Erlang
MatchSpec consisting of one or more guards
. Each guard is of the
form {function, argument_1, argument_2}
, where the arguments can be
the Table fields, literals or other nested guard functions.
Guard Spec
Simple Operator Functions:
:==
- Equality:===
- Strict Equality (For Numbers):!=
- Inequality:!==
- Strict Inequality (For Numbers):<
- Less than:<=
- Less than or equal to:>
- Greater than:>=
- Greater than or equal to
Guard Functions that take nested arguments:
:or
:and
:xor
Options
This method also takes some optional arguments mentioned below. See
options/0
for more details.
lock
(defaults to:read
)limit
(defaults tonil
, meaning return all)coerce
(defaults totrue
)
Examples
Suppose a Movie
Table with these attributes: id
, title
, year
,
and director
.
# Get all Movies
Memento.Query.select(Movie, [])
# Get all Movies named "Rush"
Memento.Query.select(Movie, {:==, :title, "Rush"})
# Get all Movies directed by Tarantino before the year 2000
# Note: We could use a nested `and` function here as well
guards = [
{:==, :director, "Quentin Tarantino"},
{:<, :year, 2000},
]
Memento.Query.select(Movie, guards)
# Get all movies directed by Tarantino or Spielberg, in 2010 or later:
guards =
{:and
{:>=, :year, 2010},
{:or,
{:==, :director, "Quentin Tarantino"},
{:==, :director, "Steven Spielberg"},
}
}
Memento.Query.select(Movie, guards)
@spec select_raw(Memento.Table.name(), term(), options()) :: [Memento.Table.record()] | [term()]
Returns all records in the given table according to the full Erlang
match_spec
.
This method accepts a pure Erlang match_spec
term as described below,
which can be used to write some very complex queries, but that also
makes it very hard to use for beginners, and overly complex for everyday
queries. It is highly recommended that you use the select/3
method
which makes it much easier to write complex queries that work just as
well in 99% of the cases, by making some assumptions.
The arguments are directly passed on to the :mnesia.select/4
method
without translating queries, as they are done in select/3
.
Options
See options/0
for details about these options:
lock
(defaults to:read
)limit
(defaults tonil
, meaning return all)coerce
(defaults totrue
)
Match Spec
An Erlang "Match Specification" or match_spec
is a term describing
a small program that tries to match something. This is most popularly
used in both :ets
and :mnesia
. Quite simply, the grammar can be
defined as:
match_spec
=[match_function, ...]
(List of match functions)match_function
={match_head, [guard, ...], [result]}
match_head
=tuple
(A tuple representing attributes to match)guard
= A tuple representing conditions for selectionresult
= Atom describing the fields to return as the result
Here, the match_head
describes the attributes to match (like in
match/3
). You can use literals to specify an exact value to be
matched against or :"$n"
variables (:$1
, :$2
, ...) that can be
used so they can be referenced in the guards. You can get a default
value by calling YourTable.__info__().query_base
.
The second element in the tuple is a list of guard
terms, where each
guard is basically a tuple representing a condition of the form
{operation, arg1, arg2}
which can be simple {:==, :"$2", literal}
tuples or nested values like {:andalso, guard1, guard2}
. Finally,
result
represents the fields to return. Use :"$_"
to return all
fields, :"$n"
to return a specific field or :"$$"
for all fields
specified as variables in the match_head
.
Examples
Suppose a Movie
Table with these attributes: id
, title
, year
,
and director
. So the tuple passed as the match_head should have
5 elements.
Return all records:
match_head = Movie.__info__().query_base
result = [:"$_"]
guards = []
Memento.Query.select_raw(Movie, [{match_head, guards, result}])
# => [%Movie{...}, ...]
Get all movies with the title "Rush":
# We're using the match_head pattern here, but you can also use guards
match_head = {Movie, :"$1", "Rush", :"$2", :"$3"}
result = [:"$_"]
guards = []
Memento.Query.select_raw(Movie, [{match_head, guards, result}])
# => [%Movie{title: "Rush", ...}, ...]
Get all movies title names, that were directed by Tarantino before the year 2000:
# Using guards only here, but you can mix and match with head.
# You can also use a nested `{:andalso, guard1, guard2}` tuple
# here instead.
#
# We used the result value `[:"$2"]` so it only returns the
# second (i.e. title) field. Because of this, we're also not
# coercing the results.
match_head = {Movie, :"$1", :"$2", :"$3", :"$4"}
result = [:"$2"]
guards = [{:<, :"$3", 2000}, {:==, :"$4", "Quentin Tarantino"}]
Memento.Query.select_raw(Movie, [{match_head, guards, result}], coerce: false)
# => ["Reservoir Dogs", "Pulp Fiction", ...]
Get all movies directed by Tarantino or Spielberg, after the year 2010:
match_head = {Movie, :"$1", :"$2", :"$3", :"$4"}
result = [:"$_"]
guards = [
{:andalso,
{:>, :"$3", 2010},
{:orelse,
{:==, :"$4", "Quentin Tarantino"},
{:==, :"$4", "Steven Spielberg"},
}
}
]
Memento.Query.select_raw(Movie, [{match_head, guards, result}], coerce: true)
# => [%Movie{...}, ...]
Notes
It's important to note that for customized results (not equal to
:"$_"
), you should specifycoerce: false
, so it doesn't raise errors.Unlike the
select/3
method, theoperation
values theguard
tuples take in this method are Erlang atoms, not Elixir ones. For example, instead of:and
&:or
, they will be:andalso
&:orelse
. Similarly, you will have to use:"/="
instead of:!=
and:"=<"
instead of:<=
.
See the Match Specification
docs, :mnesia.select/2
and :ets.select/2
more details and examples.
@spec write(Memento.Table.record(), options()) :: Memento.Table.record() | no_return()
Writes a Memento record to its Mnesia table.
Returns the written record on success, or aborts the transaction
on failure. This operatiion acquires a lock of the kind specified,
which can be either :write
or :sticky_write
(defaults to
:write
). See lock/0
and :mnesia.write/3
for more details.
Autoincrement and nil
primary keys
This method will raise an error if the primary key of the passed
Memento record is nil
and the table does not have autoincrement
enabled. If it is enabled, this will find the last numeric key
used, increment it and assign it as the primary key of the written
record (which will be returned as a result of the write operation).
To enable autoincrement, the table needs to be of the type
ordered_set
and autoincrement: true
has to be specified in
the table definition. (See Memento.Table
for more details).
Examples
Memento.Query.write(%Blog.Post{title: "something", ... })
# => %Blog.Post{id: 4, title: "something"}
Memento.Query.write(%Blog.Author{username: "sye", ... })
# => %Blog.Author{username: "sye", ... }