335 lines
11 KiB
Erlang
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.
|