aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/test/qlc_SUITE.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/stdlib/test/qlc_SUITE.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/stdlib/test/qlc_SUITE.erl')
-rw-r--r--lib/stdlib/test/qlc_SUITE.erl8179
1 files changed, 8179 insertions, 0 deletions
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
new file mode 100644
index 0000000000..ff11ebc6bf
--- /dev/null
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -0,0 +1,8179 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------
+%%% Purpose:Test Suite for the 'qlc' module.
+%%%-----------------------------------------------------------------
+-module(qlc_SUITE).
+
+-define(QLC, qlc).
+-define(QLCs, "qlc").
+
+%-define(debug, true).
+
+%% There are often many tests per testcase. Most tests are copied to a
+%% module, a file. The file is compiled and the test run. Should the
+%% test fail, the module file is not removed from ?privdir, but is
+%% kept for inspection. The name of the file is
+%% ?privdir/qlc_test_CASE.erl.
+-define(TESTMODULE, qlc_test).
+-define(TESTCASE, testcase_name).
+
+-ifdef(debug).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(datadir, ?QLCs ++ "_SUITE_data").
+-define(privdir, ?QLCs ++ "_SUITE_priv").
+-define(testcase, current_testcase). % don't know
+-define(t, test_server).
+-else.
+-include("test_server.hrl").
+-define(datadir, ?config(data_dir, Config)).
+-define(privdir, ?config(priv_dir, Config)).
+-define(testcase, ?config(?TESTCASE, Config)).
+-endif.
+
+-include_lib("stdlib/include/ms_transform.hrl").
+
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+-export([parse_transform/1,
+ badarg/1, nested_qlc/1, unused_var/1, lc/1, fun_clauses/1,
+ filter_var/1, single/1, exported_var/1, generator_vars/1,
+ nomatch/1, errors/1, pattern/1,
+
+ evaluation/1,
+ eval/1, cursor/1, fold/1, eval_unique/1, eval_cache/1, append/1,
+ evaluator/1, string_to_handle/1, table/1, process_dies/1,
+ sort/1, keysort/1, filesort/1, cache/1, cache_list/1, filter/1,
+ info/1, nested_info/1, lookup1/1, lookup2/1, lookup_rec/1,
+ indices/1, pre_fun/1, skip_filters/1,
+
+ table_impls/1,
+ ets/1, dets/1,
+
+ join/1,
+ join_option/1, join_filter/1, join_lookup/1, join_merge/1,
+ join_sort/1, join_complex/1,
+
+ tickets/1,
+ otp_5644/1, otp_5195/1, otp_6038_bug/1, otp_6359/1, otp_6562/1,
+ otp_6590/1, otp_6673/1, otp_6964/1, otp_7114/1, otp_7238/1,
+ otp_7232/1, otp_7552/1, otp_6674/1, otp_7714/1,
+
+ manpage/1,
+
+ compat/1,
+ backward/1, forward/1]).
+
+%% Internal exports.
+-export([bad_table_throw/1, bad_table_exit/1, default_table/1, bad_table/1,
+ bad_table_format/1, bad_table_format_arity/1, bad_table_traverse/1,
+ bad_table_post/1, bad_table_lookup/1, bad_table_max_lookup/1,
+ bad_table_info_arity/1, bad_table_info_fun_n_objects/1,
+ bad_table_info_fun_indices/1, bad_table_info_fun_keypos/1,
+ bad_table_key_equality/1]).
+-export([evaluator_2/2]).
+-export([prep_scratchdir/1, truncate_tmpfile/2, crash/2, crash_tmpfile/2]).
+-export([etsc/2, etsc/3, create_ets/2, lookup_keys/1]).
+-export([strip_qlc_call/1, join_info/1, join_info_count/1]).
+-export([i/1, i/2, format_info/2]).
+
+-export([table_kill_parent/2, table_parent_throws/2,
+ table_parent_exits/2, table_bad_parent_fun/2]).
+-export([table/2, table/3, stop_list/2, table_error/2, table_error/3,
+ table_lookup_error/1]).
+
+%% error_logger
+-export([install_error_logger/0, uninstall_error_logger/0,
+ read_error_logger/0]).
+-export([init/1,
+ handle_event/2, handle_call/2, handle_info/2,
+ terminate/2]).
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(5)).
+
+init_per_testcase(Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{?TESTCASE, Case}, {watchdog, Dog} | Config].
+
+fin_per_testcase(_Case, _Config) ->
+ Dog = ?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [parse_transform, evaluation, table_impls, join, tickets, manpage, compat].
+
+parse_transform(suite) ->
+ [badarg, nested_qlc, unused_var, lc, fun_clauses, filter_var,
+ single, exported_var, generator_vars, nomatch, errors, pattern].
+
+badarg(doc) ->
+ "Badarg.";
+badarg(suite) -> [];
+badarg(Config) when is_list(Config) ->
+ Ts =
+ [{badarg,
+ <<"-import(qlc, [q/1, q/2]).
+ q(_, _, _) -> ok.
+
+ badarg() ->
+ qlc:q(foo),
+ qlc:q(foo, cache_all),
+ qlc:q(foo, cache_all, extra),
+ q(bar),
+ q(bar, cache_all),
+ q(bar, cache_all, extra).
+ ">>,
+ [],
+ {errors,[{5,?QLC,not_a_query_list_comprehension},
+ {6,?QLC,not_a_query_list_comprehension},
+ {8,?QLC,not_a_query_list_comprehension},
+ {9,?QLC,not_a_query_list_comprehension}],
+ []}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+nested_qlc(doc) ->
+ "Nested qlc expressions.";
+nested_qlc(suite) -> [];
+nested_qlc(Config) when is_list(Config) ->
+ %% Nested QLC expressions. X is bound before the first one; Z and X
+ %% before the second one.
+ Ts =
+ [{nested_qlc1,
+ <<"nested_qlc() ->
+ X = 3, % X unused
+ Q = qlc:q([Y ||
+ X <- % X shadowed
+ begin Z = 3,
+ qlc:q([Y ||
+ Y <- [Z],
+ X <- [1,2,3], % X shadowed
+ X < Y])
+ end,
+ Y <-
+ [y],
+ Y > X]),
+ [y, y] = qlc:e(Q),
+ ok.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{{2,15},erl_lint,{unused_var,'X'}},
+ {{4,29},erl_lint,{shadowed_var,'X',generate}},
+ {{8,49},erl_lint,{shadowed_var,'X',generate}}]}},
+
+ {nested_qlc2,
+ <<"nested_qlc() ->
+ H0 = qlc:append([a,b], [c,d]),
+ qlc:q([{X,Y} ||
+ X <- H0,
+ Y <- qlc:q([{X,Y} ||
+ X <- H0, % X shadowed
+ Y <- H0])]),
+ ok.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{{6,39},erl_lint,{shadowed_var,'X',generate}}]}}
+ ],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+unused_var(doc) ->
+ "Unused variable with a name that should not be introduced.";
+unused_var(suite) -> [];
+unused_var(Config) when is_list(Config) ->
+ Ts =
+ [{unused_var,
+ <<"unused_var() ->
+ qlc:q([X || begin Y1 = 3, true end, % Y1 unused
+ Y <- [1,2,3],
+ X <- [a,b,c],
+ X < Y]).
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{{2,33},erl_lint,{unused_var,'Y1'}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+lc(doc) ->
+ "Ordinary LC expression.";
+lc(suite) -> [];
+lc(Config) when is_list(Config) ->
+ Ts =
+ [{lc,
+ <<"lc() ->
+ [X || X <- [], X <- X]. % X shadowed
+ ">>,
+ [],
+ {warnings,[{{2,30},erl_lint,{shadowed_var,'X',generate}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+fun_clauses(doc) ->
+ "Fun with several clauses.";
+fun_clauses(suite) -> [];
+fun_clauses(Config) when is_list(Config) ->
+ Ts =
+ [{fun_clauses,
+ <<"fun_clauses() ->
+ {X,X1,X2} = {1,2,3},
+ F = fun({X}) -> qlc:q([X || X <- X]); % X shadowed (fun, generate)
+ ([X]) -> qlc:q([X || X <- X]) % X shadowed (fun, generate)
+ end,
+ {F,X,X1,X2}.
+ ">>,
+ [],
+ {warnings,[{{3,22},erl_lint,{shadowed_var,'X','fun'}},
+ {{3,41},erl_lint,{shadowed_var,'X',generate}},
+ {{4,22},erl_lint,{shadowed_var,'X','fun'}},
+ {{4,41},erl_lint,{shadowed_var,'X',generate}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+filter_var(doc) ->
+ "Variable introduced in filter.";
+filter_var(suite) -> [];
+filter_var(Config) when is_list(Config) ->
+ Ts =
+ [{filter_var,
+ <<"filter_var() ->
+ qlc:q([X ||
+ Y <- [X ||
+ X <- [1,2,3]],
+ begin X = Y, true end]).
+ ">>,
+ [],
+ []},
+
+ {unsafe_filter_var,
+ <<"unsafe_filter_var() ->
+ qlc:q([{X,V} || X <- [1,2],
+ case {a} of
+ {_} ->
+ true;
+ V ->
+ V
+ end]).
+ ">>,
+ [],
+ {errors,[{{2,25},erl_lint,{unsafe_var,'V',{'case',{3,19}}}}],[]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+
+single(doc) ->
+ "Unused pattern variable.";
+single(suite) -> [];
+single(Config) when is_list(Config) ->
+ Ts =
+ [{single,
+ <<"single() ->
+ qlc:q([X || {X,Y} <- [{1,2}]]), % Y unused
+ qlc:q([[] || [] <- [[]]]).
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{{2,30},erl_lint,{unused_var,'Y'}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+exported_var(doc) ->
+ "Exported variable in list expression (rhs of generator).";
+exported_var(suite) -> [];
+exported_var(Config) when is_list(Config) ->
+ Ts =
+ [{exported_var,
+ <<"exported() ->
+ qlc:q([X || X <- begin
+ case foo:bar() of
+ 1 -> Z = a;
+ 2 -> Z = b
+ end,
+ [Z = 3, Z = 3] % Z exported (twice...)
+ end
+ ]).
+ ">>,
+ [warn_export_vars],
+ {warnings,[{{7,37},erl_lint,{exported_var,'Z',{'case',{3,36}}}},
+ {{7,44},erl_lint,{exported_var,'Z',{'case',{3,36}}}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+generator_vars(doc) ->
+ "Errors for generator variable used in list expression.";
+generator_vars(suite) -> [];
+generator_vars(Config) when is_list(Config) ->
+ Ts =
+ [{generator_vars,
+ <<"generator_vars() ->
+ qlc:q([X ||
+ Z <- [1,2],
+ X <- begin
+ case 1 of
+ 1 -> Z = a; % used_generator_variable
+ 2 -> Z = b % used_generator_variable
+ end,
+ [Z = 3, Z = 3] % used_generator_variable (2)
+ end
+ ]).
+ ">>,
+ [],
+ {errors,[{{6,41},?QLC,{used_generator_variable,'Z'}},
+ {{7,41},?QLC,{used_generator_variable,'Z'}},
+ {{9,33},?QLC,{used_generator_variable,'Z'}},
+ {{9,40},?QLC,{used_generator_variable,'Z'}}],
+ []}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+nomatch(doc) ->
+ "Unreachable clauses also found when compiling.";
+nomatch(suite) -> [];
+nomatch(Config) when is_list(Config) ->
+ Ts =
+ [{unreachable1,
+ <<"unreachable1() ->
+ qlc:q([X || X <- [1,2],
+ case X of
+ true -> false;
+ true -> true % clause cannot match
+ end]).
+ ">>,
+ [],
+ {warnings,[{5,v3_kernel,{nomatch_shadow,4}}]}},
+
+ {nomatch1,
+ <<"generator1() ->
+ qlc:q([3 || {3=4} <- []]).
+ ">>,
+ [],
+ {warnings,[{{2,27},qlc,nomatch_pattern}]}},
+
+ {nomatch2,
+ <<"nomatch() ->
+ etsc(fun(E) ->
+ Q = qlc:q([3 || {3=4} <- ets:table(E)]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{1},{2}]).
+ ">>,
+ [],
+ {warnings,[{{3,33},qlc,nomatch_pattern}]}},
+
+ {nomatch3,
+ <<"nomatch() ->
+ etsc(fun(E) ->
+ Q = qlc:q([{A,B,C,D} || A=B={C=D}={_,_} <-
+ ets:table(E)]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{1,2},{2,3}]).
+ ">>,
+ [],
+ {warnings,[{{3,52},qlc,nomatch_pattern}]}},
+
+ {nomatch4,
+ <<"nomatch() ->
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {<<X>>} = {<<Y>>} <-
+ ets:table(E)]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{<<34>>},{<<40>>}]).
+ ">>,
+ [],
+ {errors,[{{3,48},erl_lint,illegal_bin_pattern}],[]}},
+
+ {nomatch5,
+ <<"nomatch() ->
+ etsc(fun(E) ->
+ Q = qlc:q([t || {\"a\"++\"b\"} = {\"ac\"} <-
+ ets:table(E)]),
+ [t] = qlc:eval(Q),
+ [\"ab\"] = lookup_keys(Q)
+ end, [{\"ab\"}]).
+ ">>,
+ [],
+ {warnings,[{3,v3_core,nomatch}]}}
+
+ ],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+
+errors(doc) ->
+ "Errors within qlc expressions also found when compiling.";
+errors(suite) -> [];
+errors(Config) when is_list(Config) ->
+ Ts =
+ [{errors1,
+ <<"errors1() ->
+ qlc:q([X || X <- A]). % A unbound
+ ">>,
+ [],
+ {errors,[{{2,33},erl_lint,{unbound_var,'A'}}],[]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+pattern(doc) ->
+ "Patterns.";
+pattern(suite) -> [];
+pattern(Config) when is_list(Config) ->
+ Ts = [
+ <<"%% Records in patterns. No lookup.
+ L = [#a{k=#k{v=91}}],
+ H = qlc:q([Q || Q = #a{k=#k{v=91}} <- qlc_SUITE:table(L, 2, [])]),
+ {qlc,_,[{generate,_,{table,{call,_,_,_}}}], []} = i(H),
+ L = qlc:e(H),
+ {call, _, _q, [{lc,_,{var,_,'Q'},
+ [{generate,_,
+ {match,_,_,_},
+ {call,_,_,_}}]}]}
+ = i(H, {format,abstract_code})">>,
+
+ <<"%% No matchspec since there is a binary in the pattern.
+ etsc(fun(E) ->
+ Q = qlc:q([A || {<<A:3/unit:8>>} <- ets:table(E)]),
+ [_] = qlc:eval(Q),
+ {qlc,_,[{generate,_,{table,_}}], []} = i(Q)
+ end, [{<<\"hej\">>}])">>
+
+ ],
+ ?line run(Config, <<"-record(a, {k,v}).
+ -record(k, {t,v}).\n">>, Ts),
+ ok.
+
+evaluation(suite) ->
+ [eval, cursor, fold, eval_unique, eval_cache, append, evaluator,
+ string_to_handle, table, process_dies, sort, keysort, filesort, cache,
+ cache_list, filter, info, nested_info, lookup1, lookup2, lookup_rec,
+ indices, pre_fun, skip_filters].
+
+eval(doc) ->
+ "eval/2";
+eval(suite) -> [];
+eval(Config) when is_list(Config) ->
+ ScratchDir = filename:join([?privdir, "scratch","."]),
+
+ Ts = [<<"{'EXIT',{badarg,_}} = (catch qlc:eval(not_a_qlc)),
+ H = qlc:q([X || X <- [1,2]]),
+ {'EXIT',{{unsupported_qlc_handle,{qlc_handle,foo}},_}}=
+ (catch qlc:e({qlc_handle,foo})),
+ {'EXIT',{badarg,_}} = (catch qlc:eval(H, [{unique_all,badarg}])),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:eval(H, [{spawn_options,badarg}])),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:eval(H, [{unique_all,true},{bad,arg}])),
+ {throw,t} =
+ (catch {any_term,qlc:e(qlc:q([X || X <- throw({throw,t})]))}),
+ M = qlc,
+ {'EXIT',{badarg,_}} = (catch M:q(bad))">>,
+
+ [<<"Dir = \"">>,ScratchDir,<<"\",
+ qlc_SUITE:prep_scratchdir(Dir),
+
+ E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ H = qlc:q([{X,Y} || Y <- [1,2],
+ X <- qlc:sort(ets:table(E),{tmpdir,Dir}),
+ qlc_SUITE:truncate_tmpfile(Dir, 0)]),
+ R = qlc:eval(H),
+ ets:delete(E),
+ {error,_,{bad_object,_}} = R,
+ \"the tempo\" ++ _ = lists:flatten(qlc:format_error(R))">>],
+
+ [<<"Dir = \"">>,ScratchDir,<<"\",
+ qlc_SUITE:prep_scratchdir(Dir),
+
+ E = ets:new(foo, []),
+ Bin = term_to_binary(lists:seq(1,20000)),
+ [true || I <- lists:seq(1, 10), not ets:insert(E, {I, I, Bin})],
+ H = qlc:q([{X,Y} || Y <- [1,2],
+ X <- qlc:sort(ets:table(E),{tmpdir,Dir}),
+ qlc_SUITE:crash_tmpfile(Dir, 5)]),
+ R = qlc:eval(H),
+ ets:delete(E),
+ {error,_,{bad_object,_}} = R">>],
+
+ <<"E = ets:new(test, []),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_throw(E),
+ Y <- ets:table(E)]),
+ R1 = (catch {any_term,qlc:eval(H, {unique_all,false})}),
+ R2 = (catch {any_term,qlc:eval(H, {unique_all,true})}),
+ ets:delete(E),
+ true = {throw,bad_pre_fun} == R1,
+ true = {throw,bad_pre_fun} == R2">>,
+
+ <<"E = ets:new(test, []),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_exit(E),
+ Y <- ets:table(E)]),
+ R1 = (catch qlc:eval(H, {unique_all,false})),
+ R2 = (catch qlc:eval(H, {unique_all,true})),
+ ets:delete(E),
+ {'EXIT',{bad_pre_fun,_}} = R1,
+ {'EXIT',{bad_pre_fun,_}} = R2">>,
+
+ <<"Q = qlc:q([X || X <- [4,3,2,1,0,-1], begin 3/X > 0 end]),
+ {'EXIT',{badarith,_}} = (catch qlc:eval(Q, {unique_all,false})),
+ {'EXIT',{badarith,_}} = (catch qlc:eval(Q, {unique_all,true}))
+ ">>,
+
+ <<"[1,2] = qlc:eval(qlc:q([X || X <- [1,2]])),
+ [1,2,3,4] = qlc:eval(qlc:append([1,2],[3,4])),
+ [1,2] = qlc:eval(qlc:sort([2,1])),
+ E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ [{1},{2}] = lists:sort(qlc:eval(ets:table(E))),
+ true = ets:delete(E)">>,
+
+ <<"H = qlc:q([X || X <- [1,2],
+ begin F = fun() ->
+ qlc:e(qlc:q([Y || Y <- [1,2]])) end,
+ F() == (fun f/0)() end]),
+ [1,2] = qlc:e(H),
+ ok.
+
+ f() -> [1,2].
+ foo() -> bar">>,
+
+ <<"C1_0_1 = [1,2],
+ %% The PT cannot rename C to C1_0_1; another name is chosen.
+ [1,2] = qlc:eval(qlc:q([C || C <- C1_0_1]))">>,
+
+ <<"H = qlc:q([X || {X,X} <- [{1,a},{2,2},{b,b},{3,4}]]),
+ [2,b] = qlc:e(H),
+ H1 = qlc:q([3 || {X,X} <- [{1,a},{2,2},{b,b},{3,4}]]),
+ [3,3] = qlc:e(H1)">>,
+
+ %% Just to cover a certain line in qlc.erl (avoids returning [])
+ <<"E = ets:new(foo, []),
+ Bin = term_to_binary(lists:seq(1,20000)),
+ [true || I <- lists:seq(1, 10), not ets:insert(E, {I, I, Bin})],
+ H = qlc:q([{X,Y} || Y <- [1,2], X <- qlc:sort(ets:table(E))]),
+ R = qlc:eval(H),
+ ets:delete(E),
+ 20 = length(R)">>,
+
+ <<"H = qlc:q([{A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W} ||
+ {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W} <-
+ [{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w}]]),
+ [_] = qlc:e(H)">>,
+
+ <<"H = qlc:q([Y || Y <- [1,2]],
+ {unique, begin [T] = qlc:e(qlc:q([X || X <- [true]],
+ cache)),
+ T end}),
+ [1,2] = qlc:e(H)">>
+
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+cursor(doc) ->
+ "cursor/2";
+cursor(suite) -> [];
+cursor(Config) when is_list(Config) ->
+ ScratchDir = filename:join([?privdir, "scratch","."]),
+ Ts = [<<"{'EXIT',{badarg,_}} =
+ (catch qlc:cursor(fun() -> not_a_cursor end)),
+ H0 = qlc:q([X || X <- throw({throw,t})]),
+ {throw,t} = (catch {any_term,qlc:cursor(H0)}),
+ H = qlc:q([X || X <- [1,2]]),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:cursor(H,{spawn_options, [a|b]})),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:cursor(H,{bad_option,true}))">>,
+
+ <<"{'EXIT',{badarg,_}} = (catch qlc:delete_cursor(not_a_cursor))">>,
+
+ [<<"Dir = \"">>,ScratchDir,<<"\",
+ qlc_SUITE:prep_scratchdir(Dir), % kludge
+ E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ H = qlc:q([{X,Y} || begin put('$qlc_tmpdir', true), true end,
+ Y <- [1,2],
+ X <- qlc:sort(ets:table(E),{tmpdir,Dir}),
+ qlc_SUITE:truncate_tmpfile(Dir, 0)]),
+ C = qlc:cursor(H),
+ R = qlc:next_answers(C, all_remaining),
+ qlc:delete_cursor(C),
+ erase('$qlc_tmpdir'),
+ ets:delete(E),
+ {error,_,{bad_object,_}} = R">>],
+
+ <<"H1 = qlc:q([X || X <- [1,2]]),
+ C1 = qlc:cursor(H1),
+ [1,2] = qlc:next_answers(C1, all_remaining),
+ [] = qlc:next_answers(C1),
+ [] = qlc:next_answers(C1),
+ ok = qlc:delete_cursor(C1),
+
+ H2 = qlc:append([1,2],[3,4]),
+ C2 = qlc:cursor(H2),
+ [1,2,3,4] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2),
+
+ H3 = qlc:sort([2,1]),
+ C3 = qlc:cursor(H3),
+ [1,2] = qlc:next_answers(C3, all_remaining),
+ ok = qlc:delete_cursor(C3),
+
+ E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ H4 = ets:table(E),
+ C4 = qlc:cursor(H4),
+ [{1},{2}] = lists:sort(qlc:next_answers(C4, all_remaining)),
+ ok = qlc:delete_cursor(C4),
+ true = ets:delete(E)">>,
+
+ <<"H = qlc:q([{X,Y} || X <- [1,2], Y <- [a,b]]),
+ C = qlc:cursor(H, []),
+ [{1,a},{1,b}] = qlc:next_answers(C, 2),
+ [{2,a}] = qlc:next_answers(C, 1),
+ [{2,b}] = qlc:next_answers(C, all_remaining),
+ {'EXIT',{badarg,_}} = (catch qlc:next_answers(C, -1)),
+ P = self(),
+ Pid1 = spawn_link(fun() ->
+ {'EXIT',{not_cursor_owner,_}} =
+ (catch qlc:delete_cursor(C)),
+ P ! {self(), done} end),
+ Pid2 = spawn_link(fun() ->
+ {'EXIT',{not_cursor_owner,_}} =
+ (catch qlc:next_answers(C)),
+ P ! {self(), done} end),
+ receive {Pid1, done} -> ok end,
+ receive {Pid2, done} -> ok end,
+ ok = qlc:delete_cursor(C),
+ {'EXIT',{badarg,_}} = (catch qlc:next_answers(not_a_cursor)),
+ ok = qlc:delete_cursor(C)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ C1 = qlc:cursor(Q, [{unique_all,true}]),
+ [1,2] = qlc:next_answers(C1, all_remaining),
+ ok = qlc:delete_cursor(C1),
+ C2 = qlc:cursor(Q, [{unique_all,true}]),
+ [1,2] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ C1 = qlc:cursor(Q, [{unique_all,true},{spawn_options, []}]),
+ [1,2] = qlc:next_answers(C1, all_remaining),
+ ok = qlc:delete_cursor(C1),
+ C2 = qlc:cursor(Q, [{unique_all,true},{spawn_options, default}]),
+ [1,2] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ C1 = qlc:cursor(Q, [{unique_all,false},{spawn_options, []}]),
+ [1,2,1,2,1] = qlc:next_answers(C1, all_remaining),
+ ok = qlc:delete_cursor(C1),
+ C2 = qlc:cursor(Q, [{unique_all,false},{spawn_options, []}]),
+ [1,2,1,2,1] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ C1 = qlc:cursor(Q, [{unique_all,false}]),
+ [1,2,1,2,1] = qlc:next_answers(C1, all_remaining),
+ ok = qlc:delete_cursor(C1),
+ C2 = qlc:cursor(Q, [{unique_all,false}]),
+ [1,2,1,2,1] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+fold(doc) ->
+ "fold/4";
+fold(suite) -> [];
+fold(Config) when is_list(Config) ->
+ ScratchDir = filename:join([?privdir, "scratch","."]),
+ Ts = [<<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ {'EXIT',{badarg,_}} = (catch qlc:fold(F, [], Q, {bad,arg})),
+ {'EXIT',{badarg,_}} = (catch qlc:fold(F, [], badarg)),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:fold(F, [], {spawn_options, [a|b]})),
+ H = qlc:q([X || X <- throw({throw,t})]),
+ {throw,t} = (catch {any_term,qlc:fold(F, [], H)}),
+ [1,2] = qlc:fold(F, [], Q, {unique_all,true}),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:fold(F, [], Q, [{unique_all,bad}])),
+ [1,2,1,2,1] =
+ qlc:fold(F, [], Q, [{unique_all,false}])">>,
+
+ [<<"Dir = \"">>,ScratchDir,<<"\",
+ qlc_SUITE:prep_scratchdir(Dir),
+
+ E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ H = qlc:q([{X,Y} || Y <- [1,2],
+ X <- qlc:sort(ets:table(E),{tmpdir,Dir}),
+ qlc_SUITE:truncate_tmpfile(Dir, 0)]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ R = qlc:fold(F, [], H),
+ ets:delete(E),
+ {error,_,{bad_object,_}} = R">>],
+
+ <<"E = ets:new(test, []),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_throw(E),
+ Y <- ets:table(E)]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ R1 = (catch {any_term,qlc:fold(F, [], H, {unique_all,false})}),
+ R2 = (catch {any_term,qlc:fold(F, [], H, {unique_all,true})}),
+ ets:delete(E),
+ true = {throw,bad_pre_fun} == R1,
+ true = {throw,bad_pre_fun} == R2">>,
+
+ <<"E = ets:new(test, []),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_exit(E),
+ Y <- ets:table(E)]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ R1 = (catch qlc:fold(F, [], H, {unique_all,false})),
+ R2 = (catch qlc:fold(F, [], H, {unique_all,true})),
+ ets:delete(E),
+ {'EXIT',{bad_pre_fun,_}} = R1,
+ {'EXIT',{bad_pre_fun,_}} = R2">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ Q = qlc:q([X || X <- [1,2,1,2,1], throw({throw,wrong})]),
+ {throw,wrong} =
+ (catch {any_term,qlc:fold(F, [], Q, {unique_all,true})}),
+ {throw,wrong} =
+ (catch {any_term,qlc:fold(F, [], Q)})">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ Q = qlc:q([X || X <- [4,3,2,1,0,-1], begin 3/X > 0 end]),
+ {'EXIT',{badarith,_}} =
+ (catch qlc:fold(F, [], Q, {unique_all,true})),
+ {'EXIT',{badarith,_}} =
+ (catch qlc:fold(F, [], Q, [{unique_all,false}]))
+ ">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ [1,2] = qlc:fold(F, [], qlc:q([X || X <- [1,2]])),
+ [1,2,3,4] = qlc:fold(F, [], qlc:append([1,2],[3,4])),
+ [1,2] = qlc:fold(F, [], qlc:sort([2,1])),
+ E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ [{1},{2}] = lists:sort(qlc:fold(F, [], ets:table(E))),
+ true = ets:delete(E)">>,
+
+ <<"F = fun(_Obj, _A) -> throw({throw,fatal}) end,
+ Q = qlc:q([X || X <- [4,3,2]]),
+ {throw,fatal} =
+ (catch {any_term,qlc:fold(F, [], Q, {unique_all,true})}),
+ {throw,fatal} =
+ (catch {any_term,qlc:fold(F, [], Q, [{unique_all,false}])})">>,
+
+ <<"G = fun(_Obj, _A, D) -> 17/D end,
+ F = fun(Obj, A) -> G(Obj, A, 0) end,
+ Q = qlc:q([X || X <- [4,3,2]]),
+ {'EXIT',{badarith,_}} =
+ (catch qlc:fold(F, [], Q, {unique_all,true})),
+ {'EXIT',{badarith,_}} =
+ (catch qlc:fold(F, [], Q, [{unique_all,false}]))
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+eval_unique(doc) ->
+ "Test the unique_all option of eval.";
+eval_unique(suite) -> [];
+eval_unique(Config) when is_list(Config) ->
+ Ts = [<<"QLC1 = qlc:q([X || X <- qlc:append([[1,1,2], [1,2,3,2,3]])]),
+ [1,2,3] = qlc:eval(QLC1, {unique_all,true}),
+ QLC2 = qlc:q([X || X <- [1,2,1,2,1,2,1]]),
+ [1,2] = qlc:e(QLC2, {unique_all,true})">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc:append([ets:table(E), ets:table(E)])]),
+ R1 = qlc:e(H, {unique_all,false}),
+ R2 = qlc:e(H, {unique_all,true}),
+ ets:delete(E),
+ true = lists:sort(R1) == [{1,a},{1,a},{2,b},{2,b},{3,c},{3,c}],
+ true = lists:sort(R2) == [{1,a},{2,b},{3,c}]
+ ">>,
+
+ <<"Q1 = qlc:q([{X,make_ref()} || X <- [1,2,1,2]]),
+ [_,_] = qlc:e(Q1, {unique_all,true}),
+ [_,_,_,_] = qlc:e(Q1, {unique_all,false}),
+ [_,_] = qlc:e(Q1, [{unique_all,true}]),
+ Q2 = qlc:q([{X,make_ref()} || X <- qlc:append([[1,2,1,2]])]),
+ [_,_] = qlc:e(Q2, {unique_all,true}),
+ [_,_,_,_] = qlc:e(Q2, {unique_all,false}),
+ [_,_] = qlc:e(Q2, [{unique_all,true}])
+ ">>,
+
+ <<"Q = qlc:q([{X,make_ref()} || X <- qlc:sort([1,2,1,2])]),
+ [_, _] = qlc:e(Q, {unique_all,true}),
+ Q1 = qlc:q([X || X <- [1,2,1,2]]),
+ Q2 = qlc:q([{X,make_ref()} || X <- qlc:sort(Q1)]),
+ [_, _] = qlc:e(Q2, {unique_all,true})
+ ">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc:append([[1,2,1,2]])]),
+ [1,2,1,2] = qlc:e(H, {unique_all,false}),
+ [1,2] = qlc:e(H, {unique_all,true}),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(foo, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{1,a},{2,b},{3,c},{4,c},{4,d}]),
+ Q1 = qlc:q([{X,make_ref()} || {_, X} <- ets:table(E)]),
+ true = length(qlc:eval(Q1, {unique_all, true})) =:= 5,
+ Q2 = qlc:q([X || {_, X} <- ets:table(E)]),
+ true = length(qlc:eval(Q2, {unique_all, true})) =:= 4,
+ Q3 = qlc:q([element(2, X) || X <- ets:table(E)]),
+ true = length(qlc:eval(Q3, {unique_all, true})) =:= 4,
+ Q4 = qlc:q([1 || _X <- ets:table(E)]),
+ true = length(qlc:eval(Q4, {unique_all, true})) =:= 1,
+ true = ets:delete(E)
+ ">>,
+
+ <<"Q1 = qlc:q([X || X <- qlc:append([[1], [2,1]])]),
+ Q2 = qlc:q([X || X <- qlc:append([[2,1], [2]])]),
+ Q3 = qlc:q([{X,Y} || X <- Q1, Y <- Q2]),
+ [{1,2},{1,1},{2,2},{2,1}] = qlc:e(Q3, {unique_all,true}),
+ Q4 = qlc:q([{X,Y,make_ref()} || X <- Q1, Y <- Q2]),
+ [{1,2,_},{1,1,_},{2,2,_},{2,1,_}] = qlc:e(Q4, {unique_all,true})
+ ">>,
+
+ <<"Q1 = qlc:q([X || X <- [1,2,1]]),
+ Q2 = qlc:q([X || X <- [2,1,2]]),
+ Q3 = qlc:q([{X,Y} || X <- Q1, Y <- Q2]),
+ [{1,2},{1,1},{2,2},{2,1}] = qlc:e(Q3,{unique_all,true}),
+ Q4 = qlc:q([{X,Y,make_ref()} || X <- Q1, Y <- Q2]),
+ [{1,2,_},{1,1,_},{2,2,_},{2,1,_}] = qlc:e(Q4, {unique_all,true})
+ ">>,
+
+ <<"Q1 = qlc:q([X || {X,_} <- [{1,a},{1,b}]]),
+ [1] = qlc:e(Q1, {unique_all, true}),
+ Q2 = qlc:q([a || _ <- [{1,a},{1,b}]]),
+ [a] = qlc:e(Q2, {unique_all, true})
+ ">>,
+
+ <<"Q = qlc:q([SQV || SQV <- qlc:q([X || X <- [1,2,1]],unique)],
+ unique),
+ {call,_,_,[{lc,_,{var,_,'X'},[{generate,_,{var,_,'X'},_}]},_]} =
+ qlc:info(Q, [{format,abstract_code},unique_all]),
+ [1,2] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1]]),
+ {call,_,_,[{lc,_,{var,_,'X'},[{generate,_,{var,_,'X'},_}]},_]} =
+ qlc:info(Q, [{format,abstract_code},unique_all]),
+ [1,2] = qlc:e(Q, unique_all)">>,
+
+ <<"Q1 = qlc:sort([{1},{2},{3},{1}], [{unique,true}]),
+ Q = qlc:sort(Q1,[{unique,true}]),
+ {sort,{sort,{list,_},[{unique,true}]},[]} = i(Q)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+eval_cache(doc) ->
+ "Test the cache_all and unique_all options of eval.";
+eval_cache(suite) -> [];
+eval_cache(Config) when is_list(Config) ->
+ Ts = [
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- ets:table(E)]), % already unique, no cache...
+ {qlc, _,
+ [{generate, _, {qlc, _,
+ [{generate, _, {list, [3,4]}}],
+ [{unique,true}]}},
+ _,
+ {generate, _, {table,_}}],
+ [{unique,true}]} = i(H, [cache_all, unique_all]),
+ [{1},{2},{3},{4}] = qlc:e(H, [cache_all, unique_all]),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- ets:table(E)]), % no cache...
+ {qlc, _,
+ [{generate, _,{list, [3,4]}},
+ _,
+ {generate, _, {table,_}}],
+ []} = i(H, cache_all),
+ [{1},{2},{3},{1},{2},{3},{4}] = qlc:e(H, [cache_all]),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- qlc:q([X || X <- ets:table(E)], cache)]),
+ {qlc, _,
+ [{generate, _, {list, [3,4]}},
+ _,
+ {generate, _, {qlc, _,
+ [{generate, _, {table,_}}],
+ [{cache,ets}]}}],
+ []} = i(H, cache_all),
+ [{1},{2},{3},{1},{2},{3}] = qlc:e(H, [cache_all]),
+ ets:delete(E)">>,
+
+ <<"%% {cache_all,no} does not override {cache,true}.
+ E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- qlc:q([X || X <- ets:table(E)], cache)]),
+ {qlc, _,
+ [{generate, _, {list, [3,4]}},
+ _,
+ {generate, _, {qlc, _,
+ [{generate, _, {table,_}}],
+ [{cache,ets}]}}],
+ []} = i(H, {cache_all,no}),
+ [{1},{2},{3},{1},{2},{3}] = qlc:e(H, [cache_all]),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- ets:table(E)]),
+ {qlc, _,
+ [{generate, _, {qlc, _, [{generate, _,{list, [3,4]}}],
+ [{unique,true}]}},
+ _,
+ {generate, _,{table,_}}],
+ [{unique,true}]} = i(H, unique_all),
+ [{1},{2},{3},{4}] = qlc:e(H, [unique_all]),
+ ets:delete(E)">>,
+
+ %% cache_all is ignored
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2},{0}]),
+ H = qlc:q([X || X <- qlc:sort(ets:table(E))]),
+ {sort,_Table,[]} = i(H, cache_all),
+ [{0},{1},{2}] = qlc:e(H, cache_all),
+ ets:delete(E)">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,b},{1,a}]),
+ Q = qlc:q([X || X <- ets:table(E)], cache),
+ {table, _} = i(Q, []),
+ R = qlc:fold(F, [], Q, []),
+ ets:delete(E),
+ true = [{1,a},{1,a},{2,b}] == lists:sort(R)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2},{0}]),
+ H = qlc:q([X || X <- ets:table(E)], cache),
+ {table, _} = i(H, cache_all),
+ [{0},{1},{2}]= qlc:e(H, cache_all),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(foo, []),
+ true = ets:insert(E, [{1}, {2}]),
+ Q1 = qlc:q([{X} || X <- ets:table(E)]),
+ Q2 = qlc:q([{X,Y} || {X} <- Q1, {Y} <- Q1]),
+ {qlc, _, [{generate, _, {table, _}},
+ {generate, _, {qlc, _, [{generate, _, {table, _}}],
+ [{cache,ets}]}}],
+ []} = i(Q2, cache_all),
+ [{{1},{1}},{{1},{2}},{{2},{1}},{{2},{2}}] =
+ lists:sort(qlc:e(Q2, cache_all)),
+ ets:delete(E)">>,
+
+ <<"L1 = [1,2,3],
+ L2 = [4,5,6],
+ Q1 = qlc:append(L1, L2),
+ Q2 = qlc:q([{X} || X <- Q1]),
+ {qlc, _,[{generate, _,{append, [{list, L1}, {list, L2}]}}], []} =
+ i(Q2, [cache_all]),
+ [{1},{2},{3},{4},{5},{6}] = qlc:e(Q2, [cache_all])">>,
+
+ <<"H = qlc:sort(qlc:q([1 || _ <- [a,b]])),
+ {sort, {qlc, _, [{generate, _, {qlc, _, [{generate, _,
+ {list, [a,b]}}],
+ [{unique,true}]}}],
+ [{unique,true}]},
+ []} = i(H, unique_all),
+ [1] = qlc:e(H, unique_all)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+append(doc) ->
+ "Test the append function.";
+append(suite) -> [];
+append(Config) when is_list(Config) ->
+ Ts = [<<"C = qlc:cursor(qlc:q([X || X <- [0,1,2,3], begin 10/X > 0.0 end])),
+ R = (catch qlc:next_answers(C)),
+ {'EXIT',{badarith,_}} = R">>,
+
+ <<"C = qlc:cursor(qlc:q([X || X <- [0 | fun() -> exit(bad) end]])),
+ R = (catch qlc:next_answers(C)),
+ {'EXIT',bad} = R">>,
+
+ <<"{'EXIT',{badarg,_}} = (catch qlc:append([a], a)),
+ {'EXIT',{badarg,_}} = (catch qlc:append([[a],a]))">>,
+
+ <<"C = qlc:cursor(qlc:q([X || X <- [0,1,2,3],
+ begin throw({throw,wrong}), true end])),
+ {throw,wrong} = (catch {any_term,qlc:next_answers(C)})">>,
+
+ <<"QLC = qlc:q([X || X <- [0,1,2,3],
+ begin throw({throw,wrong}), true end]),
+ {throw,wrong} = (catch {any_term,qlc:eval(QLC)}),
+ {throw,wrong} =
+ (catch {any_term,qlc:e(QLC, {unique_all,true})})">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = qlc:q([X || X <- [4,5,6]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append([H1, H2])])),
+ true = R == [1,2,3,4,5,6]">>,
+
+ <<"H1 = [1,2,3],
+ H2 = qlc:q([X || X <- [4,5,6]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append(H1, H2)])),
+ true = R == [1,2,3,4,5,6]">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = qlc:q([X || X <- [4,5,6]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append(qlc:e(H1), H2)])),
+ true = R == [1,2,3,4,5,6]">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = [4,5,6],
+ R = qlc:e(qlc:q([X || X <- qlc:append(H1, H2)])),
+ true = R == [1,2,3,4,5,6]">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = qlc:q([X || X <- [4,5,6]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append([H1, H2, H1]), X < 5])),
+ true = R == [1,2,3,4,1,2,3]">>,
+
+ <<"R = qlc:e(qlc:q([X || X <- qlc:append([lista(), anrop()])])),
+ true = R == [a,b,1,2],
+ ok.
+
+ lista() ->
+ [a,b].
+
+ anrop() ->
+ qlc:q([X || X <- [1,2]]).
+ foo() -> bar">>,
+
+ %% Used to work up to R11B.
+ % <<"apa = qlc:e(qlc:q([X || X <- qlc:append([[1,2,3], ugly()])])),
+ % ok.
+ %
+ % ugly() ->
+ % [a | apa].
+ % foo() -> bar">>,
+
+
+ %% Maybe this one should fail.
+ <<"[a|b] = qlc:e(qlc:q([X || X <- qlc:append([[a|b]])])),
+ ok">>,
+
+ <<"17 = qlc:e(qlc:q([X || X <- qlc:append([[1,2,3],ugly2()])])),
+ ok.
+
+ ugly2() ->
+ [a | fun() -> 17 end].
+ foo() -> bar">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc:append([ets:table(E), apa])]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H)),
+ false = ets:info(E, safe_fixed),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H)),
+ false = ets:info(E, safe_fixed),
+ {'EXIT',{badarg,_}} = (catch qlc:cursor(H)),
+ false = ets:info(E, safe_fixed),
+ F = fun(Obj, A) -> A++[Obj] end,
+ {'EXIT',{badarg,_}} = (catch qlc:fold(F, [], H)),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = qlc:q([X || X <- [a,b,c]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append(H1,qlc:append(H1,H2))])),
+ true = R == [1,2,3,1,2,3,a,b,c]">>,
+
+ <<"H = qlc:q([X || X <- qlc:append([],qlc:append([[], []]))]),
+ [] = qlc:e(H)">>,
+
+ <<"Q1 = qlc:q([X || X <- [3,4,4]]),
+ Q2 = qlc:q([X || X <- qlc:sort(qlc:append([[1,2], Q1]))]),
+ [1,2,3,4,4] = qlc:e(Q2),
+ [1,2,3,4] = qlc:e(Q2, {unique_all,true})">>,
+
+ <<"[] = qlc:e(qlc:q([X || X <- qlc:append([])]))">>,
+
+ <<"Q1 = qlc:q([X || X <- [a,b]]),
+ Q2 = qlc:q([X || X <- [1,2]]),
+ Q3 = qlc:append([Q1, Q2, qlc:sort([2,1])]),
+ Q = qlc:q([X || X <- Q3]),
+ {append, [{list, [a,b]},
+ {list, [1,2]},
+ {sort,{list, [2,1]},[]}]} = i(Q),
+ [a,b,1,2,1,2] = qlc:e(Q)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+evaluator(doc) ->
+ "Simple call from evaluator.";
+evaluator(suite) -> [];
+evaluator(Config) when is_list(Config) ->
+ ?line true = is_alive(),
+ evaluator_2(Config, []),
+ ?line {ok, Node} = start_node(qlc_SUITE_evaluator),
+ ?line ok = rpc:call(Node, ?MODULE, evaluator_2, [Config, [compiler]]),
+ ?line ?t:stop_node(Node),
+ ok.
+
+evaluator_2(Config, Apps) ->
+ ?line lists:foreach(fun(App) -> true = code:del_path(App) end, Apps),
+ FileName = filename:join(?privdir, "eval"),
+ ?line ok = file:write_file(FileName,
+ <<"H = qlc:q([X || X <- L]),
+ [1,2,3] = qlc:e(H).">>),
+ ?line Bs = erl_eval:add_binding('L', [1,2,3], erl_eval:new_bindings()),
+ ?line ok = file:eval(FileName, Bs),
+
+ %% The error message is "handled" a bit too much...
+ %% (no trace of erl_lint left)
+ ?line ok = file:write_file(FileName,
+ <<"H = qlc:q([X || X <- L]), qlc:e(H).">>),
+ ?line {error,_} = file:eval(FileName),
+
+ %% Ugly error message; badarg is caught by file.erl.
+ ?line ok = file:write_file(FileName,
+ <<"H = qlc:q([Z || {X,Y} <- [{a,2}], Z <- [Y]]), qlc:e(H).">>),
+ ?line {error,_} = file:eval(FileName),
+
+ _ = file:delete(FileName),
+ ok.
+
+start_node(Name) ->
+ ?line PA = filename:dirname(code:which(?MODULE)),
+ ?t:start_node(Name, slave, [{args, "-pa " ++ PA}]).
+
+string_to_handle(doc) ->
+ "string_to_handle/1,2.";
+string_to_handle(suite) -> [];
+string_to_handle(Config) when is_list(Config) ->
+ ?line {'EXIT',{badarg,_}} = (catch qlc:string_to_handle(14)),
+ ?line {'EXIT',{badarg,_}} =
+ (catch qlc:string_to_handle("[X || X <- [a].", unique_all)),
+ ?line R1 = {error, _, {_,erl_scan,_}} = qlc:string_to_handle("'"),
+ ?line "1: unterminated " ++ _ = lists:flatten(qlc:format_error(R1)),
+ ?line {error, _, {_,erl_parse,_}} = qlc:string_to_handle("foo"),
+ ?line {'EXIT',{badarg,_}} = (catch qlc:string_to_handle("foo, bar.")),
+ ?line R3 = {error, _, {_,?QLC,not_a_query_list_comprehension}} =
+ qlc:string_to_handle("bad."),
+ ?line "1: argument is not" ++ _ = lists:flatten(qlc:format_error(R3)),
+ ?line R4 = {error, _, {_,?QLC,{used_generator_variable,'Y'}}} =
+ qlc:string_to_handle("[X || begin Y = [1,2], true end, X <- Y]."),
+ ?line "1: generated variable 'Y'" ++ _ =
+ lists:flatten(qlc:format_error(R4)),
+ ?line {error, _, {_,erl_lint,_}} = qlc:string_to_handle("[X || X <- A]."),
+ ?line H1 = qlc:string_to_handle("[X || X <- [1,2]]."),
+ ?line [1,2] = qlc:e(H1),
+ ?line H2 = qlc:string_to_handle("[X || X <- qlc:append([a,b],"
+ "qlc:e(qlc:q([X || X <- [c,d,e]])))]."),
+ ?line [a,b,c,d,e] = qlc:e(H2),
+ %% The generated fun has many arguments (erl_eval has a maximum of 20).
+ ?line H3 = qlc:string_to_handle(
+ "[{A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W} ||"
+ " {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W} <- []]."),
+ ?line [] = qlc:e(H3),
+ ?line Bs1 = erl_eval:add_binding('L', [1,2,3], erl_eval:new_bindings()),
+ ?line H4 = qlc:string_to_handle("[X || X <- L].", [], Bs1),
+ ?line [1,2,3] = qlc:e(H4),
+ ?line H5 = qlc:string_to_handle("[X || X <- [1,2,1,2]].", [unique, cache]),
+ ?line [1,2] = qlc:e(H5),
+
+ ?line Ets = ets:new(test, []),
+ ?line true = ets:insert(Ets, [{1}]),
+ ?line Bs2 = erl_eval:add_binding('E', Ets, erl_eval:new_bindings()),
+ ?line Q = "[X || {X} <- ets:table(E)].",
+ ?line [1] = qlc:e(qlc:string_to_handle(Q, [], Bs2)),
+ ?line [1] = qlc:e(qlc:string_to_handle(Q, {max_lookup,1000}, Bs2)),
+ ?line [1] = qlc:e(qlc:string_to_handle(Q, {max_lookup,infinity}, Bs2)),
+ ?line {'EXIT',{badarg,_}} =
+ (catch qlc:string_to_handle(Q, {max_lookup,-1}, Bs2)),
+ ?line {'EXIT', {no_lookup_to_carry_out, _}} =
+ (catch qlc:e(qlc:string_to_handle(Q, {lookup,true}, Bs2))),
+ ?line ets:delete(Ets),
+ ok.
+
+table(doc) ->
+ "table";
+table(suite) -> [];
+table(Config) when is_list(Config) ->
+ dets:start(),
+ Ts = [
+ <<"E = ets:new(test, []),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:e(qlc:q([X || X <- ets:table(E, [badarg])]))),
+ [] = qlc:e(qlc:q([X || X <- ets:table(E)])),
+ ets:delete(E)">>,
+
+ <<"{'EXIT',{badarg,_}} = (catch qlc:table(not_a_fun, []))">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([{X,Y} || X <- ets:table(E), Y <- ets:table(E)]),
+ R = qlc:e(H),
+ ets:delete(E),
+ [{{1,a},{1,a}},{{1,a},{2,b}},{{1,a},{3,c}},
+ {{2,b},{1,a}},{{2,b},{2,b}},{{2,b},{3,c}},
+
+ {{3,c},{1,a}},{{3,c},{2,b}},{{3,c},{3,c}}] = lists:sort(R)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc:append([ets:table(E), [a,b,c],
+ ets:table(E)])]),
+ R = qlc:e(H),
+ ets:delete(E),
+ [a,b,c,{1,a},{1,a},{2,b},{2,b},{3,c},{3,c}] = lists:sort(R)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ false = ets:info(E, safe_fixed),
+ H = qlc:q([{X,Y} || X <- ets:table(E, {n_objects, default}),
+ Y <- ets:table(E, {n_objects, 2}),
+ false =/= ets:info(E, safe_fixed),
+ throw({throw,apa})]),
+ {throw,apa} = (catch {any_term,qlc:e(H)}),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ false = ets:info(E, safe_fixed),
+ H = qlc:q([{X,Y} || X <- ets:table(E), Y <- ets:table(E),
+ false =/= ets:info(E, safe_fixed), exit(apa)]),
+ {'EXIT',apa} = (catch {any_term,qlc:e(H)}),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_throw(E),
+ Y <- ets:table(E)]),
+ R = (catch {any_term,qlc:cursor(H)}),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E),
+ {throw,bad_pre_fun} = R">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_exit(E),
+ Y <- ets:table(E)]),
+ R = (catch {any_term,qlc:cursor(H)}),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E),
+ {'EXIT',{bad_pre_fun,_}} = R">>,
+
+ <<"E = ets:new(test, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc_SUITE:default_table(E)]),
+ R = qlc:e(H),
+ ets:delete(E),
+ [{1,a},{2,b},{3,c}] = R">>,
+
+ <<"E = ets:new(test, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc_SUITE:bad_table(E)]),
+ {'EXIT', {badarg, _}} = (catch qlc:e(H)),
+ ets:delete(E)">>,
+
+ %% The info tag num_of_objects is currently not used.
+% <<"E = ets:new(test, [ordered_set]),
+% true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+% H = qlc:q([X || X <- qlc_SUITE:bad_table_info_fun_n_objects(E)]),
+% {'EXIT', finito} = (catch {any_term,qlc:e(H)}),
+% ets:delete(E)">>,
+
+ <<"E = ets:new(test, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([Y || {X,Y} <- qlc_SUITE:bad_table_info_fun_indices(E),
+ X =:= a]),
+ %% This is due to lookup. If the table were traversed there
+ %% would be no failure.
+ {throw, apa} = (catch {any_term,qlc:e(H)}),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(test, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([Y || {X,Y} <- qlc_SUITE:bad_table_info_fun_keypos(E),
+ X =:= a]),
+ {'EXIT',{keypos,_}} = (catch {any_term,qlc:info(H)}),
+ {'EXIT',{keypos,_}} = (catch {any_term,qlc:e(H)}),
+ ets:delete(E)">>,
+
+ begin
+ MS = ets:fun2ms(fun(X) when element(1, X) > 1 -> X end),
+ [<<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ H = qlc:q([{X,Y} || X <- ets:table(E,{traverse,{select, MS}}),
+ Y <- ets:table(E)]),
+ R = qlc:e(H),
+ ets:delete(E),
+ [{{2,b},{1,a}},{{2,b},{2,b}},{{2,b},{3,c}},
+ {{3,c},{1,a}},{{3,c},{2,b}},{{3,c},{3,c}}] = lists:sort(R)">>]
+ end,
+
+ begin % a short table
+ MS = ets:fun2ms(fun(X) when element(1, X) > 1 -> X end),
+ [<<"E = ets:new(test, []),
+ true = ets:insert(E, [{0,b}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ H1 = qlc:q([X || X <- ets:table(E)]),
+ R1 = qlc:e(H1),
+ H2 = qlc:q([X || X <- ets:table(E, {traverse, {select, MS}})]),
+ R2 = qlc:e(H2),
+ ets:delete(E),
+ [_] = R1,
+ [] = R2">>]
+ end,
+
+ begin
+ File = filename:join(?privdir, "detsfile"),
+ _ = file:delete(File),
+ [<<"{ok, Tab} = dets:open_file(apa, [{file,\"">>, File, <<"\"},
+ {type,bag}]),
+ ok = dets:insert(Tab, [{1,a},{1,b}]),
+ R = qlc:e(qlc:q([X || X <- dets:table(Tab)])),
+ dets:close(Tab),
+ file:delete(\"">>, File, <<"\"),
+ R">>]
+ end,
+
+ %% [T || P <- Table, F] turned into a match spec.
+ <<"E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c},{4,d}]),
+ QH = qlc:q([X || {X,_} <- ets:table(E), X > 2], unique),
+ {qlc, _, [{generate, _, {table, _}}], [{unique,true}]} = i(QH),
+ [3,4] = lists:sort(qlc:e(QH)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_format(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_format_arity(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_info_arity(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_traverse(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_post(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_max_lookup(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_lookup(E)),
+ ets:delete(E)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(2, X) || X <- qlc_SUITE:table(L, [2]),
+ (element(1, X) =:= 1)
+ or (2 =:= element(1, X))]),
+ [a,b] = lists:sort(qlc:e(QH))">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B} || {A,B} <-
+ qlc:q([{B,A} || {A,B} <- ets:table(E),
+ (A =:= 1) or (A =:= 2),
+ math:sqrt(B) < A])]),
+ [{2,2}] = qlc:eval(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{1,1},{2,2}])">>
+ ],
+ ?line run(Config, Ts),
+
+ Ts2 = [
+ %% [T || P <- Table, F] turned into a match spec. Records needed.
+ <<"E = ets:new(foo, [bag]),
+ ets:insert(E, [{a,1,2},#a{b=3,c=4},{a,3}]),
+ QH = qlc:q([X || X <- ets:table(E), is_record(X, a)]),
+ {list,{table,_}, _} = i(QH),
+ [{a,1,2},{a,3,4}] = lists:sort(qlc:eval(QH)),
+ ets:delete(E)">>
+ ],
+ ?line run(Config, <<"-record(a, {b,c}).\n">>, Ts2),
+
+ ok.
+
+process_dies(doc) ->
+ "Caller or cursor process dies.";
+process_dies(suite) -> [];
+process_dies(Config) when is_list(Config) ->
+ Ts = [
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ false = ets:info(E, safe_fixed),
+ Parent = self(),
+ F = fun() ->
+ H = qlc:q([X || X <- ets:table(E)]),
+ qlc:cursor(H),
+ Parent ! {self(),ok}
+ end,
+ Pid = spawn_link(F),
+ receive {Pid,ok} -> ok end,
+ timer:sleep(10),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"%% This is not nice. The cursor's monitor kicks in.
+ E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ false = ets:info(E, safe_fixed),
+ Parent = self(),
+ F = fun() ->
+ H = qlc:q([X || begin
+ process_flag(trap_exit, false),
+ {links, [Pid]} =
+ process_info(self(), links),
+ unlink(Pid),
+ timer:sleep(1),
+ {links, []} =
+ process_info(self(), links),
+ true
+ end,
+ X <- ets:table(E)]),
+ C = qlc:cursor(H),
+ qlc:next_answers(C),
+ Parent ! {self(),ok}
+ end,
+ Pid = spawn_link(F),
+ receive {Pid,ok} -> ok end,
+ timer:sleep(10),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"H = qlc:q([X || X <- [1,2]]),
+ {qlc_cursor, Term} = C = qlc:cursor(H),
+ PF = process_flag(trap_exit, true),
+ F = fun(T) -> not is_pid(T) end,
+ [Pid|_] = lists:dropwhile(F, tuple_to_list(Term)),
+ exit(Pid, kill),
+ timer:sleep(1),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, Pid}, _}} =
+ (catch qlc:next_answers(C)),
+ process_flag(trap_exit, PF)">>,
+ <<"H = qlc:q([X || begin process_flag(trap_exit, true), true end,
+ X <- [1,2]]),
+ {qlc_cursor, Term} = C = qlc:cursor(H),
+ PF = process_flag(trap_exit, true),
+ F = fun(T) -> not is_pid(T) end,
+ [Pid|_] = lists:dropwhile(F, tuple_to_list(Term)),
+ [1] = qlc:next_answers(C, 1),
+ exit(Pid, stop),
+ timer:sleep(1),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, Pid}, _}} =
+ (catch qlc:next_answers(C)),
+ process_flag(trap_exit, PF)">>,
+ <<"%% This is not nice. No cleanup is done...
+ H = qlc:q([X || begin process_flag(trap_exit, false), true end,
+ X <- [1,2]]),
+ {qlc_cursor, Term} = C = qlc:cursor(H),
+ PF = process_flag(trap_exit, true),
+ F = fun(T) -> not is_pid(T) end,
+ [Pid|_] = lists:dropwhile(F, tuple_to_list(Term)),
+ [1] = qlc:next_answers(C, 1),
+ exit(Pid, stop),
+ timer:sleep(1),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, Pid}, _}} =
+ (catch qlc:next_answers(C)),
+ process_flag(trap_exit, PF)">>,
+
+ <<"PF = process_flag(trap_exit, true),
+ E = ets:new(test, []),
+ %% Hard kill. No cleanup will be done.
+ H = qlc:q([X || begin exit(self(), kill), true end,
+ X <- ets:table(E)]),
+ C = qlc:cursor(H),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, _}, _}} =
+ (catch qlc:next_answers(C)),
+ false = ets:info(E, safe_fixed), % - but Ets cleans up anyway.
+ true = ets:delete(E),
+ process_flag(trap_exit, PF)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a}]),
+ %% The signal is caught by trap_exit. No process dies...
+ H = qlc:q([X || begin exit(self(), normal), true end,
+ X <- ets:table(E)]),
+ C = qlc:cursor(H, {spawn_options, []}),
+ [{1,a}] = qlc:next_answers(C),
+ qlc:delete_cursor(C),
+ false = ets:info(E, safe_fixed),
+ true = ets:delete(E)">>,
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a}]),
+ %% The same as last example.
+ H = qlc:q([X || begin
+ process_flag(trap_exit, true),
+ exit(self(), normal), true
+ end,
+ X <- ets:table(E)]),
+ C = qlc:cursor(H, {spawn_options, []}),
+ [{1,a}] = qlc:next_answers(C),
+ qlc:delete_cursor(C),
+ false = ets:info(E, safe_fixed),
+ true = ets:delete(E), ok">>,
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a}]),
+ H = qlc:q([X || X <- ets:table(E)]),
+ SpawnOpts = [link, monitor], % monitor is ignored
+ {qlc_cursor, Term} = C = qlc:cursor(H, {spawn_options, SpawnOpts}),
+ F = fun(T) -> not is_pid(T) end,
+ [Pid|_] = lists:dropwhile(F, tuple_to_list(Term)),
+ Me = self(),
+ qlc_SUITE:install_error_logger(),
+ Tuple = {this, tuple, is, writton, onto, the, error_logger},
+ SP = spawn(fun() ->
+ Pid ! Tuple,
+ Me ! {self(), done}
+ end),
+ receive {SP, done} -> ok end,
+ [{1,a}] = qlc:next_answers(C),
+ qlc:delete_cursor(C),
+ {error, _Pid, Tuple} = qlc_SUITE:read_error_logger(),
+ qlc_SUITE:uninstall_error_logger(),
+ false = ets:info(E, safe_fixed),
+ true = ets:delete(E), ok">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+sort(doc) ->
+ "The sort option.";
+sort(suite) -> [];
+sort(Config) when is_list(Config) ->
+ Ts = [
+ <<"H = qlc:q([X || X <- qlc:sort([1,2,3,2], {unique,true})]),
+ [1,2,3] = qlc:e(H),
+ C1 = qlc:cursor(H),
+ [1,2,3] = qlc:next_answers(C1, all_remaining),
+ qlc:delete_cursor(C1)">>,
+
+ <<"H = qlc:q([{X,Y} || X <- qlc:sort(qlc:q([X || X <- [1,2,3,2]]),
+ {unique,true}),
+ Y <- [a,b]]),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] = qlc:e(H),
+ C = qlc:cursor(H),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] =
+ qlc:next_answers(C, all_remaining),
+ qlc:delete_cursor(C)">>,
+
+ <<"H = qlc:q([X || X <- qlc:sort(qlc:q([X || X <- apa]))]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H))">>,
+
+ %% An example with a side effect. The result may vary...
+ <<"E = ets:new(test, [duplicate_bag]),
+ true = ets:insert(E, [{1,17},{1,a}]),
+ H_1 = qlc:q([X || X <- ets:table(E)]),
+ H = qlc:q([X || X <- [1,2,3], ets:insert(E, {1,-X}),
+ {_,Y} <- H_1,
+ X > Y]),
+ true = lists:sort(qlc:e(H)) == [1,2,2,3,3,3],
+ true = ets:delete(E)">>,
+
+ <<"E = ets:new(test, [duplicate_bag]),
+ true = ets:insert(E, [{1,17}]),
+ H_1 = qlc:q([X || X <- qlc:sort(ets:tab2list(E))]),
+ %% Note: side effect in filter!
+ H = qlc:q([X || X <- [1,2,3], ets:insert(E, {1,-X}),
+ {_,Y} <- H_1, X > Y]),
+ [] = qlc:e(H),
+ true = ets:delete(E)">>,
+
+ <<"H = qlc:q([X || X <- qlc:sort([1,2,3], {fopp,la})]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H)),
+ {'EXIT',{badarg,_}} = (catch qlc:cursor(H)),
+ F = fun(Obj, A) -> A++[Obj] end,
+ {'EXIT',{badarg,_}} = (catch qlc:fold(F, [], H))">>,
+
+ <<"Q1 = qlc:q([X || X <- [1,2]]),
+ AL = [Q1, [1,2,3]],
+ Q2 = qlc:q([X || X <- qlc:sort(qlc:append(AL))]),
+ [1,1,2,2,3] = qlc:e(Q2)">>,
+
+ <<"H = qlc:q([{X,Y} || X <- qlc:sort(qlc:q([X || X <- [1,2,3,2]]),
+ {unique,true}),
+ Y <- [a,b]]),
+ {qlc, _,
+ [{generate, _, {sort, {qlc, _, [{generate, _, {list, [1,2,3,2]}}],
+ [{unique,true}]},
+ []}},
+ {generate, _, {qlc, _, [{generate, _, {list, [a,b]}}],
+ [{unique,true}]}}],
+ [{unique,true}]} = i(H, unique_all),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] = qlc:e(H, unique_all)">>,
+
+ <<"L = [1,2,1,3,4,3,1],
+ true = lists:sort(L) == qlc:e(qlc:q([X || X <- qlc:sort(L)])),
+ true = lists:usort(L) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, {unique,true})])),
+ true = lists:reverse(lists:sort(L)) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, {order, descending})])),
+ true = lists:reverse(lists:usort(L)) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, [{order, descending},
+ {unique, true}])])),
+ CF = fun(X, Y) -> X =< Y end,
+ true = lists:sort(L) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, {order, CF})])),
+ true = lists:usort(L) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, [{order, CF},
+ {unique, true}])]))">>,
+
+ <<"E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ H = qlc:q([{X,Y} || X <- [a,b], Y <- qlc:sort(ets:table(E))]),
+ 100000 = length(qlc:e(H)),
+ ets:delete(E)">>,
+
+ begin
+ TmpDir = ?privdir,
+ [<<"TE = process_flag(trap_exit, true),
+ E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ Ports = erlang:ports(),
+ H = qlc:q([{X,Y} || X <- [a,b],
+ begin
+ [P] = erlang:ports() -- Ports,
+ exit(P, port_exit),
+ true
+ end,
+ Y <- qlc:sort(ets:table(E),
+ [{tmpdir,\"">>,
+ TmpDir, <<"\"}])]),
+ {error, qlc, {file_error, _, _}} = (catch qlc:e(H)),
+ receive {'EXIT', _, port_exit} -> ok end,
+ ets:delete(E),
+ process_flag(trap_exit, TE)">>]
+ end
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+keysort(doc) ->
+ "The sort option.";
+keysort(suite) -> [];
+keysort(Config) when is_list(Config) ->
+
+ Ts = [
+ <<"OF = fun(X, Y) -> X =< Y end,
+ F = fun(Obj, A) -> A++[Obj] end,
+ H = qlc:q([X || X <- qlc:keysort(1, [{2,a},{1,b}], {order,OF})]),
+ {'EXIT',{{badarg,order},_}} = (catch qlc:e(H)),
+ {'EXIT',{{badarg,order},_}} = (catch qlc:fold(F, [], H)),
+ {'EXIT',{{badarg,order},_}} = (catch qlc:cursor(H))">>,
+
+ <<"E = create_ets(1, 2),
+ H = qlc:q([X || X <- qlc:keysort([1], ets:table(E),
+ [{size,1},{tmpdir,\"/a/b/c\"}])]),
+ H1 = qlc:q([X || {X,_} <- qlc:e(H), X < 4]),
+ {error,_,{file_error,_,_}} = qlc:info(H1),
+ {error,_,{file_error,_,_}} = qlc:e(H1),
+ ets:delete(E)">>,
+
+ <<"L = [{1,a},{2,b},{3,c},{2,b}],
+ H = qlc:q([{X,Y} || {X,_} <- qlc:keysort(1, qlc:q([X || X <- L]),
+ {unique,true}),
+ Y <- [a,b]]),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] = qlc:e(H),
+ C = qlc:cursor(H),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] =
+ qlc:next_answers(C, all_remaining),
+ qlc:delete_cursor(C)">>,
+
+ <<"H1 = qlc:q([X || X <- qlc:keysort(0, [])]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H1)),
+ H2 = qlc:q([X || X <- qlc:keysort(1, [], {bad,arg})]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H2)),
+ H3 = qlc:q([X || X <- qlc:keysort([], [])]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H3))">>,
+
+ <<"H = qlc:q([X || X <- qlc:keysort(1, [{1,a},{2,b}],
+ [{order,descending},
+ {compressed,true}])]),
+ [{2,b},{1,a}] = qlc:e(H),
+ H2 = qlc:q([X || X <- qlc:keysort(1, [{1},{2}], compressed)]),
+ {'EXIT', {badarg, _}} = (catch qlc:e(H2))">>,
+
+ <<"H = qlc:q([X || X <- qlc:keysort(1, [{1,a},{2,b}], {compressed,false})]),
+ [{1,a},{2,b}] = qlc:e(H)">>,
+
+ <<"E = create_ets(1, 2),
+ H = qlc:q([X || X <- qlc:keysort([1], ets:table(E),
+ [{size,1},{tmpdir,\"/a/b/c\"}])]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ {error,_,{file_error,_,_}} = qlc:e(H),
+ \" \\\"no such\" ++ _ = lists:dropwhile(fun(A) -> A =/= $\s end,
+ lists:flatten(qlc:format_error(qlc:e(H)))),
+ {error,_,{file_error,_,_}} = qlc:e(H, {unique_all,true}),
+ {error,_,{file_error,_,_}} = qlc:cursor(H),
+ {error,_,{file_error,_,_}} = qlc:cursor(H, {unique_all,true}),
+ {error,_,{file_error,_,_}} = qlc:cursor(H, {spawn_options, []}),
+ {error,_,{file_error,_,_}} = qlc:cursor(H, {spawn_options,default}),
+ {error,_,{file_error,_,_}} =
+ qlc:cursor(H, [{unique_all,true},{spawn_options, []}]),
+ {error,_,{file_error,_,_}} = qlc:fold(F, [], H),
+ {error,_,{file_error,_,_}} = qlc:fold(F, [],H, {unique_all,true}),
+ ets:delete(E)">>,
+
+ <<"L = [{1,b,a},{1,b,b},{1,a,a}],
+ H = qlc:q([X || X <- qlc:keysort([4,1], L)]),
+ {error,_,bad_object} = qlc:e(H),
+ \"the keys\" ++ _ = qlc:format_error(qlc:e(H))">>,
+
+ begin
+ File = filename:join(?privdir, "afile"),
+ ok = file:write_file(File, <<>>),
+ [<<"H = qlc:q([X || X <- qlc:keysort([1], [{1},{2},{1}],
+ [{tmpdir,\"">>, File, <<"\"},
+ {size,1}])]),
+ {error,_,{file_error,_,_}} = qlc:e(H),
+ file:delete(\"">>, File, <<"\")">>]
+ end,
+
+ <<"H0 = qlc:q([X || X <- [1,2,3]]),
+ H = qlc:q([X || X <- qlc:sort(H0,{tmpdir,\".\"})]),
+ [1,2,3] = qlc:e(H)">>,
+
+ %% The global option 'tmpdir' takes precedence.
+ begin
+ PrivDir = ?privdir,
+ [<<"L = [{1,a},{2,b}],
+ H = qlc:q([X || X <- qlc:keysort([1], L, {tmpdir,\"/a/b/c\"})]),
+ H1 = qlc:q([X || X <- H, X > 3]),
+ Dir = \"">>, PrivDir, <<"\",
+ Options = [{tmpdir, Dir}],
+ {qlc,_,[{generate,_,{keysort,{list,L},[1],[{tmpdir,Dir}]}},_],[]} =
+ i(H1, Options),
+ [{1,a},{2,b}] = qlc:e(H1, Options)">>] % no check of "/a/b/c"
+ end,
+
+ <<"L = [{2,c},{1,b},{1,a},{3,a},{1,c},{2,b}],
+ true = lists:sort(L) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort([1,2], L)]))">>,
+
+ <<"L = [{1,b},{2,c},{1,a},{3,e},{4,f},{3,d}],
+ true = lists:keysort(1, L) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort(1,L)])),
+ true = lists:ukeysort(1, L) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort(1, L, {unique,true})])),
+ true = lists:reverse(lists:keysort(1, L)) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort(1,L,
+ {order, descending})])),
+ true = lists:reverse(lists:ukeysort(1, L)) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort(1, L, [{unique,true},
+ {order, descending}])]))">>,
+
+ <<"L = [{X} || X <- lists:seq(1,100000)],
+ H1 = qlc:append([L,[{1,2},{2,3},{3,4}]]),
+ H = qlc:keysort([1], qlc:keysort([1], H1, [{compressed,true}])),
+ R = qlc:e(H),
+ 100003 = length(R)">>
+
+ ],
+ ?line run(Config, Ts),
+
+ ok.
+
+filesort(doc) ->
+ "keysort/1,2, using a file.";
+filesort(suite) -> [];
+filesort(Config) when is_list(Config) ->
+ Ts = [
+ <<"Q = qlc:q([X || X <- [{3},{1},{2}]]),
+ Opts = [{size,10},{no_files,3}],
+ Q2 = qlc:q([{X,Y} || Y <- [1,2], X <- qlc:keysort([1],Q,Opts)]),
+ [{{1},1},{{2},1},{{3},1},{{1},2},{{2},2},{{3},2}] = qlc:e(Q2)">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+
+cache(doc) ->
+ "The cache option.";
+cache(suite) -> [];
+cache(Config) when is_list(Config) ->
+ Ts = [
+ <<"{'EXIT', {badarg, _}} = (catch qlc:q([X || X <- [1,2]], badarg))">>,
+
+ <<"Q1 = qlc:q([X || X <- [1,2,1,2,1]], {unique,true}),
+ [1,2] = qlc:e(Q1),
+ [1,2] = qlc:e(Q1, {unique_all,true}),
+ Q2 = qlc:q([X || X <- qlc:q([X || X <- [1,2,1,2,1]],
+ {unique,true})]),
+ [1,2] = qlc:e(Q2),
+ [1,2] = qlc:e(Q2, {unique_all,true}),
+ Q3 = qlc:q([X || X <- qlc:append([[1,2,3], [4,5,6]])]),
+ [1,2,3,4,5,6] = qlc:e(Q3)">>,
+
+ <<"Q1 = qlc:q([X || {X,_} <- [{1,a},{2,a},{1,b},{2,b}]]),
+ Q2 = qlc:q([{X,make_ref()} || X <- Q1]),
+ [{1,_},{2,_},{1,_},{2,_}] = qlc:e(Q2, {unique_all,false}),
+ [{1,_},{2,_}] = qlc:e(Q2, {unique_all,true})">>,
+
+ <<"E = ets:new(test, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,a},{1,b},{2,b}]),
+ Q1 = qlc:q([X || {X,_} <- ets:table(E)]),
+ Q2 = qlc:q([{X,make_ref()} || X <- Q1]),
+ [{1,_},{1,_},{2,_},{2,_}] = lists:sort(qlc:e(Q2, {unique_all,false})),
+ [{1,_},{2,_}] = lists:sort(qlc:e(Q2, {unique_all,true})),
+ ets:delete(E)">>,
+
+ <<"Q1 = qlc:q([X || {X,_} <- [{1,a},{2,a},{1,b},{2,b}]]),
+ Q2 = qlc:q([{X,make_ref()} || X <- qlc:append([Q1, Q1])]),
+ [{1,_},{2,_},{1,_},{2,_},{1,_},{2,_},{1,_},{2,_}] =
+ qlc:e(Q2, {unique_all,false}),
+ [{1,_},{2,_}] = qlc:e(Q2, {unique_all,true})">>,
+
+ <<"[] = qlc:e(qlc:q([X || X <- []], {unique, true})),
+ [] = qlc:e(qlc:q([X || X <- qlc:q([X || X <- qlc:append([])],
+ {unique,true})]))">>,
+
+ <<"Q1 = qlc:q([{X,make_ref()} || {X,_} <- [{1,a},{1,b}]]),
+ [{1,_},{1,_}] = qlc:e(Q1, {unique_all, true}),
+ Q2 = qlc:q([Y || {X,_} <- [{1,a},{1,b}],
+ begin Y = {X,make_ref()}, true end]),
+ [{1,_},{1,_}] = qlc:e(Q2, {unique_all,true}),
+ Q3 = qlc:q([Y || X <- [{1,a},{2,a}],
+ begin {_,Z} = X, Y = {Z,make_ref()}, true end]),
+ [{a,_},{a,_}] = qlc:e(Q3, {unique_all, true})">>,
+
+ <<"E = ets:new(apa, [duplicate_bag]),
+ ets:insert(E, [{1,a},{2,a},{1,a}]),
+ H1 = qlc:q([X || X <- qlc:append(ets:table(E),[7,3,5])],
+ {cache, true}),
+ [{_,a},{_,a},{_,a},7,3,5] = qlc:e(H1),
+ ets:delete(E)">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ H = qlc:q([X || X <- [1,3,2,4]], cache),
+ Q = qlc:q([X || X <- H]),
+ [1,3,2,4] = qlc:fold(F, [], Q, [])">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,b},{1,a}]),
+ Q1 = qlc:q([X || X <- ets:table(E)], [cache, unique]),
+ Q = qlc:q([X || X <- Q1], [cache, unique]),
+ {qlc, _, [{generate, _,{table,_}}], [{unique,true}]} = i(Q),
+ R = qlc:fold(F, [], Q, []),
+ ets:delete(E),
+ true = [{1,a},{2,b}] == lists:sort(R)">>,
+
+ <<"E = ets:new(apa, [duplicate_bag]),
+ ets:insert(E, [{1,a},{2,b},{1,a}]),
+ H1 = qlc:q([X || X <- qlc:append(ets:table(E),[7,3])], cache),
+ H2 = qlc:q([{Y,X} || Y <- [2,1,3], X <- H1]),
+ [{2,_},{2,_},{2,_},{2,7},{2,3},
+ {1,_},{1,_},{1,_},{1,7},{1,3},
+ {3,_},{3,_},{3,_},{3,7},{3,3}] = qlc:e(H2),
+ ets:delete(E)">>,
+
+ %% This case is not 100 percent determined. An Ets table
+ %% is updated in a filter and later used in a generator.
+ <<"E = ets:new(apa, [bag]),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ H1 = qlc:q([Y || Y <- ets:table(E)],
+ [{cache, no}, {unique, true}]),
+ H = qlc:q([{X,Y} || X <- [{1,a},{2,d},{3,e}],
+ ets:insert(E, X),
+ Y <- H1]),
+ [{{1,a},_}, {{1,a},_}, {{2,d},_}, {{2,d},_}, {{2,d},_},
+ {{3,e},_}, {{3,e},_}, {{3,e},_}, {{3,e},_}] = qlc:e(H),
+ ets:delete(E)">>,
+
+ %% This case is not 100 percent determined. An Ets table
+ %% is updated in a filter and later used in a generator.
+ <<"E = ets:new(apa, [bag]),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ H1 = qlc:q([Y || Y <- ets:table(E)],
+ [{cache, true}, {unique, true}]),
+ H = qlc:q([{X,Y} || X <- [{1,a},{2,d},{3,e}],
+ ets:insert(E, X),
+ Y <- H1]),
+ [{{1,a},_}, {{1,a},_}, {{2,d},_}, {{2,d},_}, {{3,e},_}, {{3,e},_}] =
+ qlc:e(H),
+ ets:delete(E)">>,
+
+ <<"%% {5979} and {5549} are both hashed to 28244 by phash2/1
+ E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{5979},{5549},{5979},{5549},{0}]),
+ H1 = qlc:q([X || X <- ets:table(E)],
+ [{cache, true}, {unique, true}]),
+ H = qlc:q([Y || _ <- [1,2], Y <- H1]),
+ {qlc, _, [{generate, _, {list, [1,2]}},
+ {generate, _, {qlc, _, [{generate, _, {table,_}}],
+ [{cache,ets},{unique,true}]}}],
+ []} = i(H),
+ [{0},{0},{5549},{5549},{5979},{5979}] = lists:sort(qlc:e(H)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H1 = qlc:q([X || X <- ets:table(E)], [cache, unique]),
+ H2 = qlc:q([X || Y <- [3,4], ets:insert(E, {Y}), X <- H1]),
+ {qlc, _, [{generate, _, {list, [3,4]}}, _,
+ {generate, _, {qlc, _, [{generate, _,
+ {table, _}}],
+ [{cache, ets}]}}],
+ []} = i(H2),
+ [{1},{2},{3},{1},{2},{3}] = qlc:e(H2),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H1 = qlc:q([X || X <- ets:table(E)], [unique]),
+ H2 = qlc:q([X || Y <- [3,4], ets:insert(E, {Y}), X <- H1]),
+ [{1},{2},{3},{1},{2},{3},{4}] = qlc:e(H2),
+ ets:delete(E)">>,
+
+ <<"H0 = qlc:append([a,b], [c,d]),
+ H = qlc:q([{X,Y} ||
+ X <- H0,
+ Y <- qlc:q([{X1,Y} ||
+ X1 <- H0,
+ Y <- qlc:q([{X2,Y} ||
+ X2 <- H0,
+ Y <- H0])])]),
+ {qlc, _,
+ [{generate, _,{append, [{list, [a,b]}, {list, [c,d]}]}},
+ {generate, _,
+ {qlc, _,
+ [{generate, _, {append,[{list, [a,b]},{list, [c,d]}]}},
+ {generate, _,
+ {qlc, _,
+ [{generate, _,{append,[{list, [a,b]}, {list, [c,d]}]}},
+ {generate, _,{append,[{list, [a,b]}, {list, [c,d]}]}}],
+ [{cache,ets}]}}],
+ [{cache,ets}]}}],
+ []} = i(H, cache_all)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+cache_list(doc) ->
+ "OTP-6038. The {cache,list} option.";
+cache_list(suite) -> [];
+cache_list(Config) when is_list(Config) ->
+ Ts = [
+ begin
+ PrivDir = ?privdir,
+ [<<"%% unique, cache list. A simple qlc.
+ Options = [{cache,list}, unique],
+ L0 = [1,2,3,4,1,2,3,4],
+ L = qlc_SUITE:table(L0, []),
+ Q1 = qlc:q([X || X <- L], Options),
+ Q = qlc:q([{X,Y} || X <- [a,b], Y <- Q1]),
+ GOptions = [{tmpdir,\"">>, PrivDir, <<"\"}],
+ {qlc,_,[{generate,_,{list,[a,b]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{table,_}}],
+ [{cache,list},{unique,true}]}}],
+ []} = i(Q, GOptions),
+ true = [{X,Y} || X <- [a,b], Y <- [1,2,3,4]] =:=
+ qlc:e(Q, GOptions)">>]
+ end,
+
+ begin
+ MS = ets:fun2ms(fun({X,_}) when X > 1 -> X end),
+ [<<"%% No cache, even if explicit match specification.
+ etsc(fun(E) ->
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ Options = [{cache,list}, unique],
+ Q = qlc:q([{X,Y} ||
+ X <- ets:table(E, {traverse, {select, MS}}),
+ Y <- [1,2,3]],
+ Options),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{list,[1,2,3]}}],
+ [{unique,true}]} = i(Q),
+ true = [{X,Y} || X <- lists:seq(2,10), Y <- [1,2,3]] =:=
+ lists:sort(qlc:e(Q))
+ end, [{keypos,1}], [{I,a} || I <- lists:seq(1, 10)])">>]
+ end,
+
+ <<"%% Temporary files.
+ %% Remove list expression caches when possible. (no visible effect)
+ T = lists:seq(1, 100000), % Huge terms on file
+ F = fun(C) ->
+ Q0 = qlc:q([{X} || X <- [T,T,T], begin X > 0 end],
+ {cache,C}),
+ Q1 = qlc:q([{X,Y,Z} ||
+ X <- Q0,
+ Y <- Q0,
+ Z <- Q0],
+ {cache,C}),
+ qlc:q([{X, Y} || Y <- [1], X <- Q1])
+ end,
+ Ql = F(list),
+ Rl = qlc:e(Ql, {max_list_size, 64*1024}),
+ Qe = F(ets),
+ Re = qlc:e(Qe),
+ Qf = F(no),
+ Rf = qlc:e(Qf),
+ Ri = qlc:e(Ql, {max_list_size, 1 bsl 35}), % heavy
+ {27,27,27,27,true,true,true} =
+ {length(Rl), length(Re), length(Rf), length(Ri),
+ Rl =:= Re, Re =:= Rf, Rf =:= Ri}">>,
+
+ <<"%% File sorter.
+ T = lists:seq(1, 10000),
+ F = fun(C) ->
+ Q0 = qlc:q([{X} || X <- [T,T,T], begin X > 0 end],
+ [{cache,C},unique]),
+ Q1 = qlc:q([{X,Y,Z} ||
+ X <- Q0,
+ Y <- Q0,
+ Z <- Q0],
+ [{cache,C},unique]),
+ qlc:q([{X, Y} || Y <- [1], X <- Q1])
+ end,
+ GOpt = [{max_list_size, 10000}],
+ Ql = F(list),
+ Rl = qlc:e(Ql, GOpt),
+ Qe = F(ets),
+ Re = qlc:e(Qe, GOpt),
+ Qf = F(no),
+ Rf = qlc:e(Qf, GOpt),
+ {1,1,1,true,true} =
+ {length(Rl), length(Re), length(Rf), Rl =:= Re, Re =:= Rf}">>,
+
+ <<"%% Remove list expression caches when possible. (no visible effect)
+ Q0 = qlc:q([{X} || X <- [1,2,3], begin X > 0 end], {cache,list}),
+ Q1 = qlc:q([{X,Y,Z} ||
+ X <- Q0,
+ Y <- Q0,
+ Z <- Q0],
+ {cache,list}),
+ Q = qlc:q([{X, Y} || Y <- [1], X <- Q1]),
+ R = qlc:e(Q),
+ L0 = [{X} || X <- [1,2,3], begin X > 0 end],
+ L1 = [{X,Y,Z} ||
+ X <- L0,
+ Y <- L0,
+ Z <- L0],
+ L = [{X, Y} || Y <- [1], X <- L1],
+ true = R =:= L">>,
+
+ <<"%% No temporary file.
+ L = [{I,a} || I <- lists:seq(1, 10)],
+ Q0 = qlc:q([X || X <- qlc_SUITE:table_error(L, 1, err),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} ||
+ X <- lists:seq(1, 5),
+ Y <- Q0]),
+ err = qlc:e(Q)">>,
+
+ <<"%% Sort internally.
+ L = [{I,a} || I <- lists:seq(1, 10)],
+ Q0 = qlc:q([X || X <- qlc_SUITE:table_error(L, 1, err),
+ begin element(1, X) > 5 end],
+ [unique,{cache,list}]),
+ Q = qlc:q([{X, element(1,Y)} ||
+ X <- lists:seq(1, 5),
+ Y <- Q0]),
+ err = qlc:e(Q, {max_list_size,0})">>,
+
+ <<"%% No temporary file.
+ etsc(fun(E) ->
+ Q0 = qlc:q([X || X <- ets:table(E),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} || X <- lists:seq(1, 5),
+ Y <- Q0]),
+ R = [{X,Y} || X <- lists:seq(1, 5),
+ Y <- lists:seq(6, 10)],
+ R = lists:sort(qlc:e(Q))
+ end, [{keypos,1}], [{I,a} || I <- lists:seq(1, 10)])">>,
+
+ <<"%% Sort internally
+ etsc(fun(E) ->
+ Q0 = qlc:q([X || X <- ets:table(E),
+ begin element(1, X) > 5 end],
+ [{cache,list},unique]),
+ Q = qlc:q([{X, element(1,Y)} || X <- lists:seq(1, 5),
+ Y <- Q0]),
+ R = [{X,Y} || X <- lists:seq(1, 5),
+ Y <- lists:seq(6, 10)],
+ R = lists:sort(qlc:e(Q))
+ end, [{keypos,1}], [{I,a} || I <- lists:seq(1, 10)])">>,
+
+ <<"%% A few more tests of unique and {cache,list}.
+ F = fun(CU) ->
+ H1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ X <- [1,2]],
+ CU),
+ qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- H1])
+ end,
+ Q1 = F([]),
+ Q2 = F([{cache,list}, unique]),
+ R1 = qlc:e(Q1),
+ R2 = qlc:e(Q2),
+ R3 = qlc:e(Q2, {max_list_size, 0}), % still in RAM
+ true = R1 =:= R2,
+ true = R2 =:= R3">>,
+
+ <<"E = ets:new(t, [duplicate_bag]),
+ true = ets:insert(E, [{2},{1},{2}]),
+ H1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ {X} <- ets:table(E)],
+ [{cache,list}, unique]),
+ H2 = qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- H1]),
+ {qlc,_,[{generate,_,{list,[3,4]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,[a,b]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{table,{ets,table,_}}}],
+ [{cache,list},{unique,true}]}}],
+ [{cache,list},{unique,true}]}}], []} = i(H2),
+ L1s = [[{X,Y} || Y <- [a,b], X <- Xs] || Xs <- [[1,2], [2,1]]],
+ L2s = [[{X,Y,Z} || X <- [3,4], {Y,Z} <- L1] || L1 <- L1s],
+ R1 = qlc:e(H2),
+ R2 = qlc:e(H2, {max_list_size, 0}), % on temporary file
+ ets:delete(E),
+ true = lists:member(R1, L2s),
+ true = R1 =:= R2">>,
+
+ <<"E = ets:new(t, [duplicate_bag]),
+ true = ets:insert(E, [{2},{1},{2}]),
+ H1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ {X} <- ets:table(E)],
+ [{cache,list}]),
+ H2 = qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- H1]),
+ L1s = [[{X,Y} || Y <- [a,b], X <- Xs] || Xs <- [[1,2,2], [2,2,1]]],
+ L2s = [[{X,Y,Z} || X <- [3,4], {Y,Z} <- L1] || L1 <- L1s],
+ R1 = qlc:e(H2),
+ R2 = qlc:e(H2, {max_list_size, 0}), % on temporary file
+ ets:delete(E),
+ true = lists:member(R1, L2s),
+ true = R1 =:= R2">>,
+
+ <<"Q1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ {X,_} <- qlc_SUITE:table_error([{a,1}], 2, err)],
+ [{cache,list}, unique]),
+ Q = qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- Q1]),
+ {qlc,_,[{generate,_,{list,[3,4]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,[a,b]}},
+ {generate,_,{table,_}}],
+ [{cache,list},{unique,true}]}}],
+ []} = i(Q),
+ err = qlc:e(Q,{max_list_size,0})">>,
+
+ begin
+ Privdir = ?privdir,
+ [<<"
+ E = ets:new(t, [duplicate_bag]),
+ N = 17000,
+ true = ets:insert(E, [{X,X} || X <- lists:seq(1, N)]),
+ N = ets:info(E, size),
+ RF = fun(GOpts) ->
+ F = fun(CU) ->
+ H1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ {X,_} <- ets:table(E)],
+ CU),
+ qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- H1])
+ end,
+ Q1 = F([{cache,list}, unique]),
+ _ = qlc:info(Q1, GOpts),
+ R1 = qlc:e(Q1, GOpts),
+ Q2 = F([unique]),
+ R2 = qlc:e(Q2, GOpts),
+ true = lists:sort(R1) =:= lists:sort(R2)
+ end,
+ GOpts = [{tmpdir,\"">>,Privdir,<<"\"}],
+ RF([{max_list_size, 1 bsl 35} | GOpts]),
+ RF(GOpts),
+ RF([{max_list_size, 40000} | GOpts]),
+ true = ets:insert(E, [{X,X} || X <- lists:seq(1, N)]),
+ true = N+N =:= ets:info(E, size),
+ RF([{max_list_size, 1 bsl 30} | GOpts]),
+ RF(GOpts),
+ RF([{max_list_size, 40000} | GOpts]),
+ ets:delete(E)">>]
+ end,
+
+ <<"%% Temporary file employed.
+ etsc(fun(E) ->
+ Q0 = qlc:q([X || X <- ets:table(E),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} || X <- lists:seq(1, 5),
+ Y <- Q0]),
+ R = [{X,Y} || X <- lists:seq(1, 5),
+ Y <- lists:seq(6, 10)],
+ R = lists:sort(qlc:e(Q, {max_list_size, 100*1024}))
+ end, [{keypos,1}], [{I,a,lists:duplicate(100000,1)} ||
+ I <- lists:seq(1, 10)])">>,
+
+ <<"%% Temporary file employed. The file is removed after error.
+ L = [{I,a,lists:duplicate(100000,1)} || I <- lists:seq(1, 10)],
+ Q0 = qlc:q([X || X <- qlc_SUITE:table_error(L, 1, err),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} ||
+ X <- lists:seq(1, 5),
+ Y <- Q0]),
+ err = qlc:e(Q)">>,
+
+ <<"%% Temporary file employed. The file is removed after error.
+ L = [{I,a,lists:duplicate(100000,1)} || I <- lists:seq(1, 10)],
+ Q0 = qlc:q([X || X <- qlc_SUITE:table(L, 1, []),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} ||
+ X <- lists:seq(1, 5),
+ Y <- Q0]),
+ {error, _, {file_error,_,_}} = qlc:e(Q, {tmpdir, \"/a/b/c\"})">>,
+
+ <<"Q = qlc:q([X || X <- [1,2]]),
+ {'EXIT', {badarg, _}} = (catch qlc:e(Q, {max_list_size, -1}))">>,
+
+ <<"Q = qlc:q([X || X <- [1,2]]),
+ {'EXIT', {badarg, _}} = (catch qlc:e(Q, {max_list_size, foo}))">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+filter(doc) ->
+ "Filters and match specs.";
+filter(suite) -> [];
+filter(Config) when is_list(Config) ->
+ Ts = [
+ <<"L = [1,2,3,4,5],
+ QH1 = qlc:q([X || X <- L, X > 1, X < 4]),
+ [2,3] = qlc:e(QH1),
+ {list,{list,L},_MS} = i(QH1)
+ ">>,
+
+ <<"L = [1,2,3,4,5],
+ QH2 = qlc:q([X || X <- L, X > 1, X < 4, X > 2]),
+ [3] = qlc:e(QH2),
+ {list,{list,L},_MS} = i(QH2)
+ ">>,
+
+ %% "X > 1" is skipped since the matchspec does the job
+ <<"QH3 = qlc:q([X || X <- [1,2,3,4,5], X > 1, begin X < 4 end]),
+ [2,3] = qlc:e(QH3),
+ {qlc,_,[{generate,_,{list,{list,[1,2,3,4,5]},_MS}},_],[]} = i(QH3)
+ ">>,
+
+ <<"QH4 = qlc:q([{X,Y} || X <- [1,2], Y <- [1,2]]),
+ [{1,1},{1,2},{2,1},{2,2}] = qlc:e(QH4),
+ {qlc,_,[{generate,_,{list,[1,2]}},{generate,_,{list,[1,2]}}],[]} =
+ i(QH4)">>,
+
+ %% "X > 1" is skipped since the matchspec does the job
+ <<"QH5 = qlc:q([{X,Y} || X <- [1,2], X > 1, Y <- [1,2]]),
+ [{2,1},{2,2}] = qlc:e(QH5),
+ {qlc,_,[{generate,_,{list,{list,[1,2]},_MS}},
+ {generate,_,{list,[1,2]}}],[]} =
+ i(QH5)">>,
+
+ <<"%% Binaries are not handled at all when trying to find lookup values
+ etsc(fun(E) ->
+ A = 2,
+ Q = qlc:q([X || {X} <- ets:table(E), <<A>> =:= <<X>>]),
+ [2] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{1},{2},{3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,_} <- ets:table(E),
+ qlc:e(qlc:q([Y || {Y,_} <- ets:table(E),
+ Y > X])) == []]),
+ [3] = qlc:e(Q)
+ end, [{1,a},{2,b},{3,c}])">>,
+
+ <<"Q = qlc:q([X || {X} <- [], (false or (X/0 > 3))]),
+ \"[]\" = qlc:info(Q),
+ [] = qlc:e(Q)">>,
+
+ <<"%% match spec
+ [] = qlc:e(qlc:q([X || {X} <- [{1},{2}],
+ (false orelse (X/0 > 3))])),
+ %% generated code
+ {'EXIT', {badarith, _}} =
+ (catch qlc:e(qlc:q([X || {X} <- [{1}],
+ begin (false orelse (X/0 > 3)) end])))">>,
+
+ <<"%% Partial evaluation in filter.
+ etsc(fun(E) ->
+ QH = qlc:q([{X+1,Y} || {X,Y} <- ets:table(E),
+ X =:= 1-1+1+(+1)]),
+ [{3,2}] = qlc:e(QH),
+ [2] = lookup_keys(QH)
+ end, [{1,1},{2,2},{3,3}])">>,
+
+ <<"%% =/2 in filters must not be recognized when 'badmatch' is
+ %% possible.
+ etsc(fun(E) ->
+ QH = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ ((Y = X) =:= 3)]),
+ {'EXIT', {{badmatch,4},_}} = (catch qlc:e(QH)),
+ false = lookup_keys(QH)
+ end, [{3,3},{4,true}])">>,
+
+ <<"%% One more of the same kind.
+ etsc(fun(E) ->
+ QH = qlc:q([{X,Y} || {X,_} <- ets:table(E),
+ (Y=X) =:= (Y=1+1)]),
+ {'EXIT', {{badmatch,2},_}} = (catch qlc:e(QH)),
+ false = lookup_keys(QH)
+ end, [{1,1},{2,2},{3,3}])">>,
+
+ <<"%% OTP-5195. Used to return a value, but badarith is correct.
+ etsc(fun(E) ->
+ QH = qlc:q([X || {X,_} <- ets:table(E),
+ (X =:= 1) and
+ if X =:= 1 -> true;
+ true -> X/0
+ end]),
+ {'EXIT',{badarith,_}} = (catch qlc:e(QH)),
+ false = lookup_keys(QH)
+ end, [{1,1},{2,2},{3,3}])">>,
+
+ <<"fun(Z) ->
+ Q = qlc:q([X || Z < 2, X <- [1,2,3]]),
+ [] = qlc:e(Q)
+ end(3)">>,
+
+ <<"H = qlc:q([{P1,A,P2,B,P3,C} ||
+ P1={A,_} <- [{1,a},{2,b}],
+ {_,B}=P2 <- [{1,a},{2,b}],
+ C=P3 <- [1],
+ {[X,_],{_,X}} <- [{[1,2],{3,1}}, {[a,b],{3,4}}],
+ A > 0,
+ B =/= c,
+ C > 0]),
+ L = [{{1,a},1,{1,a},a,1,1}, {{1,a},1,{2,b},b,1,1},
+ {{2,b},2,{1,a},a,1,1}, {{2,b},2,{2,b},b,1,1}],
+ L = qlc:e(H)">>,
+
+ <<"H = qlc:q([{X,Y} ||
+ X = _ <- [1,2,3],
+ _ = Y <- [a,b,c],
+ _ = _ <- [foo],
+ X > 1,
+ Y =/= a]),
+ [{2,b},{2,c},{3,b},{3,c}] = qlc:e(H)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+info(doc) ->
+ "info/2.";
+info(suite) -> [];
+info(Config) when is_list(Config) ->
+ Ts = [
+ <<"{list, [1,2]} = i(qlc:q([X || X <- [1,2]])),
+ {append,[{list, [1,2]}, {list, [3,4]}]} =
+ i(qlc:append([1,2],[3,4])),
+ {sort,{list, [1,2]},[]} = i(qlc:sort([1,2])),
+ E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ {table, _} = i(ets:table(E)),
+ true = ets:delete(E),
+ {list, [1,2]} = i([1,2]),
+ {append, [{list, [1,2]}, {list, [3,4]}]} =
+ i(qlc:q([X || X <- qlc:append([1,2],[3,4])])),
+
+ H0 = qlc:q([X || X <- throw({throw,t})]),
+ {throw,t} = (catch {any_term,qlc:info(H0)}),
+ {'EXIT', {badarg, _}} =
+ (catch qlc:info(foobar)),
+ {'EXIT', {badarg, _}} =
+ (catch qlc:info(qlc:q([X || X <- [1,2]]), badarg))">>,
+
+ <<"{'EXIT', {badarg, _}} =
+ (catch qlc:info([X || {X} <- []], {n_elements, 0})),
+ L = lists:seq(1, 1000),
+ \"[1,2,3,4,5,6,7,8,9,10|'...']\" = qlc:info(L, {n_elements, 10}),
+ {cons,1,{integer,1,1},{atom,1,'...'}} =
+ qlc:info(L, [{n_elements, 1},{format,abstract_code}]),
+ Q = qlc:q([{X} || X <- [a,b,c,d,e,f]]),
+ {call,_,_,[{cons,_,{atom,_,a},{cons,_,{atom,_,b},{cons,_,{atom,_,c},
+ {atom,_,'...'}}}},
+ {call,_,_,_}]} =
+ qlc:info(Q, [{n_elements, 3},{format,abstract_code}]),
+ \"ets:match_spec_run([a,b,c,d,e,f],\n\"
+ \" ets:match_spec_compile([{'$1',[true],\"
+ \"[{{'$1'}}]}]))\" =
+ qlc:info(Q, [{n_elements, infinity}])">>,
+
+ <<"Q1 = qlc:q([{X} || X <- qlc:q([X || X <- [1,2]])]),
+ {qlc, _, [{generate, _, {list, [1,2]}}],[]} = i(Q1),
+ Q2 = qlc:q([X || X <- qlc:q([{X} || X <- [1,2]])]),
+ {list,{list,[1,2]},_} = i(Q2),
+ [{1},{2}] = qlc:eval(Q2),
+ Q3 = qlc:q([{X,Y} || X <- qlc:q([X || X <- [a,b]]),
+ Y <- qlc:q([Z || Z <- [a,b]])]),
+ {qlc, _, [{generate, _, {list, [a,b]}},
+ {generate, _, {list, [a,b]}}], []} = i(Q3),
+ Q4 = qlc:q([X || X <- [a]]),
+ {list, [a]} = i(Q4),
+ Q5 = qlc:q([X || X <- qlc:q([Y || Y <- [a,b]], unique)]),
+ {qlc, _, [{generate, _, {list, [a,b]}}], [{unique,true}]} =
+ i(Q5)">>,
+
+ <<"H = qlc:q([X || X <- qlc:append([qlc:q([X || X <- [1,2]]),[1,2]])]),
+ {append, [{list, [1,2]},{list, [1,2]}]} = i(H),
+ [1,2,1,2] = qlc:e(H)">>,
+
+ <<"H = qlc:q([{X} || X <- [], X > 1]),
+ {list, []} = i(H),
+ [] = qlc:e(H)">>,
+
+ <<"H1 = qlc:q([{X} || X <- [], X > 1]),
+ H = qlc:q([{X} || X <- H1, X < 10]),
+ {list, []} = i(H),
+ [] = qlc:e(H)">>,
+
+ <<"L = [1,2,3],
+ QH1 = qlc:q([{X} || X <- L, X > 1]),
+ QH2 = qlc:q([{X} || X <- QH1]),
+ [{{2}},{{3}}] = qlc:e(QH2),
+ {list,{list,{list,L},_},_} = i(QH2)">>,
+
+ <<"H = qlc:q([X || X <- qlc:q([Y || Y <- qlc:q([Z || Z <-[1,2,1]])])]),
+ {list, [1,2,1]} = i(H),
+ [1,2,1] = qlc:eval(H)">>,
+
+ <<"%% Used to replace empty ETS tables with [], but that won't work.
+ E = ets:new(apa,[]),
+ QH1 = qlc:q([{X} || X <- ets:table(E), X > 1]),
+ QH2 = qlc:q([{X} || X <- QH1], cache),
+ [] = qlc:e(QH2),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}}],[]} = i(QH2),
+ ets:delete(E)">>,
+
+ <<"Q1 = qlc:q([W || W <- [a,b]]),
+ Q2 = qlc:q([Z || Z <- qlc:sort([1,2,300])], unique),
+ Q3 = qlc:q([{X,Y} || X <- qlc:keysort([2], [{1,a}]),
+ Y <- qlc:append([Q1, Q2]),
+ X > Y]),
+ {qlc, T1,
+ [{generate, P1, {list, [{1,a}]}},
+ {generate, P2, {append, [{list, [a,b]},
+ {qlc, T2, [{generate, P3,
+ {sort, {list,[1,2,300]},[]}}],
+ [{cache,ets},{unique,true}]}]}},F],
+ []} = i(Q3, cache_all),
+ {tuple, _, [{var,_,'X'}, {var,_,'Y'}]} = binary_to_term(T1),
+ {var, _, 'X'} = binary_to_term(P1),
+ {var, _, 'Y'} = binary_to_term(P2),
+ {var, _, 'Z'} = binary_to_term(P3),
+ {var, _, 'Z'} = binary_to_term(T2),
+ {op, _, '>', {var, _, 'X'}, {var, _, 'Y'}} = binary_to_term(F),
+ true = binary_to_list(<<
+ \"beginV1=qlc:q([Z||Z<-qlc:sort([1,2,300],[])],[{unique,true}]),\"
+ \"qlc:q([{X,Y}||X<-[{1,a}],Y<-qlc:append([[a,b],V1]),X>Y])end\"
+ >>) == format_info(Q3, true)">>,
+
+ <<"Q1 = qlc:q([{X} || X <- qlc:q([X || X <- [a,b]])]),
+ {qlc, _, [{generate, _, {list, [a,b]}}], []} = i(Q1),
+ Q2 = qlc:q([X || X <- qlc:q([{X} || X <- [a,b]])]),
+ {list,{list,[a,b]},_} = i(Q2),
+ [{a},{b}] = qlc:eval(Q2)">>,
+
+ <<"Q = qlc:keysort(2, [{1,a,b},{2,b,c},{3,4,c}]),
+ {keysort,{list,[{1,a,b},{2,b,c},{3,4,c}]},2,[]} = i(Q),
+ true = binary_to_list(<<
+ \"qlc:keysort(2,[{1,a,b},{2,b,c},{3,4,c}],[])\">>)
+ == format_info(Q, true),
+ [{3,4,c},{1,a,b},{2,b,c}] = qlc:e(Q)">>,
+
+ <<"E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ Q = qlc_SUITE:default_table(E),
+ {table,{'$MOD','$FUN',[]}} = i(Q),
+ true = binary_to_list(<<\"'$MOD':'$FUN'()\">>)
+ == format_info(Q, true),
+ true = ets:delete(E)">>,
+
+ <<"\"[]\" = qlc:info([], flat),
+ \"[]\" = qlc:info([]),
+ \"[]\" = qlc:info([], {flat, true})">>,
+
+ <<"H = qlc:q([{X} || X <- [a,b]]),
+ \"ets:match_spec_run([a,b],ets:match_spec_compile(\" ++ _ =
+ format_info(H, true),
+ \"ets:match_spec_run([a,b],ets:match_spec_compile(\" ++ _ =
+ format_info(H, false)">>,
+
+ <<"H = qlc:q([{X} || X <- [a,b], begin true end]),
+ true = binary_to_list(<<\"qlc:q([{X}||X<-[a,b],begintrueend])\">>)
+ == format_info(H, true),
+ true = binary_to_list(<<\"qlc:q([{X}||X<-[a,b],begintrueend])\">>)
+ == format_info(H, false)">>,
+
+ <<"H = qlc:q([A || {A} <- [{1},{2}], (A =:= 2) andalso true]),
+ {call,_,{remote,_,{atom,_,ets},{atom,_,match_spec_run}},_} =
+ qlc:info(H, {format,abstract_code})">>,
+
+ <<"H = qlc:q([{X} || X <- qlc:q([{X} || X <- [a,b], begin true end],
+ unique),
+ begin true end]),
+ true = binary_to_list(<<
+ \"beginV1=qlc:q([{X}||X<-[a,b],begintrueend],[{unique,true}]),\"
+ \"qlc:q([{X}||X<-V1,begintrueend])end\">>) ==
+ format_info(H, true),
+ true = binary_to_list(<<
+ \"qlc:q([{X}||X<-qlc:q([{X}||X<-[a,b],begintrueend],\"
+ \"[{unique,true}]),begintrueend])\">>) == format_info(H, false)">>,
+
+ <<"H0 = qlc:q([{V3} || V3 <- qlc:q([{V1} || V1 <- [a,b],
+ begin true end], unique),
+ begin true end]),
+ H = qlc:sort(H0),
+ true = binary_to_list(<<
+ \"qlc:sort(qlc:q([{V3}||V3<-qlc:q([{V1}||\"
+ \"V1<-[a,b],begintrueend],[{unique,true}]),begintrueend]),[])\">>)
+ == format_info(H, false),
+ true = binary_to_list(<<
+ \"beginV2=qlc:q([{V1}||V1<-[a,b],begintrueend],[{unique,true}]),\"
+ \"V4=qlc:q([{V3}||V3<-V2,begintrueend]),qlc:sort(V4,[])end\">>)
+ == format_info(H, true)">>,
+
+ <<"H0 = qlc:q([X || X <- [true], begin true end]),
+ H1 = qlc:q([{X} || X <- [a,b], begin true end],
+ [{unique,begin [T] = qlc:e(H0), T end}]),
+ {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},
+ [{lc,_,{tuple,_,[{var,_,'X'}]},
+ [{generate,_,{var,_,'X'},
+ {cons,_,{atom,_,a},_}},
+ {block, _, [{atom, _, true}]}]},
+ {cons,_,_,_}]} = i(H1, {format, abstract_code})">>,
+
+ <<"E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c},{4,d}]),
+ QH = qlc:q([X || {X,_} <- ets:tab2list(E), X > 2], unique),
+ {qlc, _, [{generate, _, {list, _, _MS}}], [{unique, true}]} =
+ i(QH),
+ [3,4] = lists:sort(qlc:e(QH)),
+ ets:delete(E)">>,
+
+ %% "Imported" variable.
+ <<"F = fun(U) -> qlc:q([{X} || X <- [1,2,3,4,5,6], X > U]) end,
+ QH = F(4),
+ {call, _ ,
+ {remote, _, {atom, _, ets},{atom, _, match_spec_run}},
+ [{string, _, [1,2,3,4,5,6]},
+ {call, _,
+ _compile,
+ [{cons, _,
+ {tuple, _,
+ [{atom, _,'$1'},
+ {cons, _,
+ {tuple,
+ _,
+ [{atom, _,'>'},
+ {atom, _,'$1'},
+ {tuple,
+ _,
+ [{atom, _,const},
+ {integer, _,4}]}]},
+ _},
+ {cons, _, _, _}]},
+ {nil,_}}]}]} = i(QH, {format, abstract_code}),
+ [{5},{6}] = qlc:e(QH),
+ [{4},{5},{6}] = qlc:e(F(3))">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+nested_info(doc) ->
+ "Nested QLC expressions. QLC expressions in filter and template.";
+nested_info(suite) -> [];
+nested_info(Config) when is_list(Config) ->
+ Ts = [
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X,R} ||
+ {X,_} <- qlc_SUITE:table(L, []),
+ begin % X imported
+ R = qlc:e(qlc:q([{X,Y} || {Y,_}
+ <- qlc_SUITE:table(L, []),
+ Y > X])),
+ true
+ end]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,R}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}]),\"
+ \"beginR=qlc:e(qlc:q([{X,Y}||{Y,_}<-qlc_SUITE:table(L,[]),Y>X]))\"
+ \",trueend])\">>) == format_info(Q, true),
+ [{1,[{1,2},{1,3}]},{2,[{2,3}]},{3,[]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q( % X imported
+ [{X,qlc:e(qlc:q([{X,Y} || {Y,_} <- qlc_SUITE:table(L, []),
+ Y > X]))} ||
+ {X,_} <- qlc_SUITE:table(L, [])]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,qlc:e(qlc:q([{X,Y}||{Y,_}<-qlc_SUITE:table(L,[]),\"
+ \"Y>X]))}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}])])\">>)
+ == format_info(Q, true),
+ [{1,[{1,2},{1,3}]},{2,[{2,3}]},{3,[]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X,R} ||
+ {X,_} <- qlc_SUITE:table(L, []),
+ begin % X imported
+ R = qlc:e(qlc:q([{X,Y} || {Y,_}
+ <- qlc_SUITE:table(L, []),
+ Y =:= X])),
+ true
+ end]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,R}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}]),\"
+ \"beginR=qlc:e(qlc:q([{X,Y}||{Y,_}<-qlc_SUITE:table(L,[]),\"
+ \"Y=:=X])),trueend])\">>) == format_info(Q, true),
+ [{1,[{1,1}]},{2,[{2,2}]},{3,[{3,3}]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X, % X imported
+ qlc:e(qlc:q([{X,Y} || {Y,_} <- qlc_SUITE:table(L, []),
+ Y =:= X]))} ||
+ {X,_} <- qlc_SUITE:table(L, [])]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,qlc:e(qlc:q([{X,Y}||{Y,_}<-qlc_SUITE:table(L,[]),\"
+ \"Y=:=X]))}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}])])\">>)
+ == format_info(Q, true),
+ [{1,[{1,1}]},{2,[{2,2}]},{3,[{3,3}]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X,R} ||
+ {X,_} <- qlc_SUITE:table(L, []),
+ begin
+ R = qlc:e(qlc:q([Y || Y <- [X]])),
+ true
+ end]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,R}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}]),\"
+ \"beginR=qlc:e(qlc:q([Y||Y<-[X]])),trueend])\">>)
+ == format_info(Q, true),
+ [{1,[1]},{2,[2]},{3,[3]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X,qlc:e(qlc:q([Y || Y <- [X]]))} ||
+ {X,_} <- qlc_SUITE:table(L, [])]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,qlc:e(qlc:q([Y||Y<-[X]]))}||{X,_}<-qlc_SUITE:\"
+ \"the_list([{1,a},{2,b},{3,c}])])\">>) == format_info(Q, true),
+ [{1,[1]},{2,[2]},{3,[3]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b}],
+ Q = qlc:q(
+ [{X,Y} ||
+ {X,_} <- qlc_SUITE:table(L, []),
+ {Y,_} <- qlc:q(
+ [{Z,V} ||
+ {Z,_} <- qlc_SUITE:table(L, []),
+ {V} <- qlc:q(
+ [{W} || W
+ <- qlc_SUITE:table(L, [])])
+ ])
+ ]),
+ true = binary_to_list(<<
+ \"beginV1=qlc:q([{W}||W<-qlc_SUITE:the_list([{1,a},{2,b}])]),\"
+ \"V2=qlc:q([{Z,V}||{Z,_}<-qlc_SUITE:the_list([{1,a},{2,b}]),\"
+ \"{V}<-V1]),qlc:q([{X,Y}||{X,_}<-qlc_SUITE:the_list([{1,a},\"
+ \"{2,b}]),{Y,_}<-V2])end\">>) == format_info(Q, true),
+ [{1,1},{1,1},{1,2},{1,2},{2,1},{2,1},{2,2},{2,2}] = qlc:e(Q)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+
+lookup1(doc) ->
+ "Lookup keys. Mostly test of patterns.";
+lookup1(suite) -> [];
+lookup1(Config) when is_list(Config) ->
+ Ts = [
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || {A=3} <- ets:table(E)]),
+ [3] = qlc:eval(Q),
+ [3] = lookup_keys(Q)
+ end, [{1},{2},{3},{4}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || {A=3} <- ets:table(E)],{max_lookup,0}),
+ [3] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{1},{2},{3},{4}])">>,
+
+ <<"%% The lookup and max_lookup options interact.
+ etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ (X =:= 1) or (X =:= 2)],
+ [{lookup,true},{max_lookup,1}]),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch qlc:e(Q))
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,C,D} || {A,B}={C,D} <- ets:table(E)]),
+ [{1,2,1,2},{3,4,3,4}] = lists:sort(qlc:eval(Q)),
+ false = lookup_keys(Q)
+ end, [{1,2},{3,4}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,D} || {A,B}={D,A} <- ets:table(E)]),
+ [{1,1,1},{2,2,2}] = lists:sort(qlc:eval(Q)),
+ false = lookup_keys(Q)
+ end, [{1,2},{2,2},{1,1}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,D} || {A,B}={D,A} <- ets:table(E),
+ (D =:= 2) or (B =:= 1)],
+ {max_lookup,infinity}),
+ [{1,1,1},{2,2,2}] = qlc:eval(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{1,2},{2,2},{1,1}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,D} || {A,B}={D,A} <- ets:table(E),
+ (D =:= 2) xor (B =:= 1)]),
+ [{1,1,1},{2,2,2}] = qlc:eval(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{1,2},{2,2},{1,1}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([3 || {{[3,4],apa}} <- ets:table(E)]),
+ [3] = qlc:e(Q),
+ [{[3,4],apa}] = lookup_keys(Q)
+ end, [{{[4,3],foo}},{{[3,4],apa}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([3 || {3} <- ets:table(E)]),
+ [3] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{2},{3},{4}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y,Z} || {{X,_},Y,Y={_,Z},X,Y} <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{{1,1},1,{1,1},1,1}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,X=2} <- ets:table(E)]),
+ [2] = qlc:e(Q),
+ [2] = lookup_keys(Q)
+ end, [{2,2},{3,3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{_,3}={4,_}=X} <- ets:table(E)]),
+ [{4,3}] = qlc:e(Q),
+ [{4,3}] = lookup_keys(Q)
+ end, [{{2,3}},{{4,3}}])">>,
+
+ <<"U = 17.0,
+ etsc(fun(E) ->
+ Q = qlc:q([X || {_=X=_} <- ets:table(E)]),
+ [U] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{U},{U+U,U}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y,Z,W} || {X=Y}=Z={V=W} <- ets:table(E),
+ V == {h,g}]),
+ [{{h,g},{h,g},{{h,g}},{h,g}}] = qlc:e(Q),
+ [{h,g}] = lookup_keys(Q)
+ end, [{h,g},{{h,g}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{C,Y,Z,X} || {{X=Y}=Z}={{A=B}=C} <- ets:table(E),
+ A == a, B =/= c]),
+ [{{a},a,{a},a}] = qlc:e(Q),
+ [{a}] = lookup_keys(Q)
+ end, [{{1}},{{a}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,D,Y,X} ||
+ {{A={B=C}},{D={C}}} = {X,Y} <- ets:table(E),
+ [] == B]),
+ [{{[]},{[]},{{[]}},{{[]}}}] = qlc:e(Q),
+ [{{[]}}] = lookup_keys(Q)
+ end, [{{{[]}},{{[]}}}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X}=X <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1},{a}])">>,
+ {warnings,[{{2,37},qlc,nomatch_pattern}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X=X,Y=Y}={Y=Y,X=X} <- ets:table(E),
+ {} == X]),
+ [{}] = qlc:e(Q),
+ [{}] = lookup_keys(Q)
+ end, [{{},{}},{[],[]}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {3+4=7,X} <- ets:table(E),
+ X =:= 3+997]),
+ [1000] = qlc:e(Q),
+ [7] = lookup_keys(Q)
+ end, [{7,1000},{8,1000}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X, Y} || [X]=[Y] <- ets:table(E)]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{a}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X={1,2,3,X,5} <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a},{b}])">>,
+ {warnings,[{{2,35},qlc,nomatch_pattern}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X=[1,2,3,X,5] <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a},{b}])">>,
+ {warnings,[{{2,35},qlc,nomatch_pattern}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X = <<X>> <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a},{b}])">>,
+
+ <<"Tre = 3.0,
+ etsc(fun(E) ->
+ Q = qlc:q([{A,B} || {A,B}={{a,C},{a,C}} <- ets:table(E),
+ C =:= Tre]),
+ [] = qlc:e(Q),
+ [{a,Tre}] = lookup_keys(Q)
+ end, [{a,b}])">>,
+
+ <<"A = 3,
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E), A =:= element(1, X)]),
+ [{3,3}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{1,a},{3,3}])">>,
+
+ <<"A = 3,
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E), A =:= erlang:element(1, X)]),
+ [{3,3}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{1,a},{3,3}])">>,
+
+ <<"A = 3,
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E), A =:= {erlang,element}(1, X)]),
+ [{3,3}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{1,a},{3,3}])">>,
+
+ <<"etsc(fun(E) ->
+ A = 3,
+ Q = qlc:q([X || X <- ets:table(E),
+ A == element(1,X),
+ element(1,X) =:= a]),
+ [] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a},{b},{c}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X = {[a,Z]},
+ Z = [foo, {[Y]}],
+ Y = {{foo,[X]}}} <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a,b,c},{d,e,f}])">>,
+ {warnings,[{{2,34},qlc,nomatch_pattern}]}}
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+lookup2(doc) ->
+ "Lookup keys. Mostly test of filters.";
+lookup2(suite) -> [];
+lookup2(Config) when is_list(Config) ->
+ Ts = [
+ <<"%% Only guards are inspected. No lookup.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ ((Y = X) =:= 3)]),
+ {'EXIT', {{badmatch,4},_}} = (catch qlc:e(Q))
+ end, [{3,3},{4,true}])">>,
+
+ <<"%% Only guards are inspected. No lookup.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ Y = (X =:= 3)]),
+ {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q))
+ end, [{false,3},{true,3}])">>,
+
+ <<"%% Only guards are inspected. No lookup.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ Y = (X =:= 3)]),
+ {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q))
+ end, [{3,true},{4,true}])">>,
+
+ <<"%% Only guards are inspected. No lookup.
+ E1 = create_ets(1, 10),
+ E2 = ets:new(join, []),
+ true = ets:insert(E2, [{true,1},{false,2}]),
+ Q = qlc:q([{X,Z} || {_,X} <- ets:table(E1),
+ {Y,Z} <- ets:table(E2),
+ Y = (X =:= 3)]),
+ {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q)),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,D} || {A,B}={D,A} <- ets:table(E),
+ (A =:= 3) or (4 =:= D)]),
+ [{3,3,3},{4,4,4}] = lists:sort(qlc:e(Q)),
+ [3,4] = lookup_keys(Q)
+ end, [{2,2},{3,3},{4,4}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,U} <- ets:table(E), X =:= U]),
+ [1] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,1}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X=Y} <- ets:table(E),
+ {[X],4} =:= {[3],X}]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+ {warnings,[{{3,46},qlc,nomatch_filter}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X == 1, X =:= 2]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+ {warnings,[{{3,43},qlc,nomatch_filter}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X=Y} <- ets:table(E),
+ {[X,Y],4} =:= {[3,X],X}]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+ {warnings,[{{3,48},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ ({X,3} =:= {Y,Y}) or (X =:= 4)]),
+ [{3,3},{4,4}] = lists:sort(qlc:e(Q)),
+ [3,4] = lookup_keys(Q)
+ end, [{2,2},{3,3},{4,4},{5,5}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), {[X]} =:= {[3,4]}]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{[3]},{[3,4]}])">>,
+ {warnings,[{{2,61},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ U = 18,
+ Q = qlc:q([{X,Y} || {X=Y} <- ets:table(E), [X|a] =:= [3|U]]),
+ [] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{2}, {3}])">>,
+
+ <<"etsc(fun(E) ->
+ U = 18, V = 19,
+ Q = qlc:q([{X,Y} || {X=Y} <- ets:table(E),
+ [X|V] =:= [3|U+1]]),
+ [{3,3}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{2},{3}])">>,
+
+ <<"%% Blocks are not handled.
+ etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), begin X == a end]),
+ [a] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a},{b}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ (3 =:= X) or (X =:= 12),
+ (8 =:= X) or (X =:= 10)]),
+ [] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{2},{3},{4},{8}])">>,
+ {warnings,[{{4,44},qlc,nomatch_filter}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ ((3 =:= X) or (X =:= 12))
+ and ((8 =:= X) or (X =:= 10))]),
+ [] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{2},{3},{4},{8}])">>,
+ {warnings,[{{4,35},qlc,nomatch_filter}]}},
+
+ <<"F = fun(U) ->
+ Q = qlc:q([X || {X} <- [a,b,c],
+ X =:= if U -> true; true -> false end]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end,
+ F(apa)">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X=1,X} <- ets:table(E), X =:= 2]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,1},{2,1}])">>,
+ {warnings,[{{2,61},qlc,nomatch_filter}]}},
+
+ <<"Two = 2.0,
+ etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), X =:= Two]),
+ [Two] = qlc:e(Q),
+ [Two] = lookup_keys(Q)
+ end, [{2.0},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ %% This float is equal (==) to an integer. Not a constant!
+ Q = qlc:q([X || {X} <- ets:table(E), X == {a,b,c,[2.0]}]),
+ [_,_] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{{a,b,c,[2]}},{{a,b,c,[2.0]}}])">>,
+
+ <<"%% Must _not_ regard floats as constants. Check imported variables
+ %% in runtime.
+ etsc(fun(E) ->
+ U = 3.0,
+ QH = qlc:q([X || {X,_} <- ets:table(E), X =:= U]),
+ [] = qlc:e(QH),
+ [U] = lookup_keys(QH)
+ end, [{1,a},{2,b},{3,c},{4,d}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ length(X) =:= 1]),
+ [[1]] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{[1]},{[2,3]}])">>,
+
+ <<"etsc(fun(E) ->
+ A=3,
+ Q = qlc:q([X || {X,Y} <- ets:table(E), X =:= A, Y =:= 3]),
+ [3] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{3,3},{4,3}])">>,
+
+ <<"etsc(fun(E) ->
+ A = 1,
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= 1, <<X>> =:= <<A>>]),
+ [1] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), X == a]),
+ [a] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a},{b},{c}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X}=Y <- ets:table(E),
+ element(2, Y) == b,
+ X =:= 1]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,b},{2,3}])">>,
+ {warnings,[{{3,48},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), element(1,{X}) =:= 1]),
+ [1] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), 1 =:= element(1,{X})]),
+ [1] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= {1},
+ element(1,X) =:= 2]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{{1}},{{2}}])">>,
+ {warnings,[{{4,47},qlc,nomatch_filter}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= {1},
+ element(1,X) =:= element(1, {2})]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{{1}},{{2}}])">>,
+ {warnings,[{{4,47},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ element(1,X) =:= 1, X =:= {1}]),
+ [{1}] = qlc:e(Q),
+ [{1}] = lookup_keys(Q)
+ end, [{{1}},{{2}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ {{element(1,element(1,{{1}}))}} =:= {X}]),
+ [{1}] = qlc:e(Q),
+ [{1}] = lookup_keys(Q)
+ end, [{{1}},{{2}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ {element(1,element(1, {{1}}))} =:=
+ {element(1,X)}]),
+ [{1}] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ (X =:= 1) and (Y =:= 2)
+ or (X =:= 3) and (Y =:= 4)]),
+ [1,3] = lists:sort(qlc:e(Q)),
+ [{1,2}, {3,4}] = lookup_keys(Q)
+ end, [{{1,2}}, {{3,4}}, {{2,3}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,a}} <- ets:table(E), X =:= 3]),
+ [3] = qlc:e(Q),
+ [{3,a}] = lookup_keys(Q)
+ end, [{{3,a}},{{3,b}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y},_Z} <- ets:table(E),
+ X =:= 3, Y =:= a]),
+ [3] = qlc:e(Q),
+ [{3,a}] = lookup_keys(Q)
+ end, [{{3,a},3}, {{4,a},3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y},_Z} <- ets:table(E),
+ (X =:= 3) and (Y =:= a)
+ or (X =:= 4) and (Y =:= a)]),
+ [3,4] = qlc:e(Q),
+ [{3,a}, {4,a}] = lookup_keys(Q)
+ end, [{{3,a},3}, {{4,a},3}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ (X =:= 3) and (X =:= a)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{3}, {4}])">>,
+ {warnings,[{{3,44},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ X =:= 3, ((Y =:= a) or (Y =:= b))]),
+ [3,3] = qlc:e(Q),
+ [{3,a},{3,b}] = lists:sort(lookup_keys(Q))
+ end, [{{3,a}},{{2,b}},{{3,b}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,Y} <- ets:table(E),
+ ((X =:= 3) or (Y =:= 4)) and (X == a)]),
+ [a] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a,4},{3,3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,Y} <- ets:table(E),
+ (X =:= 3) or ((Y =:= 4) and (X == a))]),
+ [3,a] = lists:sort(qlc:e(Q)),
+ [3,a] = lookup_keys(Q)
+ end, [{a,4},{3,3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ (X =:= 3) or ((Y =:= 4) and (X == a))]),
+ [3,a] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{{3,a}},{{2,b}},{{a,4}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ ((X =:= 3) or (Y =:= 4)) and (X == a)]),
+ [a] = lists:sort(qlc:e(Q)),
+ [{a,4}] = lookup_keys(Q)
+ end, [{{3,a}},{{2,b}},{{a,4}}])">>,
+
+ <<"etsc(fun(E) ->
+ NoAnswers = 3*3*3+2*2*2,
+ Q = qlc:q([{X,Y,Z} ||
+ {{X,Y,Z}} <- ets:table(E),
+ (((X =:= 4) or (X =:= 5)) and
+ ((Y =:= 4) or (Y =:= 5)) and
+ ((Z =:= 4) or (Z =:= 5))) or
+ (((X =:= 1) or (X =:= 2) or (X =:= 3)) and
+ ((Y =:= 1) or (Y =:= 2) or (Y =:= 3)) and
+ ((Z =:= 1) or (Z =:= 2) or (Z =:= 3)))],
+ {max_lookup, NoAnswers}),
+ {list, {table, _}, _} = i(Q),
+ [{1,1,1},{2,2,2},{3,3,3}] = lists:sort(qlc:e(Q)),
+ true = NoAnswers =:= length(lookup_keys(Q))
+ end, [{{1,1,1}},{{2,2,2}},{{3,3,3}},{{3,3,4}},{{4,1,1}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y,Z} ||
+ {{X,Y,Z}} <- ets:table(E),
+ (((X =:= 4) or (X =:= 5)) and
+ ((Y =:= 4) or (Y =:= 5)) and
+ ((Z =:= 4) or (Z =:= 5))) or
+ (((X =:= 1) or (X =:= 2) or (X =:= 3)) and
+ ((Y =:= 1) or (Y =:= 2) or (Y =:= 3)) and
+ ((Z =:= 1) or (Z =:= 2) or (Z =:= 3)))],
+ {max_lookup, 10}),
+ [{1,1,1},{2,2,2},{3,3,3}] = lists:sort(qlc:e(Q)),
+ {table,{ets,table,[_,[{traverse,{select,_}}]]}} = i(Q)
+ end, [{{1,1,1}},{{2,2,2}},{{3,3,3}},{{3,3,4}},{{4,1,1}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X={_,_,_} <- ets:table(E),
+ element(1, X) =:= 3, element(2, X) == a]),
+ [{3,a,s}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{1,c,q},{2,b,r},{3,a,s}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(0, X) =:= 3]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ F = fun(_) -> 3 end,
+ %% No occurs check; X =:= F(X) is ignored.
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= 3, X =:= F(X)]),
+ {qlc,_,[{generate,_,{list,{table,_},_}},_],[]} = i(Q),
+ [3] = lists:sort(qlc:e(Q)),
+ [3] = lookup_keys(Q)
+ end, [{2},{3},{4}])">>,
+
+ <<"etsc(fun(E) ->
+ A = a, B = a,
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ ((X =:= A) and (Y =:= B))
+ or ((X =:= B) and (Y =:= A))]),
+ [a] = qlc:e(Q),
+ %% keys are usorted, duplicate removed:
+ [{a,a}] = lookup_keys(Q)
+ end, [{{a,a}},{{b,b}}])">>,
+
+ <<"etsc(fun(E) ->
+ A = a, B = b,
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ ((X =:= A) and (Y =:= B))
+ or ((X =:= B) and (Y =:= A))]),
+ [a,b] = lists:sort(qlc:e(Q)),
+ [{a,b},{b,a}] = lookup_keys(Q)
+ end, [{{a,b}},{{b,a}},{{c,a}},{{d,b}}])">>,
+
+ %% The atom 'fail' is recognized - lookup.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || {A} <- ets:table(E),
+ (A =:= 2)
+ orelse fail
+ ]),
+ [2] = lists:sort(qlc:e(Q)),
+ [2] = lookup_keys(Q)
+ end, [{1},{2}])">>
+
+ ],
+ ?line run(Config, Ts),
+
+ TsR = [
+ %% is_record/2,3:
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ erlang:is_record(X, r, 2)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ is_record(X, r, 2)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ {erlang,is_record}(X, r, 2)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ record(X, r)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ {warnings,[{{4,45},erl_lint,{obsolete_guard,{record,2}}}]}},
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ erlang:is_record(X, r)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ is_record(X, r)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ {erlang,is_record}(X, r)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>
+
+ ],
+ ?line run(Config, <<"-record(r, {a}).\n">>, TsR),
+
+ Ts2 = [
+ <<"etsc(fun(E) ->
+ Q0 = qlc:q([X ||
+ X <- ets:table(E),
+ (element(1, X) =:= 1) or
+ (element(1, X) =:= 2)],
+ {cache,ets}),
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ Y <- Q0]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}], []} = i(Q),
+ [{1,{1}},{1,{2}},{2,{1}},{2,{2}}] = lists:sort(qlc:e(Q)),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q0 = qlc:q([X ||
+ X <- ets:table(E),
+ (element(1, X) =:= 1) or
+ (element(1, X) =:= 2)]),
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ Y <- Q0],
+ {cache,true}),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ %% One introduced QLC expression (join, ms), and the cache option.
+ <<"%% Match spec and lookup. The lookup is done twice, which might
+ %% be confusing...
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y} <- ets:table(E),
+ (Y =:= 1) or (Y =:= 2)],
+ []),
+ [{1,1},{1,2},{2,1},{2,2}] = qlc:e(Q),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{list,{table,_},_}}],[]} = i(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% The same as last example, but with cache.
+ %% No cache needed (always one lookup only).
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y} <- ets:table(E),
+ (Y =:= 1) or (Y =:= 2)],
+ [cache]),
+ [{1,1},{1,2},{2,1},{2,2}] = qlc:e(Q),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{list,{table,_},_}}],[]} = i(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+
+ <<"%% And again, this time only lookup, no mach spec.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ Y <- ets:table(E),
+ (element(1, Y) =:= 1)
+ or (element(1, Y) =:= 2)],
+ []),
+ [{1,{1}},{1,{2}},{2,{1}},{2,{2}}] = qlc:e(Q),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% As last one, but with cache.
+ %% No cache needed (always one lookup only).
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ Y <- ets:table(E),
+ (element(1, Y) =:= 1)
+ or (element(1, Y) =:= 2)],
+ [cache]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [{1,{1}},{1,{2}},{2,{1}},{2,{2}}] = qlc:e(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+
+ <<"%% Lookup only. No cache.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y=2} <- ets:table(E)],
+ []),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [{1,2},{2,2}] = qlc:e(Q),
+ [2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% Lookup only. No cache.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y=2} <- ets:table(E)],
+ [cache]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [{1,2},{2,2}] = qlc:e(Q),
+ [2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+
+ <<"%% Matchspec only. No cache.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y} <- ets:table(E),
+ Y > 1],
+ []),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,
+ {table,{ets,_,[_,[{traverse,_}]]}}}],[]} =
+ i(Q),
+ [{1,2},{1,3},{2,2},{2,3}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% Matchspec only. Cache
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y} <- ets:table(E),
+ Y > 1],
+ [cache]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{table,{ets,_,[_,[{traverse,_}]]}}}],
+ [{cache,ets}]}}],[]} = i(Q),
+ [{1,2},{1,3},{2,2},{2,3}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% An empty list. Always unique and cached.
+ Q = qlc:q([X || {X} <- [], X =:= 1, begin X > 0 end],
+ [{cache,true},{unique,true}]),
+ {qlc,_,[{generate,_,{list,[]}},_],[{unique,true}]} = i(Q),
+ _ = qlc:info(Q),
+ [] = qlc:e(Q)">>,
+ <<"%% A list is always cached.
+ Q = qlc:q([{X,Y} || Y <- [1,2], X <- [2,1,2]],
+ [cache]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{list,[2,1,2]}}],[]} = i(Q),
+ [{2,1},{1,1},{2,1},{2,2},{1,2},{2,2}] = qlc:e(Q)">>,
+ <<"%% But a processed list is never cached.
+ Q = qlc:q([{X,Y} || Y <- [1,2], X <- [2,1,2], X > 1],
+ [cache]),
+ {qlc,_,[{generate,_, {list,[1,2]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,{list,[2,1,2]},_}}],
+ [{cache,ets}]}}],[]} = i(Q),
+ [{2,1},{2,1},{2,2},{2,2}] = qlc:e(Q)">>,
+ <<"%% A bug fixed in R11B-2: coalescing simple_qlc:s works now.
+ Q0 = qlc:q([X || {X} <- [{1},{2},{3}]], {cache, ets}),
+ Q1 = qlc:q([X || X <- Q0], {cache, list}),
+ Q = qlc:q([{Y,X} || Y <- [1,2], X <- Q1, X < 2], {cache, list}),
+ {qlc,_,[{generate,_,{list,_}},
+ {generate,_,{qlc,_,[{generate,_,{list,{list,_},_}}],
+ [{cache,ets}]}},_],[]} = i(Q),
+ [{1,1},{2,1}] = qlc:e(Q)">>,
+ <<"Q = qlc:q([{X,Y} || Y <- [1,2], X <- [1,2], X > 1],
+ [cache,unique]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,{list,[1,2]},_}}],
+ [{cache,ets},{unique,true}]}}],
+ [{unique,true}]} = i(Q),
+ [{2,1},{2,2}] = qlc:e(Q)">>,
+ <<"L = [1,2,3],
+ QH1 = qlc:q([{X} || X <- L, X > 1]),
+ QH2 = qlc:q([{X} || X <- QH1, X > 0], [cache]),
+ [{{2}},{{3}}] = qlc:e(QH2),
+ {list,{list,{list,L},_},_} = i(QH2)">>,
+ <<"L = [1,2,3,1,2,3],
+ QH1 = qlc:q([{X} || X <- L, X > 1]),
+ QH2 = qlc:q([{X} || X <- QH1, X > 0], [cache,unique]),
+ [{{2}},{{3}}] = qlc:e(QH2),
+ {qlc,_,[{generate,_,{list,{list,{list,L},_},_}}],
+ [{unique,true}]} = i(QH2)">>
+
+ ],
+
+ ?line run(Config, Ts2),
+
+ LTs = [
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(1, X) =:= 1],
+ {lookup,true}),
+ {table,L} = i(Q),
+ true = is_list(L),
+ [{1,a}] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1,a},{2,b}])">>,
+ <<"%% No lookup, use the match spec for traversal instead.
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(1, X) =:= 1],
+ {lookup,false}),
+ {table,{ets,table,_}} = i(Q),
+ [{1,a}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,a},{2,b}])">>,
+ <<"%% As last one. {max_lookup,0} has the same effect.
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(1, X) =:= 1],
+ {max_lookup,0}),
+ {table,{ets,table,_}} = i(Q),
+ [{1,a}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,a},{2,b}])">>
+
+ ],
+ ?line run(Config, LTs),
+
+ ok.
+
+lookup_rec(doc) ->
+ "Lookup keys. With records.";
+lookup_rec(suite) -> [];
+lookup_rec(Config) when is_list(Config) ->
+ Ts = [
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = A} <- ets:table(E),
+ (A =:= 3) or (4 =:= A)]),
+ [3] = qlc:e(Q),
+ [3,4] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = a}, #r{a = 3}, #r{a = 5}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = 17 = A} <- ets:table(E),
+ (A =:= 3) or (4 =:= A)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = 17}, #r{a = 3}, #r{a = 5}])">>,
+ {warnings,[{{4,44},qlc,nomatch_filter}]}},
+
+ <<"%% Compares an integer and a float.
+ etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = 17 = A} <- ets:table(E),
+ (A == 17) or (17.0 == A)]),
+ [_] = qlc:e(Q),
+ [_] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = 17}, #r{a = 3}, #r{a = 5}])">>,
+
+ <<"%% Compares an integer and a float.
+ %% There is a test in qlc_pt.erl (Op =:= '=:=', C1 =:= C2), but
+ %% that case is handled in an earlier clause (unify ... E, E).
+ etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = 17.0 = A} <- ets:table(E),
+ (A =:= 17) or (17.0 =:= A)]),
+ [_] = qlc:e(Q),
+ [_] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = 17.0}, #r{a = 3}, #r{a = 5}])">>,
+
+ <<"%% Matches an integer and a float.
+ etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = 17 = A} <- ets:table(E),
+ (A =:= 17) or (17.0 =:= A)]),
+ [_] = qlc:e(Q),
+ [_] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = 17}, #r{a = 3}, #r{a = 5}])">>,
+
+ <<"etsc(fun(E) ->
+ F = fun(_) -> 17 end,
+ Q = qlc:q([A || #r{a = A} <- ets:table(E),
+ (F(A) =:= 3) and (A =:= 4)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q) % F(A) could fail
+ end, [{keypos,2}], [#r{a = 4}, #r{a = 3}, #r{a = 5}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ #r{} == X]),
+ [#r{}] = lists:sort(qlc:e(Q)),
+ {call,_,_,[_,_]} = i(Q, {format, abstract_code}),
+ [#r{}] = lookup_keys(Q)
+ end, [{#r{}},{#r{a=foo}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([R#r.a || R <- ets:table(E), R#r.a =:= foo]),
+ [foo] = qlc:e(Q),
+ [_] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a=foo}])">>
+ ],
+ ?line run(Config, <<"-record(r, {a}).\n">>, Ts),
+ ok.
+
+indices(doc) ->
+ "Using indices for lookup.";
+indices(suite) -> [];
+indices(Config) when is_list(Config) ->
+ Ts = [
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) || X <- qlc_SUITE:table(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ {list, {table,{qlc_SUITE,list_keys,[[a,b],2,L]}}, _MS} = i(QH),
+ [1,2] = qlc:eval(QH)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) || X <- qlc_SUITE:table(L, [2]),
+ begin (element(2, X) =:= a)
+ or (b =:= element(2, X)) end]),
+ {qlc,_,[{generate,_,{table,{call,_,
+ {remote,_,_,{atom,_,the_list}},_}}},_],[]}
+ = i(QH),
+ [1,2] = qlc:eval(QH)">>,
+
+ <<"L = [{1,a,q},{2,b,r},{3,c,s}],
+ QH = qlc:q([element(1, X) || X <- qlc_SUITE:table(L, [2,3]),
+ (element(3, X) =:= q)
+ or (r =:= element(3, X))]),
+ {list, {table,{qlc_SUITE,list_keys, [[q,r],3,L]}}, _MS} = i(QH),
+ [1,2] = qlc:eval(QH)">>,
+
+ <<"L = [{1,a,q},{2,b,r},{3,c,s}],
+ QH = qlc:q([element(1, X) || X <- qlc_SUITE:table(L, 1, [2]),
+ (element(3, X) =:= q)
+ or (r =:= element(3, X))]),
+ {qlc,_,[{generate,_,{table,{call,_,_,_}}},
+ _],[]} = i(QH),
+ [1,2] = qlc:eval(QH)">>,
+
+ <<"L = [{a,1},{b,2},{c,3}],
+ QH = qlc:q([E || {K,I}=E <- qlc_SUITE:table(L, 1, [2]),
+ ((K =:= a) or (K =:= b) or (K =:= c))
+ and ((I =:= 1) or (I =:= 2))],
+ {max_lookup, 3}),
+ {list, {table,{qlc_SUITE,list_keys,[[a,b,c],1,L]}}, _MS} = i(QH),
+ [{a,1},{b,2}] = qlc:eval(QH)">>,
+
+ <<"L = [{a,1},{b,2},{c,3}],
+ QH = qlc:q([E || {K,I}=E <- qlc_SUITE:table(L, 1, [2]),
+ ((K =:= a) or (K =:= b) or (K =:= c))
+ and ((I =:= 1) or (I =:= 2))],
+ {max_lookup, 2}),
+ {list, {table,{qlc_SUITE,list_keys, [[1,2],2,L]}}, _MS} = i(QH),
+ [{a,1},{b,2}] = qlc:eval(QH)">>,
+
+ <<"L = [{a,1,x,u},{b,2,y,v},{c,3,z,w}],
+ QH = qlc:q([E || {K,I1,I2,I3}=E <- qlc_SUITE:table(L, 1, [2,3,4]),
+ ((K =/= a) or (K =/= b) or (K =/= c))
+ and ((I1 =:= 1) or (I1 =:= 2) or
+ (I1 =:= 3) or (I1 =:= 4))
+ and ((I2 =:= x) or (I2 =:= z))
+ and ((I3 =:= v) or (I3 =:= w))],
+ {max_lookup, 5}),
+ {list, {table,{qlc_SUITE,list_keys, [[x,z],3,L]}}, _MS} = i(QH),
+ [{c,3,z,w}] = qlc:eval(QH)">>
+
+ ],
+ ?line run(Config, <<"-record(r, {a}).\n">>, Ts),
+ ok.
+
+pre_fun(doc) ->
+ "Test the table/2 callback functions parent_fun and stop_fun.";
+pre_fun(suite) -> [];
+pre_fun(Config) when is_list(Config) ->
+ Ts = [
+ <<"PF = process_flag(trap_exit, true),
+ %% cursor: table killing parent
+ L = [{1,a},{2,b},{3,c}],
+ F1 = fun() ->
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_kill_parent(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ _ = qlc:info(QH),
+ _ = qlc:cursor(QH)
+ end,
+ Pid1 = spawn_link(F1),
+ receive {'EXIT', Pid1, killed} ->
+ ok
+ end,
+ timer:sleep(1),
+ process_flag(trap_exit, PF)">>,
+
+ <<"PF = process_flag(trap_exit, true),
+ %% eval without cursor: table killing parent
+ L = [{1,a},{2,b},{3,c}],
+ F2 = fun() ->
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_kill_parent(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ _ = qlc:eval(QH)
+ end,
+ Pid2 = spawn_link(F2),
+ receive {'EXIT', Pid2, killed} ->
+ ok
+ end,
+ process_flag(trap_exit, PF)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_parent_throws(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ _ = qlc:info(QH),
+ {throw,thrown} = (catch {any_term,qlc:cursor(QH)}),
+ {throw,thrown} = (catch {any_term,qlc:eval(QH)})">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_parent_exits(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ _ = qlc:info(QH),
+ {'EXIT', {badarith,_}} = (catch qlc:cursor(QH)),
+ {'EXIT', {badarith,_}} = (catch qlc:eval(QH))">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_bad_parent_fun(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ {'EXIT', {badarg,_}} = (catch qlc:cursor(QH)),
+ {'EXIT', {badarg,_}} = (catch qlc:eval(QH))">>,
+
+ <<"%% Very simple test of stop_fun.
+ Ets = ets:new(apa, [public]),
+ L = [{1,a},{2,b},{3,c}],
+ H = qlc:q([X || {X,_} <- qlc_SUITE:stop_list(L, Ets)]),
+ C = qlc:cursor(H),
+ [{stop_fun,StopFun}] = ets:lookup(Ets, stop_fun),
+ StopFun(),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, _}, _}} =
+ (catch qlc:next_answers(C, all_remaining)),
+ ets:delete(Ets)">>
+
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+skip_filters(doc) ->
+ "Lookup keys. With records.";
+skip_filters(suite) -> [];
+skip_filters(Config) when is_list(Config) ->
+ %% Skipped filters
+ TsS = [
+ %% Cannot skip the filter.
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || X <- ets:table(E),
+ (element(1, X) =:= 1) xor (element(1, X) =:= 1)]),
+ [] = qlc:eval(H),
+ [1] = lookup_keys(H)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ %% The filter can be skipped. Just a lookup remains.
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || X <- ets:table(E),
+ (element(1, X) =:= 1) or (element(1, X) =:= 1)]),
+ [{1}] = qlc:eval(H),
+ {table, _} = i(H),
+ [1] = lookup_keys(H)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ %% safe_unify fails on 3 and <<X:32>>
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || X <- ets:table(E),
+ (element(1, X) =:= 1) and (3 =:= <<X:32>>)]),
+ [] = qlc:eval(H),
+ [1] = lookup_keys(H)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ %% Two filters are skipped.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{B,C,D} || {A={C},B} <- ets:table(E),
+ (A =:= {1}) or (A =:= {2}),
+ (C =:= 1) or (C =:= 2),
+ D <- [1,2]]),
+ {qlc,_,[{generate,_,{table,_}},{generate,_,{list,[1,2]}}],[]}
+ = i(Q),
+ [{1,1,1},{1,1,2},{2,2,1},{2,2,2}] = lists:sort(qlc:eval(Q)),
+ [{1},{2}] = lookup_keys(Q)
+ end, [{{1},1},{{2},2},{{3},3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{B,C} || {A={C},B} <- ets:table(E),
+ (A =:= {1}) or (A =:= {2}),
+ (C =:= 1) or (C =:= 2)]),
+ {qlc,_,[{generate,_,{table,_}}],[]} = i(Q),
+ [{1,1},{2,2}] = lists:sort(qlc:eval(Q)),
+ [{1},{2}] = lookup_keys(Q)
+ end, [{{1},1},{{2},2},{{3},3}])">>,
+
+ %% Lookup. No match spec, no filter.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(1, X) =:= 1]),
+ {table, _} = i(Q),
+ [{1}] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || X <- ets:table(E),
+ element(1, X) =:= 1,
+ Y <- [1,2]]),
+ {qlc,_,[{generate,_,{table,_}},{generate,_,{list,_}}],[]}
+ = i(Q),
+ [{{1},1},{{1},2}] = lists:sort(qlc:e(Q)),
+ [1] = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,Y} <- ets:table(E),
+ X =:= a,
+ X =:= Y]),
+ {list,{table,_},_} = i(Q),
+ [a] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a,a},{b,c},{c,a}])">>,
+
+ %% The imported variable (A) is never looked up in the current
+ %% implementation. This means that the first filter cannot be skipped;
+ %% the constant 'a' is looked up, and then the first filter evaluates
+ %% to false.
+ <<"etsc(fun(E) ->
+ A = 3,
+ Q = qlc:q([X || X <- ets:table(E),
+ A == element(1,X),
+ element(1,X) =:= a]),
+ [] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a},{b},{c}])">>,
+
+ %% No lookup.
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= 1,
+ X =:= 2]),
+ {table, _} = i(Q),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,1},{2,0}])">>,
+ {warnings,[{{4,37},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,C} ||
+ {A} <- ets:table(E),
+ A =:= 1,
+ {B} <- ets:table(E),
+ B =:= 2,
+ {C} <- ets:table(E),
+ C =:= 3]),
+ {qlc,_,[{generate,_,{list,{table,_},_}},
+ {generate,_,{list,{table,_},_}},
+ {generate,_,{list,{table,_},_}}],[]} = i(Q),
+ [{1,2,3}] = qlc:e(Q),
+ [1,2,3] = lookup_keys(Q)
+ end, [{0},{1},{2},{3},{4}])">>
+
+ ],
+ ?line run(Config, TsS),
+
+ Ts = [
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= 2]),
+ {list,{table,_},_} = i(H),
+ [2] = qlc:e(H)
+ end, [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ ((X =:= 2) or (X =:= 1)) and (X > 1)]),
+ {list,{table,_},_} = i(H),
+ [2] = qlc:e(H)
+ end, [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,Y} <- ets:table(E),
+ (X =:= 2) and (Y =:= b)]),
+ {list,{table,_},_} = i(H),
+ [2] = qlc:e(H)
+ end, [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || X <- ets:table(E),
+ (element(1,X) =:= 2) and (X =:= {2,b})]),
+ {list,{table,_},_} = i(H),
+ [{2,b}] = qlc:e(H)
+ end, [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([{X,Y,Z,W} ||
+ {X,Y} <- ets:table(E),
+ {Z,W} <- ets:table(E),
+ (Y =:= 3) or (Y =:= 4)]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{table,{ets,table,_}}}],[]} = i(H),
+ [{a,3,a,3},{a,3,b,5}] = lists:sort(qlc:e(H))
+ end, [{a,3},{b,5}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([{X,Y} ||
+ {X,Y=3} <- ets:table(E), % no matchspec
+ %% Two columns restricted, but lookup anyway
+ (X =:= a)]),
+ {qlc,_,[{generate,_,{table,_}}],[]} = i(H),
+ [{a,3}] = qlc:e(H)
+ end, [{a,3},{b,4}])">>,
+
+ <<"etsc(fun(E) ->
+ V = 3,
+ H = qlc:q([{X,Y} ||
+ {X,Y} <- ets:table(E),
+ (Y =:= V)]), % imported variable, no lookup
+ {table,{ets,table,_}} = i(H),
+ [{a,3}] = qlc:e(H)
+ end, [{a,3},{b,4}])">>,
+
+ <<"etsc(fun(E) ->
+ V = b,
+ H = qlc:q([{X,Y} ||
+ {X,Y} <- ets:table(E),
+ (X =:= V)]), % imported variable, lookup
+ {list,{table,_},_} = i(H),
+ [{b,4}] = qlc:e(H)
+ end, [{a,3},{b,4}])">>,
+
+ <<"H = qlc:q([{A,B} || {{A,B}} <- [{{1,a}},{{2,b}}],
+ A =:= 1,
+ B =:= a]),
+ {list,{list,[_,_]},_} = i(H),
+ [{1,a}] = qlc:e(H)">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([{A,B} || {{A,B}} <- ets:table(E),
+ A =:= 1,
+ B =:= a]),
+ {list,{table,_},_} = i(H),
+ [{1,a}] = qlc:e(H)
+ end, [{{1,a}},{{2,b}}])">>,
+
+ %% The filters are skipped, and the guards of the match specifications
+ %% are skipped as well. Only the transformations of the matchspecs
+ %% are kept.
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ H = qlc:q([{X,Y,Z,W} ||
+ {X,_}=Z <- ets:table(E1),
+ W={Y} <- ets:table(E2),
+ (X =:= 1) or (X =:= 2),
+ (Y =:= a) or (Y =:= b)]
+ ,{lookup,true}
+ ),
+ {qlc,_,[{generate,_,{list,{table,_},
+ [{{'$1','_'},[],['$_']}]}},
+ {generate,_,{list,{table,_},
+ [{{'$1'},[],['$_']}]}}],[]}
+ = i(H),
+ [{1,a,{1,a},{a}},
+ {1,b,{1,a},{b}},
+ {2,a,{2,b},{a}},
+ {2,b,{2,b},{b}}] = qlc:e(H)
+ end, [{a},{b}])
+ end, [{1,a},{2,b}])">>,
+
+ %% The same example again, but this time no match specs are run.
+ <<"fun(Z) ->
+ etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ H = qlc:q([{X,Y} ||
+ Z > 2,
+ X <- ets:table(E1),
+ Y <- ets:table(E2),
+ (element(1, X) =:= 1) or
+ (element(1, X) =:= 2),
+ (element(1, Y) =:= a) or
+ (element(1, Y) =:= b)]
+ ,{lookup,true}
+ ),
+ {qlc,_,[_,{generate,_,{table,_}},
+ {generate,_,{table,_}}],[]} = i(H),
+ [{{1,a},{a}},
+ {{1,a},{b}},
+ {{2,b},{a}},
+ {{2,b},{b}}] = qlc:e(H)
+ end, [{a},{b}])
+ end, [{1,a},{2,b}])
+ end(4)">>,
+
+ %% Once again, this time with a join.
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ H = qlc:q([{X,Y,Z,W} ||
+ {X,V}=Z <- ets:table(E1),
+ W={Y} <- ets:table(E2),
+ (X =:= 1) or (X =:= 2),
+ (Y =:= a) or (Y =:= b),
+ Y =:= V]
+ ,[{lookup,true},{join,merge}]
+ ),
+ {qlc,_,[{generate,_,{qlc,_,
+ [{generate,_,{qlc,_,[{generate,_,
+ {keysort,{list,{table,_},_},2,[]}},
+ _C1,_C2],[]}},
+ {generate,_,
+ {qlc,_,[{generate, _,
+ {keysort,{list,{table,_},_},1,[]}},
+ _C3],
+ []}},
+ _],
+ [{join,merge}]}},_],[]} = i(H),
+ [{1,a,{1,a},{a}},{2,b,{2,b},{b}}] =
+ lists:sort(qlc:e(H))
+ end, [{a},{b}])
+ end, [{1,a},{2,b}])">>,
+
+ %% Filters 2 and 3 are not skipped.
+ %% (Only one filter at a time is tried by the parse transform.)
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {{A,B}=X,Y} <- ets:table(E), % no matchspec
+ Y =:= 3,
+ A =:= 1,
+ B =:= a]),
+
+ {qlc,_,[{generate,_,{table,_}},_,_,_],[]}= i(H),
+ [{1,a}] = qlc:e(H)
+ end, [{{1,a},3},{{2,b},4}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X=_,_} <- ets:table(E), % no matchspec
+ (X =:= 3) and (X > 3)]),
+ {qlc,_,[{generate,_,{table,_}},_],[]} = i(H),
+ [] = qlc:e(H)
+ end, [{3,a},{4,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X=_,_} <- ets:table(E), % no matchspec
+ (X =:= 3) or true]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},_],[]} = i(H),
+ [3,4] = lists:sort(qlc:e(H))
+ end, [{3,a},{4,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X=_,_} <- ets:table(E), % no matchspec
+ (X =:= 3) or false]),
+ {qlc,_,[{generate,_,{table,_}}],[]} = i(H),
+ [3] = lists:sort(qlc:e(H))
+ end, [{3,a},{4,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X=_,_} <- ets:table(E), % no matchspec
+ (X =:= X) and (X =:= 3)]),
+ {qlc,_,[{generate,_,{table,_}}],[]} = i(H),
+ [3] = lists:sort(qlc:e(H))
+ end, [{3,a},{4,b}])">>,
+
+ %% The order of filters matters. A guard filter cannot be used
+ %% unless there are no non-guard filter placed before the guard
+ %% filter that uses the guard filter's generator. There is
+ %% more examples in join_filter().
+ <<"etsc(fun(E) ->
+ %% Lookup.
+ Q = qlc:q([{A,B,A} ||
+ {A=_,B} <- ets:table(E), % no match spec
+ A =:= 1,
+ begin 1/B > 0 end]),
+ [{1,1,1}] = lists:sort(qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ <<"etsc(fun(E) ->
+ %% No lookup.
+ Q = qlc:q([{A,B,A} ||
+ {A=_,B} <- ets:table(E), % no match spec
+ begin 1/B > 0 end,
+ A =:= 1]),
+ {'EXIT', _} = (catch qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ %% The same thing, with a match specification.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,A} ||
+ {A,B} <- ets:table(E), % match spec
+ A < 2,
+ begin 1/B > 0 end]),
+ [{1,1,1}] = lists:sort(qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,A} ||
+ {A,B} <- ets:table(E), % match spec
+ begin 1/B > 0 end,
+ A < 2]),
+ {'EXIT', _} = (catch qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ %% More examples, this time two tables.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E), % match spec
+ A < 2,
+ {C,D} <- ets:table(E),
+ begin 1/B > 0 end, %\"invalidates\" next filter
+ C =:= 1,
+ begin 1/D > 0 end]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{table,{ets,table,_}}},
+ _,_,_],[]} = i(Q),
+ [{1,1,1,1}] = lists:sort(qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E),
+ {C,D} <- ets:table(E),
+ begin 1/B > 0 end, % \"invalidates\" two filters
+ A < 2,
+ C =:= 1,
+ begin 1/D > 0 end]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{table,{ets,table,_}}},_,_,_,_],[]} = i(Q),
+ {'EXIT', _} = (catch qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ <<"%% There are objects in the ETS table, but none passes the filter.
+ %% F() would not be run if it did not \"invalidate\" the following
+ %% guards.
+ etsc(fun(E) ->
+ F = fun() -> [foo || A <- [0], 1/A] end,
+ Q1 = qlc:q([X || {X} <- ets:table(E),
+ F(), % \"invalidates\" next guard
+ X =:= 17]),
+ {'EXIT', _} = (catch qlc:e(Q1))
+ end, [{1},{2},{3}])">>,
+ <<"%% The last example works just like this one:
+ etsc(fun(E) ->
+ F = fun() -> [foo || A <- [0], 1/A] end,
+ Q1 = qlc:q([X || {X} <- ets:table(E),
+ F(),
+ begin X =:= 17 end]),
+ {'EXIT', _} = (catch qlc:e(Q1))
+ end, [{1},{2},{3}])">>
+
+ ],
+ ?line run(Config, Ts),
+
+ ok.
+
+table_impls(suite) ->
+ [ets, dets].
+
+ets(doc) ->
+ "ets:table/1,2.";
+ets(suite) -> [];
+ets(Config) when is_list(Config) ->
+ Ts = [
+ <<"E = ets:new(t, [ordered_set]),
+ true = ets:insert(E, [{1},{2}]),
+ {'EXIT', _} =
+ (catch qlc:e(qlc:q([X || {X} <- ets:table(E, bad_option)]))),
+ {'EXIT', _} =
+ (catch qlc:e(qlc:q([X || {X} <- ets:table(E,{traverse,bad})]))),
+ All = [{'$1',[],['$1']}],
+ TravAll = {traverse,{select,All}},
+ [_, _] = qlc:e(qlc:q([X || {X} <- ets:table(E, TravAll)])),
+ [_, _] = qlc:e(qlc:q([X || {X} <- ets:table(E,{traverse,select})])),
+ [1,2] =
+ qlc:e(qlc:q([X || {X} <- ets:table(E, {traverse, first_next})])),
+ [2,1] =
+ qlc:e(qlc:q([X || {X} <- ets:table(E, {traverse, last_prev})])),
+ {table,{ets,table,[_,[{traverse,{select,_}},{n_objects,1}]]}} =
+ i(qlc:q([X || {X} <- ets:table(E, {n_objects,1})])),
+ {qlc,_,[{generate,_,{table,{ets,table,[_,{n_objects,1}]}}},_],[]} =
+ i(qlc:q([X || {X} <- ets:table(E,{n_objects,1}),
+ begin (X >= 1) or (X < 1) end])),
+ {qlc,_,[{generate,_,{table,{ets,table,[_]}}},_],[]} =
+ i(qlc:q([X || {X} <- ets:table(E),
+ begin (X >= 1) or (X < 1) end])),
+ ets:delete(E)">>,
+
+ begin
+ MS = ets:fun2ms(fun({X,Y}) when X > 1 -> {X,Y} end),
+ [<<"E = ets:new(apa,[]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ Q = qlc:q([X || {X,_} <- ets:table(E, {traverse, {select, MS}}),
+ X =:= 1]),
+ R = qlc:e(Q),
+ ets:delete(E),
+ [] = R">>]
+ end
+
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+dets(doc) ->
+ "dets:table/1,2.";
+dets(suite) -> [];
+dets(Config) when is_list(Config) ->
+ dets:start(),
+ T = t,
+ Fname = filename(T, Config),
+ Ts = [
+ [<<"T = t, Fname = \"">>, Fname, <<"\",
+ file:delete(Fname),
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ ok = dets:insert(T, [{1},{2}]),
+ {'EXIT', _} =
+ (catch qlc:e(qlc:q([X || {X} <- dets:table(T, bad_option)]))),
+ {'EXIT', _} =
+ (catch qlc:e(qlc:q([X || {X} <- dets:table(T,{traverse,bad})]))),
+ {'EXIT', _} =
+ (catch
+ qlc:e(qlc:q([X || {X} <- dets:table(T,{traverse,last_prev})]))),
+ All = [{'$1',[],['$1']}],
+ TravAll = {traverse,{select,All}},
+ [_,_] = qlc:e(qlc:q([X || {X} <- dets:table(T, TravAll)])),
+ [_,_] = qlc:e(qlc:q([X || {X} <- dets:table(T,{traverse,select})])),
+ [_,_] =
+ qlc:e(qlc:q([X || {X} <- dets:table(T, {traverse, first_next})])),
+ {table,{dets,table,[T,[{traverse,{select,_}},{n_objects,1}]]}} =
+ i(qlc:q([X || {X} <- dets:table(T, {n_objects,1})])),
+ {qlc,_,[{generate,_,{table,{dets,table,[t,{n_objects,1}]}}},_],[]}=
+ i(qlc:q([X || {X} <- dets:table(T,{n_objects,1}),
+ begin (X >= 1) or (X < 1) end])),
+ {qlc,_,[{generate,_,{table,{dets,table,[_]}}},_],[]} =
+ i(qlc:q([X || {X} <- dets:table(T),
+ begin (X >= 1) or (X < 1) end])),
+ H = qlc:q([X || {X} <- dets:table(T, {n_objects, default}),
+ begin (X =:= 1) or (X =:= 2) or (X =:= 3) end]),
+ [1,2] = lists:sort(qlc:e(H)),
+ {qlc,_,[{generate,_,{table,_}},_],[]} = i(H),
+
+ H2 = qlc:q([X || {X} <- dets:table(T), (X =:= 1) or (X =:= 2)]),
+ [1,2] = lists:sort(qlc:e(H2)),
+ {list,{table,_},_} = i(H2),
+ true = binary_to_list(<<
+ \"ets:match_spec_run(lists:flatmap(fun(V)->dets:lookup(t,V)end,\"
+ \"[1,2]),ets:match_spec_compile([{{'$1'},[],['$1']}]))\">>)
+ == format_info(H2, true),
+
+ H3 = qlc:q([X || {X} <- dets:table(T), (X =:= 1)]),
+ [1] = qlc:e(H3),
+ {list,{table,_},_} = i(H3),
+
+ ok = dets:close(T),
+ file:delete(\"">>, Fname, <<"\"),
+ ok">>],
+
+ begin
+ MS = ets:fun2ms(fun({X,Y}) when X > 1 -> {X,Y} end),
+ [<<"T = t, Fname = \"">>, Fname, <<"\",
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ ok = dets:insert(T, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([X || {X,_} <- dets:table(T, {traverse, {select, MS}}),
+ X =:= 1]),
+ R = qlc:e(Q),
+ ok = dets:close(T),
+ file:delete(\"">>, Fname, <<"\"),
+ [] = R">>]
+ end,
+
+ [<<"T = t, Fname = \"">>, Fname, <<"\",
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ Objs = [{X} || X <- lists:seq(1,10)],
+ ok = dets:insert(T, Objs),
+ {ok, Where} = dets:where(T, {2}),
+ ok = dets:close(T),
+ qlc_SUITE:crash(Fname, Where),
+
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ HT = qlc:q([X || {X} <- dets:table(T, {traverse, first_next})]),
+ {'EXIT',{error,{{bad_object,_},_}}} = (catch qlc:e(HT)),
+ _ = dets:close(T),
+
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ HMS = qlc:q([X || {X} <- dets:table(T, {traverse, select})]),
+ {error,{{bad_object,_},_}} = qlc:e(HMS),
+ _ = dets:close(T),
+
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ HLU = qlc:q([X || {X} <- dets:table(T), X =:= 2]),
+ {error,{{bad_object,_},_}} = qlc:e(HLU),
+ _ = dets:close(T),
+
+ file:delete(Fname)">>]
+
+ ],
+
+ ?line run(Config, Ts),
+ _ = file:delete(Fname),
+ ok.
+
+join(suite) ->
+ [join_option, join_filter, join_lookup, join_merge,
+ join_sort, join_complex].
+
+join_option(doc) ->
+ "The 'join' option (any, lookup, merge, nested_loop). Also cache/unique.";
+join_option(suite) -> [];
+join_option(Config) when is_list(Config) ->
+ Ts = [
+ <<"Q1 = qlc:q([X || X <- [1,2,3]],{join,merge}),
+ {'EXIT', {no_join_to_carry_out,_}} = (catch {foo, qlc:info(Q1)}),
+ {'EXIT', {no_join_to_carry_out,_}} = (catch {foo, qlc:e(Q1)}),
+
+ Q2 = qlc:q([X || X <- [1,2,3], X > 1],{join,merge}),
+ {'EXIT', {no_join_to_carry_out,_}} = (catch {foo, qlc:info(Q2)}),
+ {'EXIT', {no_join_to_carry_out,_}} = (catch {foo, qlc:e(Q2)}),
+
+ Q3 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{a},{b},{c}],
+ X =:= Y],
+ {join, merge}),
+
+ {1,0,0,2} = join_info(Q3),
+ [] = qlc:e(Q3),
+
+ Q4 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{a},{b},{c}],
+ X > Y],
+ {join, lookup}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:info(Q4)}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:e(Q4)}),
+
+ Q5 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X == Y],
+ {join, merge}),
+ [{3,3}] = qlc:e(Q5),
+
+ Q6 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X == Y],
+ {join, lookup}),
+ {'EXIT', {cannot_carry_out_join, _}} = (catch {foo, qlc:info(Q6)}),
+ {'EXIT', {cannot_carry_out_join, _}} = (catch {foo, qlc:e(Q6)}),
+
+ Q7 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X == Y],
+ {join, nested_loop}),
+ {0,0,1,0} = join_info(Q7),
+ [{3,3}] = qlc:e(Q7),
+
+ Q8 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X =:= Y],
+ {join, nested_loop}),
+ {0,0,1,0} = join_info(Q8),
+ [{3,3}] = qlc:e(Q8),
+
+ %% Only guards are inspected...
+ Q9 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ begin X =:= Y end],
+ {join, nested_loop}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:info(Q9)}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:e(Q9)}),
+
+ Q10 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X < Y],
+ {join, nested_loop}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:info(Q10)}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:e(Q10)}),
+
+ F = fun(J) -> qlc:q([X || X <- [1,2]], {join,J}) end,
+ {'EXIT', {no_join_to_carry_out, _}} =
+ (catch {foo, qlc:e(F(merge))}),
+ {'EXIT', {no_join_to_carry_out, _}} =
+ (catch {foo, qlc:e(F(lookup))}),
+ {'EXIT', {no_join_to_carry_out, _}} =
+ (catch {foo, qlc:e(F(nested_loop))}),
+ [1,2] = qlc:e(F(any)),
+
+ %% No join of columns in the same table.
+ Q11 = qlc:q([{X,Y} || {a = X, X = Y} <- [{a,1},{a,a},{a,3},{a,a}]],
+ {join,merge}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch qlc:e(Q11)),
+ Q12 = qlc:q([{X,Y} || {X = a, X = Y} <- [{a,1},{a,a},{a,3},{a,a}]],
+ {join,merge}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch qlc:e(Q12)),
+ %% X and Y are \"equal\" (same constants), but must not be joined.
+ Q13 = qlc:q([{X,Y} || {X,_Z} <- [{a,1},{a,2},{b,1},{b,2}],
+ {Y} <- [{a}],
+ (X =:= a) and (Y =:= b) or
+ (X =:= b) and (Y =:= a)],
+ {join,merge}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch qlc:e(Q13))
+
+">>,
+
+ <<"Q1 = qlc:q([X || X <- [1,2,3]], {lookup,true}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:info(Q1)}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:e(Q1)}),
+ Q2 = qlc:q([{X,Y} || X <- [1,2,3], Y <- [x,y,z]], lookup),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:info(Q2)}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:e(Q2)}),
+ Q3 = qlc:q([X || {X} <- [{1},{2},{3}]], {lookup,true}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:e(Q3)}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:info(Q3)}),
+
+ E1 = create_ets(1, 10),
+ Q4 = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), X =:= 3], lookup),
+ {match_spec, _} = strip_qlc_call(Q4),
+ [{3,3}] = qlc:e(Q4),
+ Q5 = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), X =:= 3], {lookup,false}),
+ {table, ets, _} = strip_qlc_call(Q5),
+ [{3,3}] = qlc:e(Q5),
+ Q6 = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), X =:= 3], {lookup,any}),
+ {match_spec, _} = strip_qlc_call(Q6),
+ [{3,3}] = qlc:e(Q6),
+ ets:delete(E1)">>
+
+ ],
+ ?line run(Config, Ts),
+
+ %% The 'cache' and 'unique' options of qlc/2 affects join.
+ CUTs = [
+ <<"L1 = [1,2],
+ L2 = [{1,a},{2,b}],
+ L3 = [{a,1},{b,2}],
+ Q = qlc:q([{X,Y,Z} ||
+ Z <- L1,
+ {X,_} <- L2,
+ {_,Y} <- L3,
+ X =:= Y],
+ [cache, unique]),
+ {qlc,_,
+ [{generate,_,{list,L1}},
+ {generate,_,{qlc,_,[{generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,L2},1,[]}}],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{keysort,{list,L3},2,[]}}],[]}},_],
+ [{join,merge},{cache,ets},{unique,true}]}},_],
+ [{unique,true}]} = i(Q),
+ [{1,1,1},{2,2,1},{1,1,2},{2,2,2}] = qlc:e(Q)">>,
+ <<"L1 = [1,2],
+ L2 = [{1,a},{2,b}],
+ L3 = [{a,1},{b,2}],
+ Q = qlc:q([{X,Y,Z} ||
+ Z <- L1,
+ {X,_} <- L2,
+ {_,Y} <- L3,
+ X =:= Y],
+ []),
+ Options = [{cache_all,ets}, unique_all],
+ {qlc,_,[{generate,_,{qlc,_,[{generate,_,{list,L1}}],
+ [{unique,true}]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{qlc,_,[{generate,_,
+ {keysort,{qlc,_,[{generate,_,{list,L2}}],
+ [{cache,ets},{unique,true}]},
+ 1,[]}}],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{keysort,
+ {qlc,_,[{generate,_,{list,L3}}],
+ [{cache,ets},{unique,true}]},
+ 2,[]}}],[]}},_],
+ [{join,merge},{cache,ets},{unique,true}]}},
+ _],[{unique,true}]} = i(Q, Options),
+ [{1,1,1},{2,2,1},{1,1,2},{2,2,2}] = qlc:e(Q, Options)">>
+ ],
+ ?line run(Config, CUTs),
+
+ ok.
+
+join_filter(doc) ->
+ "Various aspects of filters and join.";
+join_filter(suite) -> [];
+join_filter(Config) when is_list(Config) ->
+ Ts = [
+ <<"E1 = create_ets(1, 10),
+ Q = qlc:q([X || {X,_} <- ets:table(E1),
+ begin A = X * X end, % ej true (?)
+ X >= A]),
+ {'EXIT', _} = (catch qlc:e(Q)),
+ ets:delete(E1)">>,
+
+ %% The order of filters matters. See also skip_filters().
+ <<"Q = qlc:q([{X,Y} || {X,Y} <- [{a,1},{b,2}],
+ {Z,W} <- [{a,1},{c,0}],
+ X =:= Z,
+ begin Y/W > 0 end]),
+ [{a,1}] = qlc:e(Q)">>,
+ <<"Q = qlc:q([{X,Y} || {X,Y} <- [{a,1},{b,2}],
+ {Z,W} <- [{a,1},{c,0}],
+ begin Y/W > 0 end,
+ X =:= Z]),
+ {'EXIT', _} = (catch qlc:e(Q))">>,
+
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ F = fun() -> [foo || A <- [0], 1/A] end,
+ Q1 = qlc:q([X || {X} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ F(), % invalidates next filter
+ X =:= Y]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{table,{ets,table,_}}},_,_],
+ []} = i(Q1),
+ {'EXIT', _} = (catch qlc:e(Q1))
+ end, [{1},{2},{3}])
+ end, [{a},{b},{c}])">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+join_lookup(doc) ->
+ "Lookup join.";
+join_lookup(suite) -> [];
+join_lookup(Config) when is_list(Config) ->
+ Ts = [
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ Q = qlc:q([{X,Y} || {_,Y} <- ets:table(E2),
+ {X,_} <- ets:table(E1),
+ X =:= Y], [{join,lookup}]),
+ {0,1,0,0} = join_info_count(Q),
+ R = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2),
+ [{5,5},{6,6},{7,7},{8,8},{9,9},{10,10}] = lists:sort(R)">>,
+
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ F = fun(J) -> qlc:q([{X,Y} || {X,_} <- ets:table(E1),
+ {_,Y} <- ets:table(E2),
+ X =:= Y], {join, J})
+ end,
+ Q = F(lookup),
+ {0,1,0,0} = join_info_count(Q),
+ R = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2),
+ [{5,5},{6,6},{7,7},{8,8},{9,9},{10,10}] = lists:sort(R)">>,
+
+ <<"etsc(fun(E1) ->
+ E2 = qlc_SUITE:table([{1,a},{a},{1,b},{b}], 2, []),
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), % (1)
+ {_,Z} <- E2, % (2)
+ (Z =:= Y) and (X =:= a)
+ or
+ (Z =:= Y) and (X =:= b)]),
+ %% Cannot look up in (1) (X is keypos). Can look up (2).
+ %% Lookup-join: traverse (1), look up in (2).
+ {0,1,0,0} = join_info_count(Q),
+ [{a,a},{b,a}] = qlc:e(Q)
+ end, [{a,a},{b,a},{c,3},{d,4}])">>,
+
+ <<"%% The pattern {X,_} is used to filter out looked up objects.
+ etsc(fun(E) ->
+ Q = qlc:q([X || {X,_} <- ets:table(E),
+ Y <- [{a,b},{c,d},{1,2},{3,4}],
+ X =:= element(1, Y)]),
+ {0,1,0,0} = join_info_count(Q),
+ [1] = qlc:e(Q)
+ end, [{1,2},{3}])">>,
+
+ <<"E = ets:new(e, [bag,{keypos,2}]),
+ L = lists:sort([{a,1},{b,1},{c,1},{d,1},
+ {aa,2},{bb,2},{cc,2},{dd,2}]),
+ true = ets:insert(E, L ++ [{aaa,1,1},{bbb,2,2},{ccc,3,3}]),
+ Q = qlc:q([Z || {_,Y}=Z <- ets:table(E),
+ {X} <- [{X} || X <- lists:seq(0, 10)],
+ X =:= Y]),
+ {0,1,0,0} = join_info_count(Q),
+ R = qlc:e(Q),
+ ets:delete(E),
+ L = lists:sort(R)">>,
+
+ <<"E = ets:new(e, [bag,{keypos,2}]),
+ L = lists:sort([{a,1},{b,1},{c,1},{d,1},
+ {aa,2},{bb,2},{cc,2},{dd,2}]),
+ true = ets:insert(E, L ++ [{aaa,1,1},{bbb,2,2},{ccc,3,3}]),
+ Q = qlc:q([Z || {X} <- [{X} || X <- lists:seq(0, 10)],
+ {_,Y}=Z <- ets:table(E),
+ X =:= Y]),
+ {0,1,0,0} = join_info_count(Q),
+ R = qlc:e(Q),
+ ets:delete(E),
+ L = lists:sort(R)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{b,1},{c,3}],
+ {Y,YY} <- qlc_SUITE:table_lookup_error([{1,a}]),
+ X =:= Y],
+ {join,lookup}),
+ {error, lookup, failed} = qlc:e(Q)">>,
+
+ <<"E = create_ets(1, 10),
+ Q = qlc:q([{X,Y} ||
+ {X,_} <- ets:table(E),
+ {_,Y} <- qlc_SUITE:table_error([{a,1}], 1, err),
+ X =:= Y]),
+ {0,1,0,0} = join_info_count(Q),
+ err = qlc:e(Q),
+ ets:delete(E)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+join_merge(doc) ->
+ "Merge join.";
+join_merge(suite) -> [];
+join_merge(Config) when is_list(Config) ->
+ Ts = [
+ <<"Q = qlc:q([{X,Y} || {X} <- [], {Y} <- [{1}], X =:= Y],
+ {join,merge}),
+ [] = qlc:e(Q)
+ ">>,
+
+ <<"Q = qlc:q([{X,Y} || {X} <- [{1}], {Y} <- [], X =:= Y],
+ {join,merge}),
+ [] = qlc:e(Q)
+ ">>,
+
+ <<"Q = qlc:q([{X,Y} || {X} <- [{1},{1},{1}],
+ {Y} <- [{1},{1},{1}], X =:= Y],
+ {join,merge}),
+ 9 = length(qlc:e(Q))
+ ">>,
+
+ <<"%% Two merge joins possible.
+ Q = qlc:q([{X,Y,Z,W} || {X,Y} <- [{1,a},{1,b},{1,c}],
+ {Z,W} <- [{1,a},{1,b},{1,c}],
+ X =:= Z,
+ Y =:= W]),
+ {qlc,_,[{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,_},C,[]}}],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,_},C,[]}}],[]}},
+ _],
+ [{join,merge}]}},
+ _,_],[]} = qlc:info(Q, {format,debug}),
+ [{1,a,1,a},{1,b,1,b},{1,c,1,c}] = qlc:e(Q)">>,
+
+ <<"%% As the last one, but comparison.
+ Q = qlc:q([{X,Y,Z,W} || {X,Y} <- [{1,a},{1,b},{1,c}],
+ {Z,W} <- [{1,a},{1,b},{1,c}],
+ X == Z, % skipped
+ Y =:= W]),
+ {qlc,_,[{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,_},1,[]}}],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,_},1,[]}}],[]}},
+ _],
+ [{join,merge}]}},
+ _],[]} = qlc:info(Q, {format,debug}),
+ [{1,a,1,a},{1,b,1,b},{1,c,1,c}] = qlc:e(Q)">>,
+
+ <<"%% This is no join.
+ Q = qlc:q([{X,Y,Z,W} || {X,Y} <- [], {Z,W} <- [],
+ X =:= Y, Z =:= W]),
+ {0,0,0,0} = join_info_count(Q)">>,
+
+ <<"%% Used to replace empty ETS tables with [], but that won't work.
+ E1 = ets:new(e1, []),
+ E2 = ets:new(e2, []),
+ Q = qlc:q([{X,Z,W} ||
+ {X, Z} <- ets:table(E1),
+ {W, Y} <- ets:table(E2),
+ X =:= Y],
+ {join, lookup}),
+ [] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+
+ <<"Q = qlc:q([{X,Y} || {X} <- [{3},{1},{0}],
+ {Y} <- [{1},{2},{3}],
+ X =:= Y]),
+ {1,0,0,2} = join_info_count(Q),
+ [{1,1},{3,3}] = qlc:e(Q)">>,
+
+ <<"QH = qlc:q([{X,Y,Z,W} || {X,Y} <- [{3,c},{2,b},{1,a}],
+ {Z,W} <- [{2,b},{4,d},{5,e},{3,c}],
+ X =:= Z,
+ Y =:= W]),
+ {1,0,0,2} = join_info_count(QH),
+ [{2,b,2,b},{3,c,3,c}] = qlc:e(QH)">>,
+
+ <<"%% QLC finds no join column at run time...
+ QH = qlc:q([1 || X <- [{1,2,3},{4,5,6}],
+ Y <- [{1,2},{3,4}],
+ X =:= Y]),
+ {0,0,0,0} = join_info_count(QH),
+ [] = qlc:e(QH)">>,
+
+ <<"QH = qlc:q([X || X <- [{1,2,3},{4,5,6}],
+ Y <- [{1,2},{3,4}],
+ element(1, X) =:= element(2, Y)]),
+ {1,0,0,2} = join_info_count(QH),
+ [{4,5,6}] = qlc:e(QH)">>,
+
+ <<"Q = qlc:q([{A,X,Z,W} ||
+ A <- [a,b,c],
+ {X,Z} <- [{a,1},{b,4},{c,6}],
+ {W,Y} <- [{2,a},{3,b},{4,c}],
+ X =:= Y],
+ {cache, list}),
+ _ = qlc:info(Q),
+ [{a,a,1,2},{a,b,4,3},{a,c,6,4},{b,a,1,2},{b,b,4,3},
+ {b,c,6,4},{c,a,1,2},{c,b,4,3},{c,c,6,4}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{X,Y} ||
+ {X,Z} <- [{a,1},{b,4},{c,6}],
+ {W,Y} <- [{2,a},{3,b},{4,c}],
+ Z > W,
+ X =:= Y],
+ {join,merge}),
+ {qlc,_,[{generate,_,{qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{keysort,_,1,[]}}],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{keysort,_,2,[]}}],
+ []}},_],[{join,merge}]}},
+ _,_],[]} = i(Q),
+ [{b,b},{c,c}] = qlc:e(Q)">>,
+
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ %% A match spec.; Q does not see Q1 and Q2 as lookup-tables.
+ Q1 = qlc:q([X || X <- ets:table(E1)]),
+ Q2 = qlc:q([X || X <- ets:table(E2)]),
+ F = fun(J) -> qlc:q([{X,Y} || X <- Q1,
+ Y <- Q2,
+ element(1,X) =:= element(1,Y)],
+ [{join,J}])
+ end,
+ {'EXIT',{cannot_carry_out_join,_}} = (catch qlc:e(F(lookup))),
+ Q = F(merge),
+ {1,0,0,2} = join_info(Q),
+ R = lists:sort(qlc:e(Q)),
+ ets:delete(E1),
+ ets:delete(E2),
+ true = [{Y,Y} || X <- lists:seq(5, 10), {} =/= (Y = {X,X})] =:= R
+ ">>,
+
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ Q = qlc:q([{X,Y} || X <- ets:table(E1),
+ Y <- ets:table(E2),
+ element(1,X) =:= element(1,Y)],
+ [{join,merge}]),
+ {1,0,0,2} = join_info(Q),
+ R = lists:sort(qlc:e(Q)),
+ ets:delete(E1),
+ ets:delete(E2),
+ true = [{Y,Y} || X <- lists:seq(5, 10), {} =/= (Y = {X,X})] =:= R
+ ">>,
+
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ Q1 = qlc:q([Y || X <- ets:table(E1), begin Y = {X}, true end]),
+ %% A match spec.; Q does not see Q2 as a lookup-table.
+ %%
+ %% OTP-6673: lookup join is considered but since there is no
+ %% filter that can do the job of Q2, lookup join is not an option..
+ Q2 = qlc:q([{X} || X <- ets:table(E2)]),
+ F = fun(J) ->
+ qlc:q([{X,Y} || X <- Q1,
+ Y <- Q2,
+ element(1,X) =:= element(1,Y)],
+ [{join,J}])
+ end,
+ {'EXIT',{cannot_carry_out_join,_}} = (catch qlc:e(F(lookup))),
+ Q = F(any),
+ {1,0,0,2} = join_info(Q),
+ R = lists:sort(qlc:e(Q)),
+ ets:delete(E1),
+ ets:delete(E2),
+ true = [{Y,Y} || X <- lists:seq(5, 10), {} =/= (Y = {{X,X}})] =:= R
+ ">>,
+
+ <<"L1 = [{1,a},{2,a},{1,b},{2,b},{1,c},{2,c}],
+ L2 = [{b,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ Q = qlc:q([{XX,YY} ||
+ {X,XX} <- L1,
+ {YY,Y} <- L2,
+ X == Y],
+ {join,J}),
+ qlc:q([{XX1,YY1,XX2,YY2} ||
+ {XX1,YY1} <- Q,
+ {XX2,YY2} <- Q])
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = lists:sort(qlc:e(Qm)) =:= lists:sort(qlc:e(Qn))">>,
+
+ <<"L1 = [{{1,a},2},{{3,c},4}],
+ L2 = [{a,{1,a}},{c,{4,d}}],
+ Q = qlc:q([{X,Y} || {X,_} <- L1,
+ {_,{Y,Z}} <- L2,
+ X == {Y,Z}
+ ]),
+ {qlc,_,[{generate,_,{qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,L1},1,[]}}],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,L2},2,[]}}],[]}},
+ _],
+ [{join,merge}]}}],[]} = i(Q),
+ [{{1,a},1}] = qlc:e(Q)">>,
+
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), % (1)
+ {Z} <- ets:table(E2), % (2)
+ (Z =:= X) and
+ (Y =:= a) and
+ (X =:= Y) or
+ (Y =:= b) and
+ (Z =:= Y)]),
+ %% Cannot look up in (1) (X is keypos). Can look up (2).
+ %% Lookup join not possible (cannot look up in (1)).
+ %% Merge join is possible (after lookup in (2)).
+ {1,0,0,2} = join_info_count(Q),
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,
+ {qlc,_,[{generate,_,
+ {keysort,
+ {table,{ets,table,_}},
+ 2,[]}},_C1],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,
+ {keysort,{table,_},1,[]}},_C2],
+ []}},
+ _],[{join,merge}]}},_],[]} = i(Q),
+ [{a,a}] = qlc:e(Q)
+ end, [{a}])
+ end, [{a,1},{a,a},{b,1},{b,2}])">>,
+
+ <<"Q = qlc:q([{G1,G2} ||
+ G1<- [{1}],
+ G2 <- [{1}],
+ element(1, G1) =:= element(1, G2)]),
+ {1,0,0,2} = join_info(Q),
+ [{{1},{1}}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{X,Y} ||
+ X <- [{1}],
+ Y <- [{1}],
+ element(1, X) =:= element(1, Y)],
+ {join,merge}),
+ {1,0,0,2} = join_info(Q),
+ [{{1},{1}}] = qlc:e(Q)">>,
+
+ <<"%% Generator after the join-filter.
+ Q = qlc:q([Z ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{2},{3},{4}],
+ X =:= Y,
+ Z <- [1,2]]),
+ {qlc,_,
+ [{generate,_,{qlc,_,
+ [{generate,_,{qlc,_,
+ [{generate,_,{keysort,{list,[{1},{2},{3}]},1,[]}}],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{keysort,{list,_},1,[]}}],[]}},_],
+ [{join,merge}]}}, _,{generate,_,{list,_}}],[]} = i(Q),
+ [1,2,1,2] = qlc:e(Q)">>,
+
+ <<"%% X and W occur twice in the pattern of the extra join handle.
+ Q = qlc:q([{Z,W} ||
+ {X,Z,X} <- [{1,2,1},{1,2,2}],
+ {W,Y,W} <- [{a,1,a}],
+ X =:= Y]),
+ [{2,a}] = qlc:e(Q)">>
+
+ ],
+ ?line run(Config, Ts),
+
+ %% Small examples. Returning an error term.
+ ETs = [
+ <<"F = fun(M) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2},{bb,2},{c,3},{cc,3}],
+ {Y,YY} <- [{0,a},{1,a},{1,aa},{2,b},{2,bb},{2,bbb},
+ {3,c},{3,cc}],
+ X =:= Y],
+ {join,M})
+ end,
+ R = qlc:e(F(nested_loop)),
+ R = qlc:e(F(merge))">>,
+
+ <<"F = fun(M) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2},{bb,2},{c,3},{cc,3}],
+ {Y,YY} <- [{0,a},{1,a},{1,aa},{2,b},{2,bb},{2,bbb},
+ {4,d}],
+ X =:= Y],
+ {join,M})
+ end,
+ R = qlc:e(F(nested_loop)),
+ R = qlc:e(F(merge))">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{b,1},{c,3}],
+ {Y,YY} <- [{1,a}],
+ X =:= Y],
+ {join,merge}),
+ [{b,a}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{b,1},{c,3}],
+ {Y,YY} <- qlc_SUITE:table_error([{1,a}], 1, err),
+ X =:= Y],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{aa,1}],
+ {Y,YY} <- [{1,a}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{aa,a}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- qlc_SUITE:table_error([{a,1},{aa,1}],
+ 2, err),
+ {Y,YY} <- [{1,a}],
+ X =:= Y],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1}],
+ {Y,YY} <- [{1,a},{1,aa}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{a,aa}]= qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- qlc_SUITE:table_error([{a,1}], 2, err),
+ {Y,YY} <- [{1,a},{1,aa}],
+ X =:= Y],
+ {join,merge}),
+ C = qlc:cursor(Q),
+ [{a,a}] = qlc:next_answers(C, 1),
+ qlc:delete_cursor(C),
+ err = qlc:e(Q)">>,
+
+ <<"F = fun(M) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2},{bb,2},{c,3},{cc,3}],
+ {Y,YY} <- [{0,a},{1,a},{1,aa},{2,b},
+ {2,bb},{2,bbb}],
+ X =:= Y],
+ {join,M})
+ end,
+ %% [{a,a},{a,aa},{b,b},{b,bb},{b,bbb},{bb,b},{bb,bb},{bb,bbb}]
+ R = qlc:e(F(nested_loop)),
+ R = qlc:e(F(merge))">>,
+
+
+ <<"F = fun(M) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2},{bb,2},{c,3},{cc,3}],
+ {Y,YY} <- qlc_SUITE:table_error([{0,a},{1,a},{1,aa},
+ {2,b},{2,bb},
+ {2,bbb}],
+ 1, err),
+ X =:= Y],
+ {join,M})
+ end,
+ %% [{a,a},{a,aa},{b,b},{b,bb},{b,bbb},{bb,b},{bb,bb},{bb,bbb}]
+ err = qlc:e(F(nested_loop)),
+ err = qlc:e(F(merge))">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- qlc_SUITE:table_error([], 2, err),
+ {Y,YY} <- [{2,b},{3,c}],
+ X =:= Y],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{c,3}],
+ {Y,YY} <- [{2,b},{3,c}],
+ X =:= Y],
+ {join,merge}),
+ [{c,c}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{aa,1}],
+ {Y,YY} <- [{1,a},{1,aa}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{a,aa},{aa,a},{aa,aa}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2}],
+ {Y,YY} <- [{1,a},{1,aa}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{a,aa}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2}],
+ {Y,YY} <- qlc_SUITE:table_error([{1,a},{1,aa}],
+ 1, err),
+ X =:= Y],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2}],
+ {Y,YY} <- [{1,a},{1,aa},{1,aaa},{1,aaaa}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{a,aa},{a,aaa},{a,aaaa}]= qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{element(1, X), element(2, Y)} ||
+ X <- [{a,1},{aa,1}],
+ Y <- [{1,a},{1,aa}],
+ element(2, X) =:= element(1, Y)],
+ {join,merge}),
+ [{a,a},{a,aa},{aa,a},{aa,aa}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{element(1, X), element(2, Y)} ||
+ X <- [{a,1},{aa,1}],
+ Y <- qlc_SUITE:table_error([], 1, err),
+ element(2, X) =:= element(1, Y)],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{element(1, X), element(2, Y)} ||
+ X <- qlc_SUITE:table_error([{a,1}], 2, err),
+ Y <- [{2,b}],
+ element(2, X) =:= element(1, Y)],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{1,a},{'1b',b},{2,b}],
+ {Y,YY} <- [{a,1},{b,'1b'},{c,1}],
+ X == Y],
+ {join,merge}),
+ [{1,1},{'1b','1b'},{2,'1b'}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- qlc_SUITE:table_error([{1,a},{'1b',b},{2,b}],
+ 2, err),
+ {Y,YY} <- [{a,1},{b,'1b'},{c,1}],
+ X == Y],
+ {join,merge}),
+ err = qlc:e(Q)">>
+
+ ],
+ ?line run(Config, ETs),
+
+ %% Mostly examples where temporary files are needed while merging.
+ FTs = [
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = qlc:e(Qm,{max_list_size, 0}) =:= qlc:e(Qn)">>,
+
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ Q = qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,merge}),
+ {error,_,{file_error,_,_}} =
+ qlc:e(Q, [{max_list_size,64*1024},{tmpdir,\"/a/b/c\"}])">>,
+
+ <<"L1 = qlc_SUITE:table_error([{1,a},{2,a}], 2, err),
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ err = qlc:e(Qm, {max_list_size,64*1024}),
+ err = qlc:e(Qn)">>,
+
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = qlc_SUITE:table_error([{a,Y} || Y <- lists:seq(1, 10000)],
+ 1, err),
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ err = qlc:e(Qm, {max_list_size,64*1024}),
+ err = qlc:e(Qn)">>,
+
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)] ++
+ [{'1b',b},{2,b}] ++ [{Y,d} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)] ++
+ [{b,'1b'}] ++ [{c,1}] ++ [{d,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = lists:sort(qlc:e(Qm, {max_list_size,64*1024})) =:=
+ lists:sort(qlc:e(Qn))">>,
+
+ <<"F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{Y,a} || Y <- lists:seq(1, 2)],
+ {Y,YY} <- [{a,Y} || Y <- lists:seq(1,100000)],
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = qlc:e(Qm, {max_list_size,64*1024}) =:= qlc:e(Qn)">>,
+
+ %% More than one join in one QLC expression.
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ Q = qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y,
+ begin XX > 1 end,
+ begin YY > 9999 end],
+ {join,J}),
+ qlc:q([{XX1,YY1,XX2,YY2} ||
+ {XX1,YY1} <- Q,
+ {XX2,YY2} <- Q])
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ R1 = lists:sort(qlc:e(Qm, {max_list_size,64*1024})),
+ R2 = lists:sort(qlc:e(Qm, {max_list_size,1 bsl 31})),
+ true = R1 =:= lists:sort(qlc:e(Qn)),
+ true = R1 =:= R2">>,
+
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ Q = qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y,
+ begin XX > 1 end,
+ begin YY > 9999 end],
+ {join,J}),
+ qlc:q([{XX1,YY1,XX2,YY2} ||
+ {XX1,YY1} <- Q,
+ {XX2,YY2} <- Q,
+ throw(thrown)])
+ end,
+ Qm = F(merge),
+ thrown = (catch {any_term, qlc:e(Qm, {max_list_size,64*1024})})">>,
+
+ <<"%% Bigger than 64*1024.
+ T1 = {1, lists:seq(1, 20000)},
+ L1 = [{a,T1},{b,T1}],
+ L2 = [{T1,a},{T1,b}],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ R = [{a,a},{a,b},{b,a},{b,b}],
+ R = qlc:e(Qm, {max_list_size,64*1024}),
+ R = qlc:e(Qn)">>,
+
+ <<"%% Bigger than 64*1024. No temporary files.
+ T1 = {1, lists:seq(1, 20000)},
+ L1 = [{a,T1},{b,T1}],
+ L2 = [{T1,a},{T1,b}],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ R = [{a,a},{a,b},{b,a},{b,b}],
+ R = qlc:e(Qm, {max_list_size,1 bsl 31}),
+ R = qlc:e(Qn)">>
+
+
+ ],
+ ?line run(Config, FTs),
+
+ ok.
+
+join_sort(doc) ->
+ "Merge join optimizations (avoid unnecessary sorting).";
+join_sort(suite) -> [];
+join_sort(Config) when is_list(Config) ->
+ Ts = [
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6}]),
+ H1 = qlc:q([X || X <- H1_1], unique),
+ H2 = qlc:keysort(2, [{1,2},{3,4}]),
+ H3 = qlc:q([{X,Y} || {X,_,_} <- H1,
+ {_,Y} <- H2,
+ X =:= Y]),
+ {1,0,0,2} = join_info(H3),
+ [{4,4}] = qlc:e(H3)">>,
+
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6}]),
+ H1 = qlc:q([X || X <- H1_1], unique), % keeps the order
+ H2 = qlc:keysort(2, [{1,2},{3,4}]),
+ H3 = qlc:q([{X,Y} || {X,_,_} <- H1, % no extra keysort
+ {Y,_} <- H2, % an extra keysort
+ X =:= Y]),
+ {1,0,0,3} = join_info(H3),
+ [{1,1}] = qlc:e(H3)">>,
+
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6}], {tmpdir,\"\"}),
+ H1 = qlc:q([X || X <- H1_1], unique),
+ H2 = qlc:keysort(2, [{1,2},{3,4}]),
+ H3 = qlc:q([{X,Y} || {_,X,_} <- H1,
+ {_,Y} <- H2,
+ X =:= Y]),
+ {1,0,0,3} = join_info(H3),
+ [{2,2}] = qlc:e(H3)">>,
+
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6}], {tmpdir,\"\"}),
+ H1 = qlc:q([X || X <- H1_1], unique),
+ H2 = qlc:keysort(2, [{1,2},{3,4}]),
+ H3 = qlc:q([{X,Y} || {_,X,_} <- H1,
+ {_,Y} <- H2,
+ X =:= Y]),
+ {1,0,0,3} = join_info(H3),
+ [{2,2}] = qlc:e(H3)">>,
+
+ <<"H1 = qlc:sort([{1,a},{2,b},{3,c}]),
+ %% Since H1 is sorted it is also keysorted on the first column.
+ Q = qlc:q([{X, Y} || {X,_} <- H1,
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {1,0,0,1} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"H1 = qlc:sort([{r,a,1},{r,b,2},{r,c,3}]),
+ Q = qlc:q([{X, Y} || {r,_,X} <- H1, % needs keysort(3)
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {1,0,0,2} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"QH = qlc:q([X || X <- [{1,2,3},{4,5,6}],
+ Y <- qlc:sort([{1,2},{3,4}]),
+ element(1, X) =:= element(2, Y)]),
+ {1,0,0,2} = join_info_count(QH),
+ [{4,5,6}] = qlc:e(QH)">>,
+
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6},{1,2,3}]),
+ H1 = qlc:q([X || X <- H1_1], unique),
+ H2 = qlc:keysort(2, [{2,1},{3,4}]),
+ H3 = qlc:q([{X,Y} || {X,_,_} <- H1,
+ {_,Y} <- H2,
+ X =:= Y]),
+ H4 = qlc:keysort(1, [{1,2},{3,4},{4,a}]),
+ H5 = qlc:q([{X,Y} || {X,_} <- H4,
+ {_,Y} <- H3,
+ X =:= Y]),
+ {2,0,0,3} = join_info_count(H5),
+ [{1,1},{4,4}]= qlc:e(H5)">>,
+
+ <<"
+ H1 = qlc:keysort(2, [{1,a,u},{2,b,k},{3,c,l}]),
+ H2 = qlc:q([{a,X,Y,a} || {1,X,u} <- H1,
+ {2,Y,k} <- H1]),
+ %% Neither H1 nor H2 need to be key-sorted
+ %% (the columns are constant).
+ H3 = qlc:q([{A,B,C,D,E,F,G,H} ||
+ {A,B,C,D} <- H2,
+ {E,F,G,H} <- H2,
+ A =:= H],
+ {join,merge}),
+ {1,0,0,4} = join_info_count(H3),
+ [{a,a,b,a,a,a,b,a}] = qlc:e(H3)">>,
+
+ <<"%% Q1 is sorted on X or Y.
+ Q1 = qlc:q([{X,Y} ||
+ {X,_} <- qlc:keysort(1, [{1,a},{2,b}]),
+ {_,Y} <- qlc:keysort(2, [{aa,11},{bb,22}]),
+ X < Y]),
+ [{1,11},{1,22},{2,11},{2,22}] = qlc:e(Q1),
+ Q = qlc:q([{X,Y} ||
+ {X,_} <- Q1, % no need to sort Q1
+ {Y} <- [{0},{1},{2},{3}],
+ X =:= Y]),
+ {1,0,0,3} = join_info_count(Q),
+ [{1,1},{1,1},{2,2},{2,2}] = qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort([2], [{r,1},{r,2},{r,3}]),
+ %% H1 is actually sorted, but this info is not captured.
+ Q = qlc:q([{X, Y} || {r,X} <- H1,
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {1,0,0,2} = join_info_count(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"%% Two leading constants columns and sorted objects
+ %% implies keysorted on column 3.
+ H1 = qlc:sort(qlc:q([{a,X,Y} || {X,Y} <- [{1,2},{2,3},{3,3}]])),
+ H2 = qlc:q([{X,Y} ||
+ {a,3,X} <- H1,
+ {a,2,Y} <- H1,
+ X =:= Y]),
+ {1,0,0,0} = join_info_count(H2),
+ [{3,3}] = qlc:e(H2)">>,
+
+ <<"QH = qlc:q([{X,Y} || {X,Y} <- [{1,4},{1,3}],
+ {Z} <- [{1}],
+ X =:= Z, (Y =:= 3) or (Y =:= 4)]),
+ {1,0,0,1} = join_info_count(QH),
+ [{1,4},{1,3}] = qlc:e(QH)">>,
+
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E), % no need to sort
+ {Y} <- [{0},{1},{2}],
+ X == Y], {join,merge}),
+ {1,0,0,1} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ <<"H1 = qlc:sort([{r,1,a},{r,2,b},{r,3,c}]),
+ Q = qlc:q([{X, Y} || {r,X,_} <- H1, % does not need keysort(3)
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {1,0,0,1} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort(2,[{r,1},{r,2},{r,3}]),
+ H2 = [{a},{b}],
+ %% Several columns in different qualifiers have initial
+ %% constant columns.
+ H3 = qlc:keysort(1,[{c1,c2,1},{foo,bar,2},{c1,c2,3},{c1,c2,2}]),
+ Q = qlc:q([{r,X,Y,Z} || {r,X} <- H1,
+ {Y} <- H2,
+ {c1,c2,Z} <- H3,
+ X =:= Z], {join,merge}),
+ {1,0,0,3} = join_info(Q),
+ [{r,1,a,1},{r,1,b,1},{r,2,a,2},{r,2,b,2},{r,3,a,3},{r,3,b,3}] =
+ qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort(2,[{r,1},{r,2},{r,3}]),
+ H2 = [{a},{b}],
+ %% As the last one, but one keysort less.
+ H3 = qlc:keysort(3,[{c1,c2,1},{foo,bar,2},{c1,c2,3},{c1,c2,2}]),
+ Q = qlc:q([{r,X,Y,Z} || {r,X} <- H1,
+ {Y} <- H2,
+ {c1,c2,Z} <- H3,
+ X =:= Z], {join,merge}),
+ {1,0,0,2} = join_info(Q),
+ [{r,1,a,1},{r,1,b,1},{r,2,a,2},{r,2,b,2},{r,3,a,3},{r,3,b,3}] =
+ qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort(2,[{r,1},{r,2},{r,3}]),
+ H2 = [{a},{b}],
+ H3 = qlc:keysort(1,[{c1,c2,1},{foo,bar,2},{c1,c2,3},{c1,c2,2}]),
+ %% One generator before the joined generators.
+ Q = qlc:q([{r,X,Y,Z} || {Y} <- H2,
+ {r,X} <- H1,
+ {c1,c2,Z} <- H3,
+ X =:= Z], {join,merge}),
+ {1,0,0,3} = join_info(Q),
+ [{r,1,a,1},{r,2,a,2},{r,3,a,3},{r,1,b,1},{r,2,b,2},{r,3,b,3}] =
+ qlc:e(Q)">>,
+
+ <<"H1 = [{a,1},{b,2},{c,3},{d,4}],
+ H2 = [{a},{b}],
+ H3 = [{c1,c2,a},{foo,bar,b},{c1,c2,c},{c1,c2,d}],
+ %% A couple of \"extra\" filters and generators.
+ Q = qlc:q([{X,Y,Z} || {X,_} <- H1,
+ {Y} <- H2,
+ X > Y,
+ {c1,c2,Z} <- H3,
+ {W} <- [{a},{b}],
+ W > a,
+ X =:= Z]),
+ {1,0,0,2} = join_info(Q),
+ [{c,a,c},{c,b,c},{d,a,d},{d,b,d}] = qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort(2,[{r,1},{r,2},{r,3}]),
+ H2 = qlc:sort([{c1,c2,1},{foo,bar,2},{c1,c2,3},{c1,c2,2}]),
+ %% H2 is sorted, no keysort necessary.
+ %% This example shows that the 'filter-part' of the pattern
+ %% ({c1,c2,Z}) should be evaluated _before_ the join.
+ %% Otherwise the objects cannot be assumed to be keysort:ed on the
+ %% third column (if merge join), and lookup-join would lookup
+ %% more keys than necessary.
+ Q = qlc:q([{r,X,Z} || {r,X} <- H1,
+ {c1,c2,Z} <- H2,
+ X =:= Z] ,{join,merge}),
+ {1,0,0,1} = join_info(Q),
+ [{r,1,1},{r,2,2},{r,3,3}] = qlc:e(Q)">>,
+
+ <<"H1 = [{1,a},{2,b},{3,c}],
+ H2 = [{0,0},{1,1},{2,2}],
+ H3 = qlc:q([{A,C,D} ||
+ {A,_B} <- H1,
+ {C,D} <- H2,
+ A == D, C == D]),
+ H4 = [{1,1},{2,2},{3,3}],
+ H5 = qlc:q([{X,Y} ||
+ {X,_,_} <- H3, % no need to sort this one (merge join)
+ {_,Y} <- H4,
+ X == Y]),
+ Q = qlc:q([{X,Y} ||
+ {X,_} <- H5, % no need to sort this one
+ {Y,_} <- H4,
+ X == Y]),
+ {{3,0,0,4},{3,0,0,6}} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"%% There is an extra test (_C1, element(1, X) =:= 1) that is not
+ %% necessary since the match spec does the same check. This can be
+ %% improved upon.
+ Q = qlc:q([{X,Y} ||
+ X <- [{2},{1}],
+ element(1, X) =:= 1,
+ Y=_ <- [{2},{1}],
+ element(1, X) =:= element(1, Y)]),
+ {qlc,_,
+ [{generate,_,{qlc,_,
+ [{generate,_,{qlc,_,
+ [{generate,_,{list,{list,_},_}},
+ _C1],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,[{2},{1}]}},
+ _C2],[]}},_],
+ [{join,merge}]}},_],[]} = i(Q),
+ {1,0,0,0} = join_info_count(Q),
+ [{{1},{1}}] = qlc:e(Q)">>,
+
+ <<"etsc(fun(E) ->
+ L = [{a,b,a},{c,d,b},{1,2,a},{3,4,b}],
+ Q = qlc:q([P1 || {X,2,Z}=P1 <- ets:table(E),
+ Y <- L,
+ X =:= 1,
+ Z =:= a,
+ P1 =:= Y,
+ X =:= element(1, Y)]),
+ {1,0,0,0} = join_info_count(Q),
+ [{1,2,a}] = qlc:e(Q)
+ end, [{1,2,a},{3,4,b}])">>,
+
+ %% Merge join on Z and element(3, Y). No need to sort!
+ <<"etsc(fun(E) ->
+ L = [{a,b,a},{c,d,b},{1,2,a},{3,4,b}],
+ Q = qlc:q([P1 || {X,2,Z}=P1 <- ets:table(E),
+ Y <- L,
+ (X =:= 1) or (X =:= 2),
+ Z =:= a,
+ P1 =:= Y,
+ X =:= element(1, Y)]),
+ {1,0,0,0} = join_info_count(Q),
+ [{1,2,a}] = qlc:e(Q)
+ end, [{1,2,a},{3,4,b}])">>,
+
+ <<"%% Y is constant as well as X. No keysort, which means that
+ %% Y must be filtered before merge join.
+ etsc(fun(E) ->
+ Q = qlc:q([X || {1,2}=X <- ets:table(E),
+ Y <- [{a,b},{c,d},{1,2},{3,4}],
+ X =:= Y,
+ element(1, X) =:= element(1, Y)]),
+ {1,0,0,0} = join_info_count(Q),
+ [{1,2}] = qlc:e(Q)
+ end, [{1,2},{3,4}])">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+join_complex(doc) ->
+ "Join of more than two columns.";
+join_complex(suite) -> [];
+join_complex(Config) when is_list(Config) ->
+ Ts = [{three,
+ <<"three() ->
+ L = [],
+ Q = qlc:q([{X,Y,Z} || {X,_} <- L,
+ {_,Y} <- L,
+ {Z,_} <- L,
+ X =:= Y, Y == Z
+ ]),
+ qlc:e(Q).">>,
+ [],
+ {warnings,[{3,qlc,too_complex_join}]}},
+
+ {two,
+ <<"two() ->
+ Q = qlc:q([{X,Y,Z,W} ||
+ {X} <- [],
+ {Y} <- [],
+ {Z} <- [],
+ {W} <- [],
+ X =:= Y,
+ Z =:= W],{join,merge}),
+ qlc:e(Q).">>,
+ [],
+ {warnings,[{2,qlc,too_many_joins}]}}
+ ],
+
+ ?line compile(Config, Ts),
+
+ Ts2 = [{three,
+ <<"three() ->
+ L = [],
+ Q = qlc:q([{X,Y,Z} || {X,_} <- L,
+ {_,Y} <- L,
+ {Z,_} <- L,
+ X =:= Y, Y == Z
+ ]),
+ qlc:e(Q).">>,
+ [],
+ {[],
+ ["cannot handle join of three or more generators efficiently"]}},
+
+ {two,
+ <<"two() ->
+ Q = qlc:q([{X,Y,Z,W} ||
+ {X} <- [],
+ {Y} <- [],
+ {Z} <- [],
+ {W} <- [],
+ X =:= Y,
+ Z =:= W],{join,merge}),
+ qlc:e(Q).">>,
+ [],
+ {[],["cannot handle more than one join efficiently"]}}
+ ],
+
+ ?line compile_format(Config, Ts2),
+
+ ok.
+
+tickets(suite) ->
+ [otp_5644, otp_5195, otp_6038_bug, otp_6359, otp_6562, otp_6590,
+ otp_6673, otp_6964, otp_7114, otp_7232, otp_7238, otp_7552, otp_6674,
+ otp_7714].
+
+otp_5644(doc) ->
+ "OTP-5644. Handle the new language element M:F/A.";
+otp_5644(suite) -> [];
+otp_5644(Config) when is_list(Config) ->
+ Ts = [
+ <<"Q = qlc:q([fun modul:mfa/0 || _ <- [1,2],
+ is_function(fun modul:mfa/0, 0)]),
+ [_,_] = qlc:eval(Q)">>
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+otp_5195(doc) ->
+ "OTP-5195. Allow traverse functions returning terms.";
+otp_5195(suite) -> [];
+otp_5195(Config) when is_list(Config) ->
+ %% Several minor improvements have been implemented in OTP-5195.
+ %% The test cases are spread all over... except these.
+ %%
+ %% Traverse functions returning terms.
+
+ Ts = [<<"L = [1,2,3],
+ Err = {error,modul,err},
+ H = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ Err = qlc:e(H)">>,
+
+ <<"Err = {error,modul,err},
+ TravFun = fun() -> Err end,
+ H1 = qlc:sort(qlc:q([X || X <- qlc:table(TravFun, [])])),
+ H = qlc:q([{X} || X <- H1]),
+ Err = qlc:e(H)">>,
+
+ <<"L = [1,2,3],
+ Err = {error,modul,err},
+ H = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ C = qlc:cursor(H),
+ R = qlc:next_answers(C, all_remaining),
+ qlc:delete_cursor(C),
+ Err = R">>,
+
+ <<"L = [1,2,3],
+ Err = {error,modul,err},
+ H = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ Err = qlc:fold(F, [], H)">>,
+
+ <<"Err = {error,modul,err},
+ TravFun = fun() -> Err end,
+ H1 = qlc:sort(qlc:q([X || X <- qlc:table(TravFun, [])])),
+ H = qlc:q([{X} || X <- H1]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ Err = qlc:fold(F, [], H)">>,
+
+ <<"Q1 = qlc:append([qlc:append([ugly()]),[3]]),
+ Q = qlc:q([X || X <- Q1]),
+ 42 = qlc:e(Q),
+ ok.
+
+ ugly() ->
+ [apa | fun() -> 42 end].
+ foo() -> bar">>,
+
+ <<"L = [1,2,3],
+ Err = {error,modul,err},
+ H = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ H1 = qlc:q([X || X <- H], unique),
+ Err = qlc:e(H1)">>,
+
+ <<"Err = {error, module, err},
+ L = [1,2,3],
+ H1 = qlc:q([{X} || X <- qlc_SUITE:table_error(L, Err)]),
+ H = qlc:q([{X,Y,Z} || X <- H1, Y <- H1, Z <- L], cache),
+ qlc:e(H, cache_all)">>,
+
+ <<"Err = {error, module, err},
+ L = [1,2,3],
+ H1 = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ H = qlc:q([{X,Y,Z} || X <- H1, Y <- H1, Z <- L], cache),
+ qlc:e(H, [cache_all,unique_all])">>,
+
+ <<"L = [{1},{2},{3}],
+ H = qlc:q([X || {X} <- qlc_SUITE:table_lookup_error(L),
+ X =:= 2]),
+ {error, lookup, failed} = qlc:e(H)">>,
+
+ %% The traverse function can return any value, but it must not
+ %% return an improper list. Improper lists must not be given anyway.
+ <<"{'EXIT', {{badfun,a},_}} =
+ (catch qlc:e(qlc:q([{X} || X <- [1 | a], begin true end])))">>
+
+ ],
+
+ ?line run(Config, Ts),
+
+ Ts2 = [<<"Q = qlc:q([{X,Y} || {X} <- [{1},{2},{3}],
+ begin
+ %% Used to generate a badly formed file
+ Y = 3, true
+ end,
+ X =:= Y]),
+ [{3,3}] = qlc:e(Q)">>],
+ ?line run(Config, Ts2),
+
+ ok.
+
+otp_6038_bug(doc) ->
+ "OTP-6038. Bug fixes: unique and keysort; cache.";
+otp_6038_bug(suite) -> [];
+otp_6038_bug(Config) when is_list(Config) ->
+ %% The 'unique' option can no longer be merged with the keysort options.
+ %% This used to return [{1,a},{1,c},{2,b},{2,d}], but since
+ %% file_sorter:keysort now removes duplicates based on keys, the
+ %% correct return value is [{1,a},{2,b}].
+ Ts = [<<"H1 = qlc:q([X || X <- [{1,a},{2,b},{1,c},{2,d}]], unique),
+ H2 = qlc:keysort(1, H1, [{unique,true}]),
+ [{1,a},{2,b}] = qlc:e(H2)">>],
+
+ ?line run(Config, Ts),
+
+ %% Sometimes the cache options did not empty the correct tables.
+ CTs = [
+ <<"Options = [cache,unique],
+ V1 = qlc:q([{X,Y} || X <- [1,2], Y <- [3]], Options),
+ V2 = qlc:q([{X,Y} || X <- [a,b], Y <- V1]),
+ V3 = qlc:q([{X,Y} || X <- [5,6], Y <- [7]], Options),
+ Q = qlc:q([{X,Y} || X <- V2, Y <- V3]),
+ R = qlc:e(Q),
+ L1 = [{X,Y} || X <- [1,2], Y <- [3]],
+ L2 = [{X,Y} || X <- [a,b], Y <- L1],
+ L3 = [{X,Y} || X <- [5,6], Y <- [7]],
+ L = [{X,Y} || X <- L2, Y <- L3],
+ true = R =:= L">>,
+ <<"Options = [cache,unique],
+ V1 = qlc:q([{X,Y} || X <- [1,2], Y <- [3]], Options),
+ V2 = qlc:q([{X,Y} || X <- [a,b], Y <- V1]),
+ V3 = qlc:q([{X,Y} || X <- [5,6], Y <- [7]], Options),
+ V4 = qlc:q([{X,Y} || X <- V2, Y <- V3], Options),
+ Q = qlc:q([{X,Y} || X <- [1,2], Y <- V4]),
+ R = qlc:e(Q),
+ L1 = [{X,Y} || X <- [1,2], Y <- [3]],
+ L2 = [{X,Y} || X <- [a,b], Y <- L1],
+ L3 = [{X,Y} || X <- [5,6], Y <- [7]],
+ L4 = [{X,Y} || X <- L2, Y <- L3],
+ L = [{X,Y} || X <- [1,2], Y <- L4],
+ true = R =:= L">>
+ ],
+ ?line run(Config, CTs),
+
+ ok.
+
+otp_6359(doc) ->
+ "OTP-6359. dets:select() never returns the empty list.";
+otp_6359(suite) -> [];
+otp_6359(Config) when is_list(Config) ->
+ dets:start(),
+ T = luna,
+ Fname = filename(T, Config),
+
+ Ts = [
+ [<<"T = luna, Fname = \"">>, Fname, <<"\",
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ Q = qlc:q([F ||
+ F <- dets:table(T),
+ (F band ((1 bsl 0)) =/= 0),
+ true]),
+ [] = qlc:eval(Q),
+ ok = dets:close(T),
+ file:delete(\"">>, Fname, <<"\"),
+ ok">>]
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+otp_6562(doc) ->
+ "OTP-6562. compressed = false (should be []) when sorting before join.";
+otp_6562(suite) -> [];
+otp_6562(Config) when is_list(Config) ->
+ Bug = [
+ %% This example uses a file to sort E2 on the second column. It is
+ %% not easy to verify that this happens; the file_sorter module's
+ %% size option cannot be set in this case. But it is not likely
+ %% that the default size (512 KiB) will ever change, so it should
+ %% be future safe.
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 150000),
+ Q = qlc:q([{XX,YY} ||
+ {X,XX} <- ets:table(E1),
+ {YY,Y} <- ets:table(E2),
+ X == Y],
+ {join,merge}),
+ [{5,5},{6,6},{7,7},{8,8},{9,9},{10,10}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>
+ ],
+ ?line run(Config, Bug),
+
+ Bits = [
+ {otp_6562_1,
+ <<"otp_6562_1() ->
+ Q = qlc:q([X || <<X:8>> <= <<\"hej\">>]),
+ qlc:info(Q).
+ ">>,
+ [],
+ {errors,[{2,qlc,binary_generator}],
+ []}}
+ ],
+ ?line [] = compile(Config, Bits),
+
+ ?line R1 = {error,qlc,{1,qlc,binary_generator}}
+ = qlc:string_to_handle("[X || <<X:8>> <= <<\"hej\">>]."),
+ ?line "1: cannot handle binary generators\n" =
+ lists:flatten(qlc:format_error(R1)),
+
+ ok.
+
+otp_6590(doc) ->
+ "OTP-6590. Bug fix (join info).";
+otp_6590(suite) -> [];
+otp_6590(Config) when is_list(Config) ->
+ Ts = [<<"fun(Tab1Value) ->
+ Q = qlc:q([T1#tab1.id || T1 <- [#tab1{id = id1,
+ value = v,
+ tab2_id = id}],
+ T2 <- [#tab2{id = id}],
+ T1#tab1.value =:= Tab1Value,
+ T1#tab1.tab2_id =:= T2#tab2.id]),
+ [id1] = qlc:e(Q)
+ end(v)">>],
+
+ ?line run(Config, <<"-record(tab1, {id, tab2_id, value}).
+ -record(tab2, {id, value}).\n">>, Ts),
+ ok.
+
+otp_6673(doc) ->
+ "OTP-6673. Optimizations and fixes.";
+otp_6673(suite) -> [];
+otp_6673(Config) when is_list(Config) ->
+ Ts_PT =
+ [<<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E1),
+ {C,D} <- ets:table(E2),
+ A =:= 2, % lookup
+ B =:= D, % join
+ C =:= g]), % lookup invalidated by join
+ {qlc,_,[{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,
+ {keysort,
+ {list,{table,_},
+ [{{'$1','$2'},[],['$_']}]},
+ 2,[]}},_],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,
+ {keysort,{table,_},2,[]}}],
+ []}},_],
+ [{join,merge}]}},_,_],[]} = i(Q),
+ [{2,y,g,y}] = qlc:e(Q)
+ end, [{f,x},{g,y},{h,z}])
+ end,
+ [{1,x},{2,y},{3,z}])">>,
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E1),
+ {C,D} <- ets:table(E2),
+ A =:= 2, % lookup
+ C =:= g, % lookup
+ B =:= D]), % join
+ {qlc,_,[{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,
+ {keysort,
+ {list,{table,_},
+ [{{'$1','$2'},[],['$_']}]},
+ 2,[]}},_],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,
+ {keysort,
+ {list,{table,_},
+ [{{'$1','$2'},[],['$_']}]},
+ 2,[]}},_],[]}},_],
+ [{join,merge}]}},_],[]} = i(Q),
+ [{2,y,g,y}] = qlc:e(Q)
+ end, [{f,x},{g,y},{h,z}])
+ end,
+ [{1,x},{2,y},{3,z}])">>],
+
+ ?line run(Config, Ts_PT),
+
+ MS = ets:fun2ms(fun({X,_Y}=T) when X > 1 -> T end),
+ Ts_RT = [
+ [<<"%% Explicit match-spec. ets:table() ensures there is no lookup
+ %% function, which means that lookup join will not be considered.
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ etsc(fun(E) ->
+ F = fun(J) ->
+ qlc:q([{X,W} ||
+ {X,_Y} <-
+ ets:table(E,{traverse,
+ {select,MS}}),
+ {Z,W} <- [{1,1},{2,2},{3,3}],
+ X =:= Z], {join,J})
+ end,
+ Qm = F(any),
+ [{2,2},{3,3}] = qlc:e(Qm),
+ {'EXIT',{cannot_carry_out_join,_}} =
+ (catch qlc:e(F(lookup)))
+ end, [{1,a},{2,b},{3,c}])">>],
+
+ <<"%% The filter 'A =< y' can be evaluated by traversing E1 using a
+ %% match specification, but then lookup join cannot use E1 for
+ %% looking up keys. This example shows that the filter is kept if
+ %% lookup join is employed (otherwise it is optimized away since
+ %% the match spec is used).
+ etsc(fun(E1) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E1),
+ {C,D} <- [{x,f},{y,g},{z,h}],
+ A =< y, % kept
+ A =:= C], {join,lookup}),
+ [{x,1,x,f},{y,2,y,g}] = lists:sort(qlc:e(Q))
+ end, [{x,1},{y,2},{z,3}])">>
+
+ ],
+ ?line run(Config, Ts_RT),
+
+ %% Ulf Wiger provided a patch that makes QLC work with packages:
+ Dir = filename:join(?privdir, "p"),
+ ?line ok = filelib:ensure_dir(filename:join(Dir, ".")),
+ File = filename:join(Dir, "p.erl"),
+ ?line ok = file:write_file(File,
+ <<"-module(p.p).\n"
+ "-export([q/0]).\n"
+ "-include_lib(\"stdlib/include/qlc.hrl\").\n"
+ "q() ->\n"
+ " .qlc:q([X || X <- [1,2]]).">>),
+ ?line {ok, 'p.p'} = compile:file(File, [{outdir,Dir}]),
+ ?line code:purge('p.p'),
+ ?line {module, 'p.p'} = code:load_abs(filename:rootname(File), 'p.p'),
+ ?line [1,2] = qlc:e(p.p:q()),
+
+ ok.
+
+otp_6964(doc) ->
+ "OTP-6964. New option 'tmpdir_usage'.";
+otp_6964(suite) -> [];
+otp_6964(Config) when is_list(Config) ->
+ T1 = [
+ <<"Q1 = qlc:q([{X} || X <- [1,2]]),
+ {'EXIT', {badarg,_}} = (catch qlc:e(Q1, {tmpdir_usage,bad})),
+ %% merge join
+ F = fun(Use) ->
+ L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ Q = qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,merge}),
+ qlc:e(Q, [{max_list_size,64*1024},{tmpdir_usage,Use}])
+ end,
+ D = erlang:system_flag(backtrace_depth, 0),
+ 20000 = length(F(allowed)),
+ ErrReply = F(not_allowed),
+ {error, qlc, {tmpdir_usage,joining}} = ErrReply,
+ \"temporary file was needed for joining\n\" =
+ lists:flatten(qlc:format_error(ErrReply)),
+ qlc_SUITE:install_error_logger(),
+ 20000 = length(F(warning_msg)),
+ {error, joining} = qlc_SUITE:read_error_logger(),
+ 20000 = length(F(info_msg)),
+ {info, joining} = qlc_SUITE:read_error_logger(),
+ 20000 = length(F(error_msg)),
+ {error, joining} = qlc_SUITE:read_error_logger(),
+ _ = erlang:system_flag(backtrace_depth, D),
+ qlc_SUITE:uninstall_error_logger()">>],
+ ?line run(Config, T1),
+
+ T2 = [
+ <<"%% File sorter.
+ T = lists:seq(1, 10000),
+ Q0 = qlc:q([{X} || X <- [T,T,T], begin X > 0 end],
+ [{cache,list},unique]),
+ Q1 = qlc:q([{X,Y,Z} ||
+ X <- Q0,
+ Y <- Q0,
+ Z <- Q0],
+ [{cache,list},unique]),
+ Q = qlc:q([{X, Y} || Y <- [1], X <- Q1]),
+ F = fun(Use) ->
+ qlc:e(Q, [{max_list_size,10000},{tmpdir_usage,Use}])
+ end,
+ 1 = length(F(allowed)),
+ ErrReply = F(not_allowed),
+ {error, qlc, {tmpdir_usage,caching}} = ErrReply,
+ \"temporary file was needed for caching\n\" =
+ lists:flatten(qlc:format_error(ErrReply)),
+ qlc_SUITE:install_error_logger(),
+ 1 = length(F(error_msg)),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ 1 = length(F(warning_msg)),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ 1 = length(F(info_msg)),
+ {info, caching} = qlc_SUITE:read_error_logger(),
+ {info, caching} = qlc_SUITE:read_error_logger(),
+ qlc_SUITE:uninstall_error_logger()">>],
+
+ ?line run(Config, T2),
+
+ T3 = [
+ <<"%% sort/keysort
+ E1 = create_ets(1, 10),
+ E2 = create_ets(5, 50000),
+ Q = qlc:q([{XX,YY} ||
+ {X,XX} <- ets:table(E1),
+ {YY,Y} <- ets:table(E2),
+ X == Y],
+ {join,merge}),
+ F = fun(Use) ->
+ qlc:e(Q, {tmpdir_usage,Use})
+ end,
+ ErrReply = F(not_allowed),
+ {error,qlc,{tmpdir_usage,sorting}} = ErrReply,
+ \"temporary file was needed for sorting\n\" =
+ lists:flatten(qlc:format_error(ErrReply)),
+ qlc_SUITE:install_error_logger(),
+ L = [{5,5},{6,6},{7,7},{8,8},{9,9},{10,10}],
+ L = F(allowed),
+ L = F(error_msg),
+ {error, sorting} = qlc_SUITE:read_error_logger(),
+ L = F(info_msg),
+ {info, sorting} = qlc_SUITE:read_error_logger(),
+ L = F(warning_msg),
+ {error, sorting} = qlc_SUITE:read_error_logger(),
+ qlc_SUITE:uninstall_error_logger(),
+ ets:delete(E1),
+ ets:delete(E2)">>],
+ ?line run(Config, T3),
+
+ T4 = [
+ <<"%% cache list
+ etsc(fun(E) ->
+ Q0 = qlc:q([X || X <- ets:table(E),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} || X <- lists:seq(1, 5),
+ Y <- Q0]),
+ R = [{X,Y} || X <- lists:seq(1, 5),
+ Y <- lists:seq(6, 10)],
+ F = fun(Use) ->
+ qlc:e(Q, [{max_list_size, 100*1024},
+ {tmpdir_usage, Use}])
+ end,
+ R = lists:sort(F(allowed)),
+ qlc_SUITE:install_error_logger(),
+ R = lists:sort(F(info_msg)),
+ {info, caching} = qlc_SUITE:read_error_logger(),
+ R = lists:sort(F(error_msg)),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ R = lists:sort(F(warning_msg)),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ qlc_SUITE:uninstall_error_logger(),
+ ErrReply = F(not_allowed),
+ {error,qlc,{tmpdir_usage,caching}} = ErrReply,
+ \"temporary file was needed for caching\n\" =
+ lists:flatten(qlc:format_error(ErrReply))
+ end, [{keypos,1}], [{I,a,lists:duplicate(100000,1)} ||
+ I <- lists:seq(1, 10)])">>],
+ ?line run(Config, T4),
+ ok.
+
+otp_7238(doc) ->
+ "OTP-7238. info-option 'depth', &c.";
+otp_7238(suite) -> [];
+otp_7238(Config) when is_list(Config) ->
+ dets:start(),
+ T = otp_7238,
+ Fname = filename(T, Config),
+
+ ?line ok = compile_gb_table(Config),
+
+ %% A few more warnings.
+ T1 = [
+ %% The same error message string, but with different tags
+ %% (the strings are not compared :-(
+ {nomatch_1,
+ <<"nomatch_1() ->
+ {qlc:q([X || X={X} <- []]), [t || \"a\"=\"b\" <- []]}.">>,
+ [],
+ {warnings,[{{2,30},qlc,nomatch_pattern},
+ {{2,44},v3_core,nomatch}]}},
+
+ %% Not found by qlc...
+ {nomatch_2,
+ <<"nomatch_2() ->
+ qlc:q([t || {\"a\"++\"b\"} = {\"ac\"} <- []]).">>,
+ [],
+ {warnings,[{{2,22},v3_core,nomatch}]}},
+
+ {nomatch_3,
+ <<"nomatch_3() ->
+ qlc:q([t || [$a, $b] = \"ba\" <- []]).">>,
+ [],
+ {warnings,[{{2,37},qlc,nomatch_pattern}]}},
+
+ %% Not found by qlc...
+ {nomatch_4,
+ <<"nomatch_4() ->
+ qlc:q([t || \"a\"++_=\"b\" <- []]).">>,
+ [],
+ {warnings,[{{2,22},v3_core,nomatch}]}},
+
+ %% Found neither by the compiler nor by qlc...
+ {nomatch_5,
+ <<"nomatch_5() ->
+ qlc:q([X || X = <<X>> <- [3]]).">>,
+ [],
+ []},
+
+ {nomatch_6,
+ <<"nomatch_6() ->
+ qlc:q([X || X <- [],
+ X =:= {X}]).">>,
+ [],
+ {warnings,[{{3,30},qlc,nomatch_filter}]}},
+
+ {nomatch_7,
+ <<"nomatch_7() ->
+ qlc:q([X || {X=Y,{Y}=X} <- []]).">>,
+ [],
+ {warnings,[{{2,28},qlc,nomatch_pattern}]}},
+
+ {nomatch_8,
+ <<"nomatch_8() ->
+ qlc:q([X || {X={},X=[]} <- []]).">>,
+ [],
+ {warnings,[{{2,28},qlc,nomatch_pattern}]}},
+
+ {nomatch_9,
+ <<"nomatch_9() ->
+ qlc:q([X || X <- [], X =:= {}, X =:= []]).">>,
+ [],
+ {warnings,[{{2,49},qlc,nomatch_filter}]}},
+
+ {nomatch_10,
+ <<"nomatch_10() ->
+ qlc:q([X || X <- [],
+ ((X =:= 1) or (X =:= 2)) and (X =:= 3)]).">>,
+ [],
+ {warnings,[{{3,53},qlc,nomatch_filter}]}},
+
+ {nomatch_11,
+ <<"nomatch_11() ->
+ qlc:q([X || X <- [], x =:= []]).">>,
+ [],
+ {warnings,[{{2,39},qlc,nomatch_filter}]}},
+
+ {nomatch_12,
+ <<"nomatch_12() ->
+ qlc:q([X || X={} <- [], X =:= []]).">>,
+ [],
+ {warnings,[{{2,42},qlc,nomatch_filter}]}},
+
+ {nomatch_13,
+ <<"nomatch_13() ->
+ qlc:q([Z || Z <- [],
+ X={X} <- [],
+ Y={Y} <- []]).">>,
+ [],
+ {warnings,[{{3,29},qlc,nomatch_pattern},
+ {{4,29},qlc,nomatch_pattern}]}},
+
+ {nomatch_14,
+ <<"nomatch_14() ->
+ qlc:q([X || X={X} <- [],
+ 1 > 0,
+ 1 > X]).">>,
+ [],
+ {warnings,[{{2,29},qlc,nomatch_pattern}]}},
+
+ {nomatch_15,
+ <<"nomatch_15() ->
+ qlc:q([{X,Y} || X={X} <- [1],
+ Y <- [1],
+ 1 > 0,
+ 1 > X]).">>,
+ [],
+ {warnings,[{{2,32},qlc,nomatch_pattern}]}},
+
+ %% Template warning.
+ {nomatch_template1,
+ <<"nomatch_template1() ->
+ qlc:q([{X} = {} || X <- []]).">>,
+ [],
+ {warnings,[{2,sys_core_fold,no_clause_match}]}}
+ ],
+ ?line [] = compile(Config, T1),
+
+ %% 'depth' is a new option used by info()
+ T2 = [
+ %% Firstly: lists
+ <<"L = [[a,b,c],{a,b,c},[],<<\"foobar\">>],
+ Q = qlc:q([{X} || X <- L]),
+ {call, _,
+ {remote,_,{atom,_,ets},{atom,_,match_spec_run}},
+ [{cons,_,{atom,_,'...'},
+ {cons,_,{atom,_,'...'},
+ {cons,_,{nil,_},{cons,_,{atom,_,'...'},{nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,0}]),
+
+ {call,_,_,
+ [{cons,_,{cons,_,{atom,_,'...'},{nil,_}},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$.},_,_},
+ {_,_,{_,_,$.},_,_},
+ {_,_,{_,_,$.},_,_}]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,1}]),
+
+ {call,_,
+ _,
+ [{cons,_,{cons,_,{atom,_,a},{atom,_,'...'}},
+ {cons,_,
+ {tuple,_,[{atom,_,a},{atom,_,'...'}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$f},_,_},
+ {_,_,{_,_,$.},_,_},
+ {_,_,{_,_,$.},_,_},
+ {_,_,{_,_,$.},_,_}]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,2}]),
+
+ {call,_,_,
+ [{cons,_,
+ {cons,_,{atom,_,a},{cons,_,{atom,_,b},{atom,_,'...'}}},
+ {cons,_,
+ {tuple,_,[{atom,_,a},{atom,_,b},{atom,_,'...'}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$f},_,_},
+ {_,_,{_,_,$o},_,_},_,_,_]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,3}]),
+
+ {call,_,_,
+ [{cons,_,
+ {cons,_,
+ {atom,_,a},{cons,_,{atom,_,b},{cons,_,{atom,_,c},{nil,_}}}},
+ {cons,_,
+ {tuple,_,[{atom,_,a},{atom,_,b},{atom,_,c}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$f},_,_},
+ {_,_,{_,_,$o},_,_},
+ {_,_,{_,_,$o},_,_},
+ {_,_,{_,_,$b},_,_},
+ {_,_,{_,_,$a},_,_},
+ {_,_,{_,_,$r},_,_}]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,10}]),
+
+ {call,_,_,
+ [{cons,_,
+ {cons,_,
+ {atom,_,a},{cons,_,{atom,_,b},{cons,_,{atom,_,c},{nil,_}}}},
+ {cons,_,
+ {tuple,_,[{atom,_,a},{atom,_,b},{atom,_,c}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$f},_,_},
+ {_,_,{_,_,$o},_,_},
+ {_,_,{_,_,$o},_,_},
+ {_,_,{_,_,$b},_,_},
+ {_,_,{_,_,$a},_,_},
+ {_,_,{_,_,$r},_,_}]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,infinity}])">>,
+
+ %% Secondly: looked up keys
+ <<"F = fun(D) ->
+ etsc(fun(E) ->
+ Q = qlc:q([C || {N,C} <- ets:table(E),
+ (N =:= {2,2}) or (N =:= {3,3})]),
+ F = qlc:info(Q, [{format,abstract_code},{depth,D}]),
+ {call,_,_,[{call,_,_,[_Fun,Values]},_]} = F,
+ [b,c] = lists:sort(qlc:eval(Q)),
+ Values
+ end, [{{1,1},a},{{2,2},b},{{3,3},c},{{4,4},d}])
+ end,
+
+ [{cons,_,{atom,_,'...'},{cons,_,{atom,_,'...'},{nil,_}}},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}},
+ {cons,_,
+ {tuple,_,[{integer,_,2},{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{integer,_,3},{atom,_,'...'}]},{nil,_}}},
+ {cons,_,
+ {tuple,_,[{integer,_,2},{integer,_,2}]},
+ {cons,_,{tuple,_,[{integer,_,3},{integer,_,3}]},{nil,_}}},
+ {cons,_,
+ {tuple,_,[{integer,_,2},{integer,_,2}]},
+ {cons,_,{tuple,_,[{integer,_,3},{integer,_,3}]},{nil,_}}}] =
+ lists:map(F, [0,1,2,3,infinity])">>,
+ [<<"T = otp_7238, Fname = \"">>, Fname, <<"\",
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ ok = dets:insert(T, [{{1,1},a},{{2,2},b},{{3,3},c},{{4,4},d}]),
+ Q = qlc:q([C || {N,C} <- dets:table(T),
+ (N =:= {2,2}) or (N =:= {3,3})]),
+ F = qlc:info(Q, [{format,abstract_code},{depth,1}]),
+ [b,c] = lists:sort(qlc:eval(Q)),
+ {call,_,_,
+ [{call,_,_,
+ [_,
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}}]},
+ _]} = F,
+ ok = dets:close(T),
+ file:delete(\"">>, Fname, <<"\")">>],
+
+ %% Thirdly: format_fun has been extended (in particular: gb_table)
+ <<"T = gb_trees:from_orddict([{{1,a},w},{{2,b},v},{{3,c},u}]),
+ QH = qlc:q([X || {{X,Y},_} <- gb_table:table(T),
+ ((X =:= 1) or (X =:= 2)),
+ ((Y =:= a) or (Y =:= b) or (Y =:= c))]),
+ {call,_,_,
+ [{call,_,_,
+ [{'fun',_,
+ {clauses,
+ [{clause,_,_,[],
+ [{'case',_,
+ {call,_,_,
+ [_,
+ {call,_,_,
+ [{cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}}}]}]},
+ [_,_]}]}]}},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}}}}}}]},
+ {call,_,_,
+ [{cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}]}]} =
+ qlc:info(QH, [{format,abstract_code},{depth,1}])">>,
+ <<"T1 = [{1,1,a},{2,2,b},{3,3,c},{4,4,d}],
+ T2 = [{x,1},{y,1},{z,2}],
+ QH1 = T1,
+ T = gb_trees:from_orddict(T2),
+ QH2 = qlc:q([X || {_,X} <- gb_table:table(T)], cache),
+ Q = qlc:q([{X1,X2,X3} || {X1,X2,X3} <- QH1,
+ Y2 <- QH2,
+ X2 =:= Y2]),
+ {block,_,
+ [{match,_,_,
+ {call,_,_,
+ [{lc,_,_,
+ [{generate,_,_,
+ {call,_,_,
+ [{call,_,_,
+ [{cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {atom,_,'...'}}]}]}}]},
+ _]}},
+ {call,_,_,
+ [{lc,_,_,
+ [{generate,_,_,
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}}},
+ _,_]}]}]} =
+ qlc:info(Q, [{format,abstract_code},{depth, 1},
+ {n_elements,1}])">>,
+ <<"L = [{{key,1},a},{{key,2},b},{{key,3},c}],
+ T = gb_trees:from_orddict(orddict:from_list(L)),
+ Q = qlc:q([K || {K,_} <- gb_table:table(T),
+ (K =:= {key,1}) or (K =:= {key,2})]),
+{call,_,_,
+ [{call,_,_,
+ [{'fun',_,
+ {clauses,
+ [{clause,_,_,[],
+ [{'case',_,
+ {call,_,_,
+ [_,
+ {call,_,_,
+ [{cons,_,
+ {tuple,_,[{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}]},
+ {nil,_}}}}]}]},
+ _}]}]}},
+ {cons,_,
+ {tuple,_,[{atom,_,key},{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,key},{atom,_,'...'}]},{nil,_}}}]},
+ {call,_,
+ {remote,_,{atom,_,ets},{atom,_,match_spec_compile}},
+ [{cons,_,
+ {tuple,_,[{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}]},
+ {nil,_}}]}]} =
+ qlc:info(Q, [{format,abstract_code},{depth, 2}])">>
+
+ ],
+ ?line run(Config, T2),
+
+ T3 = [
+ {nomatch_6,
+ <<"nomatch_6() ->
+ qlc:q([X || X <- [],
+ X =:= {X}]).">>,
+ [],
+ {[],["filter evaluates to 'false'"]}},
+
+ {nomatch_7,
+ <<"nomatch_7() ->
+ qlc:q([X || {X=Y,{Y}=X} <- []]).">>,
+ [],
+ {[],["pattern cannot possibly match"]}}],
+ ?line compile_format(Config, T3),
+
+ %% *Very* simple test - just check that it doesn't crash.
+ Type = [{cres,
+ <<"Q = qlc:q([X || {X} <- []]),
+ {'EXIT',{{badfun,_},_}} = (catch qlc:e(Q))">>,
+ [type_checker],
+ []}],
+ ?line run(Config, Type),
+
+ ok.
+
+otp_7114(doc) ->
+ "OTP-7114. Match spec, table and duplicated objects..";
+otp_7114(suite) -> [];
+otp_7114(Config) when is_list(Config) ->
+ Ts = [<<"T = ets:new(t, [bag]),
+ [ets:insert(T, {t, I, I div 2}) || I <- lists:seq(1,10)],
+ Q1 = qlc:q([element(3, E) || E <- ets:table(T)]),
+ [0,1,1,2,2,3,3,4,4,5] = lists:sort(qlc:e(Q1)),
+ [0,1,2,3,4,5] = qlc:e(Q1, unique_all),
+ [0,1,2,3,4,5] = qlc:e(qlc:sort(Q1), unique_all),
+ [0,1,2,3,4,5] = qlc:e(qlc:sort(qlc:e(Q1)), unique_all),
+ ets:delete(T),
+ ok">>],
+ ?line run(Config, Ts).
+
+otp_7232(doc) ->
+ "OTP-7232. qlc:info() bug (pids, ports, refs, funs).";
+otp_7232(suite) -> [];
+otp_7232(Config) when is_list(Config) ->
+ Ts = [<<"L = [fun math:sqrt/1, list_to_pid(\"<0.4.1>\"),
+ erlang:make_ref()],
+ \"[fun math:sqrt/1,<0.4.1>,#Ref<\" ++ _ = qlc:info(L),
+ {call,_,
+ {remote,_,{atom,_,qlc},{atom,_,sort}},
+ [{cons,_,
+ {'fun',_,{function,math,sqrt,_}},
+ {cons,_,
+ {string,_,\"<0.4.1>\"}, % could use list_to_pid..
+ {cons,_,{string,_,\"#Ref<\"++_},{nil,_}}}},
+ {nil,_}]} =
+ qlc:info(qlc:sort(L),{format,abstract_code})">>,
+
+ <<"Q1 = qlc:q([X || X <- [1000,2000]]),
+ Q = qlc:sort(Q1, {order, fun(A,B)-> A>B end}),
+ \"qlc:sort([1000,2000],[{order,fun'-function/0-fun-2-'/2}])\" =
+ format_info(Q, true),
+ AC = qlc:info(Q, {format, abstract_code}),
+ \"qlc:sort([1000,2000], [{order,fun '-function/0-fun-2-'/2}])\" =
+ binary_to_list(iolist_to_binary(erl_pp:expr(AC)))">>,
+
+ %% OTP-7234. erl_parse:abstract() handles bit strings
+ <<"Q = qlc:sort([<<17:9>>]),
+ \"[<<8,1:1>>]\" = qlc:info(Q)">>
+
+ ],
+ ?line run(Config, Ts).
+
+otp_7552(doc) ->
+ "OTP-7552. Merge join bug.";
+otp_7552(suite) -> [];
+otp_7552(Config) when is_list(Config) ->
+ %% The poor performance cannot be observed unless the
+ %% (redundant) join filter is skipped.
+ Ts = [<<"Few = lists:seq(1, 2),
+ Many = lists:seq(1, 10),
+ S = [d,e],
+ L1 = [{Y,a} || Y <- Few] ++ [{'1b',b},{2,b}] ++
+ [{Y,X} || X <- S, Y <- Few],
+ L2 = [{a,Y} || Y <- Many] ++
+ [{b,'1b'}] ++ [{c,1}] ++
+ [{X,Y} || X <- S, Y <- Many],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = lists:sort(qlc:e(Qm, {max_list_size,20})) =:=
+ lists:sort(qlc:e(Qn))">>],
+ ?line run(Config, Ts).
+
+otp_7714(doc) ->
+ "OTP-7714. Merge join bug.";
+otp_7714(suite) -> [];
+otp_7714(Config) when is_list(Config) ->
+ %% The original example uses Mnesia. This one does not.
+ Ts = [<<"E1 = ets:new(set,[]),
+ true = ets:insert(E1, {a,1}),
+ E2 = ets:new(set,[]),
+ _ = [true = ets:insert(E2, {I, 1}) ||
+ I <- lists:seq(1, 3)],
+ Q = qlc:q([{A,B} ||
+ {A,I1} <- ets:table(E1),
+ {B,I2} <- ets:table(E2),
+ I1 =:= I2],{join,merge}),
+ [{a,1},{a,2},{a,3}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>],
+ ?line run(Config, Ts).
+
+otp_6674(doc) ->
+ "OTP-6674. match/comparison.";
+otp_6674(suite) -> [];
+otp_6674(Config) when is_list(Config) ->
+
+ ?line ok = compile_gb_table(Config),
+
+ Ts = [%% lookup join
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {0,1,0,0} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X =:= Y]),
+ {0,1,0,0} = join_info(Q),
+ {block,_,
+ [_,
+ {match,_,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,_,{op,_,'==',_,_}]},
+ {cons,_,
+ {tuple,_,[{atom,_,join},{atom,_,lookup}]},_}]}},
+ _]} = qlc:info(Q, {format, abstract_code}),
+ [{1,1},{2,2}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(join, [set]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X == Y], {join, lookup}),
+ {'EXIT',{cannot_carry_out_join,_}} = (catch qlc:e(Q)),
+ ets:delete(E)">>,
+
+ %% Lookup join possible in both directions.
+ <<"E1 = ets:new(join, [ordered_set]),
+ E2 = ets:new(join, [set]),
+ true = ets:insert(E1, [{1.0,a},{2,b},{3,c}]),
+ true = ets:insert(E2, [{0},{1},{2}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ X == Y],{join,lookup}), % skipped
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{table,{ets,table,[_]}}}],[]}},
+ {generate,_,{table,{ets,table,[_]}}},
+ _],
+ [{join,lookup}]}}],
+ []} = qlc:info(Q, {format,debug}),
+ {0,1,0,0} = join_info(Q),
+ [{1.0,1},{2,2}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+ <<"E1 = ets:new(join, [ordered_set]),
+ E2 = ets:new(join, [set]),
+ true = ets:insert(E1, [{1.0,a},{2,b},{3,c}]),
+ true = ets:insert(E2, [{0},{1},{2}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ X =:= Y],{join,merge}), % not skipped
+ {1,0,0,1} = join_info(Q),
+ [{2,2}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+ <<"E1 = ets:new(join, [ordered_set]),
+ E2 = ets:new(join, [set]),
+ true = ets:insert(E1, [{1.0,a},{2,b},{3,c}]),
+ true = ets:insert(E2, [{0},{1},{2}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ X =:= Y],{join,lookup}), % skipped
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,
+ [{generate,_,{table,{ets,table,[_]}}}],
+ []}},
+ {generate,_,{table,{ets,table,[_]}}},
+ _],
+ [{join,lookup}]}}],
+ []} = qlc:info(Q, {format,debug}),
+ {0,1,0,0} = join_info(Q),
+ [{2,2}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+ <<"E1 = ets:new(join, [ordered_set]),
+ E2 = ets:new(join, [set]),
+ true = ets:insert(E1, [{1.0,a},{2,b},{3,c}]),
+ true = ets:insert(E2, [{0},{1},{2}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ %% Independent of term comparison:
+ X =:= Y, X == Y]),
+ {0,1,0,0} = join_info(Q),
+ [{2,2}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,1},{2,2},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,Z} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X == Z, Y == Z]), % cannot skip (yet)
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[_,_,_],[{join,lookup}]}},
+ _,_],[]} = qlc:info(Q,{format,debug}),
+ {0,1,0,0} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ %% The following moved here from skip_filters. It was buggy!
+ <<"etsc(fun(E) ->
+ A = 3,
+ Q = qlc:q([X || X <- ets:table(E),
+ A == element(1,X)]),
+ {table, _} = i(Q),
+ case qlc:e(Q) of
+ [{3},{3.0}] -> ok;
+ [{3.0},{3}] -> ok
+ end,
+ false = lookup_keys(Q)
+ end, [{3},{3.0},{c}])">>,
+ <<"H1 = qlc:sort([{{192,192.0},1,a},{{192.0,192.0},2,b},{{192,192.0},3,c}]),
+ Q = qlc:q([{X, Y} || {{A,B},X,_} <- H1, % does not need keysort(3)
+ A == 192, B =:= 192.0,
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {block,0,
+ [{match,_,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ %% Has to compare extra constant:
+ {op,_,'==',
+ {tuple,_,[{integer,_,192},{float,_,192.0}]},
+ {call,_,{atom,_,element},[{integer,_,1},{var,_,'P0'}]}}]}]}},
+ _,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ %% The join filter has been skipped.
+ {op,_,'==',{var,_,'A'},{integer,_,192}},
+ {op,_,'=:=',{var,_,'B'},{float,_,192.0}}]}]}]}
+ = qlc:info(Q, {format,abstract_code}),
+ {1,0,0,1} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ %% Does not handle more than one lookup value (conjunctive).
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X =:= 1 andalso X == 1.0]),
+ false = lookup_keys(H),
+ [1] = qlc:e(H)">>,
+
+ %% EqualConstants...
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X} <- ets:table(E),
+ {Y} <- [{{1}},{{2}},{{1.0}},{{2.0}}],
+ X =:= {1}, X == {1.0},
+ X == Y], {join, merge}),
+ [{{1},{1}},{{1},{1.0}}] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{{1}}, {{2}}])">>,
+
+ <<"T = gb_trees:from_orddict([{foo,{1}}, {bar,{2}}]),
+ Q = qlc:q([{X,Y} || {_,X} <- gb_table:table(T),
+ {Y} <- [{{1}},{{2}},{{1.0}},{{2.0}}],
+ (X =:= {1}) or (X == {2}),
+ (X == {1.0}) or (X =:= {2.0}),
+ X == Y], {join, merge}),
+ [{{1},{1}},{{1},{1.0}}] = qlc:e(Q)">>,
+
+ %% Compare key
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X == 1]),
+ [1] = lookup_keys(H),
+ [1] = qlc:e(H)">>,
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X == 1.0]),
+ [1.0] = lookup_keys(H), % this is how gb_table works...
+ [1.0] = qlc:e(H)">>,
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X == 1.0]),
+ [1] = qlc:e(H), % and this is how ETS works.
+ [1.0] = lookup_keys(H)
+ end, [ordered_set], [{1,a},{2,b}])">>,
+
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X =:= 2]),
+ [2] = lookup_keys(H),
+ %% Cannot (generally) remove the matching filter (the table
+ %% compares the key). But note that gb_table returns the given
+ %% term as key, so in this case the filter _could_ have been removed.
+ %% However, there is no callback to inform qlc about that.
+ {call,_,_,
+ [_,{call,_,_,
+ [{cons,_,{tuple,_,
+ [_,{cons,_,
+ {tuple,_,[{atom,_,'=:='},{atom,_,'$1'},{integer,_,2}]},
+ _},_]},_}]}]} = qlc:info(H, {format,abstract_code}),
+ [2] = qlc:e(H)">>,
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X =:= 2.0]),
+ %% Just shows that the term (not the key) is returned.
+ [2.0] = lookup_keys(H),
+ [2.0] = qlc:e(H)">>,
+
+ <<"I = 1,
+ T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X == I]), % imported variable
+ [1] = lookup_keys(H),
+ {call,_,_,
+ [_,{call,_,_,
+ [{cons,_,
+ {tuple,_,
+ [{tuple,_,[{atom,_,'$1'},{atom,_,'_'}]},
+ {nil,_}, % the filter has been skipped
+ {cons,_,{atom,_,'$1'},_}]},
+ _}]}]} = qlc:info(H, {format, abstract_code}),
+ [1] = qlc:e(H)">>,
+ <<"I = 2,
+ T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X =:= I]),
+ [2] = lookup_keys(H),
+ {call,_,_,
+ [_,{call,_,_,
+ [{cons,_,{tuple,_,
+ [_,{cons,_,
+ {tuple,_,
+ [{atom,_,'=:='},
+ {atom,_,'$1'},
+ {tuple,_,[{atom,_,const},{integer,_,2}]}]},
+ _},_]},
+ _}]}]} = qlc:info(H, {format, abstract_code}),
+ [2] = qlc:e(H)">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= a]), % skipped
+ [a] = qlc:e(Q),
+ {list,{table,_},_} = i(Q),
+ [a] = lookup_keys(Q)
+ end, [ordered_set], [{a,1},{b,2},{3,c}])">>,
+
+ %% Does not find that if for instance X =:= {1} then the filter
+ %% X == {1} can be removed.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= {1}, X == {1.0}]),
+ [{1}] = qlc:e(Q),
+ [{1}] = lookup_keys(Q)
+ end, [{{1}}, {{2}}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= {1}, X == {1.0}]),
+ [{1}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [ordered_set], [{{1}}, {{2}}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X == {1.0}, X =:= {1}]),
+ [{1}] = qlc:e(Q),
+ [{1}] = lookup_keys(Q)
+ end, [{{1}}, {{2}}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X == {1.0}, X =:= {1}]),
+ [{1}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [ordered_set], [{{1}}, {{2}}])">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} =
+ (catch qlc_SUITE:bad_table_key_equality(E)),
+ ets:delete(E)">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= 1, X =:= is_integer(X)]),
+ [] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X=1} <- ets:table(E),
+ X =:= is_integer(X)]),
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ {op,_,'=:=',
+ {var,_,'X'},
+ {call,_,
+ {atom,_,is_integer},
+ [{var,_,'X'}]}}]}]} =
+ qlc:info(Q, {format, abstract_code}),
+ [] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,Y} <- gb_table:table(T),
+ Y =:= a, true, X =:= 1]),
+ [1] = lookup_keys(H),
+ [1] = qlc:e(H)">>,
+
+ <<"T = gb_trees:from_orddict([{{1.0,1},a},{{2.0,2},b}]),
+ H = qlc:q([X || {{1.0,B}=X,_} <- gb_table:table(T),
+ B == 1]), % skipped
+ [{1.0, 1}] = qlc:e(H),
+ {qlc,_,[{generate,_,{table,_}}], []} = qlc:info(H, {format,debug})">>,
+ <<"T = gb_trees:from_orddict([{{1.0,1},a},{{2.0,2},b}]),
+ H = qlc:q([X || {{1.0,B}=X,_} <- gb_table:table(T),
+ B == 1.0]), % skipped
+ [{1.0, 1.0}] = qlc:e(H), % this is how gb_table works...
+ {qlc,_,[{generate,_,{table,_}}], []} = qlc:info(H, {format,debug})">>,
+ <<"T = gb_trees:from_orddict([{{1.0,1},a},{{2.0,2},b}]),
+ H = qlc:q([X || {{1.0,B}=X,_} <- gb_table:table(T),
+ B =:= 1.0]), % not skipped
+ [{1.0, 1.0}] = qlc:e(H),
+ {qlc,_,[{generate,_,{table,_}},_], []} = qlc:info(H,{format,debug})">>,
+ <<"T = gb_trees:from_orddict([{{1.0,1},a},{{2.0,2},b}]),
+ H = qlc:q([X || {{1.0,B}=X,_} <- gb_table:table(T),
+ B =:= 1]), % not skipped
+ [{1.0, 1}] = qlc:e(H),
+ {qlc,_,[{generate,_,{table,_}},_], []} = qlc:info(H,{format,debug})">>,
+
+ <<"%% The imported variables do not interfere with join.
+ E = ets:new(join, [ordered_set]),
+ {A, B} = {1,1},
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,_Z} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X =:= A, Y =:= B,
+ Y == X], % skipped
+ {join, merge}),
+ [{1,1}] = qlc:e(Q),
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{list,{table,_},_}},_],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{list,_,_}},_],[]}},
+ _],
+ [{join,merge}]}}],
+ []} = qlc:info(Q, {format, debug}),
+ ets:delete(E)">>,
+
+ <<"% An old bug: usort() should not be used when matching values
+ etsc(fun(E) ->
+ I = 1,
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= 1.0 orelse X =:= I]),
+ [1] = qlc:e(H),
+ [1.0] = lookup_keys(H) % do not look up twice
+ end, [set], [{1,a},{2,b}])">>,
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= 1.0 orelse X == 1]),
+ [1] = qlc:e(H),
+ false = lookup_keys(H) % doesn't handle this case
+ end, [ordered_set], [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ I1 = 1, I2 = 1,
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= I1 orelse X == I2]),
+ [1] = qlc:e(H), % do not look up twice
+ [1] = lookup_keys(H)
+ end, [ordered_set], [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ I1 = 1, I2 = 1, I3 = 1,
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ I1 == I2, I1 =:= I3, I3 == I2, I2 =:= I3,
+ X =:= I1 orelse X == I2
+ ]),
+ [1] = qlc:e(H),
+ [1] = lookup_keys(H)
+ end, [ordered_set], [{1,a},{2,b}])">>,
+
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b,x},{3,c}]),
+ Q = qlc:q([P || P <- ets:table(E),
+ P =:= {1,a} orelse P =:= {2,b,x}]),
+ [{1,a},{2,b,x}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,Y} <- ets:table(E),
+ ((X =:= 3) or (Y =:= 4)) and (X == a)]),
+ {list,{table,_},_} = i(Q),
+ [] = qlc:e(Q), % a is not an answer
+ [a] = lookup_keys(Q)
+ end, [{keypos,1},ordered_set], [{a,3},{b,4}])">>,
+
+ <<"Q = qlc:q([{X,Y} ||
+ {X} <- [{<<3:4>>}],
+ {Y} <- [{<<3:4>>}],
+ X =:= <<1:3,1:1>>, % <<3:4>>
+ Y =:= <<0:2,1:1,1:1>>, % <<3:4>>
+ X =:= Y]),
+ [{<<3:4>>,<<3:4>>}] = qlc:e(Q)">>
+
+
+ ],
+
+ ?line run(Config, Ts).
+
+manpage(doc) ->
+ "Examples from qlc(3).";
+manpage(suite) -> [];
+manpage(Config) when is_list(Config) ->
+
+ ?line ok = compile_gb_table(Config),
+
+ Ts = [
+ <<"QH = qlc:q([{X,Y} || X <- [a,b], Y <- [1,2]]),
+ QC = qlc:cursor(QH),
+ [{a,1}] = qlc:next_answers(QC, 1),
+ [{a,2}] = qlc:next_answers(QC, 1),
+ [{b,1},{b,2}] = qlc:next_answers(QC, all_remaining),
+ ok = qlc:delete_cursor(QC)">>,
+
+ <<"QH = qlc:q([{X,Y} || X <- [a,b], Y <- [1,2]]),
+ [{a,1},{a,2},{b,1},{b,2}] = qlc:eval(QH)">>,
+
+ <<"QH = [1,2,3,4,5,6],
+ 21 = qlc:fold(fun(X, Sum) -> X + Sum end, 0, QH)">>,
+
+ <<"QH = qlc:q([{X,Y} || X <- [x,y], Y <- [a,b]]),
+ B = \"begin\n\"
+ \" V1 =\n\"
+ \" qlc:q([ \n\"
+ \" SQV ||\n\"
+ \" SQV <- [x,y]\n\"
+ \" ],\n\"
+ \" [{unique,true}]),\n\"
+ \" V2 =\n\"
+ \" qlc:q([ \n\"
+ \" SQV ||\n\"
+ \" SQV <- [a,b]\n\"
+ \" ],\n\"
+ \" [{unique,true}]),\n\"
+ \" qlc:q([ \n\"
+ \" {X,Y} ||\n\"
+ \" X <- V1,\n\"
+ \" Y <- V2\n\"
+ \" ],\n\"
+ \" [{unique,true}])\n\"
+ \"end\",
+ true = B =:= qlc:info(QH, unique_all)">>,
+
+ <<"E1 = ets:new(e1, []),
+ E2 = ets:new(e2, []),
+ true = ets:insert(E1, [{1,a},{2,b}]),
+ true = ets:insert(E2, [{a,1},{b,2}]),
+ Q = qlc:q([{X,Z,W} ||
+ {X, Z} <- ets:table(E1),
+ {W, Y} <- ets:table(E2),
+ X =:= Y]),
+ L = \"begin\n\"
+ \" V1 =\n\"
+ \" qlc:q([ \n\"
+ \" P0 ||\n\"
+ \" P0 = {W,Y} <- ets:table(_)\n\"
+ \" ]),\n\"
+ \" V2 =\n\"
+ \" qlc:q([ \n\"
+ \" [G1|G2] ||\n\"
+ \" G2 <- V1,\n\"
+ \" G1 <- ets:table(_),\n\"
+ \" element(2, G1) =:= element(1, G2)\n\"
+ \" ],\n\"
+ \" [{join,lookup}]),\n\"
+ \" qlc:q([ \n\"
+ \" {X,Z,W} ||\n\"
+ \" [{X,Z}|{W,Y}] <- V2\n\"
+ \" ])\n\"
+ \"end\",
+ Info =
+ re:replace(qlc:info(Q),
+ \"table\\\\(-*[0-9]*\",
+ \"table(_\", [{return,list},global]),
+ L = Info,
+ ets:delete(E1),
+ ets:delete(E2)">>,
+
+ <<"Q = qlc:q([{A,X,Z,W} ||
+ A <- [a,b,c],
+ {X,Z} <- [{a,1},{b,4},{c,6}],
+ {W,Y} <- [{2,a},{3,b},{4,c}],
+ X =:= Y],
+ {cache, list}),
+ L =
+ \"begin\n\"
+ \" V1 =\n\"
+ \" qlc:q([ \n\"
+ \" P0 ||\n\"
+ \" P0 = {X,Z} <- qlc:keysort(1, [{a,1},{b,4},{c,6}], [])\n\"
+ \" ]),\n\"
+ \" V2 =\n\"
+ \" qlc:q([ \n\"
+ \" P0 ||\n\"
+ \" P0 = {W,Y} <- qlc:keysort(2, [{2,a},{3,b},{4,c}], [])\n\"
+ \" ]),\n\"
+ \" V3 =\n\"
+ \" qlc:q([ \n\"
+ \" [G1|G2] ||\n\"
+ \" G1 <- V1,\n\"
+ \" G2 <- V2,\n\"
+ \" element(1, G1) == element(2, G2)\n\"
+ \" ],\n\"
+ \" [{join,merge},{cache,list}]),\n\"
+ \" qlc:q([ \n\"
+ \" {A,X,Z,W} ||\n\"
+ \" A <- [a,b,c],\n\"
+ \" [{X,Z}|{W,Y}] <- V3,\n\"
+ \" X =:= Y\n\"
+ \" ])\n\"
+ \"end\",
+ L = qlc:info(Q)">>,
+
+ <<"E1 = ets:new(t, [set]), % uses =:=/2
+ Q1 = qlc:q([K ||
+ {K} <- ets:table(E1),
+ K == 2.71 orelse K == a]),
+ {list,{table,_}, [{{'$1'},[],['$1']}]} = i(Q1),
+ true = ets:delete(E1)">>,
+
+ <<"F = fun(E, I) ->
+ qlc:q([V || {K,V} <- ets:table(E), K == I])
+ end,
+ E2 = ets:new(t, [set]),
+ true = ets:insert(E2, [{{2,2},a},{{2,2.0},b},{{2.0,2},c}]),
+ Q2 = F(E2, {2,2}),
+ {table,{ets,table,[_,
+ [{traverse,{select,[{{'$1','$2'},
+ [{'==','$1',{const,{2,2}}}],
+ ['$2']}]}}]]}} = i(Q2),
+ [a,b,c] = lists:sort(qlc:e(Q2)),
+ true = ets:delete(E2),
+
+ E3 = ets:new(t, [ordered_set]), % uses ==/2
+ true = ets:insert(E3, [{{2,2.0},b}]),
+ Q3 = F(E3,{2,2}),
+ {list,{table,_},[{{'$1','$2'},[],['$2']}]} = i(Q3),
+ [b] = qlc:e(Q3),
+ true = ets:delete(E3)">>,
+
+ <<"T = gb_trees:empty(),
+ QH = qlc:q([X || {{X,Y},_} <- gb_table:table(T),
+ ((X == 1) or (X == 2)) andalso
+ ((Y == a) or (Y == b) or (Y == c))]),
+ L = \"ets:match_spec_run(lists:flatmap(fun(K) ->
+ case
+ gb_trees:lookup(K,
+ gb_trees:from_orddict([]))
+ of
+ {value,V} ->
+ [{K,V}];
+ none ->
+ []
+ end
+ end,
+ [{1,a},{1,b},{1,c},{2,a},{2,b},{2,c}]),
+ ets:match_spec_compile([{{{'$1','$2'},'_'},[],['$1']}]))\",
+ L = qlc:info(QH)">>
+ ],
+ ?line run(Config, Ts),
+
+ L = [1,2,3],
+ Bs = erl_eval:add_binding('L', L, erl_eval:new_bindings()),
+ QH = qlc:string_to_handle("[X+1 || X <- L].", [], Bs),
+ [2,3,4] = qlc:eval(QH),
+
+ %% ets(3)
+ MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y} end),
+ ETs = [
+ [<<"true = ets:insert(Tab = ets:new(t, []),[{1,a},{2,b},{3,c},{4,d}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ QH1 = ets:table(Tab, [{traverse, {select, MS}}]),
+
+ QH2 = qlc:q([{Y} || {X,Y} <- ets:table(Tab), (X > 1) or (X < 5)]),
+
+ true = qlc:info(QH1) =:= qlc:info(QH2),
+ true = ets:delete(Tab)">>]],
+ ?line run(Config, ETs),
+
+ %% dets(3)
+ DTs = [
+ [<<"{ok, T} = dets:open_file(t, []),
+ ok = dets:insert(T, [{1,a},{2,b},{3,c},{4,d}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ QH1 = dets:table(T, [{traverse, {select, MS}}]),
+
+ QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t), (X > 1) or (X < 5)]),
+
+ true = qlc:info(QH1) =:= qlc:info(QH2),
+ ok = dets:close(T)">>]],
+ ?line run(Config, DTs),
+
+ ok.
+
+compile_gb_table(Config) ->
+ GB_table_file = filename("gb_table.erl", Config),
+ ?line ok = file:write_file(GB_table_file, gb_table()),
+ ?line {ok, gb_table} = compile:file(GB_table_file, [{outdir,?privdir}]),
+ ?line code:purge(gb_table),
+ ?line {module, gb_table} =
+ code:load_abs(filename:rootname(GB_table_file)),
+ ok.
+
+gb_table() ->
+ <<"
+-module(gb_table).
+
+-export([table/1]).
+
+table(T) ->
+ TF = fun() -> qlc_next(gb_trees:next(gb_trees:iterator(T))) end,
+ InfoFun = fun(num_of_objects) -> gb_trees:size(T);
+ (keypos) -> 1;
+ (is_sorted_key) -> true;
+ (is_unique_objects) -> true;
+ (_) -> undefined
+ end,
+ LookupFun =
+ fun(1, Ks) ->
+ lists:flatmap(fun(K) ->
+ case gb_trees:lookup(K, T) of
+ {value, V} -> [{K,V}];
+ none -> []
+ end
+ end, Ks)
+ end,
+ FormatFun =
+ fun({all, NElements, ElementFun}) ->
+ ValsS = io_lib:format(\"gb_trees:from_orddict(~w)\",
+ [gb_nodes(T, NElements, ElementFun)]),
+ io_lib:format(\"gb_table:table(~s)\", [ValsS]);
+ ({lookup, 1, KeyValues, _NElements, ElementFun}) ->
+ ValsS = io_lib:format(\"gb_trees:from_orddict(~w)\",
+ [gb_nodes(T, infinity, ElementFun)]),
+ io_lib:format(\"lists:flatmap(fun(K) -> \"
+ \"case gb_trees:lookup(K, ~s) of \"
+ \"{value, V} -> [{K,V}];none -> [] end \"
+ \"end, ~w)\",
+ [ValsS, [ElementFun(KV) || KV <- KeyValues]])
+ end,
+ qlc:table(TF, [{info_fun, InfoFun}, {format_fun, FormatFun},
+ {lookup_fun, LookupFun},{key_equality,'=='}]).
+
+qlc_next({X, V, S}) ->
+ [{X,V} | fun() -> qlc_next(gb_trees:next(S)) end];
+qlc_next(none) ->
+ [].
+
+gb_nodes(T, infinity, ElementFun) ->
+ gb_nodes(T, -1, ElementFun);
+gb_nodes(T, NElements, ElementFun) ->
+ gb_iter(gb_trees:iterator(T), NElements, ElementFun).
+
+gb_iter(_I, 0, _EFun) ->
+ '...';
+gb_iter(I0, N, EFun) ->
+ case gb_trees:next(I0) of
+ {X, V, I} ->
+ [EFun({X,V}) | gb_iter(I, N-1, EFun)];
+ none ->
+ []
+ end.
+ ">>.
+
+compat(suite) ->
+ [backward, forward].
+
+backward(doc) ->
+ "OTP-6674. Join info and extra constants.";
+backward(suite) -> [];
+backward(Config) when is_list(Config) ->
+ case try_old_join_info(Config) of
+ ok ->
+ ok;
+ Reply ->
+ Reply
+ end.
+
+-ifdef(debug).
+try_old_join_info(_Config) ->
+ ok.
+-else.
+try_old_join_info(Config) ->
+ case ?t:is_release_available("r12b") of
+ true ->
+ %% Check join info for handlers of extra constants. Start R12B-0.
+ ?line {ok, R12} = start_node_rel(r12, r12b, slave),
+ File = filename("handle.erl", Config),
+ ?line file:write_file(File,
+ <<"-module(handle).\n"
+ "-export([create_handle/0, lookup_handle/0]).\n"
+ "-include_lib(\"stdlib/include/qlc.hrl\").\n"
+ "create_handle() ->\n"
+ " H1 = qlc:sort([{192.0,1,a},{192.0,2,b},{192.0,3,c}]),\n"
+ " qlc:q([{X, Y} || {B,X,_} <- H1,\n"
+ " B =:= 192.0,\n"
+ " {Y} <- [{0},{1},{2}],\n"
+ " X == Y]).\n",
+ "\n",
+ "lookup_handle() ->\n"
+ " E = qlc_SUITE:table([{1,a},{2,b},{3,c}], 1, [1]),\n"
+ " qlc:q([{X, Y} || {X,_} <- E,\n"
+ " {Y} <- [{0},{1},{2}],\n"
+ " X =:= Y]).\n">>),
+ ?line {ok, handle} = rpc:call(R12, compile, file,
+ [File, [{outdir,?privdir}]]),
+ ?line {module, handle} = rpc:call(R12, code, load_abs,
+ [filename:rootname(File)]),
+ ?line H = rpc:call(R12, handle, create_handle, []),
+ ?line {module, handle} = code:load_abs(filename:rootname(File)),
+ ?line {block,0,
+ [{match,_,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ {op,_,'=:=',
+ {float,_,192.0},
+ {call,_,{atom,_,element},[{integer,_,1},_]}}]}]}},
+ _,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ {op,_,'=:=',{var,_,'B'},{float,_,192.0}},
+ {op,_,'==',{var,_,'X'},{var,_,'Y'}}]}]}]}
+ = qlc:info(H,{format,abstract_code}),
+ ?line [{1,1},{2,2}] = qlc:e(H),
+ ?line H2 = rpc:call(R12, handle, lookup_handle, []),
+ ?line {qlc,_,[{generate,_,{qlc,_,_,[{join,lookup}]}},_],[]} =
+ qlc:info(H2, {format,debug}),
+ ?line [{1,1},{2,2}] = qlc:e(H2),
+ stop_node(R12);
+ false ->
+ ?line {skipped, "No support for old node"}
+ end.
+-endif.
+
+forward(doc) ->
+ "";
+forward(suite) -> [];
+forward(Config) when is_list(Config) ->
+ Ts = [
+ %% LC_fun() returns something unknown.
+ <<"FakeH = {qlc_handle,{qlc_lc,fun() -> {foo,bar} end,
+ {qlc_opt,false,false,-1,any,[],any,524288}}},
+ {'EXIT', {{unsupported_qlc_handle,_},_}} = (catch qlc:e(FakeH))">>,
+
+%% 'f1' should be used for new stuff that does not interfer with old behavior
+% %% The unused element 'f1' of #qlc_table seems to be used.
+% <<"DF = fun() -> foo end,
+% FakeH = {qlc_handle,{qlc_table,DF,
+% true,DF,DF,DF,DF,DF,
+% undefined,not_undefined,undefined,no_match_spec}},
+% {'EXIT', {{unsupported_qlc_handle,_},_}} = (catch qlc:e(FakeH))">>,
+
+ %% #qlc_opt has changed.
+ <<"H = qlc:q([X || X <- []]),
+ {qlc_handle, {qlc_lc, Fun, _Opt}} = H,
+ FakeH = {qlc_handle, {qlc_lc, Fun, {new_qlc_opt, a,b,c}}},
+ {'EXIT', {{unsupported_qlc_handle,_},_}} = (catch qlc:e(FakeH))">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+bad_table_throw(Tab) ->
+ Limit = 1,
+ Select = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ PreFun = fun(_) -> throw({throw,bad_pre_fun}) end,
+ PostFun = fun() -> throw({throw,bad_post_fun}) end,
+ InfoFun = fun(Tag) -> info(Tab, Tag) end,
+ qlc:table(Select, [{pre_fun,PreFun}, {post_fun, PostFun},
+ {info_fun, InfoFun}]).
+
+bad_table_exit(Tab) ->
+ Limit = 1,
+ Select = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ PreFun = fun(_) -> erlang:error(bad_pre_fun) end,
+ PostFun = fun() -> erlang:error(bad_post_fun) end,
+ InfoFun = fun(Tag) -> info(Tab, Tag) end,
+ qlc:table(Select, [{pre_fun,PreFun}, {post_fun, PostFun},
+ {info_fun, InfoFun}]).
+
+info(_Tab, is_unique_objects) ->
+ false;
+info(Tab, Tag) ->
+ try ets:info(Tab, Tag) catch _:_ -> undefined end.
+
+create_ets(S, E) ->
+ create_ets(lists:seq(S, E)).
+
+create_ets(L) ->
+ E1 = ets:new(e, []),
+ true = ets:insert(E1, [{X,X} || X <- L]),
+ E1.
+
+etsc(F, Objs) ->
+ etsc(F, [{keypos,1}], Objs).
+
+etsc(F, Opts, Objs) ->
+ E = ets:new(test, Opts),
+ true = ets:insert(E, Objs),
+ V = F(E),
+ ets:delete(E),
+ V.
+
+join_info(H) ->
+ {qlc, S, Options} = strip_qlc_call(H),
+ %% "Hide" the call to qlc_pt from the test in run_test().
+ LoadedPT = code:is_loaded(qlc_pt),
+ QH = qlc:string_to_handle(S, Options),
+ _ = [unload_pt() || false <- [LoadedPT]], % doesn't take long...
+ case {join_info_count(H), join_info_count(QH)} of
+ {N, N} ->
+ N;
+ Ns ->
+ Ns
+ end.
+
+strip_qlc_call(H) ->
+ S = qlc:info(H, {flat, false}),
+ {ok, Tokens, _EndLine} = erl_scan:string(S++"."),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ case Expr of
+ {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC]} ->
+ {qlc, lists:flatten([erl_pp:expr(LC), "."]), []};
+ {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC, Opts]} ->
+ {qlc, lists:flatten([erl_pp:expr(LC), "."]),
+ erl_parse:normalise(Opts)};
+ {call,_,{remote,_,{atom,_,ets},{atom,_,match_spec_run}},_} ->
+ {match_spec, Expr};
+ {call,_,{remote,_,{atom,_,M},{atom,_,table}},_} ->
+ {table, M, Expr};
+ _ ->
+ []
+ end.
+
+-record(ji, {nmerge = 0, nlookup = 0, nnested_loop = 0, nkeysort = 0}).
+
+%% Counts join options and (all) calls to qlc:keysort().
+join_info_count(H) ->
+ S = qlc:info(H, {flat, false}),
+ {ok, Tokens, _EndLine} = erl_scan:string(S++"."),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ #ji{nmerge = Nmerge, nlookup = Nlookup,
+ nkeysort = NKeysort, nnested_loop = Nnested_loop} =
+ ji(Expr, #ji{}),
+ {Nmerge, Nlookup, Nnested_loop, NKeysort}.
+
+ji({call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC,Options]}, JI) ->
+ NJI = case lists:keysearch(join, 1, erl_parse:normalise(Options)) of
+ {value, {join, merge}} ->
+ JI#ji{nmerge = JI#ji.nmerge + 1};
+ {value, {join, lookup}} ->
+ JI#ji{nlookup = JI#ji.nlookup + 1};
+ {value, {join, nested_loop}} ->
+ JI#ji{nnested_loop = JI#ji.nnested_loop + 1};
+ _ ->
+ JI
+ end,
+ ji(LC, NJI);
+ji({call,_,{remote,_,{atom,_,qlc},{atom,_,keysort}},[_KP,H,_Options]}, JI) ->
+ ji(H, JI#ji{nkeysort = JI#ji.nkeysort + 1});
+ji(T, JI) when is_tuple(T) ->
+ ji(tuple_to_list(T), JI);
+ji([E | Es], JI) ->
+ ji(Es, ji(E, JI));
+ji(_, JI) ->
+ JI.
+
+%% Designed for ETS' and gb_table's format funs.
+lookup_keys(Q) ->
+ case lists:flatten(lookup_keys(i(Q), [])) of
+ [] -> false;
+ L -> lists:usort(L)
+ end.
+
+lookup_keys([Q | Qs], L) ->
+ lookup_keys(Qs, lookup_keys(Q, L));
+lookup_keys({qlc,_,Quals,_}, L) ->
+ lookup_keys(Quals, L);
+lookup_keys({list,Q,_}, L) ->
+ lookup_keys(Q, L);
+lookup_keys({generate,_,Q}, L) ->
+ lookup_keys(Q, L);
+lookup_keys({table,Chars}, L) when is_list(Chars) ->
+ {ok, Tokens, _} = erl_scan:string(lists:flatten(Chars++".")),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ case Expr of
+ {call,_,_,[_fun,AKs]} ->
+ case erl_parse:normalise(AKs) of
+ Ks when is_list(Ks) ->
+ [lists:sort(Ks) | L];
+ K -> % assume keys are never lists (ets only)
+ [K | L]
+ end;
+ _ -> % gb_table
+ L
+ end;
+lookup_keys(_Q, L) ->
+ L.
+
+bad_table_format(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ FormatFun = {is, no, good},
+ qlc:table(SelectFun, [{format_fun, FormatFun}]).
+
+bad_table_format_arity(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ FormatFun = fun() -> {?MODULE, bad_table_format_arity, [Tab]} end,
+ qlc:table(SelectFun, [{format_fun, FormatFun}]).
+
+bad_table_traverse(Tab) ->
+ Limit = 1,
+ Select = fun(MS, _) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(Select, []).
+
+bad_table_post(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, [{pre_fun,undefined},
+ {post_fun, fun(X) -> X end},
+ {info_fun, undefined}]).
+
+bad_table_lookup(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, {lookup_fun, fun(X) -> X end}).
+
+bad_table_max_lookup(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, {max_lookup, -2}).
+
+bad_table_info_arity(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ InfoFun = fun() -> {?MODULE, bad_table_info_arity, [Tab]} end,
+ qlc:table(SelectFun, [{info_fun, InfoFun}]).
+
+default_table(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, [{format_fun, undefined},
+ {info_fun, undefined},
+ {lookup_fun, undefined},
+ {parent_fun, undefined},
+ {pre_fun,undefined},
+ {post_fun, undefined}]).
+
+bad_table(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, [{info, fun() -> ok end}]).
+
+bad_table_info_fun_n_objects(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ LookupFun = fun(_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K) end, Ks)
+ end,
+ InfoFun = fun(num_of_objects) -> exit(finito);
+ (_) -> undefined
+ end,
+ qlc:table(SelectFun, [{info_fun, InfoFun}, {lookup_fun, LookupFun}]).
+
+bad_table_info_fun_indices(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ LookupFun = fun(_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K) end, Ks)
+ end,
+ InfoFun = fun(indices) -> throw({throw,apa});
+ (_) -> undefined
+ end,
+ qlc:table(SelectFun, [{info_fun, InfoFun}, {lookup_fun, LookupFun}]).
+
+bad_table_info_fun_keypos(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ LookupFun = fun(_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K) end, Ks)
+ end,
+ InfoFun = fun(indices) -> erlang:error(keypos);
+ (_) -> undefined
+ end,
+ qlc:table(SelectFun, [{info_fun, InfoFun}, {lookup_fun, LookupFun}]).
+
+bad_table_key_equality(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ LookupFun = fun(_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K) end, Ks)
+ end,
+ qlc:table(SelectFun, [{lookup_fun, LookupFun},{key_equality,'=/='}]).
+
+cb('$end_of_table') ->
+ [];
+cb({Objects,Cont}) ->
+ Objects ++ fun() -> cb(ets:select(Cont)) end.
+
+i(H) ->
+ i(H, []).
+
+i(H, Options) when is_list(Options) ->
+ case has_format(Options) of
+ true -> qlc:info(H, Options);
+ false -> qlc:info(H, [{format, debug} | Options])
+ end;
+i(H, Option) ->
+ i(H, [Option]).
+
+has_format({format,_}) ->
+ true;
+has_format([E | Es]) ->
+ has_format(E) or has_format(Es);
+has_format(_) ->
+ false.
+
+format_info(H, Flat) ->
+ L = qlc:info(H, [{flat, Flat}, {format,string}]),
+ re:replace(L, "\s|\n|\t|\f|\r|\v", "", [{return,list},global]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% A list turned into a table...
+
+table_kill_parent(List, Indices) ->
+ ParentFun = fun() -> exit(self(), kill) end,
+ table_i(List, Indices, ParentFun).
+
+table_parent_throws(List, Indices) ->
+ ParentFun = fun() -> throw({throw,thrown}) end,
+ table_i(List, Indices, ParentFun).
+
+table_parent_exits(List, Indices) ->
+ ParentFun = fun() -> 1 + Indices end,
+ table_i(List, Indices, ParentFun).
+
+table_bad_parent_fun(List, Indices) ->
+ ParentFun = fun(X) -> X end, % parent_fun should be nullary
+ table_i(List, Indices, ParentFun).
+
+table(List, Indices) ->
+ ParentFun = fun() -> self() end,
+ table_i(List, Indices, ParentFun).
+
+table(List, KeyPos, Indices) ->
+ ParentFun = fun() -> self() end,
+ table(List, Indices, KeyPos, ParentFun).
+
+table_i(List, Indices, ParentFun) ->
+ table(List, Indices, undefined, ParentFun).
+
+table(List, Indices, KeyPos, ParentFun) ->
+ TraverseFun = fun() -> list_traverse(List) end,
+ PreFun = fun(PreArgs) ->
+ {value, {parent_value, Pid}} =
+ lists:keysearch(parent_value, 1, PreArgs),
+ true = is_pid(Pid)
+ end,
+ PostFun = fun() -> ok end,
+ InfoFun = fun(indices) ->
+ Indices;
+ (is_unique_objects) ->
+ undefined;
+ (keypos) ->
+ KeyPos;
+ (num_of_objects) ->
+ undefined;
+ (_) ->
+ undefined
+ end,
+ LookupFun =
+ fun(Column, Values) ->
+ lists:flatmap(fun(V) ->
+ case lists:keysearch(V, Column, List) of
+ false -> [];
+ {value,Val} -> [Val]
+ end
+ end, Values)
+
+ end,
+ FormatFun = fun(all) ->
+ L = 17,
+ {call,L,{remote,L,{atom,1,?MODULE},{atom,L,the_list}},
+ [erl_parse:abstract(List, 17)]};
+ ({lookup, Column, Values}) ->
+ {?MODULE, list_keys, [Values, Column, List]}
+ end,
+ qlc:table(TraverseFun, [{info_fun,InfoFun}, {pre_fun, PreFun},
+ {post_fun, PostFun}, {lookup_fun, LookupFun},
+ {format_fun, FormatFun},
+ {parent_fun, ParentFun}]).
+
+stop_list(List, Ets) ->
+ Traverse = fun() -> list_traverse(List) end,
+ PV = a_sample_parent_value,
+ ParentFun = fun() -> PV end,
+ Pre = fun(PreArgs) ->
+ {value, {parent_value, PV}} =
+ lists:keysearch(parent_value, 1, PreArgs),
+ {value, {stop_fun, Fun}} =
+ lists:keysearch(stop_fun, 1, PreArgs),
+ true = ets:insert(Ets, {stop_fun, Fun})
+ end,
+ qlc:table(Traverse, [{pre_fun, Pre}, {parent_fun, ParentFun}]).
+
+list_traverse([]) ->
+ [];
+list_traverse([E | Es]) ->
+ [E | fun() -> list_traverse(Es) end].
+
+table_error(List, Error) ->
+ table_error(List, undefined, Error).
+
+table_error(List, KeyPos, Error) ->
+ TraverseFun = fun() -> list_traverse2(lists:sort(List), Error) end,
+ InfoFun = fun(is_sorted_key) -> true;
+ (keypos) -> KeyPos;
+ (_) -> undefined
+ end,
+ qlc:table(TraverseFun, [{info_fun,InfoFun}]).
+
+list_traverse2([], Err) ->
+ Err;
+list_traverse2([E | Es], Err) ->
+ [E | fun() -> list_traverse2(Es, Err) end].
+
+table_lookup_error(List) ->
+ TraverseFun = fun() -> list_traverse(List) end,
+ LookupFun = fun(_Column, _Values) -> {error,lookup,failed} end,
+ InfoFun = fun(keypos) -> 1;
+ (_) -> undefined
+ end,
+ qlc:table(TraverseFun, [{lookup_fun,LookupFun},{info_fun,InfoFun}]).
+
+prep_scratchdir(Dir) ->
+ put('$qlc_tmpdir', true),
+ _ = filelib:ensure_dir(Dir),
+ lists:foreach(fun(F) -> file:delete(F)
+ end, filelib:wildcard(filename:join(Dir, "*"))),
+ true.
+
+%% Truncate just once.
+truncate_tmpfile(Dir, Where) ->
+ case get('$qlc_tmpdir') of
+ true ->
+ {ok, [TmpFile0 | _]} = file:list_dir(Dir),
+ TmpFile = filename:join(Dir, TmpFile0),
+ truncate(TmpFile, Where),
+ erase('$qlc_tmpdir');
+ _ ->
+ true
+ end.
+
+truncate(File, Where) ->
+ {ok, Fd} = file:open(File, [read, write]),
+ {ok, _} = file:position(Fd, Where),
+ ok = file:truncate(Fd),
+ ok = file:close(Fd).
+
+%% Crash just once.
+crash_tmpfile(Dir, Where) ->
+ case get('$qlc_tmpdir') of
+ true ->
+ {ok, [TmpFile0 | _]} = file:list_dir(Dir),
+ TmpFile = filename:join(Dir, TmpFile0),
+ crash(TmpFile, Where),
+ erase('$qlc_tmpdir');
+ _ ->
+ true
+ end.
+
+crash(File, Where) ->
+ {ok, Fd} = file:open(File, [read, write]),
+ {ok, _} = file:position(Fd, Where),
+ ok = file:write(Fd, [10]),
+ ok = file:close(Fd).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+run(Config, Tests) ->
+ run(Config, [], Tests).
+
+run(Config, Extra, Tests) ->
+ lists:foreach(fun(Body) -> run_test(Config, Extra, Body) end, Tests).
+
+run_test(Config, Extra, {cres, Body, ExpectedCompileReturn}) ->
+ run_test(Config, Extra, {cres, Body, _Opts = [], ExpectedCompileReturn});
+run_test(Config, Extra, {cres, Body, Opts, ExpectedCompileReturn}) ->
+ {SourceFile, Mod} = compile_file_mod(Config),
+ P = [Extra,<<"function() -> ">>, Body, <<", ok. ">>],
+ CompileReturn = compile_file(Config, P, Opts),
+ case comp_compare(ExpectedCompileReturn, CompileReturn) of
+ true -> ok;
+ false -> expected(ExpectedCompileReturn, CompileReturn, SourceFile)
+ end,
+ AbsFile = filename:rootname(SourceFile, ".erl"),
+ _ = code:purge(Mod),
+ {module, _} = code:load_abs(AbsFile, Mod),
+
+ Ms0 = erlang:process_info(self(),messages),
+ Before = {get(), pps(), ets:all(), Ms0},
+
+ %% Prepare the check that the qlc module does not call qlc_pt.
+ _ = [unload_pt() || {file, Name} <- [code:is_loaded(qlc_pt)],
+ Name =/= cover_compiled],
+
+ R = case catch Mod:function() of
+ {'EXIT', _Reason} = Error ->
+ ?t:format("failed, got ~p~n", [Error]),
+ fail(SourceFile);
+ Reply ->
+ Reply
+ end,
+
+ %% Check that the qlc module does not call qlc_pt:
+ case code:is_loaded(qlc_pt) of
+ {file, cover_compiled} ->
+ ok;
+ {file, _} ->
+ ?t:format("qlc_pt was loaded in runtime~n", []),
+ fail(SourceFile);
+ false ->
+ ok
+ end,
+
+ Ms = erlang:process_info(self(),messages),
+ After = {get(), pps(), ets:all(), Ms},
+ code:purge(Mod),
+ case {R, After} of
+ {ok, Before} -> ok;
+ _ -> expected({ok,Before}, {R,After}, SourceFile)
+ end;
+run_test(Config, Extra, Body) ->
+ run_test(Config, Extra, {cres,Body,[]}).
+
+unload_pt() ->
+ erlang:garbage_collect(), % get rid of references to qlc_pt...
+ _ = code:purge(qlc_pt),
+ _ = code:delete(qlc_pt).
+
+compile_format(Config, Tests) ->
+ Fun = fun(Test, Opts) ->
+ Return = compile_file(Config, Test, Opts),
+ format_messages(Return)
+ end,
+ compile(Config, Tests, Fun).
+
+format_messages({warnings,Ws}) ->
+ format_messages({errors,[],Ws});
+format_messages({errors,Es,Ws}) ->
+ {[format_msg(E, Mod) || {_Line,Mod,E} <- Es],
+ [format_msg(W, Mod) || {_Line,Mod,W} <- Ws]}.
+
+format_msg(Msg, Mod) ->
+ IOlist = Mod:format_error(Msg),
+ binary_to_list(iolist_to_binary(IOlist)).
+
+compile(Config, Tests) ->
+ Fun = fun(Test, Opts) -> catch compile_file(Config, Test, Opts) end,
+ compile(Config, Tests, Fun).
+
+compile(Config, Tests, Fun) ->
+ F = fun({TestName,Test,Opts,Expected}, BadL) ->
+ Return = Fun(Test, Opts),
+ case comp_compare(Expected, Return) of
+ true ->
+ BadL;
+ false ->
+ {File, _Mod} = compile_file_mod(Config),
+ expected(TestName, Expected, Return, File)
+ end
+ end,
+ lists:foldl(F, [], Tests).
+
+%% Compiles a test module and returns the list of errors and warnings.
+
+compile_file(Config, Test0, Opts0) ->
+ {File, Mod} = compile_file_mod(Config),
+ Test = list_to_binary(["-module(", atom_to_list(Mod), "). "
+ "-compile(export_all). "
+ "-import(qlc_SUITE, [i/1,i/2,format_info/2]). "
+ "-import(qlc_SUITE, [etsc/2, etsc/3]). "
+ "-import(qlc_SUITE, [create_ets/2]). "
+ "-import(qlc_SUITE, [strip_qlc_call/1]). "
+ "-import(qlc_SUITE, [join_info/1]). "
+ "-import(qlc_SUITE, [join_info_count/1]). "
+ "-import(qlc_SUITE, [lookup_keys/1]). "
+ "-include_lib(\"stdlib/include/qlc.hrl\"). ",
+ Test0]),
+ Opts = [export_all,return,nowarn_unused_record,{outdir,?privdir}|Opts0],
+ ok = file:write_file(File, Test),
+ case compile:file(File, Opts) of
+ {ok, _M, Ws} -> warnings(File, Ws);
+ {error, [{File,Es}], []} -> {errors, Es, []};
+ {error, [{File,Es}], [{File,Ws}]} -> {error, Es, Ws}
+ end.
+
+comp_compare(T, T) ->
+ true;
+comp_compare(T1, T2_0) ->
+ T2 = wskip(T2_0),
+ T1 =:= T2
+ %% This clause should eventually be removed.
+ orelse ln(T1) =:= T2 orelse T1 =:= ln(T2).
+
+wskip([]) ->
+ [];
+wskip([{_,sys_core_fold,{eval_failure,badarg}}|L]) ->
+ wskip(L);
+wskip([{{L,_C},sys_core_fold,M}|L]) ->
+ [{L,sys_core_fold,M}|wskip(L)];
+wskip({T,L}) ->
+ {T,wskip(L)};
+wskip([M|L]) ->
+ [M|wskip(L)];
+wskip(T) ->
+ T.
+
+%% Replaces locations like {Line,Column} with Line.
+ln({warnings,L}) ->
+ {warnings,ln0(L)};
+ln({errors,EL,WL}) ->
+ {errors,ln0(EL),ln0(WL)};
+ln(L) ->
+ ln0(L).
+
+ln0(L) ->
+ lists:sort(ln1(L)).
+
+ln1([]) ->
+ [];
+ln1([{File,Ms}|MsL]) when is_list(File) ->
+ [{File,ln0(Ms)}|ln1(MsL)];
+ln1([{{L,_C},Mod,Mess0}|Ms]) ->
+ Mess = case Mess0 of
+ {exported_var,V,{Where,{L1,_C1}}} ->
+ {exported_var,V,{Where,L1}};
+ {unsafe_var,V,{Where,{L1,_C1}}} ->
+ {unsafe_var,V,{Where,L1}};
+ %% There are more...
+ M ->
+ M
+ end,
+ [{L,Mod,Mess}|ln1(Ms)];
+ln1([M|Ms]) ->
+ [M|ln1(Ms)].
+
+%% -> {FileName, Module}; {string(), atom()}
+compile_file_mod(Config) ->
+ NameL = lists:concat([?TESTMODULE, "_", ?testcase]),
+ Name = list_to_atom(NameL),
+ File = filename(NameL ++ ".erl", Config),
+ {File, Name}.
+
+filename(Name, Config) when is_atom(Name) ->
+ filename(atom_to_list(Name), Config);
+filename(Name, Config) ->
+ filename:join(?privdir, Name).
+
+pps() ->
+ {port_list(), process_list()}.
+
+port_list() ->
+ [{P,safe_second_element(erlang:port_info(P, name))} ||
+ P <- erlang:ports()].
+
+process_list() ->
+ [{P,process_info(P, registered_name),
+ safe_second_element(process_info(P, initial_call))} ||
+ P <- processes(), is_process_alive(P)].
+
+safe_second_element({_,Info}) -> Info;
+safe_second_element(Other) -> Other.
+
+warnings(File, Ws) ->
+ case lists:append([W || {F, W} <- Ws, F =:= File]) of
+ [] -> [];
+ L -> {warnings, L}
+ end.
+
+expected(Test, Expected, Got, File) ->
+ ?t:format("~nTest ~p failed. ", [Test]),
+ expected(Expected, Got, File).
+
+expected(Expected, Got, File) ->
+ ?t:format("Expected~n ~p~n, but got~n ~p~n", [Expected, Got]),
+ fail(File).
+
+fail(Source) ->
+ io:format("failed~n"),
+ ?t:fail({failed,testcase,on,Source}).
+
+%% Copied from global_SUITE.erl.
+
+start_node_rel(Name, Rel, How) ->
+ {Release, Compat} = case Rel of
+ this ->
+ {[this], "+R8"};
+ Rel when is_atom(Rel) ->
+ {[{release, atom_to_list(Rel)}], ""};
+ RelList ->
+ {RelList, ""}
+ end,
+ ?line Pa = filename:dirname(code:which(?MODULE)),
+ ?line Res = test_server:start_node(Name, How,
+ [{args,
+ Compat ++
+ " -kernel net_setuptime 100 "
+ " -pa " ++ Pa},
+ {erl, Release}]),
+ Res.
+
+stop_node(Node) ->
+ ?line ?t:stop_node(Node).
+
+install_error_logger() ->
+ error_logger:add_report_handler(?MODULE, self()).
+
+uninstall_error_logger() ->
+ error_logger:delete_report_handler(?MODULE).
+
+read_error_logger() ->
+ receive
+ {error, Why} ->
+ {error, Why};
+ {info, Why} ->
+ {info, Why};
+ {error, Pid, Tuple} ->
+ {error, Pid, Tuple}
+ after 1000 ->
+ ?line io:format("No reply after 1 s\n", []),
+ ?line ?t:fail()
+ end.
+
+%%-----------------------------------------------------------------
+%% The error_logger handler used.
+%% (Copied from stdlib/test/proc_lib_SUITE.erl.)
+%%-----------------------------------------------------------------
+init(Tester) ->
+ {ok, Tester}.
+
+handle_event({error, _GL, {_Pid, _Msg, [Why, _]}}, Tester)
+ when is_atom(Why) ->
+ Tester ! {error, Why},
+ {ok, Tester};
+handle_event({error, _GL, {_Pid, _Msg, [P, T]}}, Tester) when is_pid(P) ->
+ Tester ! {error, P, T},
+ {ok, Tester};
+handle_event({info_msg, _GL, {_Pid, _Msg, [Why, _]}}, Tester) ->
+ Tester ! {info, Why},
+ {ok, Tester};
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(_, State) ->
+ {ok, State}.
+
+handle_call(_Query, State) -> {ok, {error, bad_query}, State}.
+
+terminate(_Reason, State) ->
+ State.