%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2004-2018. All Rights Reserved. %% %% Licensed 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. %% %% %CopyrightEnd% %% %% -module(mnesia_qlc_test). -export([init_per_testcase/2, end_per_testcase/2, init_per_group/2, end_per_group/2, all/0, groups/0]). -export([frag/1, info/1, mnesia_down/1, dirty_nice_ram_copies/1, dirty_nice_disc_copies/1, dirty_nice_disc_only_copies/1, trans_nice_ram_copies/1, trans_nice_disc_copies/1, trans_nice_disc_only_copies/1, atomic_eval/1, nested_qlc/1 ]). -include("mnesia_test_lib.hrl"). -include_lib("stdlib/include/qlc.hrl"). init_per_testcase(Func, Conf) -> setup(Conf), mnesia_test_lib:init_per_testcase(Func, Conf). end_per_testcase(Func, Conf) -> mnesia_test_lib:end_per_testcase(Func, Conf). all() -> case code:which(qlc) of non_existing -> []; _ -> all_qlc() end. groups() -> [{dirty, [], [dirty_nice_ram_copies, dirty_nice_disc_copies, dirty_nice_disc_only_copies]}, {trans, [], [trans_nice_ram_copies, trans_nice_disc_copies, trans_nice_disc_only_copies, {group, atomic}]}, {atomic, [], [atomic_eval]}]. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. all_qlc() -> [{group, dirty}, {group, trans}, frag, info, mnesia_down]. init_testcases(Type,Config) -> Nodes = [N1,N2] = ?acquire_nodes(2, Config), ?match({atomic, ok}, mnesia:create_table(a, [{Type,[N1]}, {index,[3]}])), ?match({atomic, ok}, mnesia:create_table(b, [{Type,[N2]}])), Write = fun(Id) -> ok = mnesia:write({a, {a,Id}, 100 - Id}), ok = mnesia:write({b, {b,100-Id}, Id}) end, All = fun() -> [Write(Id) || Id <- lists:seq(1,10)], ok end, ?match({atomic, ok}, mnesia:sync_transaction(All)), ?match({atomic, [{b, {b,100-1}, 1}]}, mnesia:transaction(fun() -> mnesia:read({b, {b, 99}}) end)), Nodes. %% Test cases dirty_nice_ram_copies(Setup) -> dirty_nice(Setup,ram_copies). dirty_nice_disc_copies(Setup) -> dirty_nice(Setup,disc_copies). dirty_nice_disc_only_copies(Setup) -> dirty_nice(Setup,disc_only_copies). dirty_nice(suite, _) -> []; dirty_nice(doc, _) -> []; dirty_nice(Config, Type) when is_list(Config) -> Ns = init_testcases(Type,Config), QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," " Val == 90 + Key]">>), QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," " Key == 90 + Val]">>), QC = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])), QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])), FA = fun() -> qlc:e(QA) end, FB = fun() -> qlc:e(QB) end, FC = fun() -> qlc:e(QC) end, FD = fun() -> qlc:e(QD) end, %% Currently unsupported ?match({'EXIT',{aborted,no_transaction}}, FA()), ?match({'EXIT',{aborted,no_transaction}}, FB()), %% CRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})), ?match([{a,{a,5},95}], mnesia:async_dirty(FA)), ?match([{b,{b,95},5}], mnesia:async_dirty(FB)), ?match(CRes, mnesia:async_dirty(FC)), ?match(CRes, mnesia:async_dirty(FD)), ?match([{a,{a,5},95}], mnesia:sync_dirty(FA)), ?match([{b,{b,95},5}], mnesia:sync_dirty(FB)), ?match(CRes, mnesia:sync_dirty(FC)), ?match([{a,{a,5},95}], mnesia:activity(async_dirty, FA)), ?match([{b,{b,95},5}], mnesia:activity(async_dirty, FB)), ?match([{a,{a,5},95}], mnesia:activity(sync_dirty, FA)), ?match([{b,{b,95},5}], mnesia:activity(sync_dirty, FB)), ?match(CRes, mnesia:activity(async_dirty,FC)), case Type of disc_only_copies -> skip; _ -> ?match([{a,{a,5},95}], mnesia:ets(FA)), ?match([{a,{a,5},95}], mnesia:activity(ets, FA)) end, ?verify_mnesia(Ns, []). trans_nice_ram_copies(Setup) -> trans_nice(Setup,ram_copies). trans_nice_disc_copies(Setup) -> trans_nice(Setup,disc_copies). trans_nice_disc_only_copies(Setup) -> trans_nice(Setup,disc_only_copies). trans_nice(suite, _) -> []; trans_nice(doc, _) -> []; trans_nice(Config, Type) when is_list(Config) -> Ns = init_testcases(Type,Config), QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," " Val == 90 + Key]">>), QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," " Key == 90 + Val]">>), QC = handle(recs(), <<"[Q || Q = #a{v=91} <- mnesia:table(a)]" >>), QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])), QE = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])), DRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})), FA = fun() -> qlc:e(QA) end, FB = fun() -> qlc:e(QB) end, FC = fun() -> qlc:e(QC) end, FD = fun() -> qlc:e(QD) end, FE = fun() -> qlc:e(QE) end, ?match({atomic,[{a,{a,5},95}]}, mnesia:transaction(FA)), ?match({atomic,[{b,{b,95},5}]}, mnesia:transaction(FB)), ?match({atomic,[{a,{a,9},91}]}, mnesia:transaction(FC)), ?match({atomic,[{a,{a,5},95}]}, mnesia:sync_transaction(FA)), ?match({atomic,[{b,{b,95},5}]}, mnesia:sync_transaction(FB)), ?match({atomic,[{a,{a,9},91}]}, mnesia:sync_transaction(FC)), ?match([{a,{a,5},95}], mnesia:activity(transaction,FA)), ?match([{b,{b,95},5}], mnesia:activity(transaction,FB)), ?match([{a,{a,9},91}], mnesia:activity(transaction,FC)), ?match([{a,{a,5},95}], mnesia:activity(sync_transaction,FA)), ?match([{b,{b,95},5}], mnesia:activity(sync_transaction,FB)), ?match([{a,{a,9},91}], mnesia:activity(sync_transaction,FC)), ?match({atomic, DRes}, mnesia:transaction(FD)), ?match({atomic, DRes}, mnesia:transaction(FE)), Rest = fun(Cursor,Loop) -> case qlc:next_answers(Cursor, 1) of [] -> []; [A]-> [A|Loop(Cursor,Loop)] end end, Loop = fun() -> Cursor = qlc:cursor(QD), Rest(Cursor,Rest) end, ?match({atomic, DRes}, mnesia:transaction(Loop)), ?verify_mnesia(Ns, []). %% -record(a, {k,v}). %% -record(b, {k,v}). %% -record(k, {t,v}). recs() -> <<"-record(a, {k,v}). " "-record(b, {k,v}). " "-record(k, {t,v}). " >>. atomic_eval(suite) -> []; atomic_eval(doc) -> []; atomic_eval(Config) -> Ns = init_testcases(ram_copies, Config), Q1 = handle(recs(), <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]" >>), Eval = fun(Q) -> {qlc:e(Q), mnesia:system_info(held_locks)} end, Self = self(), ?match({[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}, ok(Eval,[Q1])), Q2 = handle(recs(), <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]" >>), ?match({[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}, ok(Eval,[Q2])), Flush = fun(Loop) -> %% Clean queue receive _ -> Loop(Loop) after 0 -> ok end end, Flush(Flush), GrabLock = fun(Father) -> mnesia:read(a, {a,9}, write), Father ! locked, receive cont -> ok end end, Pid1 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait put(count,0), Restart = fun(Locker,Fun) -> Count = get(count), case {Count,(catch Fun())} of {0, {'EXIT', R}} -> Locker ! cont, put(count, Count+1), erlang:yield(), exit(R); Else -> Else end end, ?match({1,{[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}}, ok(Restart,[Pid1,fun() -> Eval(Q1) end])), Pid2 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait put(count,0), ?match({1,{[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}}, ok(Restart,[Pid2, fun() -> Eval(Q2) end])), %% Basic test Cursor = fun() -> QC = qlc:cursor(Q1), qlc:next_answers(QC) end, ?match([{a,{a,9},91}], ok(Cursor, [])), %% Lock Pid3 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end), ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait put(count,0), ?match({1,[{a,{a,9},91}]}, ok(Restart,[Pid3, Cursor])), QC1 = ok(fun() -> qlc:cursor(Q1) end, []), ?match({'EXIT', _}, (catch qlc:next_answers(QC1))), ?match({aborted,_}, ok(fun()->qlc:next_answers(QC1)end,[])), ?verify_mnesia(Ns, []). frag(suite) -> []; frag(doc) -> []; frag(Config) -> Ns = init_testcases(ram_copies,Config), QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a)," " Val == 90 + Key]">>), QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," " Key == 90 + Val]">>), Activate = fun(Tab) -> ?match({atomic,ok},mnesia:change_table_frag(Tab, {activate, []})), Dist = mnesia_frag_test:frag_dist(Tab), ?match({atomic,ok},mnesia:change_table_frag(Tab,{add_frag,Dist})) end, Activate(a), Activate(b), Fun = fun(Tab) -> mnesia:table_info(Tab, frag_names) end, FTs = mnesia:activity(sync_dirty, Fun, [a], mnesia_frag) ++ mnesia:activity(sync_dirty, Fun, [b], mnesia_frag), Size = fun(Tab) -> mnesia:dirty_rpc(Tab, mnesia, table_info, [Tab,size]) end, %% Verify that all data doesn't belong to the same frag. ?match([], [{Tab,Size(Tab)} || Tab <- FTs, Size(Tab) =< 0]), FA = fun() -> qlc:e(QA) end, FB = fun() -> qlc:e(QB) end, ?match([{a,{a,5},95}], mnesia:activity(transaction,FA,[],mnesia_frag)), ?match([{b,{b,95},5}], mnesia:activity(transaction,FB,[],mnesia_frag)), ?verify_mnesia(Ns, []). info(suite) -> []; info(doc) -> []; info(Config) -> Ns = init_testcases(ram_copies, Config), Q1 = handle(recs(), <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]" >>), Q2 = handle(recs(), <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]" >>), Q3 = handle(recs(), <<"[Q || Q = #a{v=91} <- mnesia:table(a)]" >>), %% FIXME compile and check results! ?match(ok,io:format("~s~n",[qlc:info(Q1)])), ?match(ok,io:format("~s~n",[qlc:info(Q2)])), ?match(ok,io:format("~s~n",[qlc:info(Q3)])), ?verify_mnesia(Ns, []). ok(Fun,A) -> case mnesia:transaction(Fun,A) of {atomic, R} -> R; E -> E end. mnesia_down(suite) -> []; mnesia_down(doc) -> ["Test bug OTP-7968, which crashed mnesia when a" "mnesia_down came after qlc had been invoked"]; mnesia_down(Config) when is_list(Config) -> [N1,N2] = init_testcases(ram_copies,Config), QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b)," " Val == Key - 90]">>), Tester = self(), Eval = fun() -> Cursor = qlc:cursor(QB), %% Forces another process Res = qlc:next_answers(Cursor), Tester ! {qlc, self(), Res}, {Mod, Tid, Ts} = get(mnesia_activity_state), receive continue -> io:format("Continuing ~p ~p ~n",[self(), {Mod, Tid, Ts}]), io:format("ETS ~p~n",[ets:tab2list(element(2,Ts))]), io:format("~p~n",[process_info(self(),messages)]), Res end end, spawn(fun() -> TransRes = mnesia:transaction(Eval), Tester ! {test,TransRes} end), TMInfo = fun() -> TmInfo = mnesia_tm:get_info(5000), mnesia_tm:display_info(user, TmInfo) end, receive {qlc, QPid, QRes} -> ?match([{b,{b,95},5}], QRes), TMInfo(), mnesia_test_lib:kill_mnesia([N2]), %%timer:sleep(1000), QPid ! continue after 2000 -> exit(timeout1) end, receive {test, QRes2} -> ?match({atomic, [{b,{b,95},5}]}, QRes2) after 2000 -> exit(timeout2) end, ?verify_mnesia([N1], [N2]). nested_qlc(suite) -> []; nested_qlc(doc) -> ["Test bug in OTP-7968 (the second problem) where nested" "transaction don't work as expected"]; nested_qlc(Config) when is_list(Config) -> Ns = init_testcases(ram_copies,Config), Res = as_with_bs(), ?match([_|_], Res), top_as_with_some_bs(10), ?verify_mnesia(Ns, []). %% Code from Daniel bs_by_a_id(A_id) -> find(qlc:q([ B || B={_,_,F_id} <- mnesia:table(b), F_id == A_id])). as_with_bs() -> find(qlc:q([ {A,bs_by_a_id(Id)} || A = {_, {a,Id}, _} <- mnesia:table(a)])). top_as_with_some_bs(Limit) -> top( qlc:q([ {A,bs_by_a_id(Id)} || A = {_, {a,Id}, _} <- mnesia:table(a)]), Limit, fun(A1,A2) -> A1 < A2 end ). % --- utils find(Q) -> F = fun() -> qlc:e(Q) end, {atomic, Res} = mnesia:transaction(F), Res. % --- it returns top Limit results from query Q ordered by Order sort function top(Q, Limit, Order) -> Do = fun() -> OQ = qlc:sort(Q, [{order,Order}]), QC = qlc:cursor(OQ), Res = qlc:next_answers(QC, Limit), qlc:delete_cursor(QC), Res end, {atomic, Res} = mnesia:transaction(Do), Res. %% To keep mnesia suite backward compatible, %% we compile the queries in runtime when qlc is available %% Compiles and returns a handle to a qlc handle(Expr) -> handle(<<>>,Expr). handle(Records,Expr) -> case catch handle2(Records,Expr) of {ok, Handle} -> Handle; Else -> ?match(ok, Else) end. handle2(Records,Expr) -> {FN,Mod} = temp_name(), ModStr = list_to_binary("-module(" ++ atom_to_list(Mod) ++ ").\n"), Prog = << ModStr/binary, "-include_lib(\"stdlib/include/qlc.hrl\").\n", "-export([tmp/0]).\n", Records/binary,"\n", "tmp() ->\n", %% " _ = (catch throw(fvalue_not_reset))," " qlc:q( ", Expr/binary,").\n">>, ?match(ok,file:write_file(FN,Prog)), {ok,Forms} = epp:parse_file(FN,"",""), {ok,Mod,Bin} = compile:forms(Forms), code:load_binary(Mod,FN,Bin), {ok, Mod:tmp()}. setup(Config) -> put(mts_config,Config), put(mts_tf_counter,0). temp_name() -> Conf = get(mts_config), C = get(mts_tf_counter), put(mts_tf_counter,C+1), {filename:join([proplists:get_value(priv_dir,Conf, "."), "tempfile"++integer_to_list(C)++".tmp"]), list_to_atom("tmp" ++ integer_to_list(C))}.