Refactor to support column families, direct rocksdb access
Expose low-level helpers, fix dialyzer warnings WIP column families and mrdb API Basic functionality in place started adding documentation remove doc/ from .gitignore add doc/* files recognize pre-existing tabs at startup wip: most of the functionality in place (not yet merge ops) wip: adding transaction support wip: add transaction test case (currently dumps core) First draft, mnesia plugin user guide Fix note formatting WIP working on indexing Index iterators, dialyzer, xref fixes open db with optimistic transactions Use rocksdb-1.7.0 Use seanhinde rocksdb patch, enable rollback Call the right transaction_get() function WIP add 'snap_tx' activity type tx restart using mrdb_mutex Fix test suite sync bugs WIP instrumented for debugging WIP working on migration test case Add migration test suite Migration works, subscribe to schema changes WIP fix batch handling Manage separate batches per db_ref Add mrdb:fold/3 Add some docs, erlang_ls config Use seanhinde's rocksdb vsn
This commit is contained in:
@@ -12,10 +12,23 @@
|
||||
, end_per_testcase/2
|
||||
]).
|
||||
|
||||
-export([error_handling/1]).
|
||||
-export([ encoding_sext_attrs/1
|
||||
, encoding_binary_binary/1
|
||||
, encoding_defaults/1
|
||||
]).
|
||||
-export([ mrdb_transactions/1
|
||||
, mrdb_repeated_transactions/1
|
||||
, mrdb_abort/1
|
||||
, mrdb_two_procs/1
|
||||
, mrdb_two_procs_tx_restart/1
|
||||
, mrdb_two_procs_snap/1
|
||||
, mrdb_three_procs/1
|
||||
]).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-define(TABS_CREATED, tables_created).
|
||||
|
||||
suite() ->
|
||||
[].
|
||||
|
||||
@@ -23,18 +36,38 @@ all() ->
|
||||
[{group, all_tests}].
|
||||
|
||||
groups() ->
|
||||
[{all_tests, [sequence], [error_handling]}].
|
||||
[
|
||||
{all_tests, [sequence], [ {group, checks}
|
||||
, {group, mrdb} ]}
|
||||
%% , error_handling ]}
|
||||
, {checks, [sequence], [ encoding_sext_attrs
|
||||
, encoding_binary_binary
|
||||
, encoding_defaults ]}
|
||||
, {mrdb, [sequence], [ mrdb_transactions
|
||||
, mrdb_repeated_transactions
|
||||
, mrdb_abort
|
||||
, mrdb_two_procs
|
||||
, mrdb_two_procs_tx_restart
|
||||
, mrdb_two_procs_snap
|
||||
, mrdb_three_procs ]}
|
||||
].
|
||||
|
||||
|
||||
error_handling(_Config) ->
|
||||
mnesia_rocksdb_error_handling:run().
|
||||
%% error_handling(Config) ->
|
||||
%% mnesia_rocksdb_error_handling:run(Config).
|
||||
|
||||
init_per_suite(Config) ->
|
||||
Config.
|
||||
tr_ct:set_activation_checkpoint(?TABS_CREATED, Config).
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
ok.
|
||||
|
||||
init_per_group(G, Config) when G==mrdb
|
||||
; G==checks ->
|
||||
mnesia:stop(),
|
||||
ok = mnesia_rocksdb_tlib:start_mnesia(reset),
|
||||
Config;
|
||||
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
||||
@@ -46,3 +79,453 @@ init_per_testcase(_, Config) ->
|
||||
|
||||
end_per_testcase(_, _Config) ->
|
||||
ok.
|
||||
|
||||
encoding_sext_attrs(Config) ->
|
||||
tr_ct:with_trace(fun encoding_sext_attrs_/1, Config,
|
||||
tr_patterns(mnesia_rocksdb,
|
||||
[{mnesia_rocksdb,'_',x}], tr_opts())).
|
||||
|
||||
encoding_sext_attrs_(Config) ->
|
||||
Created = create_tabs([{t, [{attributes, [k, v]}]}], Config),
|
||||
ok = mrdb:insert(t, {t, 1, a}),
|
||||
ok = mnesia:dirty_write({t, 2, b}),
|
||||
expect_error(fun() -> mrdb:insert(t, {t, a}) end, ?LINE,
|
||||
error, {mrdb_abort, badarg}),
|
||||
expect_error(fun() -> mnesia:dirty_write({t, a}) end, ?LINE,
|
||||
exit, '_'),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
encoding_defaults(Config) ->
|
||||
UP = fun(T) -> mnesia:table_info(T, user_properties) end,
|
||||
Created = create_tabs([ {a, [ {attributes, [k, v]}
|
||||
, {type, set}]}
|
||||
, {b, [ {attributes, [k, v, w]}
|
||||
, {type, ordered_set}]}
|
||||
, {c, [ {attributes, [k, v]}
|
||||
, {type, bag} ]}], Config),
|
||||
[{mrdb_encoding,{term,{value,term}}}] = UP(a),
|
||||
[{mrdb_encoding,{sext,{object,term}}}] = UP(b),
|
||||
[{mrdb_encoding,{sext,{value,term}}}] = UP(c),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
encoding_binary_binary(Config) ->
|
||||
Created = create_tabs([ {a, [ {attributes, [k,v]}
|
||||
, {user_properties,
|
||||
[{mrdb_encoding, {raw, raw}}]}]}
|
||||
, {b, [ {attributes, [k, v, w]}
|
||||
, {user_properties,
|
||||
[{mrdb_encoding, {raw, {object, term}}}]}]}
|
||||
], Config),
|
||||
expect_error(fun() ->
|
||||
create_tab(
|
||||
c, [ {attributes, [k, v, w]}
|
||||
, {user_properties,
|
||||
[{mrdb_encoding, {raw, {value, raw}}}]}])
|
||||
end, ?LINE, error, '_'),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
expect_error(F, Line, Type, Expected) ->
|
||||
try F() of
|
||||
Unexpected -> error({unexpected, Line, Unexpected})
|
||||
catch
|
||||
Type:Expected ->
|
||||
ct:log("Caught expected ~p:~p (Line: ~p)", [Type, Expected, Line]),
|
||||
ok;
|
||||
Type:Error when Expected == '_' ->
|
||||
ct:log("Caught expected ~p:_ (Line:~p): ~p", [Type, Line, Error]),
|
||||
ok
|
||||
end.
|
||||
|
||||
mrdb_transactions(Config) ->
|
||||
tr_ct:with_trace(fun mrdb_transactions_/1, Config,
|
||||
tr_patterns(
|
||||
mnesia_rocksdb_admin,
|
||||
[{mnesia_rocksdb_admin,'_',x}], tr_opts())).
|
||||
|
||||
mrdb_transactions_(Config) ->
|
||||
Created = create_tabs([{tx, []}], Config),
|
||||
mrdb:insert(tx, {tx, a, 1}),
|
||||
[_] = mrdb:read(tx, a),
|
||||
mrdb:activity(
|
||||
tx, rdb,
|
||||
fun() ->
|
||||
[{tx,a,N}] = mrdb:read(tx, a),
|
||||
N1 = N+1,
|
||||
ok = mrdb:insert(tx, {tx,a,N1})
|
||||
end),
|
||||
[{tx,a,2}] = mrdb:read(tx,a),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
mrdb_repeated_transactions(Config) ->
|
||||
Created = create_tabs([{rtx, []}], Config),
|
||||
mrdb:insert(rtx, {rtx, a, 0}),
|
||||
[_] = mrdb:read(rtx, a),
|
||||
Fun = fun() ->
|
||||
[{rtx, a, N}] = mrdb:read(rtx, a),
|
||||
N1 = N+1,
|
||||
ok = mrdb:insert(rtx, {rtx, a, N1})
|
||||
end,
|
||||
[ok = mrdb:activity(tx, rdb, Fun) || _ <- lists:seq(1,100)],
|
||||
[{rtx,a,100}] = mrdb:read(rtx, a),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
mrdb_abort(Config) ->
|
||||
Created = create_tabs([{tx_abort, []}], Config),
|
||||
mrdb:insert(tx_abort, {tx_abort, a, 1}),
|
||||
Pre = mrdb:read(tx_abort, a),
|
||||
TRes = try mrdb:activity(
|
||||
tx, rdb,
|
||||
fun() ->
|
||||
[{tx_abort, a, N}] = mrdb:read(tx_abort, a),
|
||||
error(abort_here),
|
||||
ok = mrdb:insert(tx_abort, [{tx_abort, a, N+1}]),
|
||||
noooo
|
||||
end)
|
||||
catch
|
||||
error:abort_here ->
|
||||
ok
|
||||
end,
|
||||
ok = TRes,
|
||||
Pre = mrdb:read(tx_abort, a),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
mrdb_two_procs(Config) ->
|
||||
tr_ct:with_trace(fun mrdb_two_procs_/1, Config,
|
||||
tr_flags(
|
||||
{self(), [call, sos, p]},
|
||||
tr_patterns(
|
||||
mrdb, [ {mrdb, insert, 2, x}
|
||||
, {mrdb, read, 2, x}
|
||||
, {mrdb, activity, x} ], tr_opts()))).
|
||||
|
||||
mrdb_two_procs_(Config) ->
|
||||
R = ?FUNCTION_NAME,
|
||||
Parent = self(),
|
||||
Created = create_tabs([{R, []}], Config),
|
||||
mrdb:insert(R, {R, a, 1}),
|
||||
Pre = mrdb:read(R, a),
|
||||
F0 = fun() ->
|
||||
wait_for_other(Parent, ?LINE),
|
||||
ok = mrdb:insert(R, {R, a, 17}),
|
||||
wait_for_other(Parent, ?LINE)
|
||||
end,
|
||||
{POther, MRef} = spawn_opt(
|
||||
fun() ->
|
||||
ok = mrdb:activity(tx, rdb, F0)
|
||||
end, [monitor]),
|
||||
F1 = fun() ->
|
||||
Pre = mrdb:read(R, a),
|
||||
go_ahead_other(POther),
|
||||
await_other_down(POther, MRef, ?LINE),
|
||||
[{R, a, 17}] = mrdb:read(R, a),
|
||||
ok = mrdb:insert(R, {R, a, 18})
|
||||
end,
|
||||
go_ahead_other(1, POther),
|
||||
try mrdb:activity({tx, #{no_snapshot => true,
|
||||
retries => 0}}, rdb, F1) of
|
||||
ok -> error(unexpected)
|
||||
catch
|
||||
error:{error, "Resource busy" ++ _} ->
|
||||
ok
|
||||
end,
|
||||
[{R, a, 17}] = mrdb:read(R, a),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
mrdb_two_procs_tx_restart(Config) ->
|
||||
tr_ct:with_trace(fun mrdb_two_procs_tx_restart_/1, Config,
|
||||
light_tr_opts()).
|
||||
|
||||
mrdb_two_procs_tx_restart_(Config) ->
|
||||
R = ?FUNCTION_NAME,
|
||||
Parent = self(),
|
||||
Created = create_tabs([{R, []}], Config),
|
||||
mrdb:insert(R, {R, a, 1}),
|
||||
Pre = mrdb:read(R, a),
|
||||
F0 = fun() ->
|
||||
wait_for_other(Parent, ?LINE),
|
||||
ok = mrdb:insert(R, {R, a, 17}),
|
||||
wait_for_other(Parent, ?LINE)
|
||||
end,
|
||||
{POther, MRef} = spawn_opt(
|
||||
fun() ->
|
||||
ok = mrdb:activity(tx, rdb, F0)
|
||||
end, [monitor]),
|
||||
F1 = fun() ->
|
||||
OtherWrite = [{R, a, 17}],
|
||||
Att = get_attempt(),
|
||||
Expected = case Att of
|
||||
1 -> Pre;
|
||||
_ -> OtherWrite
|
||||
end,
|
||||
Expected = mrdb:read(R, a),
|
||||
go_ahead_other(POther),
|
||||
await_other_down(POther, MRef, ?LINE),
|
||||
OtherWrite = mrdb:read(R, a),
|
||||
ok = mrdb:insert(R, {R, a, 18})
|
||||
end,
|
||||
go_ahead_other(1, POther),
|
||||
mrdb:activity({tx, #{no_snapshot => true}}, rdb, F1),
|
||||
[{R, a, 18}] = mrdb:read(R, a),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
|
||||
%
|
||||
%% For testing purposes, we use side-effects inside the transactions
|
||||
%% to synchronize the concurrent transactions. If a transaction fails due
|
||||
%% to "Resource busy", it can re-run, but then mustn't attempt to sync with
|
||||
%% the other transaction, which is already committed.
|
||||
%%
|
||||
%% To achieve this, we rely on the `mrdb:current_context()` function, which gives
|
||||
%% us information about which is the current attempt; we only sync on the first
|
||||
%% attempt, and ignore the sync ops on retries.
|
||||
%%
|
||||
-define(IF_FIRST(N, Expr),
|
||||
if N == 1 ->
|
||||
Expr;
|
||||
true ->
|
||||
ok
|
||||
end).
|
||||
|
||||
mrdb_two_procs_snap(Config) ->
|
||||
%% _snap is now the default tx mode
|
||||
R = ?FUNCTION_NAME,
|
||||
Parent = self(),
|
||||
Created = create_tabs([{R, []}], Config),
|
||||
mrdb:insert(R, {R, a, 1}),
|
||||
Pre = mrdb:read(R, a),
|
||||
mrdb:insert(R, {R, b, 11}),
|
||||
PreB = mrdb:read(R, b),
|
||||
F0 = fun() ->
|
||||
ok = mrdb:insert(R, {R, a, 17}),
|
||||
wait_for_other(Parent, ?LINE)
|
||||
end,
|
||||
{POther, MRef} =
|
||||
spawn_opt(fun() ->
|
||||
ok = mrdb:activity(tx, rdb, F0)
|
||||
end, [monitor]),
|
||||
F1 = fun() ->
|
||||
Att = get_attempt(),
|
||||
go_ahead_other(Att, POther),
|
||||
ARes = mrdb:read(R, a),
|
||||
ARes = case Att of
|
||||
1 -> Pre;
|
||||
2 -> [{R, a, 17}]
|
||||
end,
|
||||
await_other_down(POther, MRef, ?LINE),
|
||||
PreB = mrdb:read(R, b),
|
||||
mrdb:insert(R, {R, b, 18}),
|
||||
1477
|
||||
end,
|
||||
1477 = mrdb:activity(tx, rdb, F1),
|
||||
[{R, a, 17}] = mrdb:read(R, a),
|
||||
[{R, b, 18}] = mrdb:read(R, b),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
%% We spawn two helper processes, making it 3 transactions, with the one
|
||||
%% in the parent process. P2 writes to key `a`, which the other two try to read.
|
||||
%% We make sure that P2 commits before finishing the other two, and P3 and the
|
||||
%% main thread sync, so as to maximize the contention for the retry lock.
|
||||
mrdb_three_procs(Config) ->
|
||||
tr_ct:with_trace(fun mrdb_three_procs_/1, Config, light_tr_opts()).
|
||||
|
||||
mrdb_three_procs_(Config) ->
|
||||
R = ?FUNCTION_NAME,
|
||||
Parent = self(),
|
||||
Created = create_tabs([{R, []}], Config),
|
||||
A0 = {R, a, 1},
|
||||
A1 = {R, a, 11},
|
||||
A2 = {R, a, 12},
|
||||
ok = mrdb:insert(R, A0),
|
||||
F1 = fun() ->
|
||||
ok = mrdb:insert(R, A1),
|
||||
ok = mrdb:insert(R, {R, p1, 1})
|
||||
end,
|
||||
{P1, MRef1} =
|
||||
spawn_opt(fun() ->
|
||||
do_when_p_allows(
|
||||
1, Parent, ?LINE,
|
||||
fun() ->
|
||||
ok = mrdb:activity({tx,#{retries => 0}}, rdb, F1)
|
||||
end)
|
||||
end, [monitor]),
|
||||
F2 = fun() ->
|
||||
[A0] = mrdb:read(R, a),
|
||||
Att = get_attempt(),
|
||||
wait_for_other(Att, Parent, ?LINE),
|
||||
do_when_p_allows(
|
||||
Att, Parent, ?LINE,
|
||||
fun() ->
|
||||
[A1] = mrdb:read(R, a),
|
||||
ok = mrdb:insert(R, A2),
|
||||
ok = mrdb:insert(R, {R, p2, 1})
|
||||
end)
|
||||
end,
|
||||
{P2, MRef2} =
|
||||
spawn_opt(fun() ->
|
||||
try mrdb:activity(
|
||||
{tx, #{retries => 0,
|
||||
no_snapshot => true}}, rdb, F2) of
|
||||
ok -> error(unexpected)
|
||||
catch
|
||||
error:{error, "Resource busy" ++ _} ->
|
||||
ok
|
||||
end
|
||||
end, [monitor]),
|
||||
ok = mrdb:activity(tx, rdb,
|
||||
fun() ->
|
||||
Att = get_attempt(),
|
||||
ARes = case Att of
|
||||
1 -> [A0];
|
||||
2 -> [A1]
|
||||
end,
|
||||
%% First, ensure that P2 tx is running
|
||||
go_ahead_other(Att, P2),
|
||||
ARes = mrdb:read(R, a),
|
||||
allow_p(Att, P1, ?LINE),
|
||||
ARes = mrdb:read(R, a),
|
||||
allow_p(Att, P2, ?LINE),
|
||||
ARes = mrdb:read(R, a),
|
||||
await_other_down(P1, MRef1, ?LINE),
|
||||
await_other_down(P2, MRef2, ?LINE),
|
||||
ok = mrdb:insert(R, {R, p0, 1})
|
||||
end),
|
||||
[{R, p1, 1}] = mrdb:read(R, p1),
|
||||
[] = mrdb:read(R, p2),
|
||||
[A1] = mrdb:read(R, a),
|
||||
[{R, p0, 1}] = mrdb:read(R, p0),
|
||||
delete_tabs(Created),
|
||||
ok.
|
||||
|
||||
tr_opts() ->
|
||||
#{patterns => [ {mrdb, '_', '_', x}
|
||||
, {mrdb_lib, '_', '_', x}
|
||||
, {tr_ttb, event, 3, []}
|
||||
, {?MODULE, go_ahead_other, 3, x}
|
||||
, {?MODULE, wait_for_other, 3, x}
|
||||
, {?MODULE, await_other_down, 3, x}
|
||||
, {?MODULE, do_when_p_allows, 4, x}
|
||||
, {?MODULE, allow_p, 3, x}
|
||||
]}.
|
||||
|
||||
light_tr_opts() ->
|
||||
tr_flags(
|
||||
{self(), [call, sos, p]},
|
||||
tr_patterns(
|
||||
mrdb, [ {mrdb, insert, 2, x}
|
||||
, {mrdb, read, 2, x}
|
||||
, {mrdb, activity, x} ], tr_opts())).
|
||||
|
||||
tr_patterns(Mod, Ps, #{patterns := Pats} = Opts) ->
|
||||
Pats1 = [P || P <- Pats, element(1,P) =/= Mod],
|
||||
Opts#{patterns => Ps ++ Pats1}.
|
||||
|
||||
tr_flags(Flags, Opts) when is_map(Opts) ->
|
||||
Opts#{flags => Flags}.
|
||||
|
||||
wait_for_other(Parent, L) ->
|
||||
wait_for_other(get_attempt(), Parent, 1000, L).
|
||||
|
||||
wait_for_other(Att, Parent, L) ->
|
||||
wait_for_other(Att, Parent, 1000, L).
|
||||
|
||||
wait_for_other(1, Parent, Timeout, L) ->
|
||||
MRef = monitor(process, Parent),
|
||||
Parent ! {self(), ready},
|
||||
receive
|
||||
{Parent, cont} ->
|
||||
demonitor(MRef),
|
||||
ok;
|
||||
{'DOWN', MRef, _, _, Reason} ->
|
||||
ct:log("Parent died, Reason = ~p", [Reason]),
|
||||
exit(Reason)
|
||||
after Timeout ->
|
||||
demonitor(MRef),
|
||||
error({inner_timeout, L})
|
||||
end;
|
||||
wait_for_other(_, _, _, _) ->
|
||||
ok.
|
||||
|
||||
do_when_p_allows(Att, P, Line, F) ->
|
||||
wait_for_other(Att, P, Line),
|
||||
F(),
|
||||
%% Tell P that we're done
|
||||
go_ahead_other(Att, P, Line),
|
||||
%% Wait for P to acknowlege
|
||||
wait_for_other(Att, P, Line).
|
||||
|
||||
allow_p(Att, P, Line) ->
|
||||
go_ahead_other(Att, P),
|
||||
%% This is where P does its thing.
|
||||
wait_for_other(Att, P, Line),
|
||||
%% Acknowledge
|
||||
go_ahead_other(Att, P, Line).
|
||||
|
||||
go_ahead_other(POther) ->
|
||||
go_ahead_other(get_attempt(), POther).
|
||||
|
||||
go_ahead_other(Att, POther) ->
|
||||
go_ahead_other(Att, POther, 1000).
|
||||
|
||||
go_ahead_other(Att, POther, Timeout) ->
|
||||
?IF_FIRST(Att, go_ahead_other_(POther, Timeout)).
|
||||
|
||||
go_ahead_other_(POther, Timeout) ->
|
||||
receive
|
||||
{POther, ready} ->
|
||||
POther ! {self(), cont}
|
||||
after Timeout ->
|
||||
error(go_ahead_timeout)
|
||||
end.
|
||||
|
||||
%% Due to transaction restarts, we may already have collected
|
||||
%% a DOWN message. In this case, P will already be dead, and there
|
||||
%% will not be a 'DOWN' messsage still in the msg queue.
|
||||
%% This is fine (we assume it is), and we just make sure that the
|
||||
%% process didn't die abnormally.
|
||||
await_other_down(P, MRef, Line) ->
|
||||
Attempt = get_attempt(),
|
||||
?IF_FIRST(Attempt, await_other_down_(P, MRef, Line)).
|
||||
|
||||
await_other_down_(P, MRef, Line) ->
|
||||
receive {'DOWN', MRef, _, _, Reason} ->
|
||||
case Reason of
|
||||
normal -> ok;
|
||||
_ ->
|
||||
error({abnormal_termination,
|
||||
[ {pid, P}
|
||||
, {mref, MRef}
|
||||
, {line, Line}
|
||||
, {reason, Reason}]})
|
||||
end
|
||||
after 1000 ->
|
||||
error({monitor_timeout, Line})
|
||||
end.
|
||||
|
||||
get_attempt() ->
|
||||
#{attempt := Attempt} = mrdb:current_context(),
|
||||
Attempt.
|
||||
|
||||
create_tabs(Tabs, Config) ->
|
||||
Res = lists:map(fun create_tab/1, Tabs),
|
||||
tr_ct:trace_checkpoint(?TABS_CREATED, Config),
|
||||
Res.
|
||||
|
||||
create_tab({T, Opts}) -> create_tab(T, Opts).
|
||||
|
||||
create_tab(T, Opts) ->
|
||||
{atomic, ok} = mnesia:create_table(T, [{rdb,[node()]} | Opts]),
|
||||
T.
|
||||
|
||||
delete_tabs(Tabs) ->
|
||||
[{atomic,ok} = mnesia:delete_table(T) || T <- Tabs],
|
||||
ok.
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
-module(mnesia_rocksdb_error_handling).
|
||||
|
||||
-export([run/0,
|
||||
run/4]).
|
||||
|
||||
|
||||
run() ->
|
||||
setup(),
|
||||
%% run only one test for 'fatal', to save time.
|
||||
[run(Type, Op, L, MaintainSz) || MaintainSz <- [false, true],
|
||||
Type <- [set, bag],
|
||||
Op <- [insert, update, delete],
|
||||
L <- levels()]
|
||||
++ [run(set, insert, fatal, false)].
|
||||
|
||||
run(Type, Op, Level, MaintainSz) ->
|
||||
setup(),
|
||||
{ok, Tab} = create_tab(Type, Level, MaintainSz),
|
||||
mnesia:dirty_write({Tab, a, 1}), % pre-existing data
|
||||
with_mock(Level, Op, Tab, fun() ->
|
||||
try_write(Op, Type, Tab),
|
||||
expect_error(Level, Tab)
|
||||
end).
|
||||
|
||||
levels() ->
|
||||
[debug, verbose, warning, error].
|
||||
|
||||
setup() ->
|
||||
mnesia:stop(),
|
||||
start_mnesia().
|
||||
|
||||
create_tab(Type, Level, MaintainSz) ->
|
||||
TabName = tab_name(Type, Level, MaintainSz),
|
||||
%% create error store before the table
|
||||
case ets:info(?MODULE) of
|
||||
undefined ->
|
||||
?MODULE = ets:new(?MODULE, [bag, public, named_table]),
|
||||
ok;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
UserProps = user_props(Level, MaintainSz),
|
||||
{atomic, ok} = mnesia:create_table(TabName, [{rdb, [node()]},
|
||||
{user_properties, UserProps}]),
|
||||
{ok, TabName}.
|
||||
|
||||
tab_name(Type, Level, MaintainSz) ->
|
||||
binary_to_atom(iolist_to_binary(
|
||||
["t" | [["_", atom_to_list(A)]
|
||||
|| A <- [?MODULE, Type, Level, MaintainSz]]]), utf8).
|
||||
|
||||
user_props(Level, MaintainSz) ->
|
||||
[{maintain_sz, MaintainSz},
|
||||
{rocksdb_opts, [ {on_write_error, Level}
|
||||
, {on_write_error_store, ?MODULE} ]}].
|
||||
|
||||
start_mnesia() ->
|
||||
mnesia_rocksdb_tlib:start_mnesia(reset),
|
||||
ok.
|
||||
|
||||
with_mock(Level, Op, Tab, F) ->
|
||||
mnesia:subscribe(system),
|
||||
mnesia:set_debug_level(debug),
|
||||
meck:new(mnesia_rocksdb_lib, [passthrough]),
|
||||
meck:expect(mnesia_rocksdb_lib, put, 4, {error, some_put_error}),
|
||||
meck:expect(mnesia_rocksdb_lib, write, 3, {error, some_write_error}),
|
||||
meck:expect(mnesia_rocksdb_lib, delete, 3, {error,some_delete_error}),
|
||||
try {Level, Op, Tab, F()} of
|
||||
{_, _, _, ok} ->
|
||||
ok;
|
||||
Other ->
|
||||
io:fwrite("OTHER: ~p~n", [Other]),
|
||||
ok
|
||||
catch
|
||||
exit:{{aborted,_},_} ->
|
||||
Level = error,
|
||||
ok
|
||||
after
|
||||
mnesia:set_debug_level(none),
|
||||
mnesia:unsubscribe(system),
|
||||
meck:unload(mnesia_rocksdb_lib)
|
||||
end.
|
||||
|
||||
try_write(insert, set, Tab) ->
|
||||
mnesia:dirty_write({Tab, b, 2});
|
||||
try_write(insert, bag, Tab) ->
|
||||
mnesia:dirty_write({Tab, a, 2});
|
||||
try_write(update, _, Tab) ->
|
||||
mnesia:dirty_write({Tab, a, 1});
|
||||
try_write(delete, _, Tab) ->
|
||||
mnesia:dirty_delete({Tab, a}).
|
||||
|
||||
|
||||
expect_error(Level, Tab) ->
|
||||
Tag = rpt_tag(Level),
|
||||
receive
|
||||
{mnesia_system_event, {mnesia_fatal, Fmt, Args, _Core}} ->
|
||||
Tag = mnesia_fatal,
|
||||
io:fwrite("EVENT(~p, ~p):~n ~s", [Tag, Tab, io_lib:fwrite(Fmt, Args)]),
|
||||
ok;
|
||||
{mnesia_system_event, {Tag, Fmt, Args}} ->
|
||||
io:fwrite("EVENT(~p, ~p):~n ~s", [Tag, Tab, io_lib:fwrite(Fmt, Args)]),
|
||||
ok
|
||||
after 1000 ->
|
||||
error({expected_error, [Level, Tab]})
|
||||
|
||||
end,
|
||||
%% Also verify that an error entry has been written into the error store.
|
||||
1 = ets:select_delete(?MODULE, [{{{Tab, '_'}, '_', '_'}, [], [true]}]),
|
||||
ok.
|
||||
|
||||
rpt_tag(fatal ) -> mnesia_fatal;
|
||||
rpt_tag(error ) -> mnesia_error;
|
||||
rpt_tag(warning) -> mnesia_warning;
|
||||
rpt_tag(verbose) -> mnesia_info;
|
||||
rpt_tag(debug ) -> mnesia_info.
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
-define(m(A,B), fun() -> L = ?LINE,
|
||||
case {A,B} of
|
||||
{__X, __X} ->
|
||||
{X__, X__} ->
|
||||
B;
|
||||
Other ->
|
||||
error({badmatch, [Other,
|
||||
|
||||
@@ -16,13 +16,51 @@
|
||||
%% under the License.
|
||||
%%----------------------------------------------------------------
|
||||
|
||||
-module(mnesia_rocksdb_indexes).
|
||||
-module(mnesia_rocksdb_indexes_SUITE).
|
||||
|
||||
-export([
|
||||
all/0
|
||||
, groups/0
|
||||
, suite/0
|
||||
, init_per_suite/1
|
||||
, end_per_suite/1
|
||||
, init_per_group/2
|
||||
, end_per_group/2
|
||||
, init_per_testcase/2
|
||||
, end_per_testcase/2
|
||||
]).
|
||||
|
||||
-export([
|
||||
index_plugin_mgmt/1
|
||||
, add_indexes/1
|
||||
, create_bag_index/1
|
||||
, create_ordered_index/1
|
||||
, test_1_ram_copies/1
|
||||
, test_1_disc_copies/1
|
||||
, fail_1_disc_only/1
|
||||
, plugin_ram_copies1/1
|
||||
, plugin_ram_copies2/1
|
||||
, plugin_disc_copies/1
|
||||
, fail_plugin_disc_only/1
|
||||
, plugin_disc_copies_bag/1
|
||||
, plugin_rdb_ordered/1
|
||||
, index_iterator/1
|
||||
]).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-export([run/0,
|
||||
run/1,
|
||||
r1/0]).
|
||||
|
||||
-define(TAB(T), list_to_atom(lists:flatten(io_lib:fwrite("~w_~w", [T, ?LINE])))).
|
||||
|
||||
run() ->
|
||||
run([]).
|
||||
|
||||
run(Config) ->
|
||||
mnesia:stop(),
|
||||
maybe_set_dir(Config),
|
||||
ok = mnesia_rocksdb_tlib:start_mnesia(reset),
|
||||
test(1, ram_copies, r1),
|
||||
test(1, disc_copies, d1),
|
||||
@@ -33,15 +71,86 @@ run() ->
|
||||
add_del_indexes(),
|
||||
{atomic,ok} = mnesia_schema:add_index_plugin(
|
||||
{pfx},mnesia_rocksdb, ix_prefixes),
|
||||
test_index_plugin(pr1, ram_copies, ordered),
|
||||
test_index_plugin(pr2, ram_copies, bag),
|
||||
test_index_plugin(pd1, disc_copies, ordered),
|
||||
fail(test_index_plugin, [pd2, disc_only_copies, ordered]),
|
||||
test_index_plugin(pd2, disc_copies, bag),
|
||||
test_index_plugin(pl2, rdb, ordered),
|
||||
test_index_plugin_mgmt(),
|
||||
test_index_plugin(cfg([pr1, ram_copies, ordered], Config)),
|
||||
test_index_plugin(cfg([pr2, ram_copies, bag], Config)),
|
||||
test_index_plugin(cfg([pd1, disc_copies, ordered], Config)),
|
||||
fail(test_index_plugin, [cfg([pd2, disc_only_copies, ordered], Config)]),
|
||||
test_index_plugin(cfg([pd2, disc_copies, bag], Config)),
|
||||
test_index_plugin(cfg([pl2, rdb, ordered], Config)),
|
||||
index_plugin_mgmt(Config),
|
||||
ok.
|
||||
|
||||
suite() ->
|
||||
[].
|
||||
|
||||
all() ->
|
||||
[{group, all_tests}].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{all_tests, [sequence], [ {group, mgmt}, {group, access}, {group, plugin} ]}
|
||||
, {mgmt, [sequence], [
|
||||
create_bag_index
|
||||
, create_ordered_index
|
||||
, index_plugin_mgmt
|
||||
, add_indexes
|
||||
]}
|
||||
, {access, [sequence], [
|
||||
test_1_ram_copies
|
||||
, test_1_disc_copies
|
||||
, fail_1_disc_only
|
||||
, index_iterator
|
||||
]}
|
||||
, {plugin, [sequence], [
|
||||
plugin_ram_copies1
|
||||
, plugin_ram_copies2
|
||||
, plugin_disc_copies
|
||||
, fail_plugin_disc_only
|
||||
, plugin_disc_copies_bag
|
||||
, plugin_rdb_ordered
|
||||
]}
|
||||
].
|
||||
|
||||
%% ======================================================================
|
||||
|
||||
init_per_suite(Config) ->
|
||||
mnesia:stop(),
|
||||
maybe_set_dir(Config),
|
||||
Config.
|
||||
|
||||
end_per_suite(_) ->
|
||||
ok.
|
||||
|
||||
init_per_group(Grp, Config) ->
|
||||
mnesia_rocksdb_tlib:restart_reset_mnesia(),
|
||||
case Grp of
|
||||
plugin ->
|
||||
{atomic,ok} = mnesia_schema:add_index_plugin(
|
||||
{pfx},mnesia_rocksdb, ix_prefixes);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Config.
|
||||
|
||||
end_per_group(_, _) ->
|
||||
ok.
|
||||
|
||||
init_per_testcase(_, Config) ->
|
||||
Config.
|
||||
|
||||
end_per_testcase(_, _) ->
|
||||
ok.
|
||||
|
||||
%% ======================================================================
|
||||
|
||||
cfg([Tab, Type, IxType], Config) ->
|
||||
[{my_config, #{tab => Tab, type => Type, ixtype => IxType}} | Config];
|
||||
cfg(Cfg, Config) when is_map(Cfg) -> [{my_config, Cfg} | Config].
|
||||
|
||||
cfg(Config) -> ?config(my_config, Config).
|
||||
|
||||
%% ======================================================================
|
||||
|
||||
r1() ->
|
||||
mnesia:stop(),
|
||||
ok = mnesia_rocksdb_tlib:start_mnesia(reset),
|
||||
@@ -51,17 +160,28 @@ r1() ->
|
||||
dbg:tpl(mnesia_schema,x),
|
||||
dbg:tpl(mnesia_index,x),
|
||||
dbg:p(all,[c]),
|
||||
test_index_plugin(pd2, disc_only_copies, ordered).
|
||||
test_index_plugin(cfg([pd2, disc_only_copies, ordered], [])).
|
||||
|
||||
fail(F, Args) ->
|
||||
try apply(?MODULE, F, Args),
|
||||
error(should_fail)
|
||||
catch
|
||||
error:_ ->
|
||||
error:R when R =/= should_fail ->
|
||||
io:fwrite("apply(~p, ~p, ~p) -> fails as expected~n",
|
||||
[?MODULE, F, Args])
|
||||
end.
|
||||
|
||||
test_1_ram_copies( _Cfg) -> test(1, ram_copies, r1).
|
||||
test_1_disc_copies(_Cfg) -> test(1, disc_copies, d1).
|
||||
fail_1_disc_only( _Cfg) -> fail(test, [1, disc_only_copies, do1]).
|
||||
|
||||
plugin_ram_copies1(Cfg) -> test_index_plugin(cfg([pr1, ram_copies, ordered], Cfg)).
|
||||
plugin_ram_copies2(Cfg) -> test_index_plugin(cfg([pr2, ram_copies, bag], Cfg)).
|
||||
plugin_disc_copies(Cfg) -> test_index_plugin(cfg([pd1, disc_copies, ordered], Cfg)).
|
||||
fail_plugin_disc_only(Cfg) -> fail(test_index_plugin, [cfg([pd2, disc_only_copies, ordered], Cfg)]).
|
||||
plugin_disc_copies_bag(Cfg) -> test_index_plugin(cfg([pd2, disc_copies, bag], Cfg)).
|
||||
plugin_rdb_ordered(Cfg) -> test_index_plugin(cfg([pl2, rdb, ordered], Cfg)).
|
||||
|
||||
test(N, Type, T) ->
|
||||
{atomic, ok} = mnesia:create_table(T, [{Type,[node()]},
|
||||
{attributes,[k,a,b,c]},
|
||||
@@ -81,7 +201,8 @@ add_del_indexes() ->
|
||||
{atomic, ok} = mnesia:add_table_index(l1, a),
|
||||
io:fwrite("add_del_indexes() -> ok~n", []).
|
||||
|
||||
test_index_plugin(Tab, Type, IxType) ->
|
||||
test_index_plugin(Config) ->
|
||||
#{tab := Tab, type := Type, ixtype := IxType} = cfg(Config),
|
||||
{atomic, ok} = mnesia:create_table(Tab, [{Type, [node()]},
|
||||
{index, [{{pfx}, IxType}]}]),
|
||||
mnesia:dirty_write({Tab, "foobar", "sentence"}),
|
||||
@@ -100,10 +221,25 @@ test_index_plugin(Tab, Type, IxType) ->
|
||||
Res2 = lists:sort(mnesia:dirty_index_read(Tab,<<"whi">>, {pfx})),
|
||||
[{Tab,"foobar","sentence"}] = mnesia:dirty_index_read(
|
||||
Tab, <<"foo">>, {pfx})
|
||||
end,
|
||||
io:fwrite("test_index_plugin(~p, ~p, ~p) -> ok~n", [Tab,Type,IxType]).
|
||||
end.
|
||||
|
||||
test_index_plugin_mgmt() ->
|
||||
create_bag_index(_Config) ->
|
||||
{aborted, {combine_error, _, _}} =
|
||||
mnesia:create_table(bi, [{rdb, [node()]}, {index, [{val, bag}]}]),
|
||||
ok.
|
||||
|
||||
create_ordered_index(_Config) ->
|
||||
{atomic, ok} =
|
||||
mnesia:create_table(oi, [{rdb, [node()]}, {index, [{val, ordered}]}]),
|
||||
ok.
|
||||
|
||||
add_indexes(_Config) ->
|
||||
T = ?TAB(t1),
|
||||
{atomic, ok} = mnesia:create_table(T, [{rdb, [node()]}, {attributes, [k, a, b, c]}]),
|
||||
{atomic, ok} = mnesia:add_table_index(T, a),
|
||||
ok.
|
||||
|
||||
index_plugin_mgmt(_Config) ->
|
||||
{aborted,_} = mnesia:create_table(x, [{index,[{unknown}]}]),
|
||||
{aborted,_} = mnesia:create_table(x, [{index,[{{unknown},bag}]}]),
|
||||
{aborted,_} = mnesia:create_table(x, [{index,[{{unknown},ordered}]}]),
|
||||
@@ -166,9 +302,48 @@ test_index(3, T) ->
|
||||
io:fwrite("test_index(1, ~p) -> ok~n", [T]),
|
||||
ok.
|
||||
|
||||
index_iterator(_Cfg) ->
|
||||
T = ?TAB(it),
|
||||
Attrs = [ {rdb,[node()]}
|
||||
, {record_name, i}
|
||||
, {attributes, [k,a,b]}
|
||||
, {index, [a,b]} ],
|
||||
{atomic, ok} = mnesia:create_table(T, Attrs),
|
||||
ct:log("created tab T=~p: ~p", [T, Attrs]),
|
||||
L1 = [{i,K,a,y} || K <- lists:seq(4,6)],
|
||||
L2 = [{i,K,b,x} || K <- lists:seq(1,3)],
|
||||
true = lists:all(fun(X) -> X == ok end,
|
||||
[mnesia:dirty_write(T, Obj) || Obj <- L1 ++ L2]),
|
||||
ct:log("inserted ~p", [L1 ++ L2]),
|
||||
ResA = [{a,X} || X <- L1] ++ [{b,Y} || Y <- L2],
|
||||
ResB = [{x,X} || X <- L2] ++ [{y,Y} || Y <- L1],
|
||||
F = fun iter_all/1,
|
||||
ResA = mrdb_index:with_iterator(T, a, F),
|
||||
ct:log("mrdb_index:with_iterator(T, a, F) -> ~p", [ResA]),
|
||||
ResB = mrdb_index:with_iterator(T, b, F),
|
||||
ct:log("mrdb_index:with_iterator(T, b, F) -> ~p", [ResB]),
|
||||
ok.
|
||||
|
||||
iter_all(I) ->
|
||||
iter_all(mrdb_index:iterator_move(I, first), I).
|
||||
|
||||
iter_all({ok, IxVal, Obj}, I) ->
|
||||
[{IxVal, Obj} | iter_all(mrdb_index:iterator_move(I, next), I)];
|
||||
iter_all(_, _) ->
|
||||
[].
|
||||
|
||||
indexes(1) ->
|
||||
[a,{b,ordered},{c,bag}];
|
||||
indexes(2) ->
|
||||
[a,b,{c,bag}];
|
||||
indexes(3) ->
|
||||
[a,{b,ordered},{c,ordered}].
|
||||
|
||||
maybe_set_dir(Config) ->
|
||||
case proplists:get_value(priv_dir, Config) of
|
||||
undefined ->
|
||||
ok;
|
||||
PDir ->
|
||||
Dir = filename:join(PDir, "mnesia_indexes"),
|
||||
application:set_env(mnesia, dir, Dir)
|
||||
end.
|
||||
@@ -0,0 +1,190 @@
|
||||
-module(mnesia_rocksdb_migration_SUITE).
|
||||
|
||||
-export([
|
||||
all/0
|
||||
, suite/0
|
||||
, groups/0
|
||||
, init_per_suite/1
|
||||
, end_per_suite/1
|
||||
, init_per_group/2
|
||||
, end_per_group/2
|
||||
, init_per_testcase/2
|
||||
, end_per_testcase/2
|
||||
]).
|
||||
|
||||
-export([
|
||||
manual_migration/1
|
||||
, migrate_with_encoding_change/1
|
||||
, auto_migration/1
|
||||
]).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-define(TABS_CREATED, tables_created).
|
||||
|
||||
suite() ->
|
||||
[].
|
||||
|
||||
all() ->
|
||||
[{group, all_tests}].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{all_tests, [sequence], [ manual_migration
|
||||
, migrate_with_encoding_change ]}
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
ok.
|
||||
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
||||
end_per_group(_, _Config) ->
|
||||
ok.
|
||||
|
||||
init_per_testcase(_, Config) ->
|
||||
mnesia:stop(),
|
||||
ok = mnesia_rocksdb_tlib:start_mnesia(reset),
|
||||
Config.
|
||||
%% create_migrateable_db(Config).
|
||||
|
||||
end_per_testcase(_, _Config) ->
|
||||
ok.
|
||||
|
||||
manual_migration(Config) ->
|
||||
tr_ct:with_trace(fun manual_migration_/1, Config, tr_opts()).
|
||||
|
||||
manual_migration_(Config) ->
|
||||
create_migrateable_db(Config),
|
||||
Tabs = tables(),
|
||||
ct:log("Analyze (before): ~p", [analyze_tabs(Tabs)]),
|
||||
Res = mnesia_rocksdb_admin:migrate_standalone(rdb, Tabs),
|
||||
ct:log("migrate_standalone(rdb, ~p) -> ~p", [Tabs, Res]),
|
||||
AnalyzeRes = analyze_tabs(Tabs),
|
||||
ct:log("AnalyzeRes = ~p", [AnalyzeRes]),
|
||||
MigRes = mnesia_rocksdb_admin:migrate_standalone(rdb, Tabs),
|
||||
ct:log("MigRes = ~p", [MigRes]),
|
||||
AnalyzeRes2 = analyze_tabs(Tabs),
|
||||
ct:log("AnalyzeRes2 = ~p", [AnalyzeRes2]),
|
||||
ct:log("Admin State = ~p", [sys:get_state(mnesia_rocksdb_admin)]),
|
||||
ok.
|
||||
|
||||
migrate_with_encoding_change(_Config) ->
|
||||
ok = create_tab(t, [{user_properties, [{mrdb_encoding, {sext,{object,term}}},
|
||||
{rocksdb_standalone, true}]},
|
||||
{index,[val]}
|
||||
]),
|
||||
mrdb:insert(t, {t, <<"1">>, <<"a">>}),
|
||||
mrdb:insert(t, {t, <<"2">>, <<"b">>}),
|
||||
TRef = mrdb:get_ref(t),
|
||||
{ok, V1} = mrdb:rdb_get(TRef, sext:encode(<<"1">>), []),
|
||||
{ok, V2} = mrdb:rdb_get(TRef, sext:encode(<<"2">>), []),
|
||||
{t,[],<<"a">>} = binary_to_term(V1),
|
||||
{t,[],<<"b">>} = binary_to_term(V2),
|
||||
Opts = #{encoding => {raw, raw}},
|
||||
MigRes = mnesia_rocksdb_admin:migrate_standalone(rdb, [{t, Opts}]),
|
||||
ct:log("MigRes (t) = ~p", [MigRes]),
|
||||
%%
|
||||
%% Ensure that metadata reflect the migrated table
|
||||
%% (now a column family, and the rocksdb_standalone prop gone)
|
||||
%%
|
||||
TRef1 = mrdb:get_ref(t),
|
||||
ct:log("TRef1(t) = ~p", [TRef1]),
|
||||
#{type := column_family,
|
||||
properties := #{user_properties := UPs}} = TRef1,
|
||||
error = maps:find(rocksdb_standalone, UPs),
|
||||
UPsR = lists:sort(maps:values(UPs)),
|
||||
UPsM = lists:sort(mnesia:table_info(t, user_properties)),
|
||||
{UPsR,UPsM} = {UPsM,UPsR},
|
||||
ct:log("user properties (t): ~p", [UPsM]),
|
||||
[{<<"2">>, <<"b">>},
|
||||
{<<"1">>, <<"a">>}] = mrdb:rdb_fold(
|
||||
t, fun(K,V,A) -> [{K,V}|A] end, [], <<>>),
|
||||
ct:log("All data present in new column family", []),
|
||||
ct:log("Contents of mnesia dir: ~p",
|
||||
[ok(file:list_dir(mnesia:system_info(directory)))]),
|
||||
ct:log("mnesia stopped", []),
|
||||
mnesia:stop(),
|
||||
mnesia:start(),
|
||||
ct:log("mnesia started", []),
|
||||
mnesia:info(),
|
||||
ok = mnesia:wait_for_tables([t], 3000),
|
||||
ct:log("tables loaded", []),
|
||||
[{t,<<"1">>,<<"a">>},
|
||||
{t,<<"2">>,<<"b">>}] = mrdb:select(
|
||||
t, [{'_',[],['$_']}]),
|
||||
[{<<"2">>,<<"b">>},
|
||||
{<<"1">>,<<"a">>}] = mrdb:rdb_fold(
|
||||
t, fun(K,V,A) -> [{K,V}|A] end, [], <<>>),
|
||||
ok.
|
||||
|
||||
auto_migration(_Config) ->
|
||||
ok.
|
||||
|
||||
ok({ok, Value}) -> Value.
|
||||
|
||||
tr_opts() ->
|
||||
#{ patterns => [ {mnesia_rocksdb_admin, '_', []}
|
||||
, {mnesia_rocksdb_lib, '_', []}
|
||||
, {rocksdb, '_', x} | trace_exports(mrdb, x) ] }.
|
||||
|
||||
trace_exports(M, Pat) ->
|
||||
Fs = M:module_info(exports),
|
||||
[{M, F, A, Pat} || {F, A} <- Fs].
|
||||
|
||||
tables() ->
|
||||
[a].
|
||||
|
||||
create_migrateable_db(Config) ->
|
||||
Os = [{user_properties, [{rocksdb_standalone, true}]}],
|
||||
TabNames = tables(),
|
||||
Tabs = [{T, Os} || T <- TabNames],
|
||||
create_tabs(Tabs, Config),
|
||||
verify_tabs_are_standalone(TabNames),
|
||||
fill_tabs(TabNames),
|
||||
Config.
|
||||
|
||||
fill_tabs(Tabs) ->
|
||||
lists:foreach(fun(Tab) ->
|
||||
[mrdb:insert(Tab, {Tab, X, a}) || X <- lists:seq(1,3)]
|
||||
end, Tabs).
|
||||
|
||||
create_tabs(Tabs, Config) ->
|
||||
Res = lists:map(fun create_tab/1, Tabs),
|
||||
tr_ct:trace_checkpoint(?TABS_CREATED, Config),
|
||||
Res.
|
||||
|
||||
create_tab({T, Opts}) ->
|
||||
create_tab(T, Opts).
|
||||
|
||||
create_tab(T, Opts) ->
|
||||
{atomic, ok} = mnesia:create_table(T, [{rdb, [node()]} | Opts]),
|
||||
ok.
|
||||
|
||||
verify_tabs_are_standalone(Tabs) ->
|
||||
case analyze_tabs(Tabs) of
|
||||
{_, []} ->
|
||||
ok;
|
||||
{[], NotSA} ->
|
||||
error({not_standalone, NotSA})
|
||||
end.
|
||||
|
||||
analyze_tabs(Tabs) ->
|
||||
Dir = mnesia:system_info(directory),
|
||||
Files = filelib:wildcard(filename:join(Dir, "*-_tab.extrdb")),
|
||||
ct:log("Files = ~p", [Files]),
|
||||
TabNames = lists:map(
|
||||
fun(F) ->
|
||||
{match,[TStr]} =
|
||||
re:run(F, "^.+/([^/]+)-_tab\\.extrdb$",
|
||||
[{capture, [1], list}]),
|
||||
list_to_existing_atom(TStr)
|
||||
end, Files),
|
||||
ct:log("TabNames = ~p", [TabNames]),
|
||||
NotSA = Tabs -- TabNames,
|
||||
{TabNames -- NotSA, NotSA}.
|
||||
|
||||
@@ -78,6 +78,18 @@ setup_mnesia() ->
|
||||
ok = mnesia:delete_schema([node()]),
|
||||
ok = mnesia:create_schema([node()]),
|
||||
ok = mnesia:start(),
|
||||
%%
|
||||
%% dbg:tracer(),
|
||||
%% dbg:tpl(mnesia_rocksdb_admin, x),
|
||||
%% dbg:tpl(mnesia_rocksdb,x),
|
||||
%% dbg:ctpl(mnesia_rocksdb, check_definition_entry),
|
||||
%% dbg:ctpl(mnesia_rocksdb, '-check_definition/4-fun-0-'),
|
||||
%% dbg:tpl(mnesia_rocksdb_lib,x),
|
||||
%% dbg:tp(mnesia,x),
|
||||
%% dbg:tpl(mrdb,x),
|
||||
%% dbg:tp(rocksdb,x),
|
||||
%% dbg:p(all,[c]),
|
||||
%%
|
||||
{ok, rocksdb_copies} = mnesia_rocksdb:register().
|
||||
|
||||
setup() ->
|
||||
|
||||
@@ -20,23 +20,32 @@
|
||||
|
||||
-export([start_mnesia/0,
|
||||
start_mnesia/1,
|
||||
restart_reset_mnesia/0,
|
||||
create_table/1,
|
||||
create_table/3,
|
||||
trace/2]).
|
||||
|
||||
restart_reset_mnesia() ->
|
||||
mnesia:stop(),
|
||||
start_mnesia(reset).
|
||||
|
||||
start_mnesia() ->
|
||||
start_mnesia(false).
|
||||
|
||||
start_mnesia(Mode) ->
|
||||
if Mode==reset ->
|
||||
mnesia:delete_schema([node()]),
|
||||
mnesia:create_schema([node()],
|
||||
[{backend_types,
|
||||
[{rdb,mnesia_rocksdb}]}]);
|
||||
DRes = mnesia:delete_schema([node()]),
|
||||
ct:log("Delete schema: ~p", [DRes]),
|
||||
CRes = mnesia:create_schema([node()],
|
||||
[{backend_types,
|
||||
[{rdb,mnesia_rocksdb}]}]),
|
||||
ct:log("Create schema: ~p", [CRes]);
|
||||
true -> ok
|
||||
end,
|
||||
mnesia:start().
|
||||
SRes = mnesia:start(),
|
||||
ct:log("Mnesia start: ~p", [SRes]),
|
||||
true = lists:member(rdb, mnesia_schema:backend_types()),
|
||||
SRes.
|
||||
|
||||
create_table(Backend) ->
|
||||
create_table(Backend, [k,v], [v]).
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
-module(mrdb_ttb).
|
||||
|
||||
-export([ on_nodes/2
|
||||
, stop/0
|
||||
, stop_nofetch/0
|
||||
, format/2
|
||||
, format/3 ]).
|
||||
|
||||
-export([ patterns/0
|
||||
, flags/0 ]).
|
||||
|
||||
on_nodes(Ns, File) ->
|
||||
tr_ttb:on_nodes(Ns, File, ?MODULE).
|
||||
|
||||
patterns() ->
|
||||
mrdb:patterns().
|
||||
|
||||
flags() ->
|
||||
{all, call}.
|
||||
|
||||
stop() ->
|
||||
tr_ttb:stop().
|
||||
|
||||
stop_nofetch() ->
|
||||
tr_ttb:stop_nofetch().
|
||||
|
||||
format(Dir, Out) ->
|
||||
tr_ttb:format(Dir, Out).
|
||||
|
||||
format(Dir, Out, Opts) ->
|
||||
tr_ttb:format(Dir, Out, Opts).
|
||||
Reference in New Issue
Block a user