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))}.