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

Types

Types of locks that can be acquired.

Option Keyword that can be passed to some methods.

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. See lock/0 for more details.

  • limit - The maximum number of items to return in a query. This is used only read queries like match/3 or select/3, and is of the type non_neg_integer/0. Defaults to nil, 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 type Memento.Table.record/0. This is equivalent to calling Memento.Query.Data.load/1 on the returned records. This option is only available to some read methods like select/3 & match/3, and its value defaults to true.

Functions

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, {:_, :_, :_, :_})
Link to this function

delete(table, key, opts \\ [])

View Source
@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)
Link to this function

delete_record(record, opts \\ [])

View Source
@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)
Link to this function

match(table, pattern, opts \\ [])

View Source

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, {:_, :_, :_, :_, :_})
Link to this function

read(table, id, opts \\ [])

View Source
@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
Link to this function

select(table, guards, opts \\ [])

View Source
@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 to nil, meaning return all)
  • coerce (defaults to true)

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)
Link to this function

select_raw(table, match_spec, opts \\ [])

View Source
@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 to nil, meaning return all)
  • coerce (defaults to true)

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 selection
  • result = 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 specify coerce: false, so it doesn't raise errors.

  • Unlike the select/3 method, the operation values the guard 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.

Link to this function

write(record, opts \\ [])

View Source

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", ... }