Current section

Files

Jump to
reckon_db src reckon_db_ccc_paths.erl
Raw

src/reckon_db_ccc_paths.erl

%%% @doc CCC (Command Context Consistency) Khepri path helpers.
%%%
%%% Payload index paths used by the CCC extensions to the DCB conditional-
%%% append primitive. Two structures live here:
%%%
%%% [by_payload, Key, Value, SeqKey] → #{} (single-field payload index)
%%% [by_payload_hash, Hash, SeqKey] → #{} (composite SHA-256 hash index)
%%%
%%% These are DCB-scoped (SeqKey-ordered, written atomically with each DCB
%%% event in the same Khepri transaction) and entirely separate from the
%%% secondary index under [idx, ...] which is OrderKey-keyed and optional.
%%%
%%% === Horus constraint ===
%%%
%%% All functions here EXCEPT payload_combo_hash/2 are PURE and safe to call
%%% from inside a khepri:transaction/2 body.
%%%
%%% payload_combo_hash/2 calls crypto:hash/2 — a NIF that Horus cannot
%%% extract into a portable Raft log entry. It MUST be called outside any
%%% transaction (in the stamp phase or via preprocess_filter/1).
%%%
%%% @end
-module(reckon_db_ccc_paths).
-include("reckon_db.hrl").
-include_lib("khepri/include/khepri.hrl").
-export([
by_payload_path/3,
by_payload_pattern/2,
by_payload_hash_path/2,
by_payload_hash_pattern/1,
payload_combo_hash/2
]).
%% @doc Path to a single-field payload index entry:
%% [by_payload, Key, Value, SeqKey]
-spec by_payload_path(binary(), binary(), non_neg_integer()) -> [term()].
by_payload_path(Key, Value, Seq)
when is_binary(Key), is_binary(Value), is_integer(Seq), Seq >= 0 ->
?BY_PAYLOAD_PATH ++ [Key, Value, reckon_db_dcb_paths:seq_key(Seq)].
%% @doc Pattern matching every seq under a payload field value (subtree wildcard).
%% Use with khepri_tx:get_many/1 inside a transaction.
-spec by_payload_pattern(binary(), binary()) -> [term()].
by_payload_pattern(Key, Value) when is_binary(Key), is_binary(Value) ->
?BY_PAYLOAD_PATH ++ [Key, Value, ?KHEPRI_WILDCARD_STAR].
%% @doc Path to a composite payload hash index entry:
%% [by_payload_hash, Hash, SeqKey]
-spec by_payload_hash_path(binary(), non_neg_integer()) -> [term()].
by_payload_hash_path(Hash, Seq)
when is_binary(Hash), is_integer(Seq), Seq >= 0 ->
?BY_PAYLOAD_HASH_PATH ++ [Hash, reckon_db_dcb_paths:seq_key(Seq)].
%% @doc Pattern matching every seq under a composite payload hash.
%% Use with khepri_tx:get_many/1 inside a transaction.
-spec by_payload_hash_pattern(binary()) -> [term()].
by_payload_hash_pattern(Hash) when is_binary(Hash) ->
?BY_PAYLOAD_HASH_PATH ++ [Hash, ?KHEPRI_WILDCARD_STAR].
%% @doc SHA-256 of the sorted [{Key, Value}] pair list.
%%
%% Sort ensures field-order independence: the same set of fields produces
%% the same hash regardless of the order Keys and Values are supplied.
%%
%% Example: payload_combo_hash([A, B], [V1, V2]) ==
%% payload_combo_hash([B, A], [V2, V1])
%%
%% MUST NOT be called from inside a Khepri transaction — crypto:hash/2 is a
%% NIF that Horus cannot extract. Pre-compute the hash in the stamp phase or
%% via reckon_db_ccc_filter:preprocess_filter/1 before entering the
%% transaction.
-spec payload_combo_hash([binary()], [binary()]) -> binary().
payload_combo_hash(Keys, Values)
when is_list(Keys), is_list(Values), length(Keys) =:= length(Values) ->
Pairs = lists:sort(lists:zip(Keys, Values)),
crypto:hash(sha256, term_to_binary(Pairs)).