%% This Source Code Form is subject to the terms of the Mozilla Public %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% %% Copyright © 2021-2025 Broadcom. All Rights Reserved. The term "Broadcom" %% refers to Broadcom Inc. and/or its subsidiaries. %% %% @doc Khepri database API. %% %% This module exposes the database API to manipulate data. %% %% The API is mainly made of the functions used to perform simple direct atomic %% operations and queries on the database: {@link get/1}, {@link put/2}, {@link %% delete/1} and so on. In addition to that, {@link transaction/1} is the %% starting point to run transaction functions. However the API to use inside %% transaction functions is provided by {@link khepri_tx}. %% %% Functions in this module have simplified return values to cover most %% frequent use cases. If you need more details about the queried or modified %% tree nodes, like the ability to distinguish a non-existent tree node from a %% tree node with no payload, you can use the {@link khepri_adv} module. %% %% This module also provides functions to start and stop a simple unclustered %% Khepri store. For more advanced setup and clustering, see {@link %% khepri_cluster}. %% %% == A Khepri store == %% %% A Khepri store is one instance of Khepri running inside a Ra cluster (which %% could be made of a single Erlang node). It is possible to run multiple %% Khepri stores in parallel by creating multiple Ra clusters. %% %% A Khepri store is started and configured with {@link start/0}, {@link %% start/1} or {@link start/3}. To setup a cluster, see {@link %% khepri_cluster}. %% %% When a store is started, a store ID {@link store_id()} is returned. This %% store ID is then used by the rest of this module's API. The returned store %% ID currently corresponds exactly to the Ra cluster name. It must be an atom %% though; other types are unsupported. %% %% == Interacting with the Khepri store == %% %% The API provides two ways to interact with a Khepri store: %% %% %% Simple operations are calls like: %% %% %% Transactions are like Mnesia ones. The caller passes an anonymous function %% to {@link transaction/1}, etc.: %% ``` %% khepri:transaction( %% fun() -> %% khepri_tx:put(Path, Value) %% end). %% ''' %% %% Simple operations are more efficient than transactions, but transactions are %% more flexible. -module(khepri). -include_lib("kernel/include/logger.hrl"). -include("include/khepri.hrl"). -include("src/khepri_cluster.hrl"). -include("src/khepri_error.hrl"). -include("src/khepri_ret.hrl"). -export([ %% Functions to start & stop a Khepri store; for more %% advanced functions, including clustering, see `khepri_cluster'. start/0, start/1, start/2, start/3, reset/0, reset/1, reset/2, stop/0, stop/1, get_store_ids/0, is_empty/0, is_empty/1, is_empty/2, %% Simple direct atomic operations & queries. get/1, get/2, get/3, get_or/2, get_or/3, get_or/4, get_many/1, get_many/2, get_many/3, get_many_or/2, get_many_or/3, get_many_or/4, exists/1, exists/2, exists/3, has_data/1, has_data/2, has_data/3, is_sproc/1, is_sproc/2, is_sproc/3, count/1, count/2, count/3, fold/3, fold/4, fold/5, foreach/2, foreach/3, foreach/4, map/2, map/3, map/4, filter/2, filter/3, filter/4, run_sproc/2, run_sproc/3, run_sproc/4, put/2, put/3, put/4, put_many/2, put_many/3, put_many/4, create/2, create/3, create/4, update/2, update/3, update/4, compare_and_swap/3, compare_and_swap/4, compare_and_swap/5, delete/1, delete/2, delete/3, delete_many/1, delete_many/2, delete_many/3, clear_payload/1, clear_payload/2, clear_payload/3, clear_many_payloads/1, clear_many_payloads/2, clear_many_payloads/3, register_trigger/3, register_trigger/4, register_trigger/5, register_projection/2, register_projection/3, register_projection/4, unregister_projections/1, unregister_projections/2, unregister_projections/3, has_projection/1, has_projection/2, has_projection/3, %% Transactions; `khepri_tx' provides the API to use inside %% transaction functions. transaction/1, transaction/2, transaction/3, transaction/4, transaction/5, fence/0, fence/1, fence/2, handle_async_ret/1, handle_async_ret/2, %% Bang functions: they return the value directly or throw an error. 'get!'/1, 'get!'/2, 'get!'/3, 'get_or!'/2, 'get_or!'/3, 'get_or!'/4, 'get_many!'/1, 'get_many!'/2, 'get_many!'/3, 'get_many_or!'/2, 'get_many_or!'/3, 'get_many_or!'/4, 'exists!'/1, 'exists!'/2, 'exists!'/3, 'has_data!'/1, 'has_data!'/2, 'has_data!'/3, 'is_sproc!'/1, 'is_sproc!'/2, 'is_sproc!'/3, 'count!'/1, 'count!'/2, 'count!'/3, 'put!'/2, 'put!'/3, 'put!'/4, 'put_many!'/2, 'put_many!'/3, 'put_many!'/4, 'create!'/2, 'create!'/3, 'create!'/4, 'update!'/2, 'update!'/3, 'update!'/4, 'compare_and_swap!'/3, 'compare_and_swap!'/4, 'compare_and_swap!'/5, 'delete!'/1, 'delete!'/2, 'delete!'/3, 'delete_many!'/1, 'delete_many!'/2, 'delete_many!'/3, 'clear_payload!'/1, 'clear_payload!'/2, 'clear_payload!'/3, 'clear_many_payloads!'/1, 'clear_many_payloads!'/2, 'clear_many_payloads!'/3, export/2, export/3, export/4, import/2, import/3, info/0, info/1, info/2]). -compile({no_auto_import, [get/1, get/2, put/2, erase/1]}). %% FIXME: Dialyzer complains about several functions with "optional" arguments %% (but not all). I believe the specs are correct, but can't figure out how to %% please Dialyzer. So for now, let's disable this specific check for the %% problematic functions. -dialyzer({no_underspecs, [start/1, start/2, stop/0, stop/1, put/2, put/3, create/2, create/3, update/2, update/3, compare_and_swap/3, compare_and_swap/4, exists/2, has_data/2, is_sproc/2, run_sproc/3, transaction/2, transaction/3, unwrap_result/1]}). -type store_id() :: atom(). %% ID of a Khepri store. %% %% This is the same as the Ra cluster name hosting the Khepri store. -type data() :: any(). %% Data stored in a tree node's payload. -type payload_version() :: pos_integer(). %% Number of changes made to the payload of a tree node. %% %% The payload version starts at 1 when a tree node is created. It is increased %% by 1 each time the payload is added, modified or removed. -type child_list_version() :: pos_integer(). %% Number of changes made to the list of child nodes of a tree node (child %% nodes added or removed). %% %% The child list version starts at 1 when a tree 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. -type child_list_length() :: non_neg_integer(). %% Number of direct child nodes under a tree node. -type delete_reason() :: explicit | keep_while. %% The reason why a tree node was removed from the store. %% %% -type node_props() :: #{data => khepri:data(), has_data => boolean(), sproc => horus:horus_fun(), is_sproc => boolean(), payload_version => khepri:payload_version(), child_list_version => khepri:child_list_version(), child_list_length => khepri:child_list_length(), child_names => [khepri_path:node_id()], delete_reason => khepri:delete_reason()}. %% Structure used to return properties, payload and child nodes for a specific %% tree node. %% %% The payload in `data' or `sproc' is only returned if the tree node carries %% something. If that key is missing from the returned properties map, it means %% the tree node has no payload. %% %% By default, the payload (if any) and its version are returned by functions %% exposed by {@link khepri_adv}. The list of returned properties can be %% configured using the `props_to_return' option (see {@link tree_options()}). -type node_props_map() :: #{khepri_path:native_path() => khepri:node_props()}. %% Structure used to return a map of nodes and their associated properties, %% payload and child nodes. -type trigger_id() :: atom(). %% An ID to identify a registered trigger. -type async_option() :: boolean() | ra_server:command_correlation() | ra_server:command_priority() | {ra_server:command_correlation(), ra_server:command_priority()}. %% Option to indicate if the command should be synchronous or asynchronous. %% %% Values are: %% -type reply_from_option() :: leader | local | {member, ra:server_id()}. %% Options to indicate which member of the cluster should reply to a command %% request. %% %% Note that commands are always handled by the leader. This option only %% controls which member of the cluster carries out the reply. %% %% %% %% When `reply_from' is `{member, Member}' and the given member is not part of %% the cluster or when `reply_from' is `local' and there is no member local to %% the caller, the leader member will perform the reply. This mechanism uses %% the cluster membership information to decide which member should reply: if %% the given `Member' or local member is a member of the cluster but is offline %% or unreachable, no reply may be sent even though the leader may have %% successfully handled the command. -type command_options() :: #{timeout => timeout(), async => async_option(), reply_from => reply_from_option(), protect_against_dups => boolean()}. %% Options used in commands. %% %% Commands are {@link put/4}, {@link delete/3} and read-write {@link %% transaction/4}. %% %% -type favor_option() :: consistency | low_latency. %% Option to indicate where to put the cursor between freshness of the %% returned data and low latency of queries. %% %% Values are: %% %% %% As described above, queries are always evaluated locally by the cluster %% member that gets the call. The reason Ra's leader and consistent queries %% are not exposed is that the remote execution of the query function may fail %% in subtle on non-subtle ways. For instance, the remote node might run a %% different version of Erlang or Khepri. -type query_options() :: #{condition => ra:query_condition(), timeout => timeout(), favor => favor_option()}. %% Options used in queries. %% %% %% %% `favor' computes a `condition' internally. Therefore if both options are %% set, `condition' takes precedence and `favor' is ignored. %% %% NOTE: When this list of query options is modified, {@link %% khepri_machine:split_query_options/2} must be adapted. -type tree_options() :: #{expect_specific_node => boolean(), props_to_return => [known_prop_to_return() | unknown_prop_to_return()], include_root_props => boolean(), return_indirect_deletes => boolean()}. %% Options used during tree traversal. %% %% %% %% NOTE: When this list of tree options is modified, {@link %% khepri_machine:split_query_options/2} and {@link %% khepri_machine:split_command_options/2} must be adapted. -type known_prop_to_return() :: payload_version | child_list_version | child_list_length | child_names | payload | has_payload | raw_payload | delete_reason. %% Name of a known property to return. %% %% To be used in {@link khepri:tree_options()} `props_to_return' list. -type unknown_prop_to_return() :: atom(). %% Name of a property unknown to this version of Khepri. %% %% This can happen when a query is emitted by a newer version of Khepri that %% added new properties to return. %% %% Can be seen in {@link khepri:tree_options()} `props_to_return' list. -type put_options() :: #{keep_while => khepri_condition:keep_while()}. %% Options specific to updates. %% %% %% %% NOTE: When this list of tree options is modified, {@link %% khepri_machine:split_command_options/2} and {@link %% khepri_machine:split_put_options/2} must be adapted. -type fold_fun() :: fun((khepri_path:native_path(), khepri:node_props(), khepri:fold_acc()) -> khepri:fold_acc()). %% Function passed to {@link khepri:fold/5}. -type fold_acc() :: any(). %% Term passed to and returned by a {@link fold_fun/0}. -type foreach_fun() :: fun((khepri_path:native_path(), khepri:node_props()) -> any()). %% Function passed to {@link khepri:foreach/4}. -type map_fun() :: fun((khepri_path:native_path(), khepri:node_props()) -> khepri:map_fun_ret()). %% Function passed to {@link khepri:map/4}. -type map_fun_ret() :: any(). %% Value returned by {@link khepri:map_fun/0}. -type filter_fun() :: fun((khepri_path:native_path(), khepri:node_props()) -> boolean()). %% Function passed to {@link khepri:filter/4}. -type ok(Type) :: {ok, Type}. %% The result of a function after a successful call, wrapped in an "ok" tuple. -type error(Type) :: {error, Type}. %% Return value of a failed command or query. -type error() :: error(any()). %% The error tuple returned by a function after a failure. -type minimal_ret() :: ok | khepri:error(). %% The return value of update functions in the {@link khepri} module. -type payload_ret(Default) :: khepri:ok(khepri:data() | horus:horus_fun() | Default) | khepri:error(). %% The return value of query functions in the {@link khepri} module that work %% on a single tree node. %% %% `Default' is the value to return if a tree node has no payload attached to %% it. -type payload_ret() :: payload_ret(undefined). %% The return value of query functions in the {@link khepri} module that work %% on a single tree node. %% %% `undefined' is returned if a tree node has no payload attached to it. -type many_payloads_ret(Default) :: khepri:ok(#{khepri_path:path() => khepri:data() | horus:horus_fun() | Default}) | khepri:error(). %% The return value of query functions in the {@link khepri} module that work %% on many nodes. %% %% `Default' is the value to return if a tree node has no payload attached to %% it. -type many_payloads_ret() :: many_payloads_ret(undefined). %% The return value of query functions in the {@link khepri} module that work %% on a many nodes. %% %% `undefined' is returned if a tree node has no payload attached to it. -type async_ret() :: khepri_machine:write_ret() | khepri_tx:tx_fun_result() | khepri:error({not_leader, ra:server_id()}). %% The value returned from of a command function which was executed %% asynchronously. %% %% When a caller includes a correlation ID ({@link %% ra_server:command_correlation()}) {@link async_option()} in their {@link %% khepri:command_options()} on a command function, the caller will receive a %% `ra_event' message. Handling the notification with {@link %% khepri:handle_async_ret/2} will return a list of pairs of correlation IDs %% ({@link ra_server:command_correlation()}) and the return values of the %% commands which were applied, or `{error, {not_leader, LeaderId}}' if the %% commands could not be applied since they were sent to a non-leader member. %% %% Note that when commands are successfully applied, the return values are %% {@link khepri_machine:write_ret()} rather than {@link khepri:minimal_ret()}, %% even if the command was sent using a function from the {@link khepri} API %% such as {@link khepri:put/4}. %% %% See {@link khepri:handle_async_ret/2}. -export_type([store_id/0, ok/1, error/0, error/1, data/0, payload_version/0, child_list_version/0, child_list_length/0, delete_reason/0, node_props/0, node_props_map/0, trigger_id/0, async_option/0, reply_from_option/0, command_options/0, favor_option/0, query_options/0, tree_options/0, put_options/0, fold_fun/0, fold_acc/0, foreach_fun/0, map_fun/0, map_fun_ret/0, filter_fun/0, minimal_ret/0, payload_ret/0, payload_ret/1, many_payloads_ret/0, many_payloads_ret/1, unwrapped_minimal_ret/0, unwrapped_payload_ret/0, unwrapped_payload_ret/1, unwrapped_many_payloads_ret/0, unwrapped_many_payloads_ret/1, async_ret/0]). %% ------------------------------------------------------------------- %% Service management. %% ------------------------------------------------------------------- -spec start() -> Ret when Ret :: khepri:ok(StoreId) | khepri:error(), StoreId :: khepri:store_id(). %% @doc Starts a store. %% %% @see khepri_cluster:start/0. start() -> khepri_cluster:start(). -spec start(RaSystem | DataDir) -> Ret when RaSystem :: atom(), DataDir :: file:filename_all(), Ret :: khepri:ok(StoreId) | khepri:error(), StoreId :: khepri:store_id(). %% @doc Starts a store. %% %% @see khepri_cluster:start/1. start(RaSystemOrDataDir) -> khepri_cluster:start(RaSystemOrDataDir). -spec start(RaSystem | DataDir, StoreId | RaServerConfig) -> Ret when RaSystem :: atom(), DataDir :: file:filename_all(), StoreId :: store_id(), RaServerConfig :: khepri_cluster:incomplete_ra_server_config(), Ret :: khepri:ok(StoreId) | khepri:error(), StoreId :: khepri:store_id(). %% @doc Starts a store. %% %% @see khepri_cluster:start/2. start(RaSystemOrDataDir, StoreIdOrRaServerConfig) -> khepri_cluster:start(RaSystemOrDataDir, StoreIdOrRaServerConfig). -spec start(RaSystem | DataDir, StoreId | RaServerConfig, Timeout) -> Ret when RaSystem :: atom(), DataDir :: file:filename_all(), StoreId :: store_id(), RaServerConfig :: khepri_cluster:incomplete_ra_server_config(), Timeout :: timeout(), Ret :: khepri:ok(StoreId) | khepri:error(), StoreId :: khepri:store_id(). %% @doc Starts a store. %% %% @see khepri_cluster:start/3. start(RaSystemOrDataDir, StoreIdOrRaServerConfig, Timeout) -> khepri_cluster:start( RaSystemOrDataDir, StoreIdOrRaServerConfig, Timeout). -spec reset() -> Ret when Ret :: ok | error(). %% @doc Resets the store on this Erlang node. %% %% @see khepri_cluster:reset/0. reset() -> khepri_cluster:reset(). -spec reset(StoreId | Timeout) -> Ret when StoreId :: khepri:store_id(), Timeout :: timeout(), Ret :: ok | khepri:error(). %% @doc Resets the store on this Erlang node. %% %% @see khepri_cluster:reset/1. reset(StoreIdOrTimeout) -> khepri_cluster:reset(StoreIdOrTimeout). -spec reset(StoreId, Timeout) -> Ret when StoreId :: khepri:store_id(), Timeout :: timeout(), Ret :: ok | error(). %% @doc Resets the store on this Erlang node. %% %% @see khepri_cluster:reset/2. reset(StoreId, Timeout) -> khepri_cluster:reset(StoreId, Timeout). -spec stop() -> Ret when Ret :: ok | khepri:error(). %% @doc Stops a store. %% %% @see khepri_cluster:stop/0. stop() -> khepri_cluster:stop(). -spec stop(StoreId) -> Ret when StoreId :: khepri:store_id(), Ret :: ok | khepri:error(). %% @doc Stops a store. %% %% @see khepri_cluster:stop/1. stop(StoreId) -> khepri_cluster:stop(StoreId). -spec get_store_ids() -> [StoreId] when StoreId :: store_id(). %% @doc Returns the list of running stores. %% %% @see khepri_cluster:get_store_ids/0. get_store_ids() -> khepri_cluster:get_store_ids(). %% ------------------------------------------------------------------- %% is_empty(). %% ------------------------------------------------------------------- -spec is_empty() -> IsEmpty | Error when IsEmpty :: boolean(), Error :: khepri:error(). %% @doc Indicates if the store is empty or not. %% %% Calling this function is the same as calling `is_empty(StoreId)' with the %% default store ID (see {@link khepri_cluster:get_default_store_id/0}). %% %% @see is_empty/1. %% @see is_empty/2. is_empty() -> StoreId = khepri_cluster:get_default_store_id(), is_empty(StoreId). -spec is_empty (StoreId) -> IsEmpty | Error when StoreId :: khepri:store_id(), IsEmpty :: boolean(), Error :: khepri:error(); (Options) -> IsEmpty | Error when Options :: khepri:query_options() | khepri:tree_options(), IsEmpty :: boolean(), Error :: khepri:error(). %% @doc Indicates if the store is empty or not. %% %% This function accepts the following two forms: %% %% %% @see is_empty/2. is_empty(StoreId) when ?IS_KHEPRI_STORE_ID(StoreId) -> is_empty(StoreId, #{}); is_empty(Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), is_empty(StoreId, Options). -spec is_empty(StoreId, Options) -> IsEmpty | Error when StoreId :: khepri:store_id(), Options :: khepri:query_options() | khepri:tree_options(), IsEmpty :: boolean(), Error :: khepri:error(). %% @doc Indicates if the store is empty or not. %% %% @param StoreId the name of the Khepri store. %% @param Options query options such as `favor'. %% %% @returns `true' if the store is empty, `false' if it is not, or an `{error, %% Reason}' tuple. is_empty(StoreId, Options) -> Path = [], Options1 = Options#{expect_specific_node => true, props_to_return => [child_list_length]}, case khepri_adv:get_many(StoreId, Path, Options1) of {ok, #{Path := #{child_list_length := Count}}} -> Count =:= 0; {error, _} = Error -> Error end. %% ------------------------------------------------------------------- %% get(). %% ------------------------------------------------------------------- -spec get(PathPattern) -> Ret when PathPattern :: khepri_path:pattern(), Ret :: khepri:payload_ret(). %% @doc Returns the payload of the tree node pointed to by the given path %% pattern. %% %% Calling this function is the same as calling `get(StoreId, PathPattern)' %% with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see get/2. %% @see get/3. get(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), get(StoreId, PathPattern). -spec get (StoreId, PathPattern) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Ret :: khepri:payload_ret(); (PathPattern, Options) -> Ret when PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:payload_ret(). %% @doc Returns the payload of the tree node pointed to by the given path %% pattern. %% %% This function accepts the following two forms: %% %% %% @see get/3. get(StoreId, PathPattern) when ?IS_KHEPRI_STORE_ID(StoreId) -> get(StoreId, PathPattern, #{}); get(PathPattern, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), get(StoreId, PathPattern, Options). -spec get(StoreId, PathPattern, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:payload_ret(). %% @doc Returns the payload of the tree node pointed to by the given path %% pattern. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The `PathPattern' must target a specific tree node. In other words, %% updating many nodes with the same payload is denied. That fact is checked %% before the tree node is looked up: so if a condition in the path could %% potentially match several nodes, an exception is raised, even though only %% one tree node would match at the time. %% %% The returned `{ok, Payload}' tuple contains the payload of the targeted %% tree node, or `{ok, undefined}' if the tree node had no payload. %% %% Example: query a tree node which holds the atom `value' %% ``` %% %% Query the tree node at `/:foo/:bar'. %% {ok, value} = khepri:get(StoreId, [foo, bar]). %% ''' %% %% Example: query an existing tree node with no payload %% ``` %% %% Query the tree node at `/:no_payload'. %% {ok, undefined} = khepri:get(StoreId, [no_payload]). %% ''' %% %% Example: query a non-existent tree node %% ``` %% %% Query the tree node at `/:non_existent'. %% {error, ?khepri_error(node_not_found, _)} = khepri:get( %% StoreId, [non_existent]). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node to get. %% @param Options query options. %% %% @returns an `{ok, Payload | undefined}' tuple or an `{error, Reason}' %% tuple. %% %% @see get_or/3. %% @see get_many/3. %% @see khepri_adv:get/3. get(StoreId, PathPattern, Options) -> case khepri_adv:get(StoreId, PathPattern, Options) of {ok, NodePropsMap} -> NodeProps = khepri_utils:get_single_node_props(NodePropsMap), Payload = khepri_utils:node_props_to_payload(NodeProps, undefined), {ok, Payload}; {error, _} = Error -> Error end. %% ------------------------------------------------------------------- %% get_or(). %% ------------------------------------------------------------------- -spec get_or(PathPattern, Default) -> Ret when PathPattern :: khepri_path:pattern(), Default :: khepri:data(), Ret :: khepri:payload_ret(Default). %% @doc Returns the payload of the tree node pointed to by the given path %% pattern, or a default value. %% %% Calling this function is the same as calling `get_or(StoreId, PathPattern, %% Default)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see get_or/3. %% @see get_or/4. get_or(PathPattern, Default) -> StoreId = khepri_cluster:get_default_store_id(), get_or(StoreId, PathPattern, Default). -spec get_or (StoreId, PathPattern, Default) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Default :: khepri:data(), Ret :: khepri:payload_ret(Default); (PathPattern, Default, Options) -> Ret when PathPattern :: khepri_path:pattern(), Default :: khepri:data(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:payload_ret(Default). %% @doc Returns the payload of the tree node pointed to by the given path %% pattern, or a default value. %% %% This function accepts the following two forms: %% %% %% @see get_or/4. get_or(StoreId, PathPattern, Default) when ?IS_KHEPRI_STORE_ID(StoreId) -> get_or(StoreId, PathPattern, Default, #{}); get_or(PathPattern, Default, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), get_or(StoreId, PathPattern, Default, Options). -spec get_or(StoreId, PathPattern, Default, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Default :: khepri:data(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:payload_ret(Default). %% @doc Returns the payload of the tree node pointed to by the given path %% pattern, or a default value. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The `PathPattern' must target a specific tree node. In other words, %% updating many nodes with the same payload is denied. That fact is checked %% before the tree node is looked up: so if a condition in the path could %% potentially match several nodes, an exception is raised, even though only %% one tree node would match at the time. %% %% The returned `{ok, Payload}' tuple contains the payload of the targeted %% tree node, or `{ok, Default}' if the tree node had no payload or was not %% found. %% %% Example: query a tree node which holds the atom `value' %% ``` %% %% Query the tree node at `/:foo/:bar'. %% {ok, value} = khepri:get_or(StoreId, [foo, bar], default). %% ''' %% %% Example: query an existing tree node with no payload %% ``` %% %% Query the tree node at `/:no_payload'. %% {ok, default} = khepri:get_or(StoreId, [no_payload], default). %% ''' %% %% Example: query a non-existent tree node %% ``` %% %% Query the tree node at `/:non_existent'. %% {ok, default} = khepri:get_or(StoreId, [non_existent], default). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node to get. %% @param Default the default value to return in case the tree node has no %% payload or does not exist. %% @param Options query options. %% %% @returns an `{ok, Payload | Default}' tuple or an `{error, Reason}' tuple. %% %% @see get/3. %% @see get_many_or/4. %% @see khepri_adv:get/3. get_or(StoreId, PathPattern, Default, Options) -> case khepri_adv:get(StoreId, PathPattern, Options) of {ok, NodePropsMap} -> NodeProps = khepri_utils:get_single_node_props(NodePropsMap), Payload = khepri_utils:node_props_to_payload(NodeProps, Default), {ok, Payload}; {error, ?khepri_error(node_not_found, _)} -> {ok, Default}; {error, _} = Error -> Error end. %% ------------------------------------------------------------------- %% get_many(). %% ------------------------------------------------------------------- -spec get_many(PathPattern) -> Ret when PathPattern :: khepri_path:pattern(), Ret :: khepri:many_payloads_ret(). %% @doc Returns payloads of all the tree nodes matching the given path %% pattern. %% %% Calling this function is the same as calling `get_many(StoreId, %% PathPattern)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see get_many/2. %% @see get_many/3. get_many(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), get_many(StoreId, PathPattern). -spec get_many (StoreId, PathPattern) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Ret :: khepri:many_payloads_ret(); (PathPattern, Options) -> Ret when PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:many_payloads_ret(). %% @doc Returns payloads of all the tree nodes matching the given path %% pattern. %% %% This function accepts the following two forms: %% %% %% @see get_many/3. get_many(StoreId, PathPattern) when ?IS_KHEPRI_STORE_ID(StoreId) -> get_many(StoreId, PathPattern, #{}); get_many(PathPattern, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), get_many(StoreId, PathPattern, Options). -spec get_many(StoreId, PathPattern, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:many_payloads_ret(). %% @doc Returns payloads of all the tree nodes matching the given path %% pattern. %% %% Calling this function is the same as calling `get_many_or(StoreId, %% PathPattern, undefined, Options)'. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The returned `{ok, PayloadsMap}' tuple contains a map where keys correspond %% to the path to a tree node matching the path pattern. Each key then points %% to the payload of that matching tree node, or `Default' if the tree node %% had no payload. %% %% Example: query all nodes in the tree %% ``` %% %% Get all nodes in the tree. The tree is: %% %% %% %% `-- foo %% %% `-- bar = value %% {ok, #{[foo] := undefined, %% [foo, bar] := value}} = khepri:get_many( %% StoreId, %% [?KHEPRI_WILDCARD_STAR_STAR]). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree nodes to get. %% @param Options query options. %% %% @returns an `{ok, PayloadsMap}' tuple or an `{error, Reason}' tuple. %% %% @see get/3. %% @see get_many_or/4. %% @see khepri_adv:get_many/3. get_many(StoreId, PathPattern, Options) -> get_many_or(StoreId, PathPattern, undefined, Options). %% ------------------------------------------------------------------- %% get_many_or(). %% ------------------------------------------------------------------- -spec get_many_or(PathPattern, Default) -> Ret when PathPattern :: khepri_path:pattern(), Default :: khepri:data(), Ret :: khepri:many_payloads_ret(Default). %% @doc Returns payloads of all the tree nodes matching the given path %% pattern, or a default payload. %% %% Calling this function is the same as calling `get_many_or(StoreId, %% PathPattern, Default)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see get_many_or/3. %% @see get_many_or/4. get_many_or(PathPattern, Default) -> StoreId = khepri_cluster:get_default_store_id(), get_many_or(StoreId, PathPattern, Default). -spec get_many_or (StoreId, PathPattern, Default) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Default :: khepri:data(), Ret :: khepri:many_payloads_ret(Default); (PathPattern, Default, Options) -> Ret when PathPattern :: khepri_path:pattern(), Default :: khepri:data(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:many_payloads_ret(Default). %% @doc Returns payloads of all the tree nodes matching the given path %% pattern, or a default payload. %% %% This function accepts the following two forms: %% %% %% @see get_many_or/4. get_many_or(StoreId, PathPattern, Default) when ?IS_KHEPRI_STORE_ID(StoreId) -> get_many_or(StoreId, PathPattern, Default, #{}); get_many_or(PathPattern, Default, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), get_many_or(StoreId, PathPattern, Default, Options). -spec get_many_or(StoreId, PathPattern, Default, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Default :: khepri:data(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:many_payloads_ret(Default). %% @doc Returns payloads of all the tree nodes matching the given path %% pattern, or a default payload. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The returned `{ok, PayloadsMap}' tuple contains a map where keys correspond %% to the path to a tree node matching the path pattern. Each key then points %% to the payload of that matching tree node, or `Default' if the tree node %% had no payload. %% %% Example: query all nodes in the tree %% ``` %% %% Get all nodes in the tree. The tree is: %% %% %% %% `-- foo %% %% `-- bar = value %% {ok, #{[foo] := default, %% [foo, bar] := value}} = khepri:get_many_or( %% StoreId, %% [?KHEPRI_WILDCARD_STAR_STAR], %% default). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree nodes to get. %% @param Default the default value to set in `PayloadsMap' for tree nodes %% with no payload. %% @param Options query options. %% %% @returns an `{ok, PayloadsMap}' tuple or an `{error, Reason}' tuple. %% %% @see get_or/4. %% @see get_many/3. %% @see khepri_adv:get_many/3. get_many_or(StoreId, PathPattern, Default, Options) -> Fun = fun(Path, NodeProps, Acc) -> Payload = khepri_utils:node_props_to_payload( NodeProps, Default), Acc#{Path => Payload} end, khepri_machine:fold(StoreId, PathPattern, Fun, #{}, Options). %% ------------------------------------------------------------------- %% exists(). %% ------------------------------------------------------------------- -spec exists(PathPattern) -> Exists | Error when PathPattern :: khepri_path:pattern(), Exists :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path exists or not. %% %% Calling this function is the same as calling `exists(StoreId, PathPattern)' %% with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see exists/2. %% @see exists/3. exists(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), exists(StoreId, PathPattern). -spec exists (StoreId, PathPattern) -> Exists | Error when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Exists :: boolean(), Error :: khepri:error(); (PathPattern, Options) -> Exists | Error when PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), Exists :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path exists or not. %% %% This function accepts the following two forms: %% %% %% @see exists/3. exists(StoreId, PathPattern) when ?IS_KHEPRI_STORE_ID(StoreId) -> exists(StoreId, PathPattern, #{}); exists(PathPattern, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), exists(StoreId, PathPattern, Options). -spec exists(StoreId, PathPattern, Options) -> Exists | Error when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), Exists :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path exists or not. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The `PathPattern' must target a specific tree node. In other words, %% updating many nodes with the same payload is denied. That fact is checked %% before the tree node is looked up: so if a condition in the path could %% potentially match several nodes, an exception is raised, even though only %% one tree node would match at the time. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the nodes to check. %% @param Options query options such as `favor'. %% %% @returns `true' if the tree node exists, `false' if it does not, or an %% `{error, Reason}' tuple. %% %% @see get/3. exists(StoreId, PathPattern, Options) -> %% TODO: Use path condition instead. Options1 = Options#{expect_specific_node => true, props_to_return => []}, case khepri_adv:get_many(StoreId, PathPattern, Options1) of {ok, _} -> true; {error, ?khepri_error(node_not_found, _)} -> false; {error, _} = Error -> Error end. %% ------------------------------------------------------------------- %% has_data(). %% ------------------------------------------------------------------- -spec has_data(PathPattern) -> HasData | Error when PathPattern :: khepri_path:pattern(), HasData :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path has data or %% not. %% %% Calling this function is the same as calling `has_data(StoreId, %% PathPattern)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see has_data/2. %% @see has_data/3. has_data(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), has_data(StoreId, PathPattern). -spec has_data (StoreId, PathPattern) -> HasData | Error when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), HasData :: boolean(), Error :: khepri:error(); (PathPattern, Options) -> HasData | Error when PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), HasData :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path has data or %% not. %% %% This function accepts the following two forms: %% %% %% @see has_data/3. has_data(StoreId, PathPattern) when ?IS_KHEPRI_STORE_ID(StoreId) -> has_data(StoreId, PathPattern, #{}); has_data(PathPattern, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), has_data(StoreId, PathPattern, Options). -spec has_data(StoreId, PathPattern, Options) -> HasData | Error when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), HasData :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path has data or %% not. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The `PathPattern' must target a specific tree node. In other words, %% updating many nodes with the same payload is denied. That fact is checked %% before the tree node is looked up: so if a condition in the path could %% potentially match several nodes, an exception is raised, even though only %% one tree node would match at the time. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the nodes to check. %% @param Options query options such as `favor'. %% %% @returns `true' if tree the node holds data, `false' if it does not exist, %% has no payload or holds a stored procedure, or an `{error, Reason}' tuple. %% %% @see get/3. has_data(StoreId, PathPattern, Options) -> %% TODO: Use path condition instead. Options1 = Options#{expect_specific_node => true, props_to_return => [has_payload]}, case khepri_adv:get_many(StoreId, PathPattern, Options1) of {ok, NodePropsMap} -> [NodeProps] = maps:values(NodePropsMap), maps:get(has_data, NodeProps, false); {error, ?khepri_error(node_not_found, _)} -> false; {error, _} = Error -> Error end. %% ------------------------------------------------------------------- %% is_sproc(). %% ------------------------------------------------------------------- -spec is_sproc(PathPattern) -> IsSproc | Error when PathPattern :: khepri_path:pattern(), IsSproc :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path holds a stored %% procedure or not. %% %% Calling this function is the same as calling `is_sproc(StoreId, %% PathPattern)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see is_sproc/2. %% @see is_sproc/3. is_sproc(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), is_sproc(StoreId, PathPattern). -spec is_sproc (StoreId, PathPattern) -> IsSproc | Error when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), IsSproc :: boolean(), Error :: khepri:error(); (PathPattern, Options) -> IsSproc | Error when PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), IsSproc :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path holds a stored %% procedure or not. %% %% This function accepts the following two forms: %% %% %% @see is_sproc/3. is_sproc(StoreId, PathPattern) when ?IS_KHEPRI_STORE_ID(StoreId) -> is_sproc(StoreId, PathPattern, #{}); is_sproc(PathPattern, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), is_sproc(StoreId, PathPattern, Options). -spec is_sproc(StoreId, PathPattern, Options) -> IsSproc | Error when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), IsSproc :: boolean(), Error :: khepri:error(). %% @doc Indicates if the tree node pointed to by the given path holds a stored %% procedure or not. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The `PathPattern' must target a specific tree node. In other words, %% updating many nodes with the same payload is denied. That fact is checked %% before the tree node is looked up: so if a condition in the path could %% potentially match several nodes, an exception is raised, even though only %% one tree node would match at the time. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the nodes to check. %% @param Options query options such as `favor'. %% %% @returns `true' if the tree node holds a stored procedure, `false' if it %% does not exist, has no payload or holds data, or an `{error, Reason}' %% tuple. %% %% @see get/3. is_sproc(StoreId, PathPattern, Options) -> %% TODO: Use path condition instead. Options1 = Options#{expect_specific_node => true, props_to_return => [has_payload]}, case khepri_adv:get_many(StoreId, PathPattern, Options1) of {ok, NodePropsMap} -> [NodeProps] = maps:values(NodePropsMap), maps:get(is_sproc, NodeProps, false); {error, ?khepri_error(node_not_found, _)} -> false; {error, _} = Error -> Error end. %% ------------------------------------------------------------------- %% count(). %% ------------------------------------------------------------------- -spec count(PathPattern) -> Ret when PathPattern :: khepri_path:pattern(), Ret :: ok(Count) | error(), Count :: non_neg_integer(). %% @doc Counts all tree nodes matching the given path pattern. %% %% Calling this function is the same as calling `count(StoreId, %% PathPattern)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see count/2. %% @see count/3. count(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), count(StoreId, PathPattern). -spec count (StoreId, PathPattern) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Ret :: ok(Count) | error(), Count :: non_neg_integer(); (PathPattern, Options) -> Ret when PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: ok(Count) | error(), Count :: non_neg_integer(). %% @doc Counts all tree nodes matching the given path pattern. %% %% This function accepts the following two forms: %% %% %% @see count/3. count(StoreId, PathPattern) when ?IS_KHEPRI_STORE_ID(StoreId) -> count(StoreId, PathPattern, #{}); count(PathPattern, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), count(StoreId, PathPattern, Options). -spec count(StoreId, PathPattern, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: ok(Count) | error(), Count :: non_neg_integer(). %% @doc Counts all tree nodes matching the given path pattern. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The root node is not included in the count. %% %% Example: %% ``` %% %% Query the tree node at `/:foo/:bar'. %% {ok, 3} = khepri:count(StoreId, [foo, ?KHEPRI_WILDCARD_STAR]). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the nodes to count. %% @param Options query options such as `favor'. %% %% @returns an `{ok, Count}' tuple with the number of matching tree nodes, or %% an `{error, Reason}' tuple. count(StoreId, PathPattern, Options) -> Fun = fun khepri_tree:count_node_cb/3, Options1 = Options#{expect_specific_node => false}, khepri_machine:fold(StoreId, PathPattern, Fun, 0, Options1). %% ------------------------------------------------------------------- %% fold(). %% ------------------------------------------------------------------- -spec fold(PathPattern, Fun, Acc) -> Ret when PathPattern :: khepri_path:pattern(), Fun :: khepri:fold_fun(), Acc :: khepri:fold_acc(), Ret :: khepri:ok(NewAcc) | khepri:error(), NewAcc :: Acc. %% @doc Calls `Fun' on successive tree nodes matching the given path pattern, %% starting with `Acc'. %% %% Calling this function is the same as calling `fold(StoreId, PathPattern, %% Fun, Acc)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see fold/4. %% @see fold/5. fold(PathPattern, Fun, Acc) -> StoreId = khepri_cluster:get_default_store_id(), fold(StoreId, PathPattern, Fun, Acc). -spec fold (StoreId, PathPattern, Fun, Acc) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Fun :: khepri:fold_fun(), Acc :: khepri:fold_acc(), Ret :: khepri:ok(NewAcc) | khepri:error(), NewAcc :: Acc; (PathPattern, Fun, Acc, Options) -> Ret when PathPattern :: khepri_path:pattern(), Fun :: khepri:fold_fun(), Acc :: khepri:fold_acc(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:ok(NewAcc) | khepri:error(), NewAcc :: Acc. %% @doc Calls `Fun' on successive tree nodes matching the given path pattern, %% starting with `Acc'. %% %% This function accepts the following two forms: %% %% %% @see fold/5. fold(StoreId, PathPattern, Fun, Acc) when ?IS_KHEPRI_STORE_ID(StoreId) -> fold(StoreId, PathPattern, Fun, Acc, #{}); fold(PathPattern, Fun, Acc, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), fold(StoreId, PathPattern, Fun, Acc, Options). -spec fold(StoreId, PathPattern, Fun, Acc, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Fun :: khepri:fold_fun(), Acc :: khepri:fold_acc(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:ok(NewAcc) | khepri:error(), NewAcc :: Acc. %% @doc Calls `Fun' on successive tree nodes matching the given path pattern, %% starting with `Acc'. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% Like the function passed to {@link maps:fold/3}, `Fun' must accept the %% following arguments: %%
    %%
  1. the native path of the tree node being handled
  2. %%
  3. the tree node properties map
  4. %%
  5. an Erlang term which is either `Acc' for the first matched tree node, or %% the return value of the previous call to `Fun'
  6. %%
%% %% The returned `{ok, NewAcc}' tuple contains the return value of the last call %% to `Fun', or `Acc' if no tree nodes matched the given path pattern. %% %% Example: list all nodes in the tree %% ``` %% %% List all tree node paths in the tree. The tree is: %% %% %% %% `-- foo %% %% `-- bar = value %% {ok, [[foo], [foo, bar]]} = khepri:fold( %% StoreId, %% [?KHEPRI_WILDCARD_STAR_STAR], %% fun(Path, _NodeProps, Acc) -> %% [Path | Acc] %% end, []). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree nodes to get. %% @param Fun the function to call for each matching tree node. %% @param Acc the Erlang term to pass to the first call to `Fun'. %% @param Options query options. %% %% @returns an `{ok, NewAcc}' tuple or an `{error, Reason}' tuple. fold(StoreId, PathPattern, Fun, Acc, Options) -> Options1 = Options#{expect_specific_node => false}, khepri_machine:fold(StoreId, PathPattern, Fun, Acc, Options1). %% ------------------------------------------------------------------- %% foreach(). %% ------------------------------------------------------------------- -spec foreach(PathPattern, Fun) -> Ret when PathPattern :: khepri_path:pattern(), Fun :: khepri:foreach_fun(), Ret :: ok | khepri:error(). %% @doc Calls `Fun' for each tree node matching the given path pattern. %% %% Calling this function is the same as calling `foreach(StoreId, PathPattern, %% Fun)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see foreach/3. %% @see foreach/4. foreach(PathPattern, Fun) -> StoreId = khepri_cluster:get_default_store_id(), foreach(StoreId, PathPattern, Fun). -spec foreach (StoreId, PathPattern, Fun) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Fun :: khepri:foreach_fun(), Ret :: ok | khepri:error(); (PathPattern, Fun, Options) -> Ret when PathPattern :: khepri_path:pattern(), Fun :: khepri:foreach_fun(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: ok | khepri:error(). %% @doc Calls `Fun' for each tree node matching the given path pattern. %% %% This function accepts the following two forms: %%
    %%
  • `foreach(StoreId, PathPattern, Fun)'. Calling it is the same as %% calling `foreach(StoreId, PathPattern, Fun, #{})'.
  • %%
  • `foreach(PathPattern, Fun, Options)'. Calling it is the same as %% calling `foreach(StoreId, PathPattern, Fun, Options)' with the default %% store ID (see {@link khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see foreach/4. foreach(StoreId, PathPattern, Fun) when ?IS_KHEPRI_STORE_ID(StoreId) -> foreach(StoreId, PathPattern, Fun, #{}); foreach(PathPattern, Fun, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), foreach(StoreId, PathPattern, Fun, Options). -spec foreach(StoreId, PathPattern, Fun, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Fun :: khepri:foreach_fun(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: ok | khepri:error(). %% @doc Calls `Fun' for each tree node matching the given path pattern. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% Like the function passed to {@link maps:foreach/2}, `Fun' must accept the %% following arguments: %%
    %%
  1. the native path of the tree node being handled
  2. %%
  3. the tree node properties map
  4. %%
%% %% Example: print all nodes in the tree %% ``` %% %% Print all tree node paths in the tree. The tree is: %% %% %% %% `-- foo %% %% `-- bar = value %% ok = khepri:foreach( %% StoreId, %% [?KHEPRI_WILDCARD_STAR_STAR], %% fun(Path, _NodeProps) -> %% io:format("Path ~0p~n", [Path]) %% end). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree nodes to get. %% @param Fun the function to call for each matching tree node. %% @param Options query options. %% %% @returns `ok' or an `{error, Reason}' tuple. foreach(StoreId, PathPattern, Fun, Options) when is_function(Fun, 2) -> FoldFun = fun(Path, NodeProps, Acc) -> _ = Fun(Path, NodeProps), Acc end, case fold(StoreId, PathPattern, FoldFun, ok, Options) of {ok, ok} -> ok; {error, _Reason} = Error -> Error end. %% ------------------------------------------------------------------- %% map(). %% ------------------------------------------------------------------- -spec map(PathPattern, Fun) -> Ret when PathPattern :: khepri_path:pattern(), Fun :: khepri:map_fun(), Ret :: khepri:ok(Map) | khepri:error(), Map :: #{khepri_path:native_path() => khepri:map_fun_ret()}. %% @doc Produces a new map by calling `Fun' for each tree node matching the %% given path pattern. %% %% Calling this function is the same as calling `map(StoreId, PathPattern, %% Fun)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see map/3. %% @see map/4. map(PathPattern, Fun) -> StoreId = khepri_cluster:get_default_store_id(), map(StoreId, PathPattern, Fun). -spec map (StoreId, PathPattern, Fun) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Fun :: khepri:map_fun(), Ret :: khepri:ok(Map) | khepri:error(), Map :: #{khepri_path:native_path() => khepri:map_fun_ret()}; (PathPattern, Fun, Options) -> Ret when PathPattern :: khepri_path:pattern(), Fun :: khepri:map_fun(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:ok(Map) | khepri:error(), Map :: #{khepri_path:native_path() => khepri:map_fun_ret()}. %% @doc Produces a new map by calling `Fun' for each tree node matching the %% given path pattern. %% %% This function accepts the following two forms: %%
    %%
  • `map(StoreId, PathPattern, Fun)'. Calling it is the same as %% calling `map(StoreId, PathPattern, Fun, #{})'.
  • %%
  • `map(PathPattern, Fun, Options)'. Calling it is the same as %% calling `map(StoreId, PathPattern, Fun, Options)' with the default %% store ID (see {@link khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see map/4. map(StoreId, PathPattern, Fun) when ?IS_KHEPRI_STORE_ID(StoreId) -> map(StoreId, PathPattern, Fun, #{}); map(PathPattern, Fun, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), map(StoreId, PathPattern, Fun, Options). -spec map(StoreId, PathPattern, Fun, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Fun :: khepri:map_fun(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:ok(Map) | khepri:error(), Map :: #{khepri_path:native_path() => khepri:map_fun_ret()}. %% @doc Produces a new map by calling `Fun' for each tree node matching the %% given path pattern. %% %% The produced map uses the tree node path as the key, like {@link get_many/3} %% and the return value of `Fun' as the value. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% Like the function passed to {@link maps:map/2}, `Fun' must accept the %% following arguments: %%
    %%
  1. the native path of the tree node being handled
  2. %%
  3. the tree node properties map
  4. %%
%% %% Example: create a map of the form "native path => string path" %% ``` %% %% The tree is: %% %% %% %% `-- foo %% %% `-- bar = value %% {ok, #{[foo] => "/:foo", %% [foo, bar] => "/:foo/:bar"}} = khepri:map( %% StoreId, %% [?KHEPRI_WILDCARD_STAR_STAR], %% fun(Path, _NodeProps) -> %% khepri_path:to_string(Path) %% end). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree nodes to get. %% @param Fun the function to call for each matching tree node. %% @param Options query options. %% %% @returns `{ok, Map}' or an `{error, Reason}' tuple. map(StoreId, PathPattern, Fun, Options) when is_function(Fun, 2) -> FoldFun = fun(Path, NodeProps, Acc) -> Ret = Fun(Path, NodeProps), Acc#{Path => Ret} end, fold(StoreId, PathPattern, FoldFun, #{}, Options). %% ------------------------------------------------------------------- %% filter(). %% ------------------------------------------------------------------- -spec filter(PathPattern, Pred) -> Ret when PathPattern :: khepri_path:pattern(), Pred :: khepri:filter_fun(), Ret :: khepri:many_payloads_ret(). %% @doc Returns a map for which predicate `Pred' holds true in tree nodes %% matching the given path pattern. %% %% Calling this function is the same as calling `filter(StoreId, PathPattern, %% Pred)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see filter/3. %% @see filter/4. filter(PathPattern, Pred) -> StoreId = khepri_cluster:get_default_store_id(), filter(StoreId, PathPattern, Pred). -spec filter (StoreId, PathPattern, Pred) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Pred :: khepri:filter_fun(), Ret :: khepri:many_payloads_ret(); (PathPattern, Pred, Options) -> Ret when PathPattern :: khepri_path:pattern(), Pred :: khepri:filter_fun(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:many_payloads_ret(). %% @doc Returns a map for which predicate `Pred' holds true in tree nodes %% matching the given path pattern. %% %% This function accepts the following two forms: %%
    %%
  • `filter(StoreId, PathPattern, Pred)'. Calling it is the same as %% calling `filter(StoreId, PathPattern, Pred, #{})'.
  • %%
  • `filter(PathPattern, Pred, Options)'. Calling it is the same as %% calling `filter(StoreId, PathPattern, Pred, Options)' with the default %% store ID (see {@link khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see filter/4. filter(StoreId, PathPattern, Pred) when ?IS_KHEPRI_STORE_ID(StoreId) -> filter(StoreId, PathPattern, Pred, #{}); filter(PathPattern, Pred, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), filter(StoreId, PathPattern, Pred, Options). -spec filter(StoreId, PathPattern, Pred, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Pred :: khepri:filter_fun(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: khepri:many_payloads_ret(). %% @doc Returns a map for which predicate `Pred' holds true in tree nodes %% matching the given path pattern. %% %% The produced map only contains tree nodes for which `Pred' returned true. %% The map has the same form as the one returned by {@link get_many/3} %% otherwise. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% Like the function passed to {@link maps:filter/2}, `Pred' must accept the %% following arguments: %%
    %%
  1. the native path of the tree node being handled
  2. %%
  3. the tree node properties filter
  4. %%
%% %% Example: only keep tree nodes under `/:foo' %% ``` %% %% The tree is: %% %% %% %% `-- foo %% %% `-- bar = value %% {ok, #{[foo] => undefined, %% [foo, bar] => value}} = khepri:filter( %% StoreId, %% [?KHEPRI_WILDCARD_STAR_STAR], %% fun %% ([foo | _], _NodeProps) -> true; %% (_Path, _NodeProps) -> false %% end). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree nodes to get. %% @param Pred the function to call for each matching tree node. %% @param Options query options. %% %% @returns `{ok, Map}' or an `{error, Reason}' tuple. filter(StoreId, PathPattern, Pred, Options) when is_function(Pred, 2) -> FoldFun = fun(Path, NodeProps, Acc) -> case Pred(Path, NodeProps) of true -> Payload = khepri_utils:node_props_to_payload( NodeProps, undefined), Acc#{Path => Payload}; false -> Acc end end, fold(StoreId, PathPattern, FoldFun, #{}, Options). %% ------------------------------------------------------------------- %% run_sproc(). %% ------------------------------------------------------------------- -spec run_sproc(PathPattern, Args) -> Ret when PathPattern :: khepri_path:pattern(), Args :: list(), Ret :: any(). %% @doc Runs the stored procedure pointed to by the given path and returns the %% result. %% %% Calling this function is the same as calling `run_sproc(StoreId, %% PathPattern, Args)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see run_sproc/3. %% @see run_sproc/4. run_sproc(PathPattern, Args) -> StoreId = khepri_cluster:get_default_store_id(), run_sproc(StoreId, PathPattern, Args). -spec run_sproc (StoreId, PathPattern, Args) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Args :: list(), Ret :: any(); (PathPattern, Args, Options) -> Ret when PathPattern :: khepri_path:pattern(), Args :: list(), Options :: khepri:query_options() | khepri:tree_options(), Ret :: any(). %% @doc Runs the stored procedure pointed to by the given path and returns the %% result. %% %% This function accepts the following two forms: %%
    %%
  • `run_sproc(StoreId, PathPattern, Args)'. Calling it is the same as %% calling `run_sproc(StoreId, PathPattern, Args, #{})'.
  • %%
  • `run_sproc(PathPattern, Args, Options)'. Calling it is the same as %% calling `run_sproc(StoreId, PathPattern, Args, Options)' with the default %% store ID (see {@link khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see run_sproc/4. run_sproc(StoreId, PathPattern, Args) when ?IS_KHEPRI_STORE_ID(StoreId) -> run_sproc(StoreId, PathPattern, Args, #{}); run_sproc(PathPattern, Args, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), run_sproc(StoreId, PathPattern, Args, Options). -spec run_sproc(StoreId, PathPattern, Args, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Args :: list(), Options :: khepri:query_options(), Ret :: any(). %% @doc Runs the stored procedure pointed to by the given path and returns the %% result. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The `PathPattern' must target a specific tree node. In other words, %% updating many nodes with the same payload is denied. That fact is checked %% before the tree node is looked up: so if a condition in the path could %% potentially match several nodes, an exception is raised, even though only %% one tree node would match at the time. %% %% The length of the `Args' list must match the number of arguments expected by %% the stored procedure. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node holding the %% stored procedure. %% @param Args the list of args to pass to the stored procedure; its length %% must be equal to the stored procedure arity. %% @param Options query options. %% %% @returns the result of the stored procedure execution, or throws an %% exception if the tree node does not exist, does not hold a stored procedure %% or if there was an error. %% %% @see is_sproc/3. run_sproc(StoreId, PathPattern, Args, Options) -> case khepri_adv:get(StoreId, PathPattern, Options) of {ok, NodePropsMap} -> NodeProps = khepri_utils:get_single_node_props(NodePropsMap), case NodeProps of #{sproc := StandaloneFun} -> khepri_sproc:run(StandaloneFun, Args); _ -> throw(?khepri_exception( denied_execution_of_non_sproc_node, #{path => PathPattern, args => Args, node_props => NodeProps})) end; {error, Reason} -> throw(?khepri_error( failed_to_get_sproc, #{path => PathPattern, args => Args, error => Reason})) end. %% ------------------------------------------------------------------- %% put(). %% ------------------------------------------------------------------- -spec put(PathPattern, Data) -> Ret when PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Sets the payload of the tree node pointed to by the given path %% pattern. %% %% Calling this function is the same as calling `put(StoreId, PathPattern, %% Data)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see put/3. %% @see put/4. put(PathPattern, Data) -> StoreId = khepri_cluster:get_default_store_id(), put(StoreId, PathPattern, Data). -spec put(StoreId, PathPattern, Data) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Sets the payload of the tree node pointed to by the given path %% pattern. %% %% Calling this function is the same as calling `put(StoreId, PathPattern, %% Data, #{})'. %% %% @see put/4. put(StoreId, PathPattern, Data) -> put(StoreId, PathPattern, Data, #{}). -spec put(StoreId, PathPattern, Data, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Options :: khepri:command_options() | khepri:tree_options() | khepri:put_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Sets the payload of the tree node pointed to by the given path %% pattern. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The `PathPattern' must target a specific tree node. In other words, %% updating many nodes with the same payload is denied. That fact is checked %% before the tree node is looked up: so if a condition in the path could %% potentially match several nodes, an exception is raised, even though only %% one tree node would match at the time. %% %% When using a simple path (i.e. without conditions), if the targeted tree %% node does not exist, it is created using the given payload. If the %% targeted tree 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. %% %% The payload must be one of the following form: %%
    %%
  • An explicit absence of payload ({@link khepri_payload:no_payload()}), %% using the marker returned by {@link khepri_payload:none/0}, meaning there %% will be no payload attached to the tree node and the existing payload will %% be discarded if any
  • %%
  • An anonymous function; it will be considered a stored procedure and %% will be wrapped in a {@link khepri_payload:sproc()} record
  • %%
  • Any other term; it will be wrapped in a {@link khepri_payload:data()} %% record
  • %%
%% %% It is possible to wrap the payload in its internal structure explicitly %% using the {@link khepri_payload} module directly. %% %% The `Options' map may specify command-level options; see {@link %% khepri:command_options()}, {@link khepri:tree_options()} and {@link %% khepri:put_options()}. %% %% When doing an asynchronous update, the {@link handle_async_ret/2} %% function can be used to handle the message received from Ra. %% %% Example: %% ``` %% %% Insert a tree node at `/:foo/:bar', overwriting the previous value. %% ok = khepri:put(StoreId, [foo, bar], new_value). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node to create or %% modify. %% @param Data the Erlang term or function to store, or a {@link %% khepri_payload:payload()} structure. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see create/4. %% @see update/4. %% @see compare_and_swap/5. %% @see put_many/4. %% @see khepri_adv:put/4. put(StoreId, PathPattern, Data, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:put(StoreId, PathPattern, Data, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% put_many(). %% ------------------------------------------------------------------- -spec put_many(PathPattern, Data) -> Ret when PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Sets the payload of all the tree nodes matching the given path pattern. %% %% Calling this function is the same as calling `put_many(StoreId, PathPattern, %% Data)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see put_many/3. %% @see put_many/4. put_many(PathPattern, Data) -> StoreId = khepri_cluster:get_default_store_id(), put_many(StoreId, PathPattern, Data). -spec put_many(StoreId, PathPattern, Data) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Sets the payload of all the tree nodes matching the given path pattern. %% %% Calling this function is the same as calling `put_many(StoreId, PathPattern, %% Data, #{})'. %% %% @see put_many/4. put_many(StoreId, PathPattern, Data) -> put_many(StoreId, PathPattern, Data, #{}). -spec put_many(StoreId, PathPattern, Data, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Options :: khepri:command_options() | khepri:tree_options() | khepri:put_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Sets the payload of all the tree nodes matching the given path pattern. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% When using a simple path (i.e. without conditions), if the targeted tree %% node does not exist, it is created using the given payload. If the %% targeted tree 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. %% %% The payload must be one of the following form: %%
    %%
  • An explicit absence of payload ({@link khepri_payload:no_payload()}), %% using the marker returned by {@link khepri_payload:none/0}, meaning there %% will be no payload attached to the tree node and the existing payload will %% be discarded if any
  • %%
  • An anonymous function; it will be considered a stored procedure and %% will be wrapped in a {@link khepri_payload:sproc()} record
  • %%
  • Any other term; it will be wrapped in a {@link khepri_payload:data()} %% record
  • %%
%% %% It is possible to wrap the payload in its internal structure explicitly %% using the {@link khepri_payload} module directly. %% %% The `Options' map may specify command-level options; see {@link %% khepri:command_options()}, {@link khepri:tree_options()} and {@link %% khepri:put_options()}. %% %% When doing an asynchronous update, the {@link handle_async_ret/2} %% function can be used to handle the message received from Ra. %% %% Example: %% ``` %% %% Insert a tree node at `/:foo/:bar', overwriting the previous value. %% ok = khepri:put(StoreId, [foo, bar], new_value). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node to create or %% modify. %% @param Data the Erlang term or function to store, or a {@link %% khepri_payload:payload()} structure. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see put/4. %% @see khepri_adv:put_many/4. put_many(StoreId, PathPattern, Data, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:put_many(StoreId, PathPattern, Data, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% create(). %% ------------------------------------------------------------------- -spec create(PathPattern, Data) -> Ret when PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Creates a tree node with the given payload. %% %% Calling this function is the same as calling `create(StoreId, PathPattern, %% Data)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see create/3. %% @see create/4. create(PathPattern, Data) -> StoreId = khepri_cluster:get_default_store_id(), create(StoreId, PathPattern, Data). -spec create(StoreId, PathPattern, Data) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Creates a tree node with the given payload. %% %% Calling this function is the same as calling `create(StoreId, PathPattern, %% Data, #{})'. %% %% @see create/4. create(StoreId, PathPattern, Data) -> create(StoreId, PathPattern, Data, #{}). -spec create(StoreId, PathPattern, Data, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Options :: khepri:command_options() | khepri:tree_options() | khepri:put_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Creates a tree node with the given payload. %% %% The behavior is the same as {@link put/4} except that if the tree node %% already exists, an `{error, ?khepri_error(mismatching_node, Info)}' tuple is %% returned. %% %% Internally, the `PathPattern' is modified to include an %% `#if_node_exists{exists = false}' condition on its last component. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node to create. %% @param Data the Erlang term or function to store, or a {@link %% khepri_payload:payload()} structure. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see put/4. %% @see update/4. %% @see compare_and_swap/5. %% @see khepri_adv:create/4. create(StoreId, PathPattern, Data, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:create(StoreId, PathPattern, Data, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% update(). %% ------------------------------------------------------------------- -spec update(PathPattern, Data) -> Ret when PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Updates an existing tree node with the given payload. %% %% Calling this function is the same as calling `update(StoreId, PathPattern, %% Data)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see update/3. %% @see update/4. update(PathPattern, Data) -> StoreId = khepri_cluster:get_default_store_id(), update(StoreId, PathPattern, Data). -spec update(StoreId, PathPattern, Data) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Updates an existing tree node with the given payload. %% %% Calling this function is the same as calling `update(StoreId, PathPattern, %% Data, #{})'. %% %% @see update/4. update(StoreId, PathPattern, Data) -> update(StoreId, PathPattern, Data, #{}). -spec update(StoreId, PathPattern, Data, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Options :: khepri:command_options() | khepri:tree_options() | khepri:put_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Updates an existing tree node with the given payload. %% %% The behavior is the same as {@link put/4} except that if the tree node %% already exists, an `{error, ?khepri_error(mismatching_node, Info)}' tuple is %% returned. %% %% Internally, the `PathPattern' is modified to include an %% `#if_node_exists{exists = true}' condition on its last component. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node to modify. %% @param Data the Erlang term or function to store, or a {@link %% khepri_payload:payload()} structure. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see put/4. %% @see create/4. %% @see compare_and_swap/5. %% @see khepri_adv:update/4. update(StoreId, PathPattern, Data, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:update(StoreId, PathPattern, Data, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% compare_and_swap(). %% ------------------------------------------------------------------- -spec compare_and_swap(PathPattern, DataPattern, Data) -> Ret when PathPattern :: khepri_path:pattern(), DataPattern :: ets:match_pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Updates an existing tree node with the given payload only if its data %% matches the given pattern. %% %% Calling this function is the same as calling `compare_and_swap(StoreId, %% PathPattern, DataPattern, Data)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see compare_and_swap/4. %% @see compare_and_swap/5. compare_and_swap(PathPattern, DataPattern, Data) -> StoreId = khepri_cluster:get_default_store_id(), compare_and_swap(StoreId, PathPattern, DataPattern, Data). -spec compare_and_swap(StoreId, PathPattern, DataPattern, Data) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), DataPattern :: ets:match_pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Ret :: khepri:minimal_ret(). %% @doc Updates an existing tree node with the given payload only if its data %% matches the given pattern. %% %% Calling this function is the same as calling `compare_and_swap(StoreId, %% PathPattern, DataPattern, Data, #{})'. %% %% @see compare_and_swap/5. compare_and_swap(StoreId, PathPattern, DataPattern, Data) -> compare_and_swap(StoreId, PathPattern, DataPattern, Data, #{}). -spec compare_and_swap(StoreId, PathPattern, DataPattern, Data, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), DataPattern :: ets:match_pattern(), Data :: khepri_payload:payload() | khepri:data() | fun(), Options :: khepri:command_options() | khepri:tree_options() | khepri:put_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Updates an existing tree node with the given payload only if its data %% matches the given pattern. %% %% The behavior is the same as {@link put/4} except that if the tree node %% already exists, an `{error, ?khepri_error(mismatching_node, Info)}' tuple is %% returned. %% %% Internally, the `PathPattern' is modified to include an %% `#if_data_matches{pattern = DataPattern}' condition on its last component. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node to modify. %% @param Data the Erlang term or function to store, or a {@link %% khepri_payload:payload()} structure. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see put/4. %% @see create/4. %% @see update/4. %% @see khepri_adv:compare_and_swap/5. compare_and_swap(StoreId, PathPattern, DataPattern, Data, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:compare_and_swap( StoreId, PathPattern, DataPattern, Data, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% delete(). %% ------------------------------------------------------------------- -spec delete(PathPattern) -> Ret when PathPattern :: khepri_path:pattern(), Ret :: khepri:minimal_ret(). %% @doc Deletes the tree node pointed to by the given path pattern. %% %% Calling this function is the same as calling `delete(StoreId, PathPattern)' %% with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see delete/2. %% @see delete/3. delete(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), delete(StoreId, PathPattern). -spec delete (StoreId, PathPattern) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Ret :: khepri:minimal_ret(); (PathPattern, Options) -> Ret when PathPattern :: khepri_path:pattern(), Options :: khepri:command_options() | khepri:tree_options(), Ret :: khepri:minimal_ret(). %% @doc Deletes the tree node pointed to by the given path pattern. %% %% This function accepts the following two forms: %%
    %%
  • `delete(StoreId, PathPattern)'. Calling it is the same as calling %% `delete(StoreId, PathPattern, #{})'.
  • %%
  • `delete(PathPattern, Options)'. Calling it is the same as calling %% `delete(StoreId, PathPattern, Options)' with the default store ID (see %% {@link khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see delete/3. delete(StoreId, PathPattern) when ?IS_KHEPRI_STORE_ID(StoreId) -> delete(StoreId, PathPattern, #{}); delete(PathPattern, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), delete(StoreId, PathPattern, Options). -spec delete(StoreId, PathPattern, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:command_options() | khepri:tree_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Deletes the tree node pointed to by the given path pattern. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% The `PathPattern' must target a specific tree node. In other words, deleting %% many nodes is denied. That fact is checked before the tree node is looked %% up: so if a condition in the path could potentially match several nodes, an %% exception is raised, even though only one tree node would match at the time. %% If you want to delete multiple nodes at once, use {@link delete_many/3}. %% %% Example: %% ``` %% %% Delete the tree node at `/:foo/:bar'. %% ok = khepri:delete(StoreId, [foo, bar]). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the node to delete. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see delete_many/3. %% @see khepri_adv:delete/3. delete(StoreId, PathPattern, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:delete(StoreId, PathPattern, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% delete_many(). %% ------------------------------------------------------------------- -spec delete_many(PathPattern) -> Ret when PathPattern :: khepri_path:pattern(), Ret :: khepri:minimal_ret(). %% @doc Deletes all tree nodes matching the given path pattern. %% %% Calling this function is the same as calling `delete_many(StoreId, %% PathPattern)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see delete_many/2. %% @see delete_many/3. delete_many(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), delete_many(StoreId, PathPattern). -spec delete_many (StoreId, PathPattern) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Ret :: khepri:minimal_ret(); (PathPattern, Options) -> Ret when PathPattern :: khepri_path:pattern(), Options :: khepri:command_options() | khepri:tree_options(), Ret :: khepri:minimal_ret(). %% @doc Deletes all tree nodes matching the given path pattern. %% %% This function accepts the following two forms: %%
    %%
  • `delete_many(StoreId, PathPattern)'. Calling it is the same as calling %% `delete(StoreId, PathPattern, #{})'.
  • %%
  • `delete_many(PathPattern, Options)'. Calling it is the same as calling %% `delete(StoreId, PathPattern, Options)' with the default store ID (see %% {@link khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see delete_many/3. delete_many(StoreId, PathPattern) when ?IS_KHEPRI_STORE_ID(StoreId) -> delete_many(StoreId, PathPattern, #{}); delete_many(PathPattern, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), delete_many(StoreId, PathPattern, Options). -spec delete_many(StoreId, PathPattern, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:command_options() | khepri:tree_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Deletes all tree nodes matching the given path pattern. %% %% The `PathPattern' can be provided as a native path pattern (a list of tree %% node names and conditions) or as a string. See {@link %% khepri_path:from_string/1}. %% %% Example: %% ``` %% %% Delete all nodes in the tree. %% ok = khepri:delete_many(StoreId, [?KHEPRI_WILDCARD_STAR]). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the nodes to delete. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see delete/3. delete_many(StoreId, PathPattern, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:delete_many(StoreId, PathPattern, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% clear_payload(). %% ------------------------------------------------------------------- -spec clear_payload(PathPattern) -> Ret when PathPattern :: khepri_path:pattern(), Ret :: khepri:minimal_ret(). %% @doc Deletes the payload of the tree node pointed to by the given path %% pattern. %% %% Calling this function is the same as calling `clear_payload(StoreId, %% PathPattern)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see clear_payload/2. %% @see clear_payload/3. clear_payload(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), clear_payload(StoreId, PathPattern). -spec clear_payload(StoreId, PathPattern) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Ret :: khepri:minimal_ret(). %% @doc Deletes the payload of the tree node pointed to by the given path %% pattern. %% %% Calling this function is the same as calling `clear_payload(StoreId, %% PathPattern, #{})'. %% %% @see clear_payload/3. clear_payload(StoreId, PathPattern) -> clear_payload(StoreId, PathPattern, #{}). -spec clear_payload(StoreId, PathPattern, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:command_options() | khepri:tree_options() | khepri:put_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Deletes the payload of the tree node pointed to by the given path %% pattern. %% %% In other words, the payload is set to {@link khepri_payload:no_payload()}. %% Otherwise, the behavior is that of {@link put/4}. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree node to modify. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see put/4. %% @see khepri_adv:clear_payload/3. clear_payload(StoreId, PathPattern, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:clear_payload(StoreId, PathPattern, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% clear_many_payloads(). %% ------------------------------------------------------------------- -spec clear_many_payloads(PathPattern) -> Ret when PathPattern :: khepri_path:pattern(), Ret :: khepri:minimal_ret(). %% @doc Deletes the payload of all tree nodes matching the given path pattern. %% %% Calling this function is the same as calling `clear_many_payloads(StoreId, %% PathPattern)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see clear_many_payloads/2. %% @see clear_many_payloads/3. clear_many_payloads(PathPattern) -> StoreId = khepri_cluster:get_default_store_id(), clear_many_payloads(StoreId, PathPattern). -spec clear_many_payloads(StoreId, PathPattern) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Ret :: khepri:minimal_ret(). %% @doc Deletes the payload of all tree nodes matching the given path pattern. %% %% Calling this function is the same as calling `clear_many_payloads(StoreId, %% PathPattern, #{})'. %% %% @see clear_many_payloads/3. clear_many_payloads(StoreId, PathPattern) -> clear_many_payloads(StoreId, PathPattern, #{}). -spec clear_many_payloads(StoreId, PathPattern, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Options :: khepri:command_options() | khepri:tree_options() | khepri:put_options(), Ret :: khepri:minimal_ret() | khepri_machine:async_ret(). %% @doc Deletes the payload of all tree nodes matching the given path pattern. %% %% In other words, the payload is set to {@link khepri_payload:no_payload()}. %% Otherwise, the behavior is that of {@link put/4}. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path (or path pattern) to the tree nodes to modify. %% @param Options command options. %% %% @returns in the case of a synchronous call, `ok' or an `{error, Reason}' %% tuple; in the case of an asynchronous call, always `ok' (the actual return %% value may be sent by a message if a correlation ID was specified). %% %% @see delete_many/3. %% @see put/4. %% @see khepri_adv:clear_many_payloads/3. clear_many_payloads(StoreId, PathPattern, Options) -> Options1 = Options#{props_to_return => []}, Ret = khepri_adv:clear_many_payloads( StoreId, PathPattern, Options1), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% register_trigger(). %% ------------------------------------------------------------------- -spec register_trigger(TriggerId, EventFilter, StoredProcPath) -> Ret when TriggerId :: trigger_id(), EventFilter :: khepri_evf:event_filter() | khepri_path:pattern(), StoredProcPath :: khepri_path:path(), Ret :: ok | error(). %% @doc Registers a trigger. %% %% Calling this function is the same as calling `register_trigger(StoreId, %% TriggerId, EventFilter, StoredProcPath)' with the default store ID (see %% {@link khepri_cluster:get_default_store_id/0}). %% %% @see register_trigger/4. register_trigger(TriggerId, EventFilter, StoredProcPath) -> StoreId = khepri_cluster:get_default_store_id(), register_trigger(StoreId, TriggerId, EventFilter, StoredProcPath). -spec register_trigger (StoreId, TriggerId, EventFilter, StoredProcPath) -> Ret when StoreId :: khepri:store_id(), TriggerId :: trigger_id(), EventFilter :: khepri_evf:event_filter() | khepri_path:pattern(), StoredProcPath :: khepri_path:path(), Ret :: ok | error(); (TriggerId, EventFilter, StoredProcPath, Options) -> Ret when TriggerId :: trigger_id(), EventFilter :: khepri_evf:event_filter() | khepri_path:pattern(), StoredProcPath :: khepri_path:path(), Options :: command_options() | khepri:tree_options(), Ret :: ok | error(). %% @doc Registers a trigger. %% %% This function accepts the following two forms: %%
    %%
  • `register_trigger(StoreId, TriggerId, EventFilter, StoredProcPath)'. %% Calling it is the same as calling `register_trigger(StoreId, TriggerId, %% EventFilter, StoredProcPath, #{})'.
  • %%
  • `register_trigger(TriggerId, EventFilter, StoredProcPath, Options)'. %% Calling it is the same as calling `register_trigger(StoreId, TriggerId, %% EventFilter, StoredProcPath, Options)' with the default store ID (see %% {@link khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see register_trigger/5. register_trigger(StoreId, TriggerId, EventFilter, StoredProcPath) when ?IS_KHEPRI_STORE_ID(StoreId) andalso is_atom(TriggerId) -> register_trigger(StoreId, TriggerId, EventFilter, StoredProcPath, #{}); register_trigger(TriggerId, EventFilter, StoredProcPath, Options) when is_atom(TriggerId) andalso is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), register_trigger( StoreId, TriggerId, EventFilter, StoredProcPath, Options). -spec register_trigger( StoreId, TriggerId, EventFilter, StoredProcPath, Options) -> Ret when StoreId :: khepri:store_id(), TriggerId :: trigger_id(), EventFilter :: khepri_evf:event_filter() | khepri_path:pattern(), StoredProcPath :: khepri_path:path(), Options :: command_options() | khepri:tree_options(), Ret :: ok | error(). %% @doc 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. %% %% The following event filters are documented by {@link %% khepri_evf:event_filter()}. %% %% Here are examples of event filters: %% %% ``` %% %% An event filter can be explicitly created using the `khepri_evf' %% %% module. This is possible to specify properties at the same time. %% EventFilter = khepri_evf:tree([stock, wood, <<"oak">>], %% Required %% #{on_actions => [delete], %% Optional %% priority => 10}). %% Optional %% ''' %% ``` %% %% For ease of use, some terms can be automatically converted to an event %% %% filter. In this example, a Unix-like path can be used as a tree event %% %% filter. %% EventFilter = "/:stock/:wood/oak". %% ''' %% %% 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 guaranteed to run at least once. It could be executed multiple times %% if the Ra leader changes, therefore the stored procedure must be %% idempotent. %% %% @param StoreId the name of the Khepri store. %% @param TriggerId the name of the trigger. %% @param EventFilter the event filter used to associate an event with a %% stored procedure. %% @param StoredProcPath the path to the stored procedure to execute when the %% corresponding event occurs. %% %% @returns `ok' if the trigger was registered, an `{error, Reason}' tuple %% otherwise. register_trigger(StoreId, TriggerId, EventFilter, StoredProcPath, Options) -> khepri_machine:register_trigger( StoreId, TriggerId, EventFilter, StoredProcPath, Options). %% ------------------------------------------------------------------- %% register_projection(). %% ------------------------------------------------------------------- -spec register_projection(PathPattern, Projection) -> Ret when PathPattern :: khepri_path:pattern(), Projection :: khepri_projection:projection(), Ret :: ok | khepri:error(). %% @doc Registers a projection. %% %% Calling this function is the same as calling %% `register_projection(StoreId, PathPattern, Projection)' with the default %% store ID (see {@link khepri_cluster:get_default_store_id/0}). %% %% @see register_projection/3. register_projection(PathPattern, Projection) -> StoreId = khepri_cluster:get_default_store_id(), register_projection(StoreId, PathPattern, Projection). -spec register_projection (StoreId, PathPattern, Projection) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Projection :: khepri_projection:projection(), Ret :: ok | khepri:error(); (PathPattern, Projection, Options) -> Ret when PathPattern :: khepri_path:pattern(), Projection :: khepri_projection:projection(), Options :: khepri:command_options(), Ret :: ok | khepri:error(). %% @doc Registers a projection. %% %% This function accepts the following two forms: %%
    %%
  • `register_projection(StoreId, PathPattern, Projection)'. Calling it is %% the same as calling `register_projection(StoreId, PathPattern, Projection, %% #{})'.
  • %%
  • `register_projection(PathPattern, Projection, Options)'. Calling it is %% the same as calling `register_projection(StoreId, PathPattern, Projection, %% Options)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see register_projection/4. register_projection(StoreId, PathPattern, Projection) when ?IS_KHEPRI_STORE_ID(StoreId) -> register_projection(StoreId, PathPattern, Projection, #{}); register_projection(PathPattern, Projection, Options) when is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), register_projection(StoreId, PathPattern, Projection, Options). -spec register_projection(StoreId, PathPattern, Projection, Options) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Projection :: khepri_projection:projection(), Options :: khepri:command_options(), Ret :: ok | khepri:error(). %% @doc Registers a projection. %% %% A projection is a replicated ETS cache which is kept up to date by %% Khepri. See the {@link khepri_projection} module-docs for more information %% about projections. %% %% This function associates a projection created with {@link %% khepri_projection:new/3} with a pattern. Any changes to tree nodes matching %% the provided pattern will be turned into records using the projection's %% {@link khepri_projection:projection_fun()} and then applied to the %% projection's ETS table. %% %% Registering a projection fills the projection's ETS table with records from %% any tree nodes which match the `PathPattern' and are already in the store. %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the pattern of tree nodes which the projection should %% watch. %% @param Projection the projection resource, created with %% `khepri_projection:new/3'. %% @param Options command options for registering the projection. %% @returns `ok' if the projection was registered, an `{error, Reason}' tuple %% otherwise. register_projection(StoreId, PathPattern, Projection, Options) -> khepri_machine:register_projection( StoreId, PathPattern, Projection, Options). %% ------------------------------------------------------------------- %% unregister_projections(). %% ------------------------------------------------------------------- -spec unregister_projections(Names) -> Ret when Names :: all | [khepri_projection:name()], Ret :: ok | khepri:error(). %% @doc Removes the given projections from the store. %% %% Calling this function is the same as calling %% `unregister_projections(StoreId, Names)' with the default store ID (see %% {@link khepri_cluster:get_default_store_id/0}). %% %% @see unregister_projections/2. unregister_projections(Names) when Names =:= all orelse is_list(Names) -> StoreId = khepri_cluster:get_default_store_id(), unregister_projections(StoreId, Names). -spec unregister_projections (StoreId, Names) -> Ret when StoreId :: khepri:store_id(), Names :: all | [khepri_projection:name()], Ret :: ok | khepri:error(); (Names, Options) -> Ret when Names :: all | [khepri_projection:name()], Options :: khepri:command_options(), Ret :: ok | khepri:error(). %% @doc Removes the given projections from the store. %% %% This function accepts the following two forms: %%
    %%
  • `unregister_projections(StoreId, Names)'. Calling it is the same as %% calling `unregister_projections(StoreId, Names, #{})'.
  • %%
  • `unregister_projections(Names, Options)'. Calling it is the same as %% calling `unregister_projections(StoreId, Names, Options)' with %% the default store ID (see {@link khepri_cluster:get_default_store_id/0}). %%
  • %%
%% %% @see unregister_projections/3. unregister_projections(StoreId, Names) when ?IS_KHEPRI_STORE_ID(StoreId) andalso (Names =:= all orelse is_list(Names)) -> unregister_projections(StoreId, Names, #{}); unregister_projections(Names, Options) when (Names =:= all orelse is_list(Names)) andalso is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), unregister_projections(StoreId, Names, Options). -spec unregister_projections(StoreId, Names, Options) -> Ret when StoreId :: khepri:store_id(), Names :: all | [khepri_projection:name()], Options :: khepri:command_options(), Ret :: ok | khepri:error(). %% @doc Removes the given projections from the store. %% %% `Names' may either be a list of projection names to remove or the atom %% `all'. When `all' is passed, every projection in the store is removed. %% %% @param StoreId the name of the Khepri store. %% @param Names the names of projections to unregister or the atom `all' to %% remove all projections. %% @param Options command options for unregistering the projections. %% @returns `ok' if the projections were unregistered, an `{error, Reason}' %% tuple otherwise. %% %% @see khepri_adv:unregister_projections/3. unregister_projections(StoreId, Names, Options) when ?IS_KHEPRI_STORE_ID(StoreId) andalso (Names =:= all orelse is_list(Names)) andalso is_map(Options) -> Ret = khepri_adv:unregister_projections(StoreId, Names, Options), ?result_ret_to_minimal_ret(Ret). %% ------------------------------------------------------------------- %% has_projection(). %% ------------------------------------------------------------------- -spec has_projection(ProjectionName) -> Ret when ProjectionName :: khepri_projection:name(), Ret :: boolean() | khepri:error(). %% @doc Determines whether the store has a projection registered with the given %% name. %% %% Calling this function is the same as calling %% `has_projection(StoreId, ProjectionName)' with the default store ID %% (see {@link khepri_cluster:get_default_store_id/0}). %% %% @see has_projection/2. has_projection(ProjectionName) when is_atom(ProjectionName) -> StoreId = khepri_cluster:get_default_store_id(), has_projection(StoreId, ProjectionName). -spec has_projection (StoreId, ProjectionName) -> Ret when StoreId :: khepri:store_id(), ProjectionName :: khepri_projection:name(), Ret :: boolean() | khepri:error(); (ProjectionName, Options) -> Ret when ProjectionName :: khepri_projection:name(), Options :: khepri:query_options(), Ret :: boolean() | khepri:error(). %% @doc Determines whether the store has a projection registered with the given %% name. %% %% This function accepts the following two forms: %%
    %%
  • `has_projection(StoreId, ProjectionName)'. Calling it is the same %% as calling `has_projection(StoreId, ProjectionName, #{})'.
  • %%
  • `has_projection(ProjectionName, Options)'. Calling it is the same %% as calling `has_projection(StoreId, ProjectionName, Options)' with %% the default store ID (see {@link khepri_cluster:get_default_store_id/0}). %%
  • %%
%% %% @see has_projection/3. has_projection(StoreId, ProjectionName) when ?IS_KHEPRI_STORE_ID(StoreId) andalso is_atom(ProjectionName) -> has_projection(StoreId, ProjectionName, #{}); has_projection(ProjectionName, Options) when is_atom(ProjectionName) andalso is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), has_projection(StoreId, ProjectionName, Options). -spec has_projection(StoreId, ProjectionName, Options) -> Ret when StoreId :: khepri:store_id(), ProjectionName :: khepri_projection:name(), Options :: khepri:query_options(), Ret :: boolean() | khepri:error(). %% @doc Determines whether the store has a projection registered with the given %% name. %% %% @param StoreId the name of the Khepri store. %% @param ProjectionName the name of the projection to has as passed to %% {@link khepri_projection:new/3}. %% @param Options query options. %% @returns `true' if the store contains a projection registered with the given %% name, `false' if it does not, or an `{error, Reason}' tuple if the query %% failed. has_projection(StoreId, ProjectionName, Options) when ?IS_KHEPRI_STORE_ID(StoreId) andalso is_atom(ProjectionName) andalso is_map(Options) -> case khepri_machine:get_projections_state(StoreId, Options) of {ok, ProjectionTree} -> khepri_machine:has_projection(ProjectionTree, ProjectionName); {error, _} = Error -> Error end. %% ------------------------------------------------------------------- %% transaction(). %% ------------------------------------------------------------------- -spec transaction(FunOrPath) -> Ret when FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Ret :: khepri_machine:tx_ret(). %% @doc Runs a transaction and returns its result. %% %% Calling this function is the same as calling `transaction(FunOrPath, [])' %% with the default store ID. %% %% @see transaction/2. transaction(FunOrPath) -> transaction(FunOrPath, []). -spec transaction (StoreId, FunOrPath) -> Ret when StoreId :: store_id(), FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Ret :: khepri_machine:tx_ret(); (FunOrPath, Args) -> Ret when FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Args :: list(), Ret :: khepri_machine:tx_ret(); (FunOrPath, ReadWriteOrOptions) -> Ret when FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), ReadWriteOrOptions :: ReadWrite | Options, ReadWrite :: ro | rw | auto, Options :: command_options() | query_options(), Ret :: khepri_machine:tx_ret() | khepri_machine:async_ret(). %% @doc Runs a transaction and returns its result. %% %% This function accepts the following two forms: %%
    %%
  • `transaction(StoreId, FunOrPath)'. Calling it is the same as calling %% `transaction(StoreId, FunOrPath, [])'.
  • %%
  • `transaction(FunOrPath, Args)'. Calling it is the same as calling %% `transaction(StoreId, FunOrPath, Args)' with the default store ID.
  • %%
  • `transaction(FunOrPath, ReadWriteOrOptions)'. Calling it is the same as %% calling `transaction(StoreId, FunOrPath, [], ReadWriteOrOptions)' with the %% default store ID.
  • %%
%% %% @see transaction/3. transaction(FunOrPath, Args) when (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso is_list(Args) -> StoreId = khepri_cluster:get_default_store_id(), transaction(StoreId, FunOrPath, Args); transaction(FunOrPath, ReadWriteOrOptions) when (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso (is_atom(ReadWriteOrOptions) orelse is_map(ReadWriteOrOptions)) -> StoreId = khepri_cluster:get_default_store_id(), transaction(StoreId, FunOrPath, ReadWriteOrOptions); transaction(StoreId, FunOrPath) when ?IS_KHEPRI_STORE_ID(StoreId) andalso (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) -> transaction(StoreId, FunOrPath, []). -spec transaction (StoreId, FunOrPath, Args) -> Ret when StoreId :: store_id(), FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Args :: list(), Ret :: khepri_machine:tx_ret(); (StoreId, FunOrPath, ReadWriteOrOptions) -> Ret when StoreId :: store_id(), FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), ReadWriteOrOptions :: ReadWrite | Options, ReadWrite :: ro | rw | auto, Options :: command_options() | query_options(), Ret :: khepri_machine:tx_ret() | khepri_machine:async_ret(); (FunOrPath, Args, ReadWriteOrOptions) -> Ret when FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Args :: list(), ReadWriteOrOptions :: ReadWrite | Options, ReadWrite :: ro | rw | auto, Options :: command_options() | query_options(), Ret :: khepri_machine:tx_ret() | khepri_machine:async_ret(); (FunOrPath, ReadWrite, Options) -> Ret when FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), ReadWrite :: ro | rw | auto, Options :: command_options() | query_options(), Ret :: khepri_machine:tx_ret() | khepri_machine:async_ret(). %% @doc Runs a transaction and returns its result. %% %% This function accepts the following two forms: %%
    %%
  • `transaction(StoreId, FunOrPath, Args)'. Calling it is the same as %% calling `transaction(StoreId, FunOrPath, Args, auto)'.
  • %%
  • `transaction(StoreId, FunOrPath, ReadWriteOrOptions)'. Calling it is %% the same as calling `transaction(StoreId, FunOrPath, [], %% ReadWriteOrOptions)'.
  • %%
  • `transaction(FunOrPath, Args, ReadWriteOrOptions)'. Calling it is the %% same as calling `transaction(StoreId, FunOrPath, Args, ReadWriteOrOptions)' %% with the default store ID.
  • %%
%% %% @see transaction/4. transaction(StoreId, FunOrPath, Args) when ?IS_KHEPRI_STORE_ID(StoreId) andalso (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso is_list(Args) -> transaction(StoreId, FunOrPath, Args, auto); transaction(StoreId, FunOrPath, ReadWriteOrOptions) when ?IS_KHEPRI_STORE_ID(StoreId) andalso (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso (is_atom(ReadWriteOrOptions) orelse is_map(ReadWriteOrOptions)) -> transaction(StoreId, FunOrPath, [], ReadWriteOrOptions); transaction(FunOrPath, Args, ReadWriteOrOptions) when (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso is_list(Args) andalso (is_atom(ReadWriteOrOptions) orelse is_map(ReadWriteOrOptions)) -> StoreId = khepri_cluster:get_default_store_id(), transaction(StoreId, FunOrPath, Args, ReadWriteOrOptions); transaction(FunOrPath, ReadWrite, Options) when (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso is_atom(ReadWrite) andalso is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), transaction(StoreId, FunOrPath, ReadWrite, Options). -spec transaction (StoreId, FunOrPath, Args, ReadWrite) -> Ret when StoreId :: store_id(), FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Args :: list(), ReadWrite :: ro | rw | auto, Ret :: khepri_machine:tx_ret(); (StoreId, FunOrPath, Args, Options) -> Ret when StoreId :: store_id(), FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Args :: list(), Options :: command_options() | query_options(), Ret :: khepri_machine:tx_ret() | khepri_machine:async_ret(); (StoreId, FunOrPath, ReadWrite, Options) -> Ret when StoreId :: store_id(), FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), ReadWrite :: ro | rw | auto, Options :: command_options() | query_options(), Ret :: khepri_machine:tx_ret() | khepri_machine:async_ret(); (FunOrPath, Args, ReadWrite, Options) -> Ret when FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Args :: list(), ReadWrite :: ro | rw | auto, Options :: command_options() | query_options(), Ret :: khepri_machine:tx_ret() | khepri_machine:async_ret(). %% @doc Runs a transaction and returns its result. %% %% This function accepts the following three forms: %%
    %%
  • `transaction(StoreId, FunOrPath, Args, ReadWrite)'. Calling it is the %% same as calling `transaction(StoreId, FunOrPath, Args, ReadWrite, %% #{})'.
  • %%
  • `transaction(StoreId, FunOrPath, Args, Options)'. Calling it is the %% same as calling `transaction(StoreId, FunOrPath, Args, auto, %% Options)'.
  • %%
  • `transaction(FunOrPath, Args, ReadWrite, Options)'. Calling it is the %% same as calling `transaction(StoreId, FunOrPath, Args, ReadWrite, Options)' %% with the default store ID.
  • %%
%% %% @see transaction/5. transaction(StoreId, FunOrPath, Args, ReadWrite) when ?IS_KHEPRI_STORE_ID(StoreId) andalso (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso is_list(Args) andalso is_atom(ReadWrite) -> transaction(StoreId, FunOrPath, Args, ReadWrite, #{}); transaction(StoreId, FunOrPath, Args, Options) when ?IS_KHEPRI_STORE_ID(StoreId) andalso (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso is_list(Args) andalso is_map(Options) -> transaction(StoreId, FunOrPath, Args, auto, Options); transaction(StoreId, FunOrPath, ReadWrite, Options) when ?IS_KHEPRI_STORE_ID(StoreId) andalso (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso is_atom(ReadWrite) andalso is_map(Options) -> transaction(StoreId, FunOrPath, [], ReadWrite, Options); transaction(FunOrPath, Args, ReadWrite, Options) when (is_function(FunOrPath) orelse ?IS_KHEPRI_PATH_PATTERN(FunOrPath)) andalso is_list(Args) andalso is_atom(ReadWrite) andalso is_map(Options) -> StoreId = khepri_cluster:get_default_store_id(), transaction(StoreId, FunOrPath, Args, ReadWrite, Options). -spec transaction(StoreId, FunOrPath, Args, ReadWrite, Options) -> Ret when StoreId :: store_id(), FunOrPath :: Fun | PathPattern, Fun :: khepri_tx:tx_fun(), PathPattern :: khepri_path:pattern(), Args :: list(), ReadWrite :: ro | rw | auto, Options :: khepri:command_options() | khepri:query_options(), Ret :: khepri_machine:tx_ret() | khepri_machine:async_ret(). %% @doc Runs a transaction and returns its result. %% %% `Fun' is an arbitrary anonymous function which takes the content of `Args' %% as its arguments. In other words, the length of `Args' must correspond to %% the arity of `Fun'. %% %% Instead of `Fun', `PathPattern' can be passed. It must point to an existing %% stored procedure. The length to `Args' must correspond to the arity of that %% stored procedure. %% %% The `ReadWrite' flag determines what the `Fun' anonymous function is %% allowed to do and in which context it runs: %% %%
    %%
  • If `ReadWrite' is `ro', `Fun' can do whatever it wants, except modify %% the content of the store. In other words, uses of {@link khepri_tx:put/2} %% or {@link khepri_tx:delete/1} are forbidden and will abort the function. %% `Fun' is executed from a process on the leader Ra member.
  • %%
  • If `ReadWrite' is `rw', `Fun' can use the {@link khepri_tx} transaction %% API as well as any calls to other modules as long as those functions or what %% they do is permitted. See {@link khepri_tx} for more details. If `Fun' does %% or calls something forbidden, the transaction will be aborted. `Fun' is %% executed in the context of the state machine process on each Ra %% members.
  • %%
  • If `ReadWrite' is `auto', `Fun' is analyzed to determine if it calls %% {@link khepri_tx:put/2} or {@link khepri_tx:delete/1}, or uses any denied %% operations for a read/write transaction. If it does, this is the same as %% setting `ReadWrite' to true. Otherwise, this is the equivalent of setting %% `ReadWrite' to false.
  • %%
%% %% When using `PathPattern', a `ReadWrite' of `auto' is synonymous of `rw'. %% %% `Options' is relevant for both read-only and read-write transactions %% (including audetected ones). However note that both types expect different %% options. %% %% The result of `FunOrPath' can be any term. That result is returned in an %% `{ok, Result}' tuple if the transaction is synchronous. The result is sent %% by message if the transaction is asynchronous and a correlation ID was %% specified. %% %% @param StoreId the name of the Khepri store. %% @param FunOrPath an arbitrary anonymous function or a path pattern pointing %% to a stored procedure. %% @param Args a list of arguments to pass to `FunOrPath'. %% @param ReadWrite the read/write or read-only nature of the transaction. %% @param Options command options such as the command type. %% %% @returns in the case of a synchronous transaction, `{ok, Result}' where %% `Result' is the return value of `FunOrPath', or `{error, Reason}' if the %% anonymous function was aborted; in the case of an asynchronous transaction, %% always `ok' (the actual return value may be sent by a message if a %% correlation ID was specified). transaction(StoreId, FunOrPath, Args, ReadWrite, Options) -> khepri_machine:transaction(StoreId, FunOrPath, Args, ReadWrite, Options). %% ------------------------------------------------------------------- %% fence(). %% ------------------------------------------------------------------- -spec fence() -> Ret when Ret :: ok | khepri:error(). %% @doc Blocks until all updates received by the cluster leader are applied %% locally. %% %% Calling this function is the same as calling `fence(StoreId)' with the %% default store ID (see {@link khepri_cluster:get_default_store_id/0}). %% %% @see fence/1. %% @see fence/2. fence() -> StoreId = khepri_cluster:get_default_store_id(), fence(StoreId). -spec fence(StoreId | Timeout) -> Ret when StoreId :: khepri:store_id(), Timeout :: timeout(), Ret :: ok | khepri:error(). %% @doc Blocks until all updates received by the cluster leader are applied %% locally. %% %% This function accepts the following two forms: %%
    %%
  • `fence(StoreId)'. Calling it is the same as calling `fence(StoreId, %% Timeout)' with the default timeout (see {@link %% khepri_app:get_default_timeout/0}).
  • %%
  • `fence(Timeout)'. Calling it is the same as calling `fence(StoreId, %% Timeout)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see fence/2. fence(Timeout) when Timeout =:= infinity orelse is_integer(Timeout) -> StoreId = khepri_cluster:get_default_store_id(), fence(StoreId, Timeout); fence(StoreId) -> Timeout = khepri_app:get_default_timeout(), fence(StoreId, Timeout). -spec fence(StoreId, Timeout) -> Ret when StoreId :: khepri:store_id(), Timeout :: timeout(), Ret :: ok | khepri:error(). %% @doc Blocks until all updates received by the cluster leader are applied %% locally. %% %% This ensures that a subsequent query will see the result of synchronous and %% asynchronous updates. %% %% This can't work however if: %%
    %%
  • Asynchronous updates have a correlation ID, in which case the caller is %% responsible for waiting for the replies.
  • %%
  • The default `reply_from => local' command option is overridden by %% something else.
  • %%
%% %% @param StoreId the name of the Khepri store. %% @param Timeout the time limit after which the call returns with an error. %% %% @returns `ok' or an `{error, Reason}' tuple. fence(StoreId, Timeout) -> khepri_machine:fence(StoreId, Timeout). %% ------------------------------------------------------------------- %% handle_async_ret(). %% ------------------------------------------------------------------- -spec handle_async_ret(RaEvent) -> Ret when RaEvent :: ra_server_proc:ra_event(), Ret :: [{CorrelationId, AsyncRet}, ...], CorrelationId :: ra_server:command_correlation(), AsyncRet :: khepri:async_ret(). %% @doc Handles the Ra event sent for asynchronous call results. %% %% Calling this function is the same as calling %% `handle_async_ret(StoreId, RaEvent)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see handle_async_ret/2. handle_async_ret(RaEvent) -> StoreId = khepri_cluster:get_default_store_id(), handle_async_ret(StoreId, RaEvent). -spec handle_async_ret(StoreId, RaEvent) -> Ret when StoreId :: khepri:store_id(), RaEvent :: ra_server_proc:ra_event(), Ret :: [{CorrelationId, AsyncRet}, ...], CorrelationId :: ra_server:command_correlation(), AsyncRet :: khepri:async_ret(). %% @doc Handles the Ra event sent for asynchronous call results. %% %% When sending commands with `async' {@link command_options()}, the calling %% process will receive Ra events with the following structure: %% %% `{ra_event, CurrentLeader, {applied, [{Correlation1, Reply1}, ...]}}' %% %% or %% %% `{ra_event, %% FromId, %% {rejected, {not_leader, Leader | undefined, Correlation}}}' %% %% The first event acknowledges all commands handled in a batch while the %% second is sent per-command when commands are sent against a non-leader %% member. %% %% These events should be passed to this function in order to map the return %% values from the async commands and to update leader information. This %% function does not handle retrying rejected commands or return values from %% applied commands - the caller is responsible for those tasks. %% %% Example: %% ``` %% ok = khepri:put(StoreId, [stock, wood, <<"oak">>], 200, #{async => 1}), %% ok = khepri:put(StoreId, [stock, wood, <<"maple">>], 150, #{async => 2}), %% RaEvent = receive {ra_event, _, _} = Event -> Event end, %% ?assertMatch( %% [{1, {ok, #{[stock, wood, <<"oak">>] => _}}}, %% {2, {ok, #{[stock, wood, <<"maple">>] => _}}}], %% khepri:handle_async_ret(RaEvent)). %% ''' %% %% @see async_option(). %% @see ra:pipeline_command/4. handle_async_ret( StoreId, {ra_event, _CurrentLeader, {applied, Correlations0}}) when ?IS_KHEPRI_STORE_ID(StoreId) -> lists:map( fun({CorrelationId, Reply0}) -> Reply = case Reply0 of {exception, _, _, _} = Exception -> khepri_machine:handle_tx_exception(Exception); ok -> Reply0; {ok, _} -> Reply0; {error, _} -> Reply0 end, {CorrelationId, Reply} end, Correlations0); handle_async_ret( StoreId, {ra_event, _RaServer, {rejected, {not_leader, LeaderId, CorrelationId}}}) when ?IS_KHEPRI_STORE_ID(StoreId) -> [{CorrelationId, {error, {not_leader, LeaderId}}}]. %% ------------------------------------------------------------------- %% Bang functions. %% ------------------------------------------------------------------- -include("khepri_bang.hrl"). %% ------------------------------------------------------------------- %% Import/export (backup & restore in Mnesia terms). %% ------------------------------------------------------------------- -spec export(Module, ModulePriv) -> Ret when Module :: module(), ModulePriv :: khepri_import_export:module_priv(), Ret :: ok | {ok, ModulePriv} | {error, any()}. %% @doc Exports a Khepri store using the `Module' callback module. %% %% Calling this function is the same as calling `export(StoreId, Module, %% ModulePriv)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see export/3. %% @see export/4. export(Module, ModulePriv) when is_atom(Module) -> StoreId = khepri_cluster:get_default_store_id(), export(StoreId, Module, ModulePriv). -spec export(StoreId | PathPattern, Module, ModulePriv) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Module :: module(), ModulePriv :: khepri_import_export:module_priv(), Ret :: ok | {ok, ModulePriv} | {error, any()}. %% @doc Exports a Khepri store using the `Module' callback module. %% %% This function accepts the following two forms: %%
    %%
  • `export(StoreId, Module, ModulePriv)'. Calling it is the same as %% calling `export(StoreId, "**", Module, ModulePriv)'.
  • %%
  • `export(PathPattern, Module, ModulePriv)'. Calling it is the same as %% calling `export(StoreId, PathPattern, Module, ModulePriv)' with the default %% store ID (see {@link khepri_cluster:get_default_store_id/0}).
  • %%
%% %% @see export/4. export(StoreId, Module, ModulePriv) when?IS_KHEPRI_STORE_ID(StoreId) andalso is_atom(Module) -> export(StoreId, [?KHEPRI_WILDCARD_STAR_STAR], Module, ModulePriv); export(PathPattern, Module, ModulePriv) when is_atom(Module) -> StoreId = khepri_cluster:get_default_store_id(), export(StoreId, PathPattern, Module, ModulePriv). -spec export(StoreId, PathPattern, Module, ModulePriv) -> Ret when StoreId :: khepri:store_id(), PathPattern :: khepri_path:pattern(), Module :: module(), ModulePriv :: khepri_import_export:module_priv(), Ret :: ok | {ok, ModulePriv} | {error, any()}. %% @doc Exports a Khepri store using the `Module' callback module. %% %% The `PathPattern' allows to filter which tree nodes are exported. The path %% pattern can be provided as a native path pattern (a list of tree node names %% and conditions) or as a string. See {@link khepri_path:from_string/1}. %% %% `Module' is the callback module called to perform the actual export. It %% must conform to the Mnesia Backup & Restore API. See {@link %% khepri_import_export} for more details. %% %% `ModulePriv' is the term passed to `Module:open_write/1'. %% %% Example: export the full Khepri store using {@link khepri_export_erlang} as %% the callback module %% ``` %% ok = khepri:export(StoreId, khepri_export_erlang, "export-1.erl"). %% ''' %% %% Example: export a subset of the Khepri store %% ``` %% ok = khepri:export( %% StoreId, %% "/:stock/:wood/**", %% khepri_export_erlang, %% "export-wood-stock-1.erl"). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param PathPattern the path pattern matching the tree nodes to export. %% @param Module the callback module to use to export. %% @param ModulePriv arguments passed to `Module:open_write/1'. %% %% @returns `ok' or an `{ok, Term}' tuple if the export succeeded (the actual %% return value depends on whether the callback module wants to return anything %% to the caller), or an `{error, Reason}' tuple if it failed. %% %% @see import/3. export(StoreId, PathPattern, Module, ModulePriv) when ?IS_KHEPRI_STORE_ID(StoreId) andalso is_atom(Module) -> khepri_import_export:export(StoreId, PathPattern, Module, ModulePriv). -spec import(Module, ModulePriv) -> Ret when Module :: module(), ModulePriv :: khepri_import_export:module_priv(), Ret :: ok | {ok, ModulePriv} | {error, any()}. %% @doc Imports a previously exported set of tree nodes using the `Module' %% callback module. %% %% Calling this function is the same as calling `import(StoreId, Module, %% ModulePriv)' with the default store ID (see {@link %% khepri_cluster:get_default_store_id/0}). %% %% @see import/3. import(Module, ModulePriv) when is_atom(Module) -> StoreId = khepri_cluster:get_default_store_id(), import(StoreId, Module, ModulePriv). -spec import(StoreId, Module, ModulePriv) -> Ret when StoreId :: khepri:store_id(), Module :: module(), ModulePriv :: khepri_import_export:module_priv(), Ret :: ok | {ok, ModulePriv} | {error, any()}. %% @doc Imports a previously exported set of tree nodes using the `Module' %% callback module. %% %% `Module' is the callback module called to perform the actual import. It %% must conform to the Mnesia Backup & Restore API. See {@link %% khepri_import_export} for more details. %% %% `ModulePriv' is the term passed to `Module:open_read/1'. %% %% Importing something doesn't delete existing tree nodes. The caller is %% responsible for deleting the existing content of a store if he needs to. %% %% Example: import a set of tree nodes using {@link khepri_export_erlang} as %% the callback module %% ``` %% ok = khepri:import(StoreId, khepri_export_erlang, "export-1.erl"). %% ''' %% %% @param StoreId the name of the Khepri store. %% @param Module the callback module to use to import. %% @param ModulePriv arguments passed to `Module:open_read/1'. %% %% @returns `ok' or an `{ok, Term}' tuple if the import succeeded (the actual %% return value depends on whether the callback module wants to return anything %% to the caller), or an `{error, Reason}' tuple if it failed. %% %% @see export/3. import(StoreId, Module, ModulePriv) when ?IS_KHEPRI_STORE_ID(StoreId) andalso is_atom(Module) -> khepri_import_export:import(StoreId, Module, ModulePriv). %% ------------------------------------------------------------------- %% Public helpers. %% ------------------------------------------------------------------- -spec info() -> ok. %% @doc Lists the running stores on stdout. info() -> StoreIds = get_store_ids(), case StoreIds of [] -> io:format("No stores running~n"); _ -> io:format("Running stores:~n"), lists:foreach( fun(StoreId) -> io:format(" ~ts~n", [StoreId]) end, StoreIds) end, ok. -spec info(StoreId) -> ok when StoreId :: store_id(). %% @doc Lists the content of specified store on stdout. %% %% @param StoreId the name of the Khepri store. info(StoreId) -> info(StoreId, #{}). -spec info(StoreId, Options) -> ok when StoreId :: khepri:store_id(), Options :: khepri:query_options(). %% @doc Lists the content of specified store on stdout. %% %% @param StoreId the name of the Khepri store. %% @param Options query options. info(StoreId, Options) -> io:format("~n\033[1;32m== CLUSTER MEMBERS ==\033[0m~n~n", []), case khepri_cluster:nodes(StoreId) of {ok, Nodes} -> Nodes1 = lists:sort(Nodes), lists:foreach(fun(Node) -> io:format("~ts~n", [Node]) end, Nodes1); {error, _} = Error -> io:format("Failed to query cluster members: ~p~n", [Error]) end, case khepri_machine:get_keep_while_conds_state(StoreId, Options) of {ok, KeepWhileConds} when KeepWhileConds =/= #{} -> io:format("~n\033[1;32m== LIFETIME DEPS ==\033[0m~n", []), WatcherList = lists:sort(maps:keys(KeepWhileConds)), lists:foreach( fun(Watcher) -> io:format("~n\033[1m~p depends on:\033[0m~n", [Watcher]), WatchedsMap = maps:get(Watcher, KeepWhileConds), Watcheds = lists:sort(maps:keys(WatchedsMap)), lists:foreach( fun(Watched) -> Condition = maps:get(Watched, WatchedsMap), io:format( " ~p:~n" " ~p~n", [Watched, Condition]) end, Watcheds) end, WatcherList); _ -> ok end, case khepri_machine:get_projections_state(StoreId, Options) of {ok, ProjectionTree} -> case khepri_pattern_tree:is_empty(ProjectionTree) of true -> ok; false -> io:format("~n\033[1;32m== PROJECTIONS ==\033[0m~n", []), khepri_pattern_tree:foreach( ProjectionTree, fun(PathPattern, Projections) -> [begin Name = khepri_projection:name(Projection), io:format( "~n~p:~n" " ~p~n", [Name, PathPattern]) end || Projection <- Projections] end) end; _ -> ok end, case khepri_adv:get_many(StoreId, [?KHEPRI_WILDCARD_STAR_STAR], Options) of {ok, Result} -> io:format("~n\033[1;32m== TREE ==\033[0m~n~n●~n", []), Tree = khepri_utils:flat_struct_to_tree(Result), khepri_utils:display_tree(Tree); _ -> ok end, ok.