%%%------------------------------------------------------------------- %%% @doc %%% Common Shards Utilities. %%% @end %%%------------------------------------------------------------------- -module(shards_lib). %% API -export([ shard_name/2, list_shards/2, object_key/2, iterator/1, get_pid/1, pick/3, keyfind/2, keyfind/3, keyupdate/3, keyupdate/4, reduce_while/3, to_string/1, read_tabfile/1, write_tabfile/2 ]). -type kv_list() :: [{any(), any()}]. -export_type([kv_list/0]). %%%=================================================================== %%% API %%%=================================================================== %% @doc %% Builds a shard name `ShardName'. %% %% @end -spec shard_name(Tab :: atom(), Shard :: non_neg_integer()) -> ShardName :: atom(). shard_name(Tab, Shard) -> Bin = <<(atom_to_binary(Tab, utf8))/binary, ".", (integer_to_binary(Shard))/binary>>, binary_to_atom(Bin, utf8). %% @doc %% Returns the list of shard names associated to the given `TabName'. %% The shard names that were created in the `shards:new/2,3' fun. %% %% @end -spec list_shards(Tab :: atom(), NumShards :: pos_integer()) -> ShardNames :: [atom()]. list_shards(Tab, NumShards) -> [shard_name(Tab, Shard) || Shard <- iterator(NumShards)]. %% @doc %% Returns the key for the given object or list of objects. %% %% @end -spec object_key( ObjOrObjs :: tuple() | [tuple()], State :: shards_state:state() | pos_integer() ) -> Key :: any(). object_key(ObjOrObjs, State) when is_tuple(ObjOrObjs) -> element(shards_state:keypos(State), ObjOrObjs); object_key(ObjOrObjs, State) when is_list(ObjOrObjs) -> element(shards_state:keypos(State), hd(ObjOrObjs)). %% @doc %% Returns a sequence of integers that starts with `0' and contains the %% successive results of adding `+1' to the previous element, until %% `NumShards' is reached or passed (in the latter case, `NumShards' %% is not an element of the sequence). %% %% @end -spec iterator(StateOrNumShards :: shards_state:state() | pos_integer()) -> Iterator :: [integer()] . iterator(StateOrNumShards) -> N = case shards_state:is_state(StateOrNumShards) of true -> shards_state:n_shards(StateOrNumShards); false when is_integer(StateOrNumShards) -> StateOrNumShards end, lists:seq(0, N - 1). %% @doc %% Returns the PID associated to the `Name'. %% %% @end -spec get_pid(Name :: atom()) -> pid() | no_return(). get_pid(Name) -> case whereis(Name) of undefined -> error(badarg); Pid -> Pid end. %% @doc %% Pick/computes the shard where the `Key' will be handled. %% %% @end -spec pick( Key :: shards_state:key(), Range :: shards_state:range(), Op :: atom() ) -> Result :: non_neg_integer(). pick(Key, NumShards, _) -> erlang:phash2(Key, NumShards). %% @equiv keyfind(Key, KVList, undefined) keyfind(Key, KVList) -> keyfind(Key, KVList, undefined). %% @doc %% Returns the value to the given `Key' or `Default' if it doesn't exist. %% @end -spec keyfind(term(), kv_list(), term()) -> term(). keyfind(Key, KVList, Default) -> case lists:keyfind(Key, 1, KVList) of {Key, Value} -> Value; _ -> Default end. %% @equiv keyupdate(Fun, Keys, undefined, TupleList) keyupdate(Fun, Keys, TupleList) -> keyupdate(Fun, Keys, undefined, TupleList). %% @doc %% Updates the given `Keys' by the result of calling `Fun(OldValue)'. %% If `Key' doesn't exist, then `Init' is set. %% @end -spec keyupdate( Fun :: fun((Key :: any(), Value :: any()) -> any()), Keys :: [any()], Init :: any(), KVList1 :: kv_list() ) -> KVList2 :: kv_list(). keyupdate(Fun, Keys, Init, KVList1) when is_function(Fun, 2) -> lists:foldl(fun(Key, Acc) -> NewKV = case lists:keyfind(Key, 1, Acc) of {Key, Value} -> {Key, Fun(Key, Value)}; false -> {Key, Init} end, lists:keystore(Key, 1, Acc, NewKV) end, KVList1, Keys). %% @doc %% Reduces the `List' until fun returns `{halt, any()}''. %% %% @end -spec reduce_while( Fun :: fun((Elem :: any(), Acc :: any()) -> FunRes), AccIn :: any(), ListOrMap :: [any()] | map() ) -> Result :: any() when FunRes :: {cont | halt, AccOut :: any()}. reduce_while(Fun, AccIn, List) when is_function(Fun, 2) -> try do_fold(Fun, AccIn, List) catch throw:{halt, AccOut} -> AccOut end. %% @private do_fold(Fun, AccIn, List) when is_list(List) -> lists:foldl(fun(Elem, Acc) -> case Fun(Elem, Acc) of {cont, AccOut} -> AccOut; {halt, AccOut} -> throw({halt, AccOut}) end end, AccIn, List); do_fold(Fun, AccIn, Map) when is_map(Map) -> maps:fold(fun(Key, Value, Acc) -> case Fun({Key, Value}, Acc) of {cont, AccOut} -> AccOut; {halt, AccOut} -> throw({halt, AccOut}) end end, AccIn, Map). %% @doc %% Converts the input data to a string. %% @end -spec to_string(Data :: any()) -> string() | no_return(). to_string(Data) when is_binary(Data) -> binary_to_list(Data); to_string(Data) when is_integer(Data) -> integer_to_list(Data); to_string(Data) when is_float(Data) -> lists:flatten(io_lib:format("~.1f", [Data])); to_string(Data) when is_atom(Data) -> atom_to_list(Data); to_string(Data) when is_list(Data) -> case io_lib:printable_list(Data) of true -> Data; false -> error({badarg, Data}) end; to_string(Data) -> error({badarg, Data}). %% @doc %% Reads the file info related to a tabfile saved previously. %% @end -spec read_tabfile(shards_local:filename()) -> term() | no_return(). read_tabfile(Filename) -> case file:read_file(Filename) of {ok, Binary} -> binary_to_term(Binary); Error -> throw(Error) end. %% @doc %% Writes to a file a content related to a table. %% @end -spec write_tabfile(shards_local:filename(), term()) -> ok | {error, term()}. write_tabfile(Filename, Content) -> file:write_file(Filename, term_to_binary(Content)).