Packages
launchdarkly_server_sdk
2.1.2
3.11.0
3.10.1
3.9.0
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.0
3.5.0
3.4.0
3.3.1
3.3.0
3.2.0
3.1.0
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.1.2
2.1.1
2.1.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.6.0
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.3
1.1.2
1.1.1
retired
1.1.0
retired
1.0.1
retired
1.0.0
retired
1.0.0-beta4
retired
1.0.0-beta3
retired
1.0.0-beta2
retired
LaunchDarkly SDK for Erlang
Current section
Files
Jump to
Current section
Files
src/ldclient_storage_ets_server.erl
%%-------------------------------------------------------------------
%% @doc `ldclient_storage_ets_server' module
%% @private
%% This is a simple in-memory implementation of an storage server using ETS.
%% @end
%%-------------------------------------------------------------------
-module(ldclient_storage_ets_server).
-behaviour(gen_server).
%% Supervision
-export([start_link/2, init/1]).
%% Behavior callbacks
-export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
%% API
-export([create/2]).
-export([empty/2]).
-export([get/3]).
-export([all/2]).
-export([upsert/3]).
-export([upsert_clean/3]).
-export([delete/3]).
%% Types
-type state() :: #{
tids => map(),
tag => atom()
}.
%%===================================================================
%% Supervision
%%===================================================================
start_link(WorkerRegName, Tag) ->
error_logger:info_msg("Starting ets server with name ~p", [WorkerRegName]),
gen_server:start_link({local, WorkerRegName}, ?MODULE, [Tag], []).
init([Tag]) ->
true = ldclient_update_processor_state:set_storage_initialized_state(Tag, true),
{ok, #{tids => #{}, tag => Tag}}.
%%===================================================================
%% API
%%===================================================================
%% @doc Create a bucket
%%
%% @end
-spec create(ServerRef :: atom(), Bucket :: atom()) ->
ok |
{error, already_exists, string()}.
create(ServerRef, Bucket) when is_atom(Bucket) ->
gen_server:call(ServerRef, {create, Bucket}).
%% @doc Empty a bucket
%%
%% @end
-spec empty(ServerRef :: atom(), Bucket :: atom()) ->
ok |
{error, bucket_not_found, string()}.
empty(ServerRef, Bucket) when is_atom(Bucket) ->
gen_server:call(ServerRef, {empty, Bucket}).
%% @doc Get an item from the bucket by its key
%%
%% @end
-spec get(ServerRef :: atom(), Bucket :: atom(), Key :: binary()) ->
[{Key :: binary(), Value :: any()}] |
{error, bucket_not_found, string()}.
get(ServerRef, Bucket, Key) when is_atom(Bucket), is_binary(Key) ->
gen_server:call(ServerRef, {get, Bucket, Key}).
%% @doc List all items in a bucket
%%
%% @end
-spec all(ServerRef :: atom(), Bucket :: atom()) ->
[{Key :: binary(), Value :: any()}] |
{error, bucket_not_found, string()}.
all(ServerRef, Bucket) when is_atom(Bucket) ->
gen_server:call(ServerRef, {all, Bucket}).
%% @doc Upsert item key value pairs in an existing bucket
%%
%% @end
-spec upsert(ServerRef :: atom(), Bucket :: atom(), Items :: #{Key :: binary() => Value :: any()}) ->
ok |
{error, bucket_not_found, string()}.
upsert(ServerRef, Bucket, Items) when is_atom(Bucket), is_map(Items) ->
ok = gen_server:call(ServerRef, {upsert, Bucket, Items}).
%% @doc Perform an atomic empty and upsert
%%
%% @end
-spec upsert_clean(ServerRef :: atom(), Bucket :: atom(), Items :: #{Key :: binary() => Value :: any()}) ->
ok |
{error, bucket_not_found, string()}.
upsert_clean(ServerRef, Bucket, Items) when is_atom(Bucket), is_map(Items) ->
ok = gen_server:call(ServerRef, {upsert_clean, Bucket, Items}).
%% @doc Delete an item from the bucket by its key
%%
%% @end
-spec delete(ServerRef :: atom(), Bucket :: atom(), Key :: binary()) ->
ok |
{error, bucket_not_found, string()}.
delete(ServerRef, Bucket, Key) ->
ok = gen_server:call(ServerRef, {delete, Bucket, Key}).
%%===================================================================
%% Behavior callbacks
%%===================================================================
-type from() :: {pid(), term()}.
-spec handle_call(term(), from(), state()) -> {reply, term(), state()}.
handle_call({create, Bucket}, _From, #{tids := Tids} = State) ->
{Reply, NewTids} = create_bucket(bucket_exists(Bucket, Tids), Bucket, Tids),
NewState = State#{tids := NewTids},
{reply, Reply, NewState};
handle_call({empty, Bucket}, _From, #{tids := Tids} = State) ->
{reply, empty_bucket(bucket_exists(Bucket, Tids), Bucket, Tids), State};
handle_call({get, Bucket, Key}, _From, #{tids := Tids} = State) ->
{reply, lookup_key(bucket_exists(Bucket, Tids), Key, Bucket, Tids), State};
handle_call({all, Bucket}, _From, #{tids := Tids} = State) ->
{reply, all_items(bucket_exists(Bucket, Tids), Bucket, Tids), State};
handle_call({upsert, Bucket, Item}, _From, #{tids := Tids} = State) ->
{reply, upsert_items(bucket_exists(Bucket, Tids), Item, Bucket, Tids), State};
handle_call({upsert_clean, Bucket, Item}, _From, #{tids := Tids} = State) ->
{reply, upsert_clean_items(bucket_exists(Bucket, Tids), Item, Bucket, Tids), State};
handle_call({delete, Bucket, Key}, _From, #{tids := Tids} = State) ->
{reply, delete_key(bucket_exists(Bucket, Tids), Key, Bucket, Tids), State}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Msg, State) -> {noreply, State}.
terminate(_Reason, #{tag := Tag} = _State) ->
true = ldclient_update_processor_state:set_storage_initialized_state(Tag, reload),
ok.
code_change(_OldVersion, State, _Extra) -> {ok, State}.
%%===================================================================
%% Internal functions
%%===================================================================
%% @doc Check whether bucket already exists
%% @private
%%
%% @end
-spec bucket_exists(Bucket :: atom(), Tids :: map()) -> boolean().
bucket_exists(Bucket, Tids) when is_atom(Bucket) ->
maps:is_key(Bucket, Tids).
%% @doc Create a bucket
%% @private
%%
%% If bucket with the same name already exists an error will be returned.
%% @end
-spec create_bucket(BucketExists :: boolean(), Bucket :: atom(), Tids :: map()) ->
{ok|{error, already_exists, string()}, NewTids :: map()}.
create_bucket(true, Bucket, Tids) ->
{{error, already_exists, "ETS table " ++ atom_to_list(Bucket) ++ " already exists."}, Tids};
create_bucket(false, Bucket, Tids) ->
Tid = ets:new(Bucket, [set]),
{ok, Tids#{Bucket => Tid}}.
%% @doc Empty a bucket
%% @private
%%
%% If no such bucket exists, error will be returned.
%% @end
-spec empty_bucket(BucketExists :: boolean(), Bucket :: atom(), Tids :: map()) ->
ok |
{error, bucket_not_found, string()}.
empty_bucket(false, Bucket, _Tids) ->
{error, bucket_not_found, "ETS table " ++ atom_to_list(Bucket) ++ " does not exist."};
empty_bucket(true, Bucket, Tids) ->
Tid = maps:get(Bucket, Tids),
true = ets:delete_all_objects(Tid),
ok.
%% @doc List all items in a bucket
%% @private
%%
%% Returns all items stored in the bucket.
%% @end
-spec all_items(BucketExists :: boolean(), Bucket :: atom(), Tids :: map()) ->
[tuple()] |
{error, bucket_not_found, string()}.
all_items(false, Bucket, _Tids) ->
{error, bucket_not_found, "ETS table " ++ atom_to_list(Bucket) ++ " does not exist."};
all_items(true, Bucket, Tids) ->
Tid = maps:get(Bucket, Tids),
ets:tab2list(Tid).
%% @doc Look up an item by key
%% @private
%%
%% Search for an item by its key.
%% @end
-spec lookup_key(BucketExists :: boolean(), Key :: binary(), Bucket :: atom(), Tids :: map()) ->
[tuple()] |
{error, bucket_not_found, string()}.
lookup_key(false, _Key, Bucket, _Tids) ->
{error, bucket_not_found, "ETS table " ++ atom_to_list(Bucket) ++ " does not exist."};
lookup_key(true, Key, Bucket, Tids) ->
Tid = maps:get(Bucket, Tids),
ets:lookup(Tid, Key).
%% @doc Upsert key value pairs in bucket
%% @private
%%
%% @end
-spec upsert_items(BucketExists :: boolean(), Items :: #{Key :: binary() => Value :: any()}, Bucket :: atom(), Tids :: map()) ->
ok |
{error, bucket_not_found, string()}.
upsert_items(false, _Items, Bucket, _Tids) ->
{error, bucket_not_found, "ETS table " ++ atom_to_list(Bucket) ++ " does not exist."};
upsert_items(true, Items, Bucket, Tids) ->
Tid = maps:get(Bucket, Tids),
true = ets:insert(Tid, maps:to_list(Items)),
ok.
%% @doc Empty bucket and upsert key value pairs
%% @private
%%
%% @end
-spec upsert_clean_items(BucketExists :: boolean(), Items :: #{Key :: binary() => Value :: any()}, Bucket :: atom(), Tids :: map()) ->
ok |
{error, bucket_not_found, string()}.
upsert_clean_items(false, _Items, Bucket, _Tids) ->
{error, bucket_not_found, "ETS table " ++ atom_to_list(Bucket) ++ " does not exist."};
upsert_clean_items(true, Items, Bucket, Tids) ->
Tid = maps:get(Bucket, Tids),
true = ets:delete_all_objects(Tid),
true = ets:insert(Tid, maps:to_list(Items)),
ok.
%% @doc Delete an item by key
%% @private
%%
%% Delete an item by its key.
%% @end
-spec delete_key(BucketExists :: boolean(), Key :: binary(), Bucket :: atom(), Tids :: map()) ->
ok |
{error, bucket_not_found, string()}.
delete_key(false, _Key, Bucket, _Tids) ->
{error, bucket_not_found, "ETS table " ++ atom_to_list(Bucket) ++ " does not exist."};
delete_key(true, Key, Bucket, Tids) ->
Tid = maps:get(Bucket, Tids),
true = ets:delete(Tid, Key),
ok.