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