Allow custom cache objects using callback modules
This patch allows custom objects derived from design documents to be
exposed in the ddoc_cache. A custom object is defined by a module
exporting the recover/1 function which accepts the name of a database
and returns {ok, term()}. The term will be cached as if it were a design
doc. For the sake of simplicity all custom cache objects associated with
a database are evicted when any ddoc in the database changes.
Once a custom callback module has been defined the object associated
with the module can be retrieved via ddoc_cache:open(DbName, Mod).
BugzID: 42707
diff --git a/src/ddoc_cache.erl b/src/ddoc_cache.erl
index 039a0a0..ed93309 100644
--- a/src/ddoc_cache.erl
+++ b/src/ddoc_cache.erl
@@ -75,12 +75,28 @@
ddoc_cache_opener:recover_validation_funs(DbName)
end.
+open_custom(DbName, Mod) ->
+ Key = {DbName, Mod},
+ case ddoc_cache_opener:lookup(Key) of
+ {ok, _} = Resp ->
+ couch_stats:increment_counter([ddoc_cache, hit]),
+ Resp;
+ missing ->
+ couch_stats:increment_counter([ddoc_cache, miss]),
+ ddoc_cache_opener:open_doc(DbName, Mod);
+ recover ->
+ couch_stats:increment_counter([ddoc_cache, recovery]),
+ Mod:recover(DbName)
+ end.
+
evict(ShardDbName, DDocIds) ->
DbName = mem3:dbname(ShardDbName),
ddoc_cache_opener:evict_docs(DbName, DDocIds).
open(DbName, validation_funs) ->
open_validation_funs(DbName);
+open(DbName, Module) when is_atom(Module) ->
+ open_custom(DbName, Module);
open(DbName, <<"_design/", _/binary>>=DDocId) when is_binary(DbName) ->
open_doc(DbName, DDocId);
open(DbName, DDocId) when is_binary(DDocId) ->
diff --git a/src/ddoc_cache_opener.erl b/src/ddoc_cache_opener.erl
index 1ef3ec8..0236921 100644
--- a/src/ddoc_cache_opener.erl
+++ b/src/ddoc_cache_opener.erl
@@ -187,7 +187,10 @@
handle_cast({do_evict, DbName, DDocIds}, St);
handle_cast({do_evict, DbName, DDocIds}, St) ->
- ets_lru:remove(?CACHE, {DbName, validation_funs}),
+ CustomKeys = lists:flatten(ets_lru:match(?CACHE, {DbName, '$1'}, '_')),
+ lists:foreach(fun(Mod) ->
+ ets_lru:remove(?CACHE, {DbName, Mod})
+ end, CustomKeys),
lists:foreach(fun(DDocId) ->
Revs = ets_lru:match(?CACHE, {DbName, DDocId, '$1'}, '_'),
lists:foreach(fun([Rev]) ->
@@ -230,12 +233,26 @@
{ok, State}.
-spec fetch_doc_data({dbname(), validation_funs}) -> no_return();
+ ({dbname(), atom()}) -> no_return();
({dbname(), docid()}) -> no_return();
({dbname(), docid(), revision()}) -> no_return().
fetch_doc_data({DbName, validation_funs}=OpenerKey) ->
{ok, Funs} = recover_validation_funs(DbName),
ok = ets_lru:insert(?CACHE, OpenerKey, Funs),
exit({open_ok, OpenerKey, {ok, Funs}});
+fetch_doc_data({DbName, Mod}=OpenerKey) when is_atom(Mod) ->
+ % This is not actually a docid but rather a custom cache key.
+ % Treat the argument as a code module and invoke its recover function.
+ try Mod:recover(DbName) of
+ {ok, Result} ->
+ ok = ets_lru:insert(?CACHE, OpenerKey, Result),
+ exit({open_ok, OpenerKey, {ok, Result}});
+ Else ->
+ exit({open_ok, OpenerKey, Else})
+ catch
+ Type:Reason ->
+ exit({open_error, OpenerKey, Type, Reason})
+ end;
fetch_doc_data({DbName, DocId}=OpenerKey) ->
try recover_doc(DbName, DocId) of
{ok, Doc} ->