1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2004-2011. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: 
   20: %%
   21: -module(mnesia_qlc_test).
   22: 
   23: -compile(export_all).
   24: 
   25: -export([all/0,groups/0,init_per_group/2,end_per_group/2]).
   26: 
   27: -include("mnesia_test_lib.hrl").
   28: -include_lib("stdlib/include/qlc.hrl"). 
   29: 
   30: init_per_testcase(Func, Conf) ->
   31:     setup(Conf),
   32:     mnesia_test_lib:init_per_testcase(Func, Conf).
   33: 
   34: end_per_testcase(Func, Conf) ->
   35:     mnesia_test_lib:end_per_testcase(Func, Conf).
   36: 
   37: all() -> 
   38:     case code:which(qlc) of
   39: 	non_existing -> [];
   40: 	_ -> all_qlc()
   41:     end.
   42: 
   43: groups() -> 
   44:     [{dirty, [],
   45:       [dirty_nice_ram_copies, dirty_nice_disc_copies,
   46:        dirty_nice_disc_only_copies]},
   47:      {trans, [],
   48:       [trans_nice_ram_copies, trans_nice_disc_copies,
   49:        trans_nice_disc_only_copies, {group, atomic}]},
   50:      {atomic, [], [atomic_eval]}].
   51: 
   52: init_per_group(_GroupName, Config) ->
   53:     Config.
   54: 
   55: end_per_group(_GroupName, Config) ->
   56:     Config.
   57: 
   58: 
   59: all_qlc() -> 
   60:     [{group, dirty}, {group, trans}, frag, info,
   61:      mnesia_down].
   62: 
   63: init_testcases(Type,Config) ->
   64:     Nodes = [N1,N2] = ?acquire_nodes(2, Config),
   65:     ?match({atomic, ok}, mnesia:create_table(a, [{Type,[N1]}, {index,[3]}])),
   66:     ?match({atomic, ok}, mnesia:create_table(b, [{Type,[N2]}])),
   67:     Write = fun(Id) -> 
   68: 		    ok = mnesia:write({a, {a,Id}, 100 - Id}),
   69: 		    ok = mnesia:write({b, {b,100-Id}, Id})
   70: 	    end,
   71:     All = fun() -> [Write(Id) || Id <- lists:seq(1,10)], ok end,
   72:     ?match({atomic, ok}, mnesia:sync_transaction(All)),
   73:     ?match({atomic, [{b, {b,100-1}, 1}]}, mnesia:transaction(fun() -> mnesia:read({b, {b, 99}}) end)),
   74:     Nodes.
   75: 
   76: %% Test cases       
   77: 
   78: dirty_nice_ram_copies(Setup) -> dirty_nice(Setup,ram_copies).
   79: dirty_nice_disc_copies(Setup) -> dirty_nice(Setup,disc_copies).
   80: dirty_nice_disc_only_copies(Setup) -> dirty_nice(Setup,disc_only_copies).
   81: 
   82: dirty_nice(suite, _) -> [];
   83: dirty_nice(doc, _) -> [];
   84: dirty_nice(Config, Type) when is_list(Config) ->
   85:     Ns = init_testcases(Type,Config),
   86:     QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a),"
   87: 		 "     Val == 90 + Key]">>),
   88:     QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b),"
   89: 		 "     Key == 90 + Val]">>),
   90:     QC = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])),
   91:     QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])),
   92: 
   93:     FA = fun() -> qlc:e(QA) end,
   94:     FB = fun() -> qlc:e(QB) end,
   95:     FC = fun() -> qlc:e(QC) end,
   96:     FD = fun() -> qlc:e(QD) end,
   97: 
   98:     %% Currently unsupported
   99:     ?match({'EXIT',{aborted,no_transaction}}, FA()),
  100:     ?match({'EXIT',{aborted,no_transaction}}, FB()),
  101:     %%
  102:     CRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})),
  103:     ?match([{a,{a,5},95}], mnesia:async_dirty(FA)),
  104:     ?match([{b,{b,95},5}], mnesia:async_dirty(FB)),
  105:     ?match(CRes, mnesia:async_dirty(FC)),
  106:     ?match(CRes, mnesia:async_dirty(FD)),
  107:     ?match([{a,{a,5},95}], mnesia:sync_dirty(FA)),
  108:     ?match([{b,{b,95},5}], mnesia:sync_dirty(FB)),
  109:     ?match(CRes, mnesia:sync_dirty(FC)),
  110:     ?match([{a,{a,5},95}], mnesia:activity(async_dirty, FA)),
  111:     ?match([{b,{b,95},5}], mnesia:activity(async_dirty, FB)),
  112:     ?match([{a,{a,5},95}], mnesia:activity(sync_dirty, FA)),
  113:     ?match([{b,{b,95},5}], mnesia:activity(sync_dirty, FB)),
  114:     ?match(CRes, mnesia:activity(async_dirty,FC)),
  115:     case Type of
  116: 	disc_only_copies -> skip;
  117: 	_ -> 
  118: 	    ?match([{a,{a,5},95}], mnesia:ets(FA)),
  119: 	    ?match([{a,{a,5},95}], mnesia:activity(ets, FA))
  120:     end,
  121:     ?verify_mnesia(Ns, []).
  122: 
  123: 
  124: trans_nice_ram_copies(Setup) -> trans_nice(Setup,ram_copies).
  125: trans_nice_disc_copies(Setup) -> trans_nice(Setup,disc_copies).
  126: trans_nice_disc_only_copies(Setup) -> trans_nice(Setup,disc_only_copies).
  127: 
  128: trans_nice(suite, _) -> [];
  129: trans_nice(doc, _) -> [];
  130: trans_nice(Config, Type) when is_list(Config) ->
  131:     Ns = init_testcases(Type,Config),
  132:     QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a),"
  133: 		 "     Val == 90 + Key]">>),
  134:     QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b),"
  135: 		 "     Key == 90 + Val]">>),
  136:     QC = handle(recs(), 
  137:  		<<"[Q || Q = #a{v=91} <- mnesia:table(a)]"
  138:  		 >>),
  139: 
  140:     QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])),
  141:     QE = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])),
  142: 
  143:     DRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})),
  144: 
  145:     FA = fun() -> qlc:e(QA) end,
  146:     FB = fun() -> qlc:e(QB) end,
  147:     FC = fun() -> qlc:e(QC) end,
  148:     FD = fun() -> qlc:e(QD) end,
  149:     FE = fun() -> qlc:e(QE) end,
  150: 		  
  151:     ?match({atomic,[{a,{a,5},95}]}, mnesia:transaction(FA)),
  152:     ?match({atomic,[{b,{b,95},5}]}, mnesia:transaction(FB)),
  153:     ?match({atomic,[{a,{a,9},91}]}, mnesia:transaction(FC)),
  154:     ?match({atomic,[{a,{a,5},95}]}, mnesia:sync_transaction(FA)),
  155:     ?match({atomic,[{b,{b,95},5}]}, mnesia:sync_transaction(FB)),
  156:     ?match({atomic,[{a,{a,9},91}]}, mnesia:sync_transaction(FC)),
  157:     ?match([{a,{a,5},95}], mnesia:activity(transaction,FA)),
  158:     ?match([{b,{b,95},5}], mnesia:activity(transaction,FB)),
  159:     ?match([{a,{a,9},91}], mnesia:activity(transaction,FC)),
  160:     ?match([{a,{a,5},95}], mnesia:activity(sync_transaction,FA)),
  161:     ?match([{b,{b,95},5}], mnesia:activity(sync_transaction,FB)),
  162:     ?match([{a,{a,9},91}], mnesia:activity(sync_transaction,FC)),
  163: 
  164:     ?match({atomic, DRes}, mnesia:transaction(FD)),
  165:     ?match({atomic, DRes}, mnesia:transaction(FE)),
  166: 
  167:     Rest = fun(Cursor,Loop) -> 
  168: 		   case qlc:next_answers(Cursor, 1) of
  169: 		       [] -> [];
  170: 		       [A]-> [A|Loop(Cursor,Loop)] 
  171: 		   end
  172: 	   end,
  173:     Loop = fun() -> 
  174: 		   Cursor = qlc:cursor(QD),
  175: 		   Rest(Cursor,Rest)
  176: 	   end,
  177:     ?match({atomic, DRes}, mnesia:transaction(Loop)),
  178: 
  179:     ?verify_mnesia(Ns, []).
  180: 
  181: %% -record(a, {k,v}).
  182: %% -record(b, {k,v}).
  183: %% -record(k, {t,v}).
  184: 
  185: recs() ->
  186:     <<"-record(a, {k,v}). "
  187:       "-record(b, {k,v}). "
  188:       "-record(k, {t,v}). "
  189:      >>.
  190:  
  191: 
  192: atomic_eval(suite) -> [];
  193: atomic_eval(doc) -> []; 
  194: atomic_eval(Config) ->
  195:     Ns = init_testcases(ram_copies, Config),    
  196:     Q1 = handle(recs(), 
  197: 		<<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]"
  198: 		 >>),
  199:     Eval = fun(Q) -> 
  200: 		   {qlc:e(Q),
  201: 		    mnesia:system_info(held_locks)}
  202: 	   end,
  203:     Self = self(),
  204:     ?match({[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]},
  205: 	   ok(Eval,[Q1])),
  206:     
  207:     Q2 = handle(recs(), 
  208: 		<<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]"
  209: 		 >>),
  210:     
  211:     ?match({[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]},
  212: 	   ok(Eval,[Q2])),
  213: 
  214:     Flush = fun(Loop) -> %% Clean queue
  215: 		    receive _ -> Loop(Loop) 
  216: 		    after 0 -> ok end
  217: 	    end,
  218:     
  219:     Flush(Flush),
  220: 
  221:     GrabLock = fun(Father) ->  
  222: 		       mnesia:read(a, {a,9}, write),
  223: 		       Father ! locked,
  224: 		       receive cont -> ok end end,
  225: 
  226:     Pid1 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end),    
  227:     ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait
  228: 
  229:     put(count,0),
  230:     Restart = fun(Locker,Fun) ->
  231: 		      Count = get(count),
  232: 		      case {Count,(catch Fun())}  of
  233: 			  {0, {'EXIT', R}} ->
  234: 			      Locker ! cont,
  235: 			      put(count, Count+1),
  236: 			      erlang:yield(),
  237: 			      exit(R);
  238: 			  Else ->
  239: 			      Else
  240: 		      end
  241: 	      end,
  242:     
  243:     ?match({1,{[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}},
  244: 	   ok(Restart,[Pid1,fun() -> Eval(Q1) end])),
  245:     
  246:     Pid2 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end),
  247:     ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait
  248:     put(count,0),
  249:     ?match({1,{[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}},
  250: 	   ok(Restart,[Pid2, fun() -> Eval(Q2) end])),
  251: 
  252: %% Basic test     
  253:     Cursor = fun() ->
  254: 		     QC = qlc:cursor(Q1),
  255: 		     qlc:next_answers(QC) 
  256: 	     end,
  257: 
  258:     ?match([{a,{a,9},91}], ok(Cursor, [])),
  259:     %% Lock 
  260: 
  261:     Pid3 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end),    
  262:     ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait
  263:     put(count,0),
  264:     
  265:     ?match({1,[{a,{a,9},91}]}, ok(Restart,[Pid3, Cursor])),
  266:     QC1 = ok(fun() -> qlc:cursor(Q1) end, []),
  267:     ?match({'EXIT', _},  qlc:next_answers(QC1)),
  268:     ?match({aborted,_},  ok(fun()->qlc:next_answers(QC1)end,[])),
  269:     ?verify_mnesia(Ns, []).
  270: 
  271: 
  272: frag(suite) -> [];
  273: frag(doc) -> [];
  274: frag(Config) ->
  275:     Ns = init_testcases(ram_copies,Config),
  276:     QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a),"
  277: 		 "     Val == 90 + Key]">>),
  278:     QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b),"
  279: 		 "     Key == 90 + Val]">>),
  280:     
  281:     Activate = 
  282: 	fun(Tab) ->
  283: 		?match({atomic,ok},mnesia:change_table_frag(Tab, {activate, []})),
  284: 		Dist = mnesia_frag_test:frag_dist(Tab),
  285: 		?match({atomic,ok},mnesia:change_table_frag(Tab,{add_frag,Dist}))
  286: 	end,
  287:     Activate(a),
  288:     Activate(b),
  289: 
  290:     Fun = fun(Tab) -> mnesia:table_info(Tab, frag_names) end,
  291:     FTs = mnesia:activity(sync_dirty, Fun, [a], mnesia_frag) ++
  292: 	mnesia:activity(sync_dirty, Fun, [b], mnesia_frag),
  293:     Size = fun(Tab) -> mnesia:dirty_rpc(Tab, mnesia, table_info, [Tab,size]) end,
  294: 
  295:     %% Verify that all data doesn't belong to the same frag.
  296:     ?match([], [{Tab,Size(Tab)} || Tab <- FTs,
  297: 				   Size(Tab) =< 0]),
  298:     
  299:     FA = fun() -> qlc:e(QA) end,
  300:     FB = fun() -> qlc:e(QB) end,
  301:     ?match([{a,{a,5},95}], mnesia:activity(transaction,FA,[],mnesia_frag)),
  302:     ?match([{b,{b,95},5}], mnesia:activity(transaction,FB,[],mnesia_frag)),
  303:     
  304:     ?verify_mnesia(Ns, []).
  305: 
  306: info(suite) -> [];
  307: info(doc) -> [];
  308: info(Config) ->
  309:     Ns = init_testcases(ram_copies, Config),
  310:     Q1 = handle(recs(), 
  311: 		<<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]"
  312: 		 >>),
  313:     
  314:     Q2 = handle(recs(), 
  315: 		<<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]"
  316: 		 >>),
  317:     
  318:     Q3 = handle(recs(), 
  319: 		<<"[Q || Q = #a{v=91} <- mnesia:table(a)]"
  320: 		 >>),
  321:     
  322:     %% FIXME compile and check results!
  323:     
  324:     ?match(ok,io:format("~s~n",[qlc:info(Q1)])),
  325:     ?match(ok,io:format("~s~n",[qlc:info(Q2)])),
  326:     ?match(ok,io:format("~s~n",[qlc:info(Q3)])),
  327:  
  328:     ?verify_mnesia(Ns, []).
  329: 
  330: ok(Fun,A) ->
  331:     case mnesia:transaction(Fun,A) of
  332: 	{atomic, R} -> R;
  333: 	E -> E
  334:     end.
  335: 
  336: 
  337: mnesia_down(suite) -> [];
  338: mnesia_down(doc) ->
  339:     ["Test bug OTP-7968, which crashed mnesia when a"
  340:      "mnesia_down came after qlc had been invoked"];
  341: mnesia_down(Config) when is_list(Config) ->
  342:     [N1,N2] = init_testcases(ram_copies,Config),
  343:     QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b),"
  344: 		 "     Val == Key - 90]">>),
  345: 
  346:     Tester = self(),    
  347:     
  348:     Eval = fun() -> 
  349: 		   Cursor = qlc:cursor(QB), %% Forces another process
  350: 		   Res = qlc:next_answers(Cursor),
  351: 		   Tester ! {qlc, self(), Res},
  352: 		   {Mod, Tid, Ts} = get(mnesia_activity_state),
  353: 		   receive
  354: 		       continue ->
  355: 			   io:format("Continuing ~p ~p ~n",[self(), {Mod, Tid, Ts}]),
  356: 			   io:format("ETS ~p~n",[ets:tab2list(element(2,Ts))]),
  357: 			   io:format("~p~n",[process_info(self(),messages)]),
  358: 			   Res
  359: 		   end
  360: 	   end,
  361:     spawn(fun() -> TransRes = mnesia:transaction(Eval), Tester ! {test,TransRes} end),
  362: 
  363:     TMInfo = fun() ->
  364: 		     TmInfo = mnesia_tm:get_info(5000),
  365: 		     mnesia_tm:display_info(user, TmInfo)
  366: 	     end,
  367:     receive
  368: 	{qlc, QPid, QRes} ->
  369: 	    ?match([{b,{b,95},5}], QRes),
  370: 	    TMInfo(),
  371: 	    mnesia_test_lib:kill_mnesia([N2]),
  372: 	    %%timer:sleep(1000),
  373: 	    QPid ! continue
  374:     after 2000 ->
  375: 	    exit(timeout1)
  376:     end,
  377: 
  378:     receive
  379: 	{test, QRes2} ->
  380: 	    ?match({atomic, [{b,{b,95},5}]}, QRes2)
  381:     after 2000 ->
  382: 	    exit(timeout2)
  383:     end,
  384:     
  385:     ?verify_mnesia([N1], [N2]).
  386: 
  387: 
  388: nested_qlc(suite) -> [];
  389: nested_qlc(doc) ->
  390:     ["Test bug in OTP-7968 (the second problem) where nested"
  391:      "transaction don't work as expected"];
  392: nested_qlc(Config) when is_list(Config) ->
  393:     Ns = init_testcases(ram_copies,Config),    
  394:     Res = as_with_bs(),
  395:     ?match([_|_], Res),
  396:     top_as_with_some_bs(10),
  397:     
  398:     ?verify_mnesia(Ns, []).
  399: 
  400: 
  401: %% Code from Daniel 
  402: bs_by_a_id(A_id) ->
  403:     find(qlc:q([ B || B={_,_,F_id} <- mnesia:table(b), F_id == A_id])).
  404: 
  405: as_with_bs() ->
  406:     find(qlc:q([ {A,bs_by_a_id(Id)} ||
  407: 		   A = {_, {a,Id}, _} <- mnesia:table(a)])).
  408: 
  409: top_as_with_some_bs(Limit) ->
  410:     top(
  411:       qlc:q([ {A,bs_by_a_id(Id)} ||
  412: 		A = {_, {a,Id}, _} <- mnesia:table(a)]),
  413:       Limit,
  414:       fun(A1,A2) -> A1 < A2  end
  415:      ).
  416: 
  417: % --- utils
  418: 
  419: find(Q) ->
  420:     F = fun() -> qlc:e(Q) end,
  421:     {atomic, Res} = mnesia:transaction(F),
  422:     Res.
  423: 
  424: % --- it returns top Limit results from query Q ordered by Order sort function
  425: top(Q, Limit, Order) ->
  426:     Do = fun() ->
  427: 		 OQ = qlc:sort(Q, [{order,Order}]),
  428: 		 QC = qlc:cursor(OQ),
  429: 		 Res = qlc:next_answers(QC, Limit),
  430: 		 qlc:delete_cursor(QC),
  431: 		 Res
  432: 	 end,
  433:     {atomic, Res} = mnesia:transaction(Do),
  434:     Res.
  435: 
  436: %% To keep mnesia suite backward compatible,
  437: %% we compile the queries in runtime when qlc is available
  438: %% Compiles and returns a handle to a qlc
  439: handle(Expr) ->
  440:     handle(<<>>,Expr).
  441: handle(Records,Expr) ->
  442:     case catch handle2(Records,Expr) of
  443: 	{ok, Handle} ->
  444: 	    Handle;
  445: 	Else ->
  446: 	    ?match(ok, Else)
  447:     end.
  448: 
  449: handle2(Records,Expr) ->
  450:     {FN,Mod} = temp_name(),
  451:     ModStr = list_to_binary("-module(" ++ atom_to_list(Mod) ++ ").\n"),
  452:     Prog = <<
  453: 	    ModStr/binary,
  454: 	    "-include_lib(\"stdlib/include/qlc.hrl\").\n",
  455: 	    "-export([tmp/0]).\n",
  456: 	    Records/binary,"\n",
  457: 	    "tmp() ->\n",
  458: %%	    "   _ = (catch throw(fvalue_not_reset)),"
  459: 	    "   qlc:q( ",
  460: 	    Expr/binary,").\n">>,
  461: 
  462:     ?match(ok,file:write_file(FN,Prog)),
  463:     {ok,Forms} = epp:parse_file(FN,"",""),
  464:     {ok,Mod,Bin} = compile:forms(Forms),
  465:     code:load_binary(Mod,FN,Bin),
  466:     {ok, Mod:tmp()}.
  467: 
  468: setup(Config) ->
  469:     put(mts_config,Config),
  470:     put(mts_tf_counter,0).
  471: 
  472: temp_name() ->
  473:     Conf = get(mts_config),
  474:     C = get(mts_tf_counter),
  475:     put(mts_tf_counter,C+1),
  476:     {filename:join([proplists:get_value(priv_dir,Conf, "."),
  477: 		    "tempfile"++integer_to_list(C)++".tmp"]),
  478:      list_to_atom("tmp" ++ integer_to_list(C))}.