Current section

Files

Jump to
erldns src erldns_storage_mnesia.erl
Raw

src/erldns_storage_mnesia.erl

%% Copyright (c) 2015, SiftLogic LLC
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(erldns_storage_mnesia).
%% The function takes advantage of mnesia record matches
-dialyzer({nowarn_function, list_table/1}).
-include("erldns.hrl").
%% API
-export([create/1,
insert/2,
delete_table/1,
delete/2,
backup_table/1,
backup_tables/0,
select/2,
select/3,
foldl/3,
empty_table/1,
list_table/1]).
-spec create(atom()) -> ok.
%% @doc Create the schema for mnesia, get the configuration from the config. Function sends 'ok'
%% if schema is created, or if it already exists.
%% @end
create(schema) ->
ok = ensure_mnesia_started(),
case erldns_config:storage_dir() of
undefined ->
lager:error("You need to add a directory for mnesia in erldns.config");
Dir ->
ok = filelib:ensure_dir(Dir),
ok = application:set_env(mnesia, dir, Dir)
end,
case application:stop(mnesia) of
ok ->
ok;
{error, Reason} ->
lager:warning("Could not stop mnesia (reason: ~p)", [Reason])
end,
case mnesia:create_schema([node()]) of
{error, {_, {already_exists, _}}} ->
lager:warning("The schema already exists (node: ~p)", [node()]),
ok;
ok ->
ok
end,
application:start(mnesia);
%% @doc Match the table names for every create. This enables different records to be used and
%% attributes to be sent to the tables.
%% @end
%% @doc Zones table has a discrepancy between record name, and table name. Be sure to use necesssary
%% functions for this difference.
%% @end
create(zones) ->
ok = ensure_mnesia_started(),
case mnesia:create_table(zones,
[{attributes, record_info(fields, zone)},
{record_name, zone},
{disc_copies, [node()]}]) of
{aborted, {already_exists, zones}} ->
lager:warning("The zone table already exists (node: ~p)", [node()]),
ok;
{atomic, ok} ->
ok;
Error ->
{error, Error}
end;
create(authorities) ->
ok = ensure_mnesia_started(),
case mnesia:create_table(authorities,
[{attributes, record_info(fields, authorities)},
{disc_copies, [node()]}]) of
{aborted, {already_exists, authorities}} ->
lager:warning("The authority table already exists (node: ~p)", [node()]),
ok;
{atomic, ok} ->
ok;
Error ->
{error, Error}
end.
%% @doc Insert into specified table. zone_cache calls this by {name, #zone{}}
-spec insert(atom(), any()) -> any().
insert(zones, #zone{} = Zone)->
Write = fun() -> mnesia:write(zones, Zone, write) end,
case mnesia:activity(transaction, Write) of
ok ->
ok;
Error ->
{error, Error}
end;
insert(zones, {_N, #zone{} = Zone})->
Write = fun() -> mnesia:write(zones, Zone, write) end,
case mnesia:activity(transaction, Write) of
ok ->
ok;
Error ->
{error, Error}
end;
insert(authorities, #authorities{} = Auth) ->
Write = fun() -> mnesia:write(authorities, Auth, write) end,
case mnesia:activity(transaction, Write) of
ok ->
ok;
Error ->
{error, Error}
end.
%% @doc delete the entire table.
-spec delete_table(atom()) -> ok | {aborted, any()}.
delete_table(Table) ->
case mnesia:delete_table(Table) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%% @doc Delete a mnesia record, have to do things different for zones since we specified {record_name, zone}
%% in the table creation.
-spec delete(Table :: atom(), Key :: term()) -> ok | any().
delete(zones, Key)->
mnesia:dirty_delete({zones, Key});
delete(Table, Key)->
case mnesia:is_transaction() of
true ->
Delete = fun() -> mnesia:delete({Table, Key}) end,
mnesia:activity(transaction, Delete);
false ->
mnesia:dirty_delete({Table, Key})
end.
%% @doc Should backup the tables in the schema.
%% @see https://github.com/SiftLogic/erl-dns/issues/3
-spec backup_table(atom()) -> ok | {error, Reason :: term()}.
backup_table(_Table)->
Backup = fun() -> mnesia:backup(mnesia:schema()) end,
mnesia:activity(transaction, Backup).
%% @see https://github.com/SiftLogic/erl-dns/issues/3
-spec backup_tables() -> ok | {error, Reason :: term()}.
backup_tables()->
{error, not_implemented}.
%% @doc Select based on key value.
-spec select(Table :: atom(), Key :: term()) -> [tuple()].
select(Table, Key)->
Select = fun () ->
case mnesia:read({Table, Key}) of
[Record] -> [{Key,Record}];
_ -> []
end
end,
mnesia:activity(transaction, Select).
%% @doc Select using a match spec.
-spec select(atom(), list(), infinite | integer()) -> [tuple()].
select(_Table, MatchSpec, _Limit) ->
MatchObject = fun() -> mnesia:match_object(MatchSpec) end,
mnesia:activity(transaction, MatchObject).
%% @doc Wrapper for foldl.
-spec foldl(fun(), list(), atom()) -> Acc :: term() | {error, Reason :: term()}.
foldl(Iterator, _Acc, Table) ->
Exec = fun() -> mnesia:foldl(Iterator, [], Table) end,
case mnesia:is_transaction() of
true ->
Exec();
false ->
mnesia:activity(transaction, Exec)
end.
%% @doc Clear all objects from given table in mnesia DB.
-spec empty_table(atom()) -> ok | {aborted, term()}.
empty_table(Table) ->
case mnesia:clear_table(Table) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%% @doc Lists the contents of the given table.
-spec list_table(atom()) ->
[] | [#zone{}] | [#authorities{}] | [tuple()] | {error, doesnt_exist}.
list_table(zones) ->
Pattern = #zone{name ='_', version = '_', authority = '_', record_count = '_',
records = '_', records_by_name = '_', records_by_type = '_'},
mnesia:dirty_match_object(zones, Pattern);
list_table(authorities) ->
Pattern = #authorities{owner_name = '_', ttl = '_', class = '_', name_server = '_', email_addr = '_',
serial_num = '_', refresh = '_', retry = '_', expiry = '_', nxdomain = '_'},
select(authorities, Pattern, 0);
list_table(_Name) ->
{error, doesnt_exist}.
%% Private
%% @doc Checks if mnesia is started, if not if starts mnesia.
-spec ensure_mnesia_started() -> ok | {error, any()}.
ensure_mnesia_started() ->
case application:start(mnesia) of
ok ->
ok;
{error,{already_started, mnesia}} ->
ok;
{error, Reason} ->
{error, Reason}
end.