Skip to content

Commit

Permalink
Added match API to lookup items using partial key (#22)
Browse files Browse the repository at this point in the history
* Added match API to lookup items using partial key

- minor readability improvement

* RC fixes
  • Loading branch information
matyasmarkovics authored Dec 19, 2022
1 parent 72a21e0 commit fde4578
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 36 deletions.
18 changes: 9 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
REBAR ?= rebar
ifneq ($(wildcard rebar),)
REBAR := ./rebar
REBAR ?= rebar3
ifneq ($(wildcard rebar3),)
REBAR := ./rebar3
endif

.PHONY: clean test docs benchmark docsclean go quick dialyzer
Expand All @@ -12,22 +12,22 @@ get-deps:

compile:
$(REBAR) compile
$(REBAR) skip_deps=true xref
$(REBAR) xref

quick:
$(REBAR) skip_deps=true compile
$(REBAR) skip_deps=true xref
$(REBAR) compile
$(REBAR) xref

clean:
$(REBAR) clean
rm -f erl_cache

test: compile
$(REBAR) skip_deps=true eunit
$(REBAR) eunit

docs: docsclean
ln -s . doc/doc
$(REBAR) skip_deps=true doc
$(REBAR) doc

docsclean:
rm -f doc/*.html doc/*.css doc/erlang.png doc/edoc-info doc/doc
Expand All @@ -36,7 +36,7 @@ go:
erl -name erl_cache -pa deps/*/ebin -pa ebin/ -s erl_cache start ${EXTRA_ARGS}

dialyzer:
dialyzer -c ebin/ -Wunmatched_returns -Werror_handling -Wrace_conditions
$(REBAR) dialyzer

erl_cache:
REBAR_BENCH=1 $(REBAR) get-deps compile
Expand Down
12 changes: 12 additions & 0 deletions src/erl_cache.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

-export([
get/2, get/3,
match/2, match/3,
get_stats/1,
list_cache_servers/0,
set/3, set/4,
Expand Down Expand Up @@ -206,6 +207,17 @@ get(Name, Key, Opts) ->
{error, _}=E -> E
end.

match(Name, Key) ->
match(Name, Key, []).

match(Name, Key, Opts) ->
case validate_opts(Opts, get_name_defaults(Name)) of
{ok, ValidatedOpts} ->
WaitForRefresh = proplists:get_value(wait_for_refresh, ValidatedOpts),
erl_cache_server:match(Name, Key, WaitForRefresh);
{error, _} = E -> E
end.

%% @doc Retrieves the stats associated with a cache instance
-spec get_stats(name()) -> cache_stats() | {error, invalid_opt_error()}.
%% @end
Expand Down
81 changes: 55 additions & 26 deletions src/erl_cache_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
-export([
start_link/1,
get/3,
match/3,
is_valid_name/1,
set/9,
evict/3,
Expand Down Expand Up @@ -69,24 +70,52 @@ start_link(Name) ->
get(Name, Key, WaitForRefresh) ->
Now = now_ms(),
case ets:lookup(get_table_name(Name), Key) of
[#cache_entry{validity=Validity, value=Value}] when Now < Validity ->
gen_server:cast(Name, {increase_stat, hit}),
{ok, Value};
[#cache_entry{evict=Evict, value=Value, refresh_callback=undefined}] when Now < Evict ->
gen_server:cast(Name, {increase_stat, overdue}),
{ok, Value};
[#cache_entry{evict=Evict, refresh_callback=Cb}=Entry] when Now < Evict, Cb /=undefined ->
?DEBUG("Refreshing overdue key ~p", [Key]),
gen_server:cast(Name, {increase_stat, overdue}),
{ok, NewVal} = refresh(Name, Entry, WaitForRefresh),
{ok, NewVal};
[#cache_entry{value=_ExpiredValue}] ->
{error, not_found};
[Entry] ->
send_stat(Name, Now, Entry),
case get_valid_value(Name, WaitForRefresh, Now, Entry) of
{true, Value} -> {ok, Value};
false -> {error, not_found}
end;
[] ->
gen_server:cast(Name, {increase_stat, miss}),
send_stat(Name, Now, #cache_entry{}),
{error, not_found}
end.

send_stat(Name, _Now, #cache_entry{key=undefined}) ->
gen_server:cast(Name, {increase_stat, miss});
send_stat(Name, Now, #cache_entry{validity=Validity}) when Now < Validity ->
gen_server:cast(Name, {increase_stat, hit});
send_stat(Name, Now, #cache_entry{evict=Evict}) when Now < Evict ->
gen_server:cast(Name, {increase_stat, overdue});
send_stat(_Name, _Now, _) ->
ok.

get_valid_value(_Name, _IsSync, Now, #cache_entry{validity=Validity} = E) when Now < Validity ->
{true, E#cache_entry.value};
get_valid_value(Name, IsSync, Now, #cache_entry{evict=Evict} = E) when Now < Evict ->
refresh(Name, E, IsSync);
get_valid_value(_Name, _IsSync, _Now, #cache_entry{value=_ExpiredValue}) ->
false.

-spec match(erl_cache:name(), erl_cache:key(), erl_cache:wait_for_refresh()) ->
{ok, erl_cache:value()} | {error, not_found}.
match(Name, Key, WaitForRefresh) ->
Now = now_ms(),
case ets:match_object(get_table_name(Name), #cache_entry{key = Key, _ = '_'}) of
[] ->
send_stat(Name, Now, #cache_entry{}),
{error, not_found};
Matches ->
IsValid = fun(E) ->
send_stat(Name, Now, E),
get_valid_value(Name, WaitForRefresh, Now, E)
end,
case lists:filtermap(IsValid, Matches) of
[] -> {error, not_found};
Values -> {ok, Values}
end
end.

-spec set(erl_cache:name(), erl_cache:key(), erl_cache:value(), pos_integer(), non_neg_integer(),
erl_cache:refresh_callback(), erl_cache:wait_until_done(), erl_cache:error_validity(),
erl_cache:is_error_callback()) -> ok.
Expand Down Expand Up @@ -167,7 +196,7 @@ handle_cast(_Msg, State) ->

%% @private
-spec handle_info(any(), #state{}) -> {noreply, #state{}}.
handle_info(purge_cache, #state{name=Name}=State) ->
handle_info(purge_cache, #state{name=Name} = State) ->
purge_cache(Name),
EvictInterval = erl_cache:get_cache_option(Name, evict_interval),
{ok, _} = timer:send_after(EvictInterval, Name, purge_cache),
Expand Down Expand Up @@ -241,21 +270,21 @@ purge_cache( Name, TableName, Now ) ->
%% @private
-spec refresh(erl_cache:name(), #cache_entry{}, erl_cache:wait_for_refresh()) ->
{ok, erl_cache:value()}.
refresh(Name, #cache_entry{refresh_callback=Callback}=Entry, true) when Callback/=undefined ->
NewVal = do_refresh(Name, Entry, true),
{ok, NewVal};
refresh(Name, #cache_entry{value=Value, refresh_callback=Callback}=Entry, false)
when Callback/=undefined ->
F = fun () -> do_refresh(Name, Entry, false) end,
_ = spawn(F),
{ok, Value}.
refresh(_Name, #cache_entry{refresh_callback=undefined} = Entry, _WaitForRefresh) ->
{true, Entry#cache_entry.value};
refresh(Name, #cache_entry{} = Entry, true) ->
{true, do_refresh(Name, Entry, true)};
refresh(Name, #cache_entry{} = Entry, false) ->
spawn(fun () -> do_refresh(Name, Entry, false) end),
{true, Entry#cache_entry.value}.

%% @private
-spec do_refresh(erl_cache:name(), #cache_entry{}, erl_cache:wait_for_refresh()) ->
erl_cache:value().
do_refresh(Name, #cache_entry{key=Key, validity_delta=ValidityDelta, evict_delta=EvictDelta,
refresh_callback=Callback, is_error_callback=IsErrorCb}=Entry,
WaitForRefresh) ->
do_refresh(Name, #cache_entry{} = Entry, WaitForRefresh) ->
#cache_entry{key=Key, validity_delta=ValidityDelta, evict_delta=EvictDelta,
refresh_callback=Callback, is_error_callback=IsErrorCb} = Entry,
?DEBUG("Refreshing overdue key ~p in cache: ~p", [Key, Name]),
NewVal = do_apply(Callback),
Now = now_ms(),
RefreshedEntry = case is_error_value(IsErrorCb, NewVal) of
Expand Down
21 changes: 20 additions & 1 deletion test/erl_cache_eunit.erl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ get_set_evict_test_() ->
{"Evict interval cache cleanup + correct stats", fun evict_interval/0},
{"Mem limit with tiny interval", fun mem_limit_forces_purge/0},
{"Parse transform basic usage", fun parse_transform/0},
{"Configurable callback for keys", fun key_generation/0}
{"Configurable callback for keys", fun key_generation/0},
{"Get value by mathcing partial key", fun partial_key_match/0}
]}.

%% Test Sets
Expand Down Expand Up @@ -241,6 +242,19 @@ key_generation() ->
generate_key(Cache, Module, _Function, Args) ->
{Cache, Module, Args}.

partial_key_match() ->
?assertEqual(ok, set_in_cache({test_a, key_1}, <<"test_value_1">>)),
?assertEqual(ok, set_in_cache({test_a, key_2}, <<"test_value_2">>)),
?assertEqual(ok, set_in_cache({test_b, key_1}, <<"test_value_3">>)),
timer:sleep(1),
?assertEqual({ok, [<<"test_value_1">>, <<"test_value_2">>]},
sorted(erl_cache:match(?TEST_CACHE, {test_a, '_'}, []))),
?assertEqual({ok, [<<"test_value_1">>, <<"test_value_3">>]},
sorted(erl_cache:match(?TEST_CACHE, {'_', key_1}, []))),
?assertEqual({error, not_found},
erl_cache:match(?TEST_CACHE, {'_', key_a}, [])).


%% Internal functions

set_in_cache(K, V) ->
Expand Down Expand Up @@ -289,3 +303,8 @@ sum(A, B) when A > 5 ->
{key_generation, ?MODULE}, {wait_until_done, true}]).
function_name(arg1, "arg2") ->
value.

sorted({OK, Results}) when is_list(Results) ->
{OK, lists:sort(Results)};
sorted(Any) ->
Any.

0 comments on commit fde4578

Please sign in to comment.