mnesia_rocksdb/test/mnesia_rocksdb_xform.erl
2018-02-06 09:43:57 +01:00

335 lines
11 KiB
Erlang

%%----------------------------------------------------------------
%% Copyright (c) 2013-2016 Klarna AB
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%----------------------------------------------------------------
%% This module is used to test backend plugin extensions to the mnesia
%% backend. It also indirectly tests the mnesia backend plugin
%% extension machinery
%%
%% Usage: mnesia_ext_rocksdb_test:recompile(Extension).
%% Usage: mnesia_ext_rocksdb_test:recompile().
%% This command is executed in the release/tests/test_server directory
%% before running the normal tests. The command patches the test code,
%% via a parse_transform, to replace disc_only_copies with the Alias.
-module(mnesia_rocksdb_xform).
-author("roland.karlsson@erlang-solutions.com").
-author("ulf.wiger@klarna.com").
%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Exporting API
-export([recompile/0, recompile/1]).
%% Exporting parse_transform callback
-export([parse_transform/2]).
%% Exporting replacement for mnesia:create_table/2
-export([create_table/1, create_table/2, rpc/4]).
%% API %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Recompiling the test code, replacing disc_only_copies with
%% Extension.
recompile() ->
[{Module,Alias}|_] = extensions(),
recompile(Module, Alias).
recompile(MorA) ->
case { lists:keyfind(MorA, 1, extensions()),
lists:keyfind(MorA, 2, extensions())
} of
{{Module,Alias}, _} ->
recompile(Module, Alias);
{false, {Module,Alias}} ->
recompile(Module, Alias);
{false,false} ->
{error, cannot_find_module_or_alias}
end.
recompile(Module, Alias) ->
io:format("recompile(~p,~p)~n",[Module, Alias]),
put_ext(module, Module),
put_ext(alias, Alias),
Modules = [ begin {M,_} = lists:split(length(F)-4, F),
list_to_atom(M) end ||
F <- begin {ok,L} = file:list_dir("."), L end,
lists:suffix(".erl", F),
F=/= atom_to_list(?MODULE) ++ ".erl" ],
io:format("Modules = ~p~n",[Modules]),
lists:foreach(fun(M) ->
c:c(M, [{parse_transform, ?MODULE}])
end, Modules).
%% TEST REPLACEMENT CALLBACKS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% replacement for mnesia:create_table that ensures that
create_table(Name, Parameters) ->
create_table([{name,Name} | Parameters]).
create_table(Parameters) when is_list(Parameters) ->
case lists:keymember(rocksdb_copies, 1, Parameters) of
true ->
%% case lists:member({type, bag}, Parameters) of
%% true ->
%% ct:comment("ERROR: Contains rocksdb table with bag"),
%% {aborted, {rocksdb_does_not_support_bag, Parameters}};
%% false ->
ct:comment("INFO: Contains rocksdb table"),
io:format("INFO: create_table(~p)~n", [Parameters]),
mnesia:create_table(Parameters);
%% end;
false ->
mnesia:create_table(Parameters)
end;
create_table(Param) ->
%% Probably bad input, e.g. from mnesia_evil_coverage_SUITE.erl
mnesia:create_table(Param).
rpc(N, mnesia, start, [Opts]) ->
case lists:keymember(schema, 1, Opts) of
true -> rpc:call(N, mnesia, call, [Opts]);
false ->
Opts1 = [{schema, [{backend_types, backends()}]}|Opts],
rpc:call(N, mnesia, start, [Opts1])
end;
rpc(N, M, F, A) ->
rpc:call(N, M, F, A).
%% PARSE_TRANSFORM CALLBACK %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% The callback for c:c(Module, [{parse_transform,?MODULE}])
parse_transform(Forms, _Options) ->
plain_transform(fun do_transform/1, Forms).
do_transform({'attribute', _, module, Module}) ->
io:format("~n~nMODULE: ~p~n", [Module]),
continue;
do_transform({'atom', Line, disc_only_copies}) ->
io:format("replacing disc_only_copies with ~p~n", [get_ext(alias)]),
{'atom', Line, get_ext(alias)};
do_transform(Form = { call, L1,
{ remote, L2,
{atom, L3, mnesia},
{atom, L4, create_table}},
Arguments}) ->
NewForm = { call, L1,
{ remote, L2,
{atom, L3, ?MODULE},
{atom, L4, create_table}},
plain_transform(fun do_transform/1, Arguments)},
io:format("~nConvert Form:~n~s~n~s~n", [pp_form(Form), pp_form(NewForm)]),
NewForm;
do_transform(Form = { call, L1,
{ remote, L2,
{atom, L3, rpc},
{atom, L4, call}},
[{var, _, _} = N, {atom, _, mnesia} = Mnesia,
{atom, _, start} = Start, Args]}) ->
NewForm = { call, L1, { remote, L2,
{atom, L3, ?MODULE},
{atom, L4, rpc}},
[N, Mnesia, Start, Args]},
io:format("~nConvert Form:~n~s~n~s~n", [pp_form(Form), pp_form(NewForm)]),
NewForm;
do_transform(Form = { call, L1,
{ remote, L2,
{atom, L3, mnesia},
{atom, L4, create_schema}},
[Nodes]}) ->
P = element(2, Nodes),
NewForm = { call, L1,
{ remote, L2,
{atom, L3, mnesia},
{atom, L4, create_schema}},
[Nodes, erl_parse:abstract([{backend_types, backends()}], P)]},
io:format("~nConvert Form:~n~s~n~s~n", [pp_form(Form), pp_form(NewForm)]),
NewForm;
do_transform(Form = { call, L1,
{ remote, L2,
{atom, L3, mnesia},
{atom, L4, start}},
[]}) ->
NewForm = { call, L1,
{ remote, L2,
{atom, L3, mnesia},
{atom, L4, start}},
[erl_parse:abstract(
[{schema, [{backend_types, backends()}]}], L4)]},
io:format("~nConvert Form:~n~s~n~s~n", [pp_form(Form), pp_form(NewForm)]),
NewForm;
do_transform(Form = { call, L1,
{ remote, L2,
{atom, L3, mnesia},
{atom, L4, start}},
[Opts]}) ->
P = element(2, Opts),
NewForm = { call, L1,
{ remote, L2,
{atom, L3, mnesia},
{atom, L4, start}},
[{cons, P,
erl_parse:abstract(
{schema, [{backend_types, backends()}]}, L4), Opts}]},
io:format("~nConvert Form:~n~s~n~s~n", [pp_form(Form), pp_form(NewForm)]),
NewForm;
%% 1354:unsupp_user_props(doc) ->
%% 1355: ["Simple test of adding user props in a schema_transaction"];
%% 1356:unsupp_user_props(suite) -> [];
%% 1357:unsupp_user_props(Config) when is_list(Config) ->
do_transform(Form = { function, L1, F, 1, [C1, C2, C3] })
when F == unsupp_user_props ->
L3 = element(2, C3),
NewForm = { function, L1, F, 1,
[C1, C2, {clause, L3, [{var, L3, '_'}], [],
[{tuple, L3, [{atom, L3, skip},
erl_parse:abstract(
"Skipped for rocksdb test", L3)]}
]} ] },
io:format("~nConvert Form:"
"~n=============~n~s"
"==== To: ====~n~s"
"=============~n",
[cut(20, pp_form(Form)), cut(20, pp_form(NewForm))]),
NewForm;
do_transform(Form = { function, L1, F, 1, [C1, C2] })
when F == storage_options ->
L2 = element(2, C2),
NewForm = { function, L1, F, 1,
[C1, {clause, L2, [{var, L2, '_'}], [],
[{tuple, L2, [{atom, L2, skip},
erl_parse:abstract(
"Skipped for rocksdb test", L2)]}
]} ] },
io:format("~nConvert Form:"
"~n=============~n~s"
"==== To: ====~n~s"
"=============~n",
[cut(20, pp_form(Form)), cut(20, pp_form(NewForm))]),
NewForm;
do_transform(_Form) ->
continue.
pp_form(F) when element(1,F) == attribute; element(1,F) == function ->
erl_pp:form(F);
pp_form(F) ->
erl_pp:expr(F).
cut(Lines, S) ->
case re:split(S, "\\v", [{return,list}]) of
Lns when length(Lns) =< Lines ->
S;
Lns ->
lists:flatten(
add_lf(lists:sublist(Lns, 1, Lines) ++ ["...\n"]))
end.
add_lf([H|T]) ->
[H | ["\n" ++ L || L <- T]].
%% INTERNAL %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A trick for doing parse transforms easier
plain_transform(Fun, Forms) when is_function(Fun, 1), is_list(Forms) ->
plain_transform1(Fun, Forms).
plain_transform1(_, []) ->
[];
plain_transform1(Fun, [F|Fs]) when is_atom(element(1,F)) ->
case Fun(F) of
continue ->
[list_to_tuple(plain_transform1(Fun, tuple_to_list(F))) |
plain_transform1(Fun, Fs)];
{done, NewF} ->
[NewF | Fs];
{error, Reason} ->
io:format("Error: ~p (~p)~n", [F,Reason]);
NewF when is_tuple(NewF) ->
[NewF | plain_transform1(Fun, Fs)]
end;
plain_transform1(Fun, [L|Fs]) when is_list(L) ->
[plain_transform1(Fun, L) | plain_transform1(Fun, Fs)];
plain_transform1(Fun, [F|Fs]) ->
[F | plain_transform1(Fun, Fs)];
plain_transform1(_, F) ->
F.
%% Existing extensions.
%% NOTE: The first is default.
extensions() ->
[ {mnesia_rocksdb, rocksdb_copies}
].
%% {mnesia_ext_filesystem, fs_copies},
%% {mnesia_ext_filesystem, fstab_copies},
%% {mnesia_ext_filesystem, raw_fs_copies}
%% ].
backends() ->
[{T,M} || {M,T} <- extensions()].
%% Process global storage
put_ext(Key, Value) ->
ets:insert(global_storage(), {Key, Value}).
global_storage() ->
case whereis(?MODULE) of
undefined ->
Me = self(),
P = spawn(fun() ->
T = ets:new(?MODULE, [public,named_table]),
init_ext(T),
register(?MODULE, self()),
Me ! {self(), done},
wait()
end),
receive {P, done} ->
ok
end;
_ ->
ok
end,
?MODULE.
init_ext(T) ->
[{Mod,Alias}|_] = extensions(),
ets:insert(T, {alias, Alias}),
ets:insert(T, {module, Mod}).
wait() ->
receive stop ->
ok
end.
get_ext(Key) ->
case catch ets:lookup(global_storage(), Key) of
[] ->
io:format("Data for ~p not stored~n", [Key]),
undefined;
{'EXIT', Reason} ->
io:format("Get value for ~p failed (~p)~n", [Key, Reason]),
undefined;
[{Key,Value}] ->
Value
end.