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