diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/stdlib/test/qlc_SUITE.erl | |
download | otp-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.erl | 8179 |
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. |