2016-10-13 21:02:15 +01:00
|
|
|
%% -------- Key Codec ---------
|
|
|
|
%%
|
2016-10-14 18:43:16 +01:00
|
|
|
%% Functions for manipulating keys and values within leveled.
|
|
|
|
%%
|
2018-12-06 15:31:11 +00:00
|
|
|
%% Any thing specific to handling of a given tag should be encapsulated
|
|
|
|
%% within the leveled_head module
|
2016-10-14 18:43:16 +01:00
|
|
|
|
2016-10-13 21:02:15 +01:00
|
|
|
|
|
|
|
-module(leveled_codec).
|
|
|
|
|
2016-10-18 01:59:03 +01:00
|
|
|
-include("include/leveled.hrl").
|
2016-10-13 21:02:15 +01:00
|
|
|
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
|
2016-10-25 23:13:14 +01:00
|
|
|
-export([
|
|
|
|
inker_reload_strategy/1,
|
2016-10-13 21:02:15 +01:00
|
|
|
strip_to_seqonly/1,
|
|
|
|
strip_to_statusonly/1,
|
2018-11-05 01:21:08 +00:00
|
|
|
strip_to_segmentonly/1,
|
2016-10-31 17:26:28 +00:00
|
|
|
strip_to_keyseqonly/1,
|
2018-10-29 21:16:38 +00:00
|
|
|
strip_to_indexdetails/1,
|
2018-10-29 20:24:54 +00:00
|
|
|
striphead_to_v1details/1,
|
2016-10-31 12:12:06 +00:00
|
|
|
is_active/3,
|
2016-10-13 21:02:15 +01:00
|
|
|
endkey_passed/2,
|
|
|
|
key_dominates/2,
|
2016-10-21 16:08:41 +01:00
|
|
|
maybe_reap_expiredkey/2,
|
2016-10-14 18:43:16 +01:00
|
|
|
to_ledgerkey/3,
|
2016-10-18 01:59:03 +01:00
|
|
|
to_ledgerkey/5,
|
|
|
|
from_ledgerkey/1,
|
2017-11-17 14:54:53 +00:00
|
|
|
from_ledgerkey/2,
|
2018-12-07 09:07:22 +00:00
|
|
|
isvalid_ledgerkey/1,
|
2018-05-03 17:18:13 +01:00
|
|
|
to_inkerkey/2,
|
2017-11-06 18:44:08 +00:00
|
|
|
to_inkerkv/6,
|
2016-10-25 23:13:14 +01:00
|
|
|
from_inkerkv/1,
|
2017-03-29 15:37:04 +01:00
|
|
|
from_inkerkv/2,
|
2016-10-25 23:13:14 +01:00
|
|
|
from_journalkey/1,
|
2018-12-06 22:45:05 +00:00
|
|
|
revert_to_keydeltas/2,
|
2020-03-09 15:12:48 +00:00
|
|
|
is_full_journalentry/1,
|
2016-10-25 23:13:14 +01:00
|
|
|
split_inkvalue/1,
|
|
|
|
check_forinkertype/2,
|
2018-12-06 22:45:05 +00:00
|
|
|
get_tagstrategy/2,
|
2017-11-06 21:16:46 +00:00
|
|
|
maybe_compress/2,
|
2017-11-06 15:54:58 +00:00
|
|
|
create_value_for_journal/3,
|
2016-10-14 18:43:16 +01:00
|
|
|
generate_ledgerkv/5,
|
|
|
|
get_size/2,
|
2017-06-30 16:31:22 +01:00
|
|
|
get_keyandobjhash/2,
|
2017-06-30 10:03:36 +01:00
|
|
|
idx_indexspecs/5,
|
2018-02-15 16:14:46 +00:00
|
|
|
obj_objectspecs/3,
|
2017-10-20 23:04:29 +01:00
|
|
|
segment_hash/1,
|
2017-11-01 11:51:51 +00:00
|
|
|
to_lookup/1,
|
2018-12-06 21:00:59 +00:00
|
|
|
next_key/1,
|
2020-03-15 22:14:42 +00:00
|
|
|
return_proxy/4,
|
|
|
|
get_metadata/1]).
|
2016-11-28 22:26:09 +00:00
|
|
|
|
2017-06-30 10:03:36 +01:00
|
|
|
-define(LMD_FORMAT, "~4..0w~2..0w~2..0w~2..0w~2..0w").
|
2017-06-30 16:31:22 +01:00
|
|
|
-define(NRT_IDX, "$aae.").
|
2017-06-30 10:03:36 +01:00
|
|
|
|
2018-05-03 18:26:02 +01:00
|
|
|
-type tag() ::
|
2018-12-07 09:07:22 +00:00
|
|
|
leveled_head:object_tag()|?IDX_TAG|?HEAD_TAG|atom().
|
2018-11-01 10:41:46 +00:00
|
|
|
-type key() ::
|
|
|
|
binary()|string()|{binary(), binary()}.
|
|
|
|
% Keys SHOULD be binary()
|
|
|
|
% string() support is a legacy of old tests
|
2018-10-29 21:16:38 +00:00
|
|
|
-type sqn() ::
|
|
|
|
% SQN of the object in the Journal
|
|
|
|
pos_integer().
|
2018-05-03 17:18:13 +01:00
|
|
|
-type segment_hash() ::
|
2018-10-29 21:16:38 +00:00
|
|
|
% hash of the key to an aae segment - to be used in ledger filters
|
2018-05-04 11:19:37 +01:00
|
|
|
{integer(), integer()}|no_lookup.
|
2018-10-29 21:16:38 +00:00
|
|
|
-type metadata() ::
|
|
|
|
tuple()|null. % null for empty metadata
|
|
|
|
-type last_moddate() ::
|
|
|
|
% modified date as determined by the object (not this store)
|
|
|
|
% if the object has siblings in the store will be the maximum of those
|
|
|
|
% dates
|
|
|
|
integer()|undefined.
|
2018-10-31 00:09:24 +00:00
|
|
|
-type lastmod_range() :: {integer(), pos_integer()|infinity}.
|
2018-10-29 21:16:38 +00:00
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-type ledger_status() ::
|
|
|
|
tomb|{active, non_neg_integer()|infinity}.
|
|
|
|
-type ledger_key() ::
|
2018-06-21 15:46:42 +01:00
|
|
|
{tag(), any(), any(), any()}|all.
|
2018-10-29 21:16:38 +00:00
|
|
|
-type ledger_value() ::
|
|
|
|
ledger_value_v1()|ledger_value_v2().
|
|
|
|
-type ledger_value_v1() ::
|
|
|
|
{sqn(), ledger_status(), segment_hash(), metadata()}.
|
|
|
|
-type ledger_value_v2() ::
|
|
|
|
{sqn(), ledger_status(), segment_hash(), metadata(), last_moddate()}.
|
2018-05-03 17:18:13 +01:00
|
|
|
-type ledger_kv() ::
|
|
|
|
{ledger_key(), ledger_value()}.
|
2018-12-06 15:31:11 +00:00
|
|
|
-type compaction_method() ::
|
|
|
|
retain|skip|recalc.
|
2018-05-03 17:18:13 +01:00
|
|
|
-type compaction_strategy() ::
|
2018-12-06 15:31:11 +00:00
|
|
|
list({tag(), compaction_method()}).
|
2018-05-03 17:18:13 +01:00
|
|
|
-type journal_key_tag() ::
|
|
|
|
?INKT_STND|?INKT_TOMB|?INKT_MPUT|?INKT_KEYD.
|
|
|
|
-type journal_key() ::
|
2018-12-06 15:31:11 +00:00
|
|
|
{sqn(), journal_key_tag(), ledger_key()}.
|
|
|
|
-type journal_ref() ::
|
|
|
|
{ledger_key(), sqn()}.
|
2018-11-01 10:41:46 +00:00
|
|
|
-type object_spec_v0() ::
|
|
|
|
{add|remove, key(), key(), key()|null, any()}.
|
2018-11-01 12:40:24 +00:00
|
|
|
-type object_spec_v1() ::
|
|
|
|
{add|remove, v1, key(), key(), key()|null,
|
|
|
|
list(erlang:timestamp())|undefined, any()}.
|
2018-11-01 10:41:46 +00:00
|
|
|
-type object_spec() ::
|
2018-11-01 12:40:24 +00:00
|
|
|
object_spec_v0()|object_spec_v1().
|
2018-05-03 17:18:13 +01:00
|
|
|
-type compression_method() ::
|
|
|
|
lz4|native.
|
2018-05-04 15:24:08 +01:00
|
|
|
-type index_specs() ::
|
|
|
|
list({add|remove, any(), any()}).
|
2018-06-04 10:57:37 +01:00
|
|
|
-type journal_keychanges() ::
|
|
|
|
{index_specs(), infinity|integer()}. % {KeyChanges, TTL}
|
2018-10-29 21:16:38 +00:00
|
|
|
-type maybe_lookup() ::
|
|
|
|
lookup|no_lookup.
|
2018-11-05 16:02:19 +00:00
|
|
|
-type regular_expression() ::
|
|
|
|
{re_pattern, term(), term(), term(), term()}|undefined.
|
|
|
|
% first element must be re_pattern, but tuple may change legnth with
|
|
|
|
% versions
|
|
|
|
|
2018-12-06 21:00:59 +00:00
|
|
|
-type value_fetcher() ::
|
|
|
|
{fun((pid(), leveled_codec:journal_key()) -> any()),
|
|
|
|
pid(), leveled_codec:journal_key()}.
|
|
|
|
% A 2-arity function, which when passed the other two elements of the tuple
|
|
|
|
% will return the value
|
|
|
|
-type proxy_object() ::
|
|
|
|
{proxy_object, leveled_head:head(), non_neg_integer(), value_fetcher()}.
|
|
|
|
% Returns the head, size and a tuple for accessing the value
|
|
|
|
-type proxy_objectbin() ::
|
|
|
|
binary().
|
|
|
|
% using term_to_binary(proxy_object())
|
2018-06-04 10:57:37 +01:00
|
|
|
|
2018-05-04 15:24:08 +01:00
|
|
|
|
|
|
|
-type segment_list()
|
|
|
|
:: list(integer())|false.
|
|
|
|
|
|
|
|
-export_type([tag/0,
|
2018-11-01 10:41:46 +00:00
|
|
|
key/0,
|
|
|
|
object_spec/0,
|
2018-05-04 15:24:08 +01:00
|
|
|
segment_hash/0,
|
|
|
|
ledger_status/0,
|
|
|
|
ledger_key/0,
|
|
|
|
ledger_value/0,
|
|
|
|
ledger_kv/0,
|
|
|
|
compaction_strategy/0,
|
2018-12-06 15:31:11 +00:00
|
|
|
compaction_method/0,
|
2018-05-04 15:24:08 +01:00
|
|
|
journal_key_tag/0,
|
|
|
|
journal_key/0,
|
2018-12-06 15:31:11 +00:00
|
|
|
journal_ref/0,
|
2018-05-04 15:24:08 +01:00
|
|
|
compression_method/0,
|
|
|
|
journal_keychanges/0,
|
|
|
|
index_specs/0,
|
2018-10-29 21:16:38 +00:00
|
|
|
segment_list/0,
|
2018-10-29 21:50:32 +00:00
|
|
|
maybe_lookup/0,
|
2018-10-31 00:09:24 +00:00
|
|
|
last_moddate/0,
|
2018-11-05 16:02:19 +00:00
|
|
|
lastmod_range/0,
|
2018-12-06 21:00:59 +00:00
|
|
|
regular_expression/0,
|
|
|
|
value_fetcher/0,
|
|
|
|
proxy_object/0]).
|
2018-05-04 15:24:08 +01:00
|
|
|
|
2017-10-20 23:04:29 +01:00
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
%%%============================================================================
|
|
|
|
%%% Ledger Key Manipulation
|
|
|
|
%%%============================================================================
|
|
|
|
|
|
|
|
-spec segment_hash(ledger_key()|binary()) -> {integer(), integer()}.
|
2017-10-20 23:04:29 +01:00
|
|
|
%% @doc
|
2017-10-24 15:16:25 +01:00
|
|
|
%% Return two 16 bit integers - the segment ID and a second integer for spare
|
2017-10-20 23:04:29 +01:00
|
|
|
%% entropy. The hashed should be used in blooms or indexes such that some
|
|
|
|
%% speed can be gained if just the segment ID is known - but more can be
|
|
|
|
%% gained should the extended hash (with the second element) is known
|
|
|
|
segment_hash(Key) when is_binary(Key) ->
|
2018-11-09 14:51:38 +00:00
|
|
|
{segment_hash, SegmentID, ExtraHash, _AltHash}
|
|
|
|
= leveled_tictac:keyto_segment48(Key),
|
2017-10-20 23:04:29 +01:00
|
|
|
{SegmentID, ExtraHash};
|
2018-12-06 15:31:11 +00:00
|
|
|
segment_hash(KeyTuple) when is_tuple(KeyTuple) ->
|
2018-12-06 22:45:05 +00:00
|
|
|
BinKey =
|
2018-12-06 22:55:00 +00:00
|
|
|
case element(1, KeyTuple) of
|
2018-12-06 22:45:05 +00:00
|
|
|
?HEAD_TAG ->
|
|
|
|
headkey_to_canonicalbinary(KeyTuple);
|
|
|
|
_ ->
|
|
|
|
leveled_head:key_to_canonicalbinary(KeyTuple)
|
|
|
|
end,
|
|
|
|
segment_hash(BinKey).
|
|
|
|
|
|
|
|
|
|
|
|
headkey_to_canonicalbinary({?HEAD_TAG, Bucket, Key, SubK})
|
|
|
|
when is_binary(Bucket), is_binary(Key), is_binary(SubK) ->
|
|
|
|
<<Bucket/binary, Key/binary, SubK/binary>>;
|
|
|
|
headkey_to_canonicalbinary({?HEAD_TAG, Bucket, Key, null})
|
|
|
|
when is_binary(Bucket), is_binary(Key) ->
|
|
|
|
<<Bucket/binary, Key/binary>>;
|
|
|
|
headkey_to_canonicalbinary({?HEAD_TAG, {BucketType, Bucket}, Key, SubKey})
|
|
|
|
when is_binary(BucketType), is_binary(Bucket) ->
|
|
|
|
headkey_to_canonicalbinary({?HEAD_TAG,
|
|
|
|
<<BucketType/binary, Bucket/binary>>,
|
|
|
|
Key,
|
|
|
|
SubKey});
|
|
|
|
headkey_to_canonicalbinary(Key) when element(1, Key) == ?HEAD_TAG ->
|
|
|
|
% In unit tests head specs can have non-binary keys, so handle
|
|
|
|
% this through hashing the whole key
|
|
|
|
term_to_binary(Key).
|
2017-10-20 23:04:29 +01:00
|
|
|
|
2017-10-31 23:28:35 +00:00
|
|
|
|
2018-10-29 21:16:38 +00:00
|
|
|
-spec to_lookup(ledger_key()) -> maybe_lookup().
|
2017-06-30 10:03:36 +01:00
|
|
|
%% @doc
|
|
|
|
%% Should it be possible to lookup a key in the merge tree. This is not true
|
|
|
|
%% For keys that should only be read through range queries. Direct lookup
|
|
|
|
%% keys will have presence in bloom filters and other lookup accelerators.
|
|
|
|
to_lookup(Key) ->
|
|
|
|
case element(1, Key) of
|
|
|
|
?IDX_TAG ->
|
|
|
|
no_lookup;
|
|
|
|
_ ->
|
|
|
|
lookup
|
|
|
|
end.
|
2016-10-14 18:43:16 +01:00
|
|
|
|
2016-10-18 01:59:03 +01:00
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
%% @doc
|
|
|
|
%% Some helper functions to get a sub_components of the key/value
|
2016-10-13 21:02:15 +01:00
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec strip_to_statusonly(ledger_kv()) -> ledger_status().
|
2018-10-29 20:24:54 +00:00
|
|
|
strip_to_statusonly({_, V}) -> element(2, V).
|
2016-10-13 21:02:15 +01:00
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec strip_to_seqonly(ledger_kv()) -> non_neg_integer().
|
2018-10-29 20:24:54 +00:00
|
|
|
strip_to_seqonly({_, V}) -> element(1, V).
|
2016-10-13 21:02:15 +01:00
|
|
|
|
2018-11-05 01:21:08 +00:00
|
|
|
-spec strip_to_segmentonly(ledger_kv()) -> segment_hash().
|
|
|
|
strip_to_segmentonly({_LK, LV}) -> element(3, LV).
|
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec strip_to_keyseqonly(ledger_kv()) -> {ledger_key(), integer()}.
|
2018-10-29 20:24:54 +00:00
|
|
|
strip_to_keyseqonly({LK, V}) -> {LK, element(1, V)}.
|
2016-10-13 21:02:15 +01:00
|
|
|
|
2018-10-29 21:50:32 +00:00
|
|
|
-spec strip_to_indexdetails(ledger_kv()) ->
|
|
|
|
{integer(), segment_hash(), last_moddate()}.
|
|
|
|
strip_to_indexdetails({_, V}) when tuple_size(V) == 4 ->
|
2018-10-31 11:44:46 +00:00
|
|
|
% A v1 value
|
|
|
|
{element(1, V), element(3, V), undefined};
|
|
|
|
strip_to_indexdetails({_, V}) when tuple_size(V) > 4 ->
|
|
|
|
% A v2 value should have a fith element - Last Modified Date
|
|
|
|
{element(1, V), element(3, V), element(5, V)}.
|
2016-12-11 01:02:56 +00:00
|
|
|
|
2018-10-29 20:24:54 +00:00
|
|
|
-spec striphead_to_v1details(ledger_value()) -> ledger_value().
|
|
|
|
striphead_to_v1details(V) ->
|
|
|
|
{element(1, V), element(2, V), element(3, V), element(4, V)}.
|
2016-10-31 17:26:28 +00:00
|
|
|
|
2020-03-15 22:14:42 +00:00
|
|
|
-spec get_metadata(ledger_value()) -> metadata().
|
|
|
|
get_metadata(LV) ->
|
|
|
|
element(4, LV).
|
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec key_dominates(ledger_kv(), ledger_kv()) ->
|
|
|
|
left_hand_first|right_hand_first|left_hand_dominant|right_hand_dominant.
|
|
|
|
%% @doc
|
|
|
|
%% When comparing two keys in the ledger need to find if one key comes before
|
|
|
|
%% the other, or if the match, which key is "better" and should be the winner
|
2018-10-29 20:24:54 +00:00
|
|
|
key_dominates({LK, _LVAL}, {RK, _RVAL}) when LK < RK ->
|
|
|
|
left_hand_first;
|
|
|
|
key_dominates({LK, _LVAL}, {RK, _RVAL}) when RK < LK ->
|
|
|
|
right_hand_first;
|
|
|
|
key_dominates(LObj, RObj) ->
|
|
|
|
case strip_to_seqonly(LObj) >= strip_to_seqonly(RObj) of
|
|
|
|
true ->
|
2016-10-13 21:02:15 +01:00
|
|
|
left_hand_dominant;
|
2018-10-29 20:24:54 +00:00
|
|
|
false ->
|
2016-10-13 21:02:15 +01:00
|
|
|
right_hand_dominant
|
|
|
|
end.
|
2016-10-16 15:41:09 +01:00
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec maybe_reap_expiredkey(ledger_kv(), {boolean(), integer()}) -> boolean().
|
|
|
|
%% @doc
|
|
|
|
%% Make a reap decision based on the level in the ledger (needs to be expired
|
|
|
|
%% and in the basement). the level is a tuple of the is_basement boolean, and
|
|
|
|
%% a timestamp passed into the calling function
|
2016-10-21 21:26:28 +01:00
|
|
|
maybe_reap_expiredkey(KV, LevelD) ->
|
2016-10-21 16:08:41 +01:00
|
|
|
Status = strip_to_statusonly(KV),
|
2016-10-21 21:26:28 +01:00
|
|
|
maybe_reap(Status, LevelD).
|
2016-10-21 16:08:41 +01:00
|
|
|
|
|
|
|
maybe_reap({_, infinity}, _) ->
|
|
|
|
false; % key is not set to expire
|
2016-10-21 21:26:28 +01:00
|
|
|
maybe_reap({_, TS}, {true, CurrTS}) when CurrTS > TS ->
|
2016-10-21 16:08:41 +01:00
|
|
|
true; % basement and ready to expire
|
2016-10-21 21:26:28 +01:00
|
|
|
maybe_reap(tomb, {true, _CurrTS}) ->
|
2016-10-21 16:08:41 +01:00
|
|
|
true; % always expire in basement
|
|
|
|
maybe_reap(_, _) ->
|
|
|
|
false.
|
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec is_active(ledger_key(), ledger_value(), non_neg_integer()) -> boolean().
|
|
|
|
%% @doc
|
|
|
|
%% Is this an active KV pair or has the timestamp expired
|
2016-10-31 12:12:06 +00:00
|
|
|
is_active(Key, Value, Now) ->
|
2016-10-16 15:41:09 +01:00
|
|
|
case strip_to_statusonly({Key, Value}) of
|
|
|
|
{active, infinity} ->
|
|
|
|
true;
|
|
|
|
tomb ->
|
2016-10-31 12:12:06 +00:00
|
|
|
false;
|
2016-10-31 16:02:32 +00:00
|
|
|
{active, TS} when TS >= Now ->
|
2016-10-31 12:12:06 +00:00
|
|
|
true;
|
|
|
|
{active, _TS} ->
|
2016-10-16 15:41:09 +01:00
|
|
|
false
|
|
|
|
end.
|
|
|
|
|
2017-11-17 14:54:53 +00:00
|
|
|
-spec from_ledgerkey(atom(), tuple()) -> false|tuple().
|
|
|
|
%% @doc
|
|
|
|
%% Return the "significant information" from the Ledger Key (normally the
|
|
|
|
%% {Bucket, Key} pair) if and only if the ExpectedTag matched the tag -
|
|
|
|
%% otherwise return false
|
|
|
|
from_ledgerkey(ExpectedTag, {ExpectedTag, Bucket, Key, SubKey}) ->
|
|
|
|
from_ledgerkey({ExpectedTag, Bucket, Key, SubKey});
|
|
|
|
from_ledgerkey(_ExpectedTag, _OtherKey) ->
|
|
|
|
false.
|
|
|
|
|
|
|
|
-spec from_ledgerkey(tuple()) -> tuple().
|
|
|
|
%% @doc
|
|
|
|
%% Return identifying information from the LedgerKey
|
2017-07-03 18:03:13 +01:00
|
|
|
from_ledgerkey({?IDX_TAG, Bucket, {_IdxFld, IdxVal}, Key}) ->
|
|
|
|
{Bucket, Key, IdxVal};
|
2018-08-31 15:29:38 +01:00
|
|
|
from_ledgerkey({?HEAD_TAG, Bucket, Key, SubKey}) ->
|
|
|
|
{Bucket, {Key, SubKey}};
|
2018-02-15 16:14:46 +00:00
|
|
|
from_ledgerkey({_Tag, Bucket, Key, _SubKey}) ->
|
2016-10-23 22:45:43 +01:00
|
|
|
{Bucket, Key}.
|
2016-10-18 01:59:03 +01:00
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec to_ledgerkey(any(), any(), tag(), any(), any()) -> ledger_key().
|
|
|
|
%% @doc
|
|
|
|
%% Convert something into a ledger key
|
2016-10-18 01:59:03 +01:00
|
|
|
to_ledgerkey(Bucket, Key, Tag, Field, Value) when Tag == ?IDX_TAG ->
|
|
|
|
{?IDX_TAG, Bucket, {Field, Value}, Key}.
|
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec to_ledgerkey(any(), any(), tag()) -> ledger_key().
|
|
|
|
%% @doc
|
|
|
|
%% Convert something into a ledger key
|
2018-02-16 14:16:28 +00:00
|
|
|
to_ledgerkey(Bucket, {Key, SubKey}, ?HEAD_TAG) ->
|
|
|
|
{?HEAD_TAG, Bucket, Key, SubKey};
|
2016-10-14 18:43:16 +01:00
|
|
|
to_ledgerkey(Bucket, Key, Tag) ->
|
|
|
|
{Tag, Bucket, Key, null}.
|
2016-10-13 21:02:15 +01:00
|
|
|
|
2018-12-07 09:07:22 +00:00
|
|
|
%% No spec - due to tests
|
|
|
|
%% @doc
|
|
|
|
%% Check that the ledgerkey is a valid format, to handle un-checksummed keys
|
|
|
|
%% that may be returned corrupted (such as from the Journal)
|
|
|
|
isvalid_ledgerkey({Tag, _B, _K, _SK}) ->
|
|
|
|
is_atom(Tag);
|
|
|
|
isvalid_ledgerkey(_LK) ->
|
|
|
|
false.
|
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
-spec endkey_passed(ledger_key(), ledger_key()) -> boolean().
|
|
|
|
%% @oc
|
|
|
|
%% Compare a key against a query key, only comparing elements that are non-null
|
|
|
|
%% in the Query key. This is used for comparing against end keys in queries.
|
|
|
|
endkey_passed(all, _) ->
|
|
|
|
false;
|
|
|
|
endkey_passed({EK1, null, null, null}, {CK1, _, _, _}) ->
|
|
|
|
EK1 < CK1;
|
|
|
|
endkey_passed({EK1, EK2, null, null}, {CK1, CK2, _, _}) ->
|
|
|
|
{EK1, EK2} < {CK1, CK2};
|
|
|
|
endkey_passed({EK1, EK2, EK3, null}, {CK1, CK2, CK3, _}) ->
|
|
|
|
{EK1, EK2, EK3} < {CK1, CK2, CK3};
|
|
|
|
endkey_passed(EndKey, CheckingKey) ->
|
|
|
|
EndKey < CheckingKey.
|
|
|
|
|
|
|
|
|
|
|
|
%%%============================================================================
|
|
|
|
%%% Journal Compaction functions
|
|
|
|
%%%============================================================================
|
|
|
|
|
|
|
|
-spec inker_reload_strategy(compaction_strategy()) -> compaction_strategy().
|
|
|
|
%% @doc
|
2020-03-15 22:14:42 +00:00
|
|
|
%% Take the default strategy for compaction, and override the approach for any
|
2018-05-03 17:18:13 +01:00
|
|
|
%% tags passed in
|
|
|
|
inker_reload_strategy(AltList) ->
|
2020-03-16 12:51:14 +00:00
|
|
|
DefaultList =
|
2018-12-06 15:31:11 +00:00
|
|
|
lists:map(fun leveled_head:default_reload_strategy/1,
|
|
|
|
leveled_head:defined_objecttags()),
|
2020-03-16 12:51:14 +00:00
|
|
|
lists:ukeymerge(1,
|
|
|
|
lists:ukeysort(1, AltList),
|
|
|
|
lists:ukeysort(1, DefaultList)).
|
2018-05-03 17:18:13 +01:00
|
|
|
|
|
|
|
|
2020-03-15 22:14:42 +00:00
|
|
|
-spec get_tagstrategy(ledger_key()|tag()|dummy, compaction_strategy())
|
2018-05-03 17:18:13 +01:00
|
|
|
-> skip|retain|recalc.
|
|
|
|
%% @doc
|
2018-12-06 21:00:59 +00:00
|
|
|
%% Work out the compaction strategy for the key
|
2018-05-03 20:14:36 +01:00
|
|
|
get_tagstrategy({Tag, _, _, _}, Strategy) ->
|
2020-03-15 22:14:42 +00:00
|
|
|
get_tagstrategy(Tag, Strategy);
|
|
|
|
get_tagstrategy(Tag, Strategy) ->
|
2018-05-03 20:14:36 +01:00
|
|
|
case lists:keyfind(Tag, 1, Strategy) of
|
|
|
|
{Tag, TagStrat} ->
|
|
|
|
TagStrat;
|
|
|
|
false ->
|
|
|
|
leveled_log:log("IC012", [Tag, Strategy]),
|
2018-12-07 09:07:22 +00:00
|
|
|
retain
|
2018-05-03 17:18:13 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
%%%============================================================================
|
|
|
|
%%% Manipulate Journal Key and Value
|
|
|
|
%%%============================================================================
|
|
|
|
|
|
|
|
-spec to_inkerkey(ledger_key(), non_neg_integer()) -> journal_key().
|
|
|
|
%% @doc
|
|
|
|
%% convertion from ledger_key to journal_key to allow for the key to be fetched
|
|
|
|
to_inkerkey(LedgerKey, SQN) ->
|
|
|
|
{SQN, ?INKT_STND, LedgerKey}.
|
2018-02-15 16:14:46 +00:00
|
|
|
|
2017-11-06 18:44:08 +00:00
|
|
|
|
2018-05-04 11:19:37 +01:00
|
|
|
-spec to_inkerkv(ledger_key(), non_neg_integer(), any(), journal_keychanges(),
|
2018-05-03 17:18:13 +01:00
|
|
|
compression_method(), boolean()) -> {journal_key(), any()}.
|
|
|
|
%% @doc
|
|
|
|
%% Convert to the correct format of a Journal key and value
|
2017-11-06 18:44:08 +00:00
|
|
|
to_inkerkv(LedgerKey, SQN, Object, KeyChanges, PressMethod, Compress) ->
|
2016-10-26 11:39:27 +01:00
|
|
|
InkerType = check_forinkertype(LedgerKey, Object),
|
2017-11-06 15:54:58 +00:00
|
|
|
Value =
|
2017-11-06 18:44:08 +00:00
|
|
|
create_value_for_journal({Object, KeyChanges}, Compress, PressMethod),
|
2016-10-26 11:39:27 +01:00
|
|
|
{{SQN, InkerType, LedgerKey}, Value}.
|
2016-10-25 23:13:14 +01:00
|
|
|
|
2018-12-06 22:45:05 +00:00
|
|
|
-spec revert_to_keydeltas(journal_key(), any()) -> {journal_key(), any()}.
|
|
|
|
%% @doc
|
|
|
|
%% If we wish to retain key deltas when an object in the Journal has been
|
|
|
|
%% replaced - then this converts a Journal Key and Value into one which has no
|
|
|
|
%% object body just the key deltas.
|
2020-03-09 15:12:48 +00:00
|
|
|
%% Only called if retain strategy and has passed
|
|
|
|
%% leveled_codec:is_full_journalentry/1 - so no need to consider other key
|
|
|
|
%% types
|
2018-12-06 22:45:05 +00:00
|
|
|
revert_to_keydeltas({SQN, ?INKT_STND, LedgerKey}, InkerV) ->
|
|
|
|
{_V, KeyDeltas} = revert_value_from_journal(InkerV),
|
2020-03-09 15:12:48 +00:00
|
|
|
{{SQN, ?INKT_KEYD, LedgerKey}, {null, KeyDeltas}}.
|
2018-12-06 22:45:05 +00:00
|
|
|
|
2016-10-25 23:13:14 +01:00
|
|
|
%% Used when fetching objects, so only handles standard, hashable entries
|
|
|
|
from_inkerkv(Object) ->
|
2017-03-29 15:37:04 +01:00
|
|
|
from_inkerkv(Object, false).
|
|
|
|
|
|
|
|
from_inkerkv(Object, ToIgnoreKeyChanges) ->
|
2016-10-25 23:13:14 +01:00
|
|
|
case Object of
|
|
|
|
{{SQN, ?INKT_STND, PK}, Bin} when is_binary(Bin) ->
|
2017-03-29 15:37:04 +01:00
|
|
|
{{SQN, PK}, revert_value_from_journal(Bin, ToIgnoreKeyChanges)};
|
2016-10-25 23:13:14 +01:00
|
|
|
_ ->
|
|
|
|
Object
|
|
|
|
end.
|
|
|
|
|
2018-05-04 11:19:37 +01:00
|
|
|
|
|
|
|
-spec create_value_for_journal({any(), journal_keychanges()|binary()},
|
|
|
|
boolean(), compression_method()) -> binary().
|
|
|
|
%% @doc
|
|
|
|
%% Serialise the value to be stored in the Journal
|
2017-11-06 15:54:58 +00:00
|
|
|
create_value_for_journal({Object, KeyChanges}, Compress, Method)
|
2017-03-29 15:37:04 +01:00
|
|
|
when not is_binary(KeyChanges) ->
|
2017-03-20 15:43:54 +00:00
|
|
|
KeyChangeBin = term_to_binary(KeyChanges, [compressed]),
|
2017-11-06 15:54:58 +00:00
|
|
|
create_value_for_journal({Object, KeyChangeBin}, Compress, Method);
|
|
|
|
create_value_for_journal({Object, KeyChangeBin}, Compress, Method) ->
|
2017-03-20 15:43:54 +00:00
|
|
|
KeyChangeBinLen = byte_size(KeyChangeBin),
|
2017-11-06 15:54:58 +00:00
|
|
|
ObjectBin = serialise_object(Object, Compress, Method),
|
|
|
|
TypeCode = encode_valuetype(is_binary(Object), Compress, Method),
|
2017-03-20 15:43:54 +00:00
|
|
|
<<ObjectBin/binary,
|
|
|
|
KeyChangeBin/binary,
|
|
|
|
KeyChangeBinLen:32/integer,
|
|
|
|
TypeCode:8/integer>>.
|
|
|
|
|
2017-11-06 21:16:46 +00:00
|
|
|
maybe_compress({null, KeyChanges}, _PressMethod) ->
|
2017-11-06 15:54:58 +00:00
|
|
|
create_value_for_journal({null, KeyChanges}, false, native);
|
2017-11-06 21:16:46 +00:00
|
|
|
maybe_compress(JournalBin, PressMethod) ->
|
2017-03-29 15:37:04 +01:00
|
|
|
Length0 = byte_size(JournalBin) - 5,
|
|
|
|
<<JBin0:Length0/binary,
|
|
|
|
KeyChangeLength:32/integer,
|
|
|
|
Type:8/integer>> = JournalBin,
|
2017-11-06 15:54:58 +00:00
|
|
|
{IsBinary, IsCompressed, IsLz4} = decode_valuetype(Type),
|
2017-03-20 15:43:54 +00:00
|
|
|
case IsCompressed of
|
|
|
|
true ->
|
|
|
|
JournalBin;
|
|
|
|
false ->
|
2017-03-29 15:37:04 +01:00
|
|
|
Length1 = Length0 - KeyChangeLength,
|
|
|
|
<<OBin2:Length1/binary, KCBin2:KeyChangeLength/binary>> = JBin0,
|
2017-11-06 15:54:58 +00:00
|
|
|
V0 = {deserialise_object(OBin2, IsBinary, IsCompressed, IsLz4),
|
2017-03-29 15:37:04 +01:00
|
|
|
binary_to_term(KCBin2)},
|
2017-11-06 15:54:58 +00:00
|
|
|
create_value_for_journal(V0, true, PressMethod)
|
2017-03-20 15:43:54 +00:00
|
|
|
end.
|
|
|
|
|
2017-11-06 15:54:58 +00:00
|
|
|
serialise_object(Object, false, _Method) when is_binary(Object) ->
|
2017-03-20 15:43:54 +00:00
|
|
|
Object;
|
2017-11-06 15:54:58 +00:00
|
|
|
serialise_object(Object, true, Method) when is_binary(Object) ->
|
|
|
|
case Method of
|
|
|
|
lz4 ->
|
|
|
|
{ok, Bin} = lz4:pack(Object),
|
|
|
|
Bin;
|
|
|
|
native ->
|
|
|
|
zlib:compress(Object)
|
|
|
|
end;
|
|
|
|
serialise_object(Object, false, _Method) ->
|
2017-03-20 15:43:54 +00:00
|
|
|
term_to_binary(Object);
|
2017-11-06 15:54:58 +00:00
|
|
|
serialise_object(Object, true, _Method) ->
|
2017-03-20 15:43:54 +00:00
|
|
|
term_to_binary(Object, [compressed]).
|
|
|
|
|
2018-05-04 11:19:37 +01:00
|
|
|
-spec revert_value_from_journal(binary()) -> {any(), journal_keychanges()}.
|
|
|
|
%% @doc
|
|
|
|
%% Revert the object back to its deserialised state, along with the list of
|
|
|
|
%% key changes associated with the change
|
2017-03-20 15:43:54 +00:00
|
|
|
revert_value_from_journal(JournalBin) ->
|
2017-03-29 15:37:04 +01:00
|
|
|
revert_value_from_journal(JournalBin, false).
|
|
|
|
|
|
|
|
revert_value_from_journal(JournalBin, ToIgnoreKeyChanges) ->
|
|
|
|
Length0 = byte_size(JournalBin) - 5,
|
|
|
|
<<JBin0:Length0/binary,
|
|
|
|
KeyChangeLength:32/integer,
|
|
|
|
Type:8/integer>> = JournalBin,
|
2017-11-06 15:54:58 +00:00
|
|
|
{IsBinary, IsCompressed, IsLz4} = decode_valuetype(Type),
|
2017-03-29 15:37:04 +01:00
|
|
|
Length1 = Length0 - KeyChangeLength,
|
|
|
|
case ToIgnoreKeyChanges of
|
|
|
|
true ->
|
|
|
|
<<OBin2:Length1/binary, _KCBin2:KeyChangeLength/binary>> = JBin0,
|
2018-05-04 11:19:37 +01:00
|
|
|
{deserialise_object(OBin2, IsBinary, IsCompressed, IsLz4),
|
|
|
|
{[], infinity}};
|
2017-03-29 15:37:04 +01:00
|
|
|
false ->
|
|
|
|
<<OBin2:Length1/binary, KCBin2:KeyChangeLength/binary>> = JBin0,
|
2017-11-06 15:54:58 +00:00
|
|
|
{deserialise_object(OBin2, IsBinary, IsCompressed, IsLz4),
|
2017-03-29 15:37:04 +01:00
|
|
|
binary_to_term(KCBin2)}
|
|
|
|
end.
|
2017-03-20 15:43:54 +00:00
|
|
|
|
2017-11-06 15:54:58 +00:00
|
|
|
deserialise_object(Binary, true, true, true) ->
|
2017-11-05 21:48:57 +00:00
|
|
|
{ok, Deflated} = lz4:unpack(Binary),
|
|
|
|
Deflated;
|
2017-11-06 15:54:58 +00:00
|
|
|
deserialise_object(Binary, true, true, false) ->
|
2017-03-20 15:43:54 +00:00
|
|
|
zlib:uncompress(Binary);
|
2017-11-06 15:54:58 +00:00
|
|
|
deserialise_object(Binary, true, false, _IsLz4) ->
|
2017-03-20 15:43:54 +00:00
|
|
|
Binary;
|
2017-11-06 15:54:58 +00:00
|
|
|
deserialise_object(Binary, false, _, _IsLz4) ->
|
2017-03-20 15:43:54 +00:00
|
|
|
binary_to_term(Binary).
|
|
|
|
|
2017-11-06 15:54:58 +00:00
|
|
|
encode_valuetype(IsBinary, IsCompressed, Method) ->
|
|
|
|
Bit3 =
|
|
|
|
case Method of
|
|
|
|
lz4 -> 4;
|
|
|
|
native -> 0
|
|
|
|
end,
|
2017-03-20 15:43:54 +00:00
|
|
|
Bit2 =
|
|
|
|
case IsBinary of
|
|
|
|
true -> 2;
|
|
|
|
false -> 0
|
|
|
|
end,
|
|
|
|
Bit1 =
|
|
|
|
case IsCompressed of
|
|
|
|
true -> 1;
|
|
|
|
false -> 0
|
|
|
|
end,
|
2017-11-06 15:54:58 +00:00
|
|
|
Bit1 + Bit2 + Bit3.
|
2017-03-20 15:43:54 +00:00
|
|
|
|
2018-05-04 11:19:37 +01:00
|
|
|
|
|
|
|
-spec decode_valuetype(integer()) -> {boolean(), boolean(), boolean()}.
|
|
|
|
%% @doc
|
|
|
|
%% Check bit flags to confirm how the object has been serialised
|
2017-03-20 15:43:54 +00:00
|
|
|
decode_valuetype(TypeInt) ->
|
|
|
|
IsCompressed = TypeInt band 1 == 1,
|
|
|
|
IsBinary = TypeInt band 2 == 2,
|
2017-11-06 21:16:46 +00:00
|
|
|
IsLz4 = TypeInt band 4 == 4,
|
2017-11-06 15:54:58 +00:00
|
|
|
{IsBinary, IsCompressed, IsLz4}.
|
2017-03-20 15:43:54 +00:00
|
|
|
|
2018-05-04 11:19:37 +01:00
|
|
|
-spec from_journalkey(journal_key()) -> {integer(), ledger_key()}.
|
|
|
|
%% @doc
|
|
|
|
%% Return just SQN and Ledger Key
|
2016-10-25 23:13:14 +01:00
|
|
|
from_journalkey({SQN, _Type, LedgerKey}) ->
|
|
|
|
{SQN, LedgerKey}.
|
|
|
|
|
2017-03-13 14:32:46 +00:00
|
|
|
|
2017-11-07 13:43:29 +00:00
|
|
|
split_inkvalue(VBin) when is_binary(VBin) ->
|
|
|
|
revert_value_from_journal(VBin).
|
2016-10-25 23:13:14 +01:00
|
|
|
|
|
|
|
check_forinkertype(_LedgerKey, delete) ->
|
2016-10-26 11:39:27 +01:00
|
|
|
?INKT_TOMB;
|
2018-02-15 16:14:46 +00:00
|
|
|
check_forinkertype(_LedgerKey, head_only) ->
|
|
|
|
?INKT_MPUT;
|
2016-10-25 23:13:14 +01:00
|
|
|
check_forinkertype(_LedgerKey, _Object) ->
|
2016-10-26 11:39:27 +01:00
|
|
|
?INKT_STND.
|
2016-10-25 23:13:14 +01:00
|
|
|
|
2020-03-09 15:12:48 +00:00
|
|
|
-spec is_full_journalentry(journal_key()) -> boolean().
|
2019-07-24 18:03:22 +01:00
|
|
|
%% @doc
|
|
|
|
%% Only journal keys with standard objects should be scored for compaction
|
2020-03-09 15:12:48 +00:00
|
|
|
is_full_journalentry({_SQN, ?INKT_STND, _LK}) ->
|
2019-07-24 18:03:22 +01:00
|
|
|
true;
|
2020-03-09 15:12:48 +00:00
|
|
|
is_full_journalentry(_OtherJKType) ->
|
2019-07-24 18:03:22 +01:00
|
|
|
false.
|
2016-10-13 21:02:15 +01:00
|
|
|
|
2018-05-03 17:18:13 +01:00
|
|
|
|
|
|
|
%%%============================================================================
|
2018-05-04 15:24:08 +01:00
|
|
|
%%% Other Ledger Functions
|
2018-05-03 17:18:13 +01:00
|
|
|
%%%============================================================================
|
2016-10-13 21:02:15 +01:00
|
|
|
|
2018-02-15 16:14:46 +00:00
|
|
|
|
2018-05-04 11:19:37 +01:00
|
|
|
-spec obj_objectspecs(list(tuple()), integer(), integer()|infinity)
|
|
|
|
-> list(ledger_kv()).
|
|
|
|
%% @doc
|
|
|
|
%% Convert object specs to KV entries ready for the ledger
|
2018-02-15 16:14:46 +00:00
|
|
|
obj_objectspecs(ObjectSpecs, SQN, TTL) ->
|
2018-11-01 10:41:46 +00:00
|
|
|
lists:map(fun(ObjectSpec) -> gen_headspec(ObjectSpec, SQN, TTL) end,
|
2018-02-15 16:14:46 +00:00
|
|
|
ObjectSpecs).
|
|
|
|
|
2018-05-04 15:24:08 +01:00
|
|
|
-spec idx_indexspecs(index_specs(),
|
2018-05-04 11:19:37 +01:00
|
|
|
any(), any(), integer(), integer()|infinity)
|
|
|
|
-> list(ledger_kv()).
|
|
|
|
%% @doc
|
|
|
|
%% Convert index specs to KV entries ready for the ledger
|
2017-06-30 10:03:36 +01:00
|
|
|
idx_indexspecs(IndexSpecs, Bucket, Key, SQN, TTL) ->
|
2017-06-30 16:31:22 +01:00
|
|
|
lists:map(
|
|
|
|
fun({IdxOp, IdxFld, IdxTrm}) ->
|
|
|
|
gen_indexspec(Bucket, Key, IdxOp, IdxFld, IdxTrm, SQN, TTL)
|
|
|
|
end,
|
|
|
|
IndexSpecs
|
|
|
|
).
|
|
|
|
|
|
|
|
gen_indexspec(Bucket, Key, IdxOp, IdxField, IdxTerm, SQN, TTL) ->
|
2018-02-16 20:56:12 +00:00
|
|
|
Status = set_status(IdxOp, TTL),
|
2018-10-29 16:56:58 +00:00
|
|
|
{to_ledgerkey(Bucket, Key, ?IDX_TAG, IdxField, IdxTerm),
|
|
|
|
{SQN, Status, no_lookup, null}}.
|
2016-10-14 18:43:16 +01:00
|
|
|
|
2018-11-01 10:41:46 +00:00
|
|
|
-spec gen_headspec(object_spec(), integer(), integer()|infinity) -> ledger_kv().
|
|
|
|
%% @doc
|
|
|
|
%% Take an object_spec as passed in a book_mput, and convert it into to a
|
|
|
|
%% valid ledger key and value. Supports different shaped tuples for different
|
|
|
|
%% versions of the object_spec
|
2018-11-01 12:40:24 +00:00
|
|
|
gen_headspec({IdxOp, v1, Bucket, Key, SubKey, LMD, Value}, SQN, TTL) ->
|
|
|
|
% v1 object spec
|
|
|
|
Status = set_status(IdxOp, TTL),
|
|
|
|
K = to_ledgerkey(Bucket, {Key, SubKey}, ?HEAD_TAG),
|
|
|
|
{K, {SQN, Status, segment_hash(K), Value, get_last_lastmodification(LMD)}};
|
2018-11-01 10:41:46 +00:00
|
|
|
gen_headspec({IdxOp, Bucket, Key, SubKey, Value}, SQN, TTL) ->
|
|
|
|
% v0 object spec
|
2018-02-16 20:56:12 +00:00
|
|
|
Status = set_status(IdxOp, TTL),
|
2018-02-16 14:16:28 +00:00
|
|
|
K = to_ledgerkey(Bucket, {Key, SubKey}, ?HEAD_TAG),
|
2018-11-01 12:40:24 +00:00
|
|
|
{K, {SQN, Status, segment_hash(K), Value, undefined}}.
|
|
|
|
|
2018-02-15 16:14:46 +00:00
|
|
|
|
2018-12-06 21:00:59 +00:00
|
|
|
-spec return_proxy(leveled_head:object_tag()|leveled_head:headonly_tag(),
|
|
|
|
leveled_head:object_metadata(),
|
|
|
|
pid(), journal_ref())
|
|
|
|
-> proxy_objectbin()|leveled_head:object_metadata().
|
|
|
|
%% @doc
|
|
|
|
%% If the object has a value, return the metadata and a proxy through which
|
|
|
|
%% the applictaion or runner can access the value. If it is a ?HEAD_TAG
|
|
|
|
%% then it has no value, so just return the metadata
|
|
|
|
return_proxy(?HEAD_TAG, ObjectMetadata, _InkerClone, _JR) ->
|
|
|
|
% Object has no value - so proxy object makese no sense, just return the
|
|
|
|
% metadata as is
|
|
|
|
ObjectMetadata;
|
|
|
|
return_proxy(Tag, ObjMetadata, InkerClone, JournalRef) ->
|
|
|
|
Size = leveled_head:get_size(Tag, ObjMetadata),
|
|
|
|
HeadBin = leveled_head:build_head(Tag, ObjMetadata),
|
|
|
|
term_to_binary({proxy_object,
|
|
|
|
HeadBin,
|
|
|
|
Size,
|
|
|
|
{fun leveled_bookie:fetch_value/2,
|
|
|
|
InkerClone,
|
|
|
|
JournalRef}}).
|
2018-02-15 16:14:46 +00:00
|
|
|
|
2018-02-16 20:56:12 +00:00
|
|
|
set_status(add, TTL) ->
|
|
|
|
{active, TTL};
|
|
|
|
set_status(remove, _TTL) ->
|
|
|
|
%% TODO: timestamps for delayed reaping
|
|
|
|
tomb.
|
|
|
|
|
2017-06-30 16:31:22 +01:00
|
|
|
-spec generate_ledgerkv(
|
|
|
|
tuple(), integer(), any(), integer(), tuple()|infinity) ->
|
2017-10-20 23:04:29 +01:00
|
|
|
{any(), any(), any(),
|
|
|
|
{{integer(), integer()}|no_lookup, integer()},
|
|
|
|
list()}.
|
2017-06-27 16:25:09 +01:00
|
|
|
%% @doc
|
|
|
|
%% Function to extract from an object the information necessary to populate
|
|
|
|
%% the Penciller's ledger.
|
|
|
|
%% Outputs -
|
|
|
|
%% Bucket - original Bucket extracted from the PrimaryKey
|
|
|
|
%% Key - original Key extracted from the PrimaryKey
|
|
|
|
%% Value - the value to be used in the Ledger (essentially the extracted
|
|
|
|
%% metadata)
|
2017-06-30 16:31:22 +01:00
|
|
|
%% {Hash, ObjHash} - A magic hash of the key to accelerate lookups, and a hash
|
|
|
|
%% of the value to be used for equality checking between objects
|
2017-06-27 16:25:09 +01:00
|
|
|
%% LastMods - the last modified dates for the object (may be multiple due to
|
|
|
|
%% siblings)
|
2016-10-14 18:43:16 +01:00
|
|
|
generate_ledgerkv(PrimaryKey, SQN, Obj, Size, TS) ->
|
|
|
|
{Tag, Bucket, Key, _} = PrimaryKey,
|
2016-10-16 15:41:09 +01:00
|
|
|
Status = case Obj of
|
|
|
|
delete ->
|
|
|
|
tomb;
|
|
|
|
_ ->
|
|
|
|
{active, TS}
|
|
|
|
end,
|
2017-10-20 23:04:29 +01:00
|
|
|
Hash = segment_hash(PrimaryKey),
|
2018-12-06 15:31:11 +00:00
|
|
|
{MD, LastMods} = leveled_head:extract_metadata(Tag, Size, Obj),
|
|
|
|
ObjHash = leveled_head:get_hash(Tag, MD),
|
2016-12-11 01:02:56 +00:00
|
|
|
Value = {SQN,
|
|
|
|
Status,
|
2017-01-05 21:58:33 +00:00
|
|
|
Hash,
|
2018-10-31 11:44:46 +00:00
|
|
|
MD,
|
|
|
|
get_last_lastmodification(LastMods)},
|
2017-06-30 16:31:22 +01:00
|
|
|
{Bucket, Key, Value, {Hash, ObjHash}, LastMods}.
|
2016-10-16 15:41:09 +01:00
|
|
|
|
2018-11-01 12:40:24 +00:00
|
|
|
-spec get_last_lastmodification(list(erlang:timestamp())|undefined)
|
|
|
|
-> pos_integer()|undefined.
|
2018-10-31 11:44:46 +00:00
|
|
|
%% @doc
|
|
|
|
%% Get the highest of the last modifications measured in seconds. This will be
|
|
|
|
%% stored as 4 bytes (unsigned) so will last for another 80 + years
|
2018-11-01 12:40:24 +00:00
|
|
|
get_last_lastmodification(undefined) ->
|
|
|
|
undefined;
|
2018-10-31 11:44:46 +00:00
|
|
|
get_last_lastmodification([]) ->
|
2018-11-01 12:40:24 +00:00
|
|
|
undefined;
|
2018-10-31 11:44:46 +00:00
|
|
|
get_last_lastmodification(LastMods) ->
|
|
|
|
{Mega, Sec, _Micro} = lists:max(LastMods),
|
|
|
|
Mega * 1000000 + Sec.
|
|
|
|
|
2016-10-14 18:43:16 +01:00
|
|
|
get_size(PK, Value) ->
|
|
|
|
{Tag, _Bucket, _Key, _} = PK,
|
2018-10-31 11:44:46 +00:00
|
|
|
MD = element(4, Value),
|
2018-12-06 15:31:11 +00:00
|
|
|
leveled_head:get_size(Tag, MD).
|
2017-06-30 16:31:22 +01:00
|
|
|
|
|
|
|
-spec get_keyandobjhash(tuple(), tuple()) -> tuple().
|
|
|
|
%% @doc
|
2017-07-03 18:03:13 +01:00
|
|
|
%% Return a tucple of {Bucket, Key, Hash} where hash is a hash of the object
|
2017-06-30 16:31:22 +01:00
|
|
|
%% not the key (for example with Riak tagged objects this will be a hash of
|
|
|
|
%% the sorted vclock)
|
|
|
|
get_keyandobjhash(LK, Value) ->
|
2016-10-31 16:02:32 +00:00
|
|
|
{Tag, Bucket, Key, _} = LK,
|
2018-10-31 11:44:46 +00:00
|
|
|
MD = element(4, Value),
|
2016-10-31 16:02:32 +00:00
|
|
|
case Tag of
|
2017-06-19 11:36:57 +01:00
|
|
|
?IDX_TAG ->
|
2017-06-30 16:31:22 +01:00
|
|
|
from_ledgerkey(LK); % returns {Bucket, Key, IdxValue}
|
|
|
|
_ ->
|
2018-12-06 15:31:11 +00:00
|
|
|
{Bucket, Key, leveled_head:get_hash(Tag, MD)}
|
2016-10-14 18:43:16 +01:00
|
|
|
end.
|
|
|
|
|
2018-11-05 16:02:19 +00:00
|
|
|
-spec next_key(key()) -> key().
|
2018-09-03 12:28:31 +01:00
|
|
|
%% @doc
|
|
|
|
%% Get the next key to iterate from a given point
|
2018-08-31 15:29:38 +01:00
|
|
|
next_key(Key) when is_binary(Key) ->
|
|
|
|
<<Key/binary, 0>>;
|
|
|
|
next_key(Key) when is_list(Key) ->
|
2018-09-27 09:34:40 +01:00
|
|
|
Key ++ [0];
|
|
|
|
next_key({Type, Bucket}) when is_binary(Type), is_binary(Bucket) ->
|
|
|
|
{Type, next_key(Bucket)}.
|
2018-08-31 15:29:38 +01:00
|
|
|
|
2016-10-13 21:02:15 +01:00
|
|
|
|
|
|
|
%%%============================================================================
|
|
|
|
%%% Test
|
|
|
|
%%%============================================================================
|
|
|
|
|
|
|
|
-ifdef(TEST).
|
|
|
|
|
2018-12-07 09:07:22 +00:00
|
|
|
valid_ledgerkey_test() ->
|
|
|
|
UserDefTag = {user_defined, <<"B">>, <<"K">>, null},
|
|
|
|
?assertMatch(true, isvalid_ledgerkey(UserDefTag)),
|
|
|
|
KeyNotTuple = [?STD_TAG, <<"B">>, <<"K">>, null],
|
|
|
|
?assertMatch(false, isvalid_ledgerkey(KeyNotTuple)),
|
|
|
|
TagNotAtom = {"tag", <<"B">>, <<"K">>, null},
|
|
|
|
?assertMatch(false, isvalid_ledgerkey(TagNotAtom)),
|
|
|
|
?assertMatch(retain, get_tagstrategy(UserDefTag, inker_reload_strategy([]))).
|
2016-10-13 21:02:15 +01:00
|
|
|
|
|
|
|
indexspecs_test() ->
|
|
|
|
IndexSpecs = [{add, "t1_int", 456},
|
|
|
|
{add, "t1_bin", "adbc123"},
|
|
|
|
{remove, "t1_bin", "abdc456"}],
|
2017-06-30 10:03:36 +01:00
|
|
|
Changes = idx_indexspecs(IndexSpecs, "Bucket", "Key2", 1, infinity),
|
2016-10-13 21:02:15 +01:00
|
|
|
?assertMatch({{i, "Bucket", {"t1_int", 456}, "Key2"},
|
2016-12-11 01:02:56 +00:00
|
|
|
{1, {active, infinity}, no_lookup, null}},
|
|
|
|
lists:nth(1, Changes)),
|
2016-10-13 21:02:15 +01:00
|
|
|
?assertMatch({{i, "Bucket", {"t1_bin", "adbc123"}, "Key2"},
|
2016-12-11 01:02:56 +00:00
|
|
|
{1, {active, infinity}, no_lookup, null}},
|
|
|
|
lists:nth(2, Changes)),
|
2016-10-13 21:02:15 +01:00
|
|
|
?assertMatch({{i, "Bucket", {"t1_bin", "abdc456"}, "Key2"},
|
2016-12-11 01:02:56 +00:00
|
|
|
{1, tomb, no_lookup, null}},
|
|
|
|
lists:nth(3, Changes)).
|
2016-10-14 22:58:01 +01:00
|
|
|
|
|
|
|
endkey_passed_test() ->
|
|
|
|
TestKey = {i, null, null, null},
|
|
|
|
K1 = {i, 123, {"a", "b"}, <<>>},
|
|
|
|
K2 = {o, 123, {"a", "b"}, <<>>},
|
|
|
|
?assertMatch(false, endkey_passed(TestKey, K1)),
|
|
|
|
?assertMatch(true, endkey_passed(TestKey, K2)).
|
|
|
|
|
|
|
|
|
2017-03-13 14:32:46 +00:00
|
|
|
|
2016-12-20 20:55:56 +00:00
|
|
|
%% Test below proved that the overhead of performing hashes was trivial
|
|
|
|
%% Maybe 5 microseconds per hash
|
|
|
|
|
2017-03-20 20:28:47 +00:00
|
|
|
hashperf_test() ->
|
2017-07-31 20:20:39 +02:00
|
|
|
OL = lists:map(fun(_X) -> leveled_rand:rand_bytes(8192) end, lists:seq(1, 1000)),
|
2017-03-20 20:28:47 +00:00
|
|
|
SW = os:timestamp(),
|
|
|
|
_HL = lists:map(fun(Obj) -> erlang:phash2(Obj) end, OL),
|
|
|
|
io:format(user, "1000 object hashes in ~w microseconds~n",
|
|
|
|
[timer:now_diff(os:timestamp(), SW)]).
|
|
|
|
|
2018-03-22 17:12:58 +00:00
|
|
|
head_segment_compare_test() ->
|
|
|
|
% Reminder to align native and parallel(leveled_ko) key stores for
|
|
|
|
% kv_index_tictactree
|
|
|
|
H1 = segment_hash({?HEAD_TAG, <<"B1">>, <<"K1">>, null}),
|
|
|
|
H2 = segment_hash({?RIAK_TAG, <<"B1">>, <<"K1">>, null}),
|
|
|
|
H3 = segment_hash({?HEAD_TAG, <<"B1">>, <<"K1">>, <<>>}),
|
|
|
|
?assertMatch(H1, H2),
|
|
|
|
?assertMatch(H1, H3).
|
2017-11-10 10:08:30 +00:00
|
|
|
|
2018-11-01 12:40:24 +00:00
|
|
|
headspec_v0v1_test() ->
|
|
|
|
% A v0 object spec generates the same outcome as a v1 object spec with the
|
|
|
|
% last modified date undefined
|
|
|
|
V1 = {add, v1, <<"B">>, <<"K">>, <<"SK">>, undefined, <<"V">>},
|
|
|
|
V0 = {add, <<"B">>, <<"K">>, <<"SK">>, <<"V">>},
|
|
|
|
TTL = infinity,
|
|
|
|
?assertMatch(true, gen_headspec(V0, 1, TTL) == gen_headspec(V1, 1, TTL)).
|
|
|
|
|
|
|
|
|
2017-07-31 20:20:39 +02:00
|
|
|
-endif.
|