Current section
Files
Jump to
Current section
Files
src/ets_cas.erl
-module(ets_cas).
-behaviour(gen_server).
%% API exports
-export([new/1,
close/1,
get_doc/2,
create_doc/4,
put_doc/2,
put_doc_cas/2,
delete_doc/2,
delete_doc_cas/2]).
-export([set_val/2, get_val/1,
set_map/2, get_map/1,
set_mapval/3, get_mapval/1]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2]).
-record(doc, {id, rev, map, val}).
-type doc() :: #doc{}.
-type tab() :: atom().
-export_types([doc/0]).
-include_lib("stdlib/include/ms_transform.hrl").
%%====================================================================
%% API functions
%%====================================================================
%% @doc create a new table.
-spec new(tab()) -> {ok, pid()}.
new(Name) when is_atom(Name) ->
gen_server:start_link({local, Name}, ?MODULE, [Name], []).
%% @doc close a table
-spec close(tab()) -> ok.
close(Name) ->
gen_server:call(Name, stop).
%% @doc get a document from the table by its ID.
-spec get_doc(tab(), term()) -> {ok, doc()} | error.
get_doc(Name, Id) when is_binary(Id) ->
case ets:lookup(Name, Id) of
[Doc] -> {ok, Doc};
[] -> error
end;
get_doc(_, _) ->
erlang:error(badarg).
%% @doc create a new document and store it
-spec create_doc(tab(), term(), map(), any()) -> {ok, doc()} | false.
create_doc(Name, Key, Map, Val) ->
Doc = #doc{ id=Key,rev=uniqid(), map=Map, val=Val },
case ets:insert_new(Name, Doc) of
true -> {ok, Doc};
false -> false
end.
%% @doc replace a new doc without checking its version
-spec put_doc(Tab :: tab(), Doc :: doc()) -> {ok, Doc1::doc()} | error.
put_doc(Name, Doc0) ->
Doc1 = Doc0#doc{ rev=uniqid() },
case ets:insert(Name, Doc1) of
true -> {ok, Doc1};
Error -> Error
end.
-ifdef(USE_SELECT_REPLACE).
%% @doc replace a new doc if we try to update the latest version
-spec put_doc_cas(Tab :: tab(), Doc :: doc()) -> {ok, Doc1::doc()} | error.
put_doc_cas(Name, Doc = #doc{ id=Id, rev=Rev, val=Val, map=Map } ) ->
NewRev = uniqid(),
MS = ets:fun2ms(
fun(#doc{ id=FoundId, rev=FoundRev}=FoundDoc) when FoundId =:= Id, FoundRev =:= Rev ->
FoundDoc#doc{ rev = NewRev, val=Val, map=Map }
end),
Replaced = (1 =:= ets:select_replace(Name, MS)),
case Replaced of
true -> {ok, Doc#doc{ rev=NewRev}};
false -> false
end.
-else.
%% @doc replace a new doc if we try to update the latest version
-spec put_doc_cas(Tab :: tab(), Doc :: doc()) -> {ok, Doc1::doc()} | error.
put_doc_cas(Name, Doc) ->
gen_server:call(Name, {put_cas, Doc}).
-endif.
%% @doc delete without checkiing the version
-spec delete_doc(tab(), doc()) -> ok.
delete_doc(Name, #doc{ id= Id }) ->
ets:delete(Name, Id);
delete_doc(Name, Id) when is_binary(Id) ->
ets:delete(Name, Id);
delete_doc(_, _) ->
erlang:error(badarg).
-spec delete_doc_cas(Tab :: tab(), Doc :: doc()) -> ok.
delete_doc_cas(Name, #doc{id=Id, rev=Rev}) ->
MS = ets:fun2ms(
fun(#doc{ id=FoundId, rev=FoundRev}) when FoundId =:= Id, FoundRev =:= Rev ->
true
end),
(1 =:= ets:select_delete(Name, MS)).
%% get the value from doc
-spec get_val(Doc :: doc()) -> Val :: term().
get_val(#doc{ val=Val } ) -> Val.
%% @doc set the value of the doc
-spec set_val(Val :: term(), Doc :: doc()) -> Doc1 :: doc().
set_val(Val, Doc) -> Doc#doc{ val = Val }.
%% @doc get the map of the doc
-spec get_map(Doc :: doc()) -> Map :: map().
get_map(#doc{ map=Map }) -> Map.
%% @doc set the map of the doc
-spec set_map(Map :: map(), Doc :: doc()) -> Doc1 :: map().
set_map(Map, Doc) when is_map(Map) -> Doc#doc{map=Map};
set_map(_, _) -> erlang:error(badarg).
%% @doc get the map an dvalue of a doc
-spec get_mapval(Doc :: doc()) -> { Map :: map(), Val :: term()}.
get_mapval(#doc{ map=Map, val=Val }) -> {Map, Val}.
%% @doc set the map and value of a doc
-spec set_mapval(Map :: map(), Val :: term(), Doc :: doc()) -> Doc1 :: doc().
set_mapval(Map, Val, Doc) when is_map(Val) ->
Doc#doc{ map=Map, val=Val };
set_mapval(_, _, _) ->
erlang:error(badarg).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Name]) ->
_ = ets:new(Name, [ordered_set, public, named_table,
{keypos, #doc.id},
{read_concurrency, true},
{write_concurrency, true}
]),
{ok, Name}.
handle_call({put_cas, Doc = #doc{id=Id, rev=Rev}}, _From, State) ->
Reply = case ets:lookup(State, Id) of
[#doc{ rev=Rev }] ->
NewDoc = Doc#doc{ rev = uniqid() },
case ets:insert(State, NewDoc) of
true ->
{ok, NewDoc};
_Error ->
false
end;
_ ->
false
end,
{reply, Reply, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call(_Msg, _From, State) ->
{reply, badcall, State}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Msg, State) -> {noreply, State}.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
terminate(_Reason, _State) -> ok.
%% ------------------
%% helpers
uniqid() -> <<Id:128>> = uuid:get_v4(), Id.