Module khepri_machine

Khepri low-level API.

Behaviours: ra_machine.

Description

Khepri low-level API.

This module exposes the "low-level" API to the Khepri database and state machine. All functions in khepri are built on top of this module.

The API is divided into two parts:
  1. Functions to manipulate a simple set of tree nodes directly.
  2. Functions to perform transactional queries and updates.

The store ID

All functions require a store ID (store_id/0). The store ID corresponds to the name of the Ra cluster Khepri was started with.

See khepri for more details about Ra systems and clusters.

Direct manipulation on tree nodes

The API provides the following three functions:

All functions take a native path pattern. They do not accept Unix-like paths.

All functions return one of these tuples:

Transactional queries and updates

Transactions are handled by transaction/2 and transaction/3.

Both functions take an anonymous function. See khepri_tx for more details about those functions and in particular their restrictions.

The return value is whatever the anonymous function returns if it succeeded or the reason why it aborted, similar to what mnesia:transaction/1 returns.

Data Types

child_list_length()

child_list_length() = non_neg_integer()

Number of direct child nodes under a tree node.

child_list_version()

child_list_version() = pos_integer()

Number of changes made to the list of child nodes of a node (child nodes added or removed).

The child list version starts at 1 when a node is created. It is increased by 1 each time a child is added or removed. Changes made to existing nodes are not reflected in this version.

data()

data() = any()

Data stored in a node's payload.

event_filter()

event_filter() = event_filter_tree()

event_filter_tree()

event_filter_tree() = #kevf_tree{path = khepri_path:pattern(), props = #{on_actions => [create | update | delete], priority => integer()}}

keep_while_conds_map()

keep_while_conds_map() = #{khepri_path:path() => khepri_condition:keep_while()}

Internal index of the per-node keep_while conditions.

keep_while_conds_revidx()

keep_while_conds_revidx() = #{khepri_path:path() => #{khepri_path:path() => ok}}

Internal reverse index of the keep_while conditions. If node A depends on a condition on node B, then this reverse index will have a "node B => node A" entry.

machine_config()

machine_config() = #config{store_id = khepri:store_id(), snapshot_interval = non_neg_integer()}

Configuration record, holding read-only or rarely changing fields.

node_props()

node_props() = #{data => data(), sproc => khepri_fun:standalone_fun(), payload_version => payload_version(), child_list_version => child_list_version(), child_list_length => child_list_length(), child_nodes => #{khepri_path:node_id() => node_props()}}

Structure used to return properties, payload and child nodes for a specific node.

node_props_map()

node_props_map() = #{khepri_path:path() => node_props()}

Structure used to return a map of nodes and their associated properties, payload and child nodes.

This structure is used in the return value of all commands and queries.

operation_options()

operation_options() = #{expect_specific_node => boolean(), include_child_names => boolean()}

Options used in find_matching_nodes/3.

payload()

payload() = none | payload_data() | payload_sproc()

All types of payload stored in the nodes of the tree structure.

Beside the absence of payload, the only type of payload supported is data.

payload_data()

payload_data() = #kpayload_data{data = khepri_machine:data()}

payload_sproc()

payload_sproc() = #kpayload_sproc{sproc = khepri_fun:standalone_fun()}

payload_version()

payload_version() = pos_integer()

Number of changes made to the payload of a node.

The payload version starts at 1 when a node is created. It is increased by 1 each time the payload is added, modified or removed.

result()

result() = khepri:ok(node_props_map()) | khepri:error()

Return value of a command or query.

stat()

stat() = #{payload_version := payload_version(), child_list_version := child_list_version()}

Stats attached to each node in the tree structure.

state()

state() = #khepri_machine{config = khepri_machine:machine_config(), root = khepri_machine:tree_node(), keep_while_conds = khepri_machine:keep_while_conds_map(), keep_while_conds_revidx = khepri_machine:keep_while_conds_revidx(), triggers = #{khepri_machine:trigger_id() => #{sproc := khepri_path:path(), event_filter := khepri_machine:event_filter()}}, emitted_triggers = [khepri_machine:triggered()], metrics = #{applied_command_count => non_neg_integer()}}

State of this Ra state machine.

tree_node()

tree_node() = #node{stat = khepri_machine:stat(), payload = khepri_machine:payload(), child_nodes = #{khepri_path:component() := #node{stat = khepri_machine:stat(), payload = khepri_machine:payload(), child_nodes = #{khepri_path:component() := #node{}}}}}

A node in the tree structure.

trigger_id()

trigger_id() = atom()

An ID to identify a registered trigger.

triggered()

triggered() = #triggered{id = khepri_machine:trigger_id(), event_filter = khepri_machine:event_filter(), sproc = khepri_fun:standalone_fun(), props = map()}

Function Index

put/3Creates or modifies a specific tree node in the tree structure.
put/4Creates or modifies a specific tree node in the tree structure.
get/2Returns all tree nodes matching the path pattern.
get/3Returns all tree nodes matching the path pattern.
delete/2Deletes all tree nodes matching the path pattern.
transaction/2Runs a transaction and returns the result.
transaction/3Runs a transaction and returns the result.
run_sproc/3Executes a stored procedure.
register_trigger/4Registers a trigger.

Function Details

put/3

put(StoreId, PathPattern, Payload) -> Result

Creates or modifies a specific tree node in the tree structure.

Calling this function is the same as calling put(StoreId, PathPattern, Payload, #{}).

See also: put/4.

put/4

put(StoreId, PathPattern, Payload, Extra) -> Result

StoreId: the name of the Ra cluster.
PathPattern: the path (or path pattern) to the node to create or modify.
Payload: the payload to put in the specified node.
Extra: extra options such as keep_while conditions.

returns: an "ok" tuple with a map with one entry, or an "error" tuple.

Creates or modifies a specific tree node in the tree structure.

The path or path pattern must target a specific tree node.

When using a simple path, if the target node does not exists, it is created using the given payload. If the target node exists, it is updated with the given payload and its payload version is increased by one. Missing parent nodes are created on the way.

When using a path pattern, the behavior is the same. However if a condition in the path pattern is not met, an error is returned and the tree structure is not modified.

If the target node is modified, the returned structure in the "ok" tuple will have a single key corresponding to the path of the target node. That key will point to a map containing the properties and payload (if any) of the node before the modification.

If the target node is created , the returned structure in the "ok" tuple will have a single key corresponding to the path of the target node. That key will point to empty map, indicating there was no existing node (i.e. there was no properties or payload to return).

The payload must be one of the following form: Example:
%% Insert a node at `/foo/bar', overwriting the previous value.
Result = khepri_machine:put(
           ra_cluster_name, [foo, bar], #kpayload_data{data = new_value}),
 
%% Here is the content of `Result'.
{ok, #{[foo, bar] => #{data => old_value,
                       payload_version => 1,
                       child_list_version => 1,
                       child_list_length => 0}}} = Result.

get/2

get(StoreId, PathPattern) -> Result

Returns all tree nodes matching the path pattern.

Calling this function is the same as calling get(StoreId, PathPattern, #{}).

See also: get/3.

get/3

get(StoreId, PathPattern, Options) -> Result

StoreId: the name of the Ra cluster.
PathPattern: the path (or path pattern) to match against the nodes to retrieve.
Options: options to tune the tree traversal or the returned structure content.

returns: an "ok" tuple with a map with zero, one or more entries, or an "error" tuple.

Returns all tree nodes matching the path pattern.

The returned structure in the "ok" tuple will have a key corresponding to the path per node which matched the pattern. Each key will point to a map containing the properties and payload of that matching node.

Example:
%% Query the node at `/foo/bar'.
Result = khepri_machine:get(ra_cluster_name, [foo, bar]),
 
%% Here is the content of `Result'.
{ok, #{[foo, bar] => #{data => new_value,
                       payload_version => 2,
                       child_list_version => 1,
                       child_list_length => 0}}} = Result.

delete/2

delete(StoreId, PathPattern) -> Result

StoreId: the name of the Ra cluster.
PathPattern: the path (or path pattern) to match against the nodes to delete.

returns: an "ok" tuple with a map with zero, one or more entries, or an "error" tuple.

Deletes all tree nodes matching the path pattern.

The returned structure in the "ok" tuple will have a key corresponding to the path per node which was deleted. Each key will point to a map containing the properties and payload of that deleted node.

Example:
%% Delete the node at `/foo/bar'.
Result = khepri_machine:delete(ra_cluster_name, [foo, bar]),
 
%% Here is the content of `Result'.
{ok, #{[foo, bar] => #{data => new_value,
                       payload_version => 2,
                       child_list_version => 1,
                       child_list_length => 0}}} = Result.

transaction/2

transaction(StoreId, Fun) -> Ret

Runs a transaction and returns the result.

Calling this function is the same as calling transaction(StoreId, Fun, auto).

See also: transaction/3.

transaction/3

transaction(StoreId, Fun, ReadWrite) -> Ret

StoreId: the name of the Ra cluster.
Fun: an arbitrary anonymous function.

returns: {atomic, Result} with the return value of Fun, or {aborted, Reason} if the anonymous function was aborted.

Runs a transaction and returns the result.

Fun is an arbitrary anonymous function which takes no arguments.

The ReadWrite flag determines what the anonymous function is allowed to do and in which context it runs:

The result of Fun can be any term. That result is returned in an {atomic, Result} tuple.

run_sproc/3

run_sproc(StoreId, PathPattern, Args) -> Ret

StoreId: the name of the Ra cluster.
PathPattern: the path to the stored procedure.
Args: the list of args to pass to the stored procedure; its length must be equal to the stored procedure arity.

returns: the return value of the stored procedure.

Executes a stored procedure.

The stored procedure is executed in the context of the caller of run_sproc/3.

register_trigger/4

register_trigger(StoreId, TriggerId, EventFilter, StoredProcPath) -> Ret

StoreId: the name of the Ra cluster.
TriggerId: the name of the trigger.
EventFilter: the event filter used to associate an event with a stored procedure.
StoredProcPath: the path to the stored procedure to execute when the corresponding event occurs.

returns: ok if the trigger was registered, an "error" tuple otherwise.

Registers a trigger.

A trigger is based on an event filter. It associates an event with a stored procedure. When an event matching the event filter is emitted, the stored procedure is executed. Here is an example of an event filter:

EventFilter = #kevf_tree{path = [stock, wood, <<"oak">>],  %% Required
                         props = #{on_actions => [delete], %% Optional
                                   priority => 10}},       %% Optional

The stored procedure is expected to accept a single argument. This argument is a map containing the event properties. Here is an example:

my_stored_procedure(Props) ->
    #{path := Path},
      on_action => Action} = Props.

The stored procedure is executed on the leader's Erlang node.

It is guarantied to run at least once. It could be executed multiple times if the Ra leader changes, therefore the stored procedure must be idempotent.


Generated by EDoc