%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(select_SUITE).
-author('pan@erix.ericsson.se').
-export([test/0]).
%%
%% Define to run outside of test server
%%
%%-define(STANDALONE,1).
%%
%% Define for debug output
%%
%%-define(debug,1).
-ifdef(STANDALONE).
-define(config(A,B),config(A,B)).
-export([config/2]).
-else.
-include_lib("common_test/include/ct.hrl").
-endif.
-define(fmt(A,B), io:format(A, B)).
-ifdef(debug).
-ifdef(STANDALONE).
-define(line, erlang:display({?MODULE,?LINE}), ).
-endif.
-define(dbgformat(A,B),io:format(A,B)).
-else.
-ifdef(STANDALONE).
-define(line, noop, ).
-endif.
-define(dbgformat(A,B),noop).
-endif.
-ifdef(STANDALONE).
config(priv_dir,_) ->
".".
-else.
%% When run in test server.
-export([all/0, suite/0,
select_test/1, return_values/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
all() ->
[return_values, select_test].
%% Test select in numerous ways.
select_test(Config) when is_list(Config) ->
ct:timetrap({minutes,40}), %% valgrinds needs a lot of time
do_test(Config).
%% Test return values in specific situations for select/3 and select/1.
return_values(Config) when is_list(Config) ->
do_return_values().
-endif.
table_factor({dets,_}) ->
1;
table_factor({ets,_}) ->
100.
gen_dets_filename(Config,N) ->
filename:join(proplists:get_value(priv_dir,Config),
"testdets_" ++ integer_to_list(N) ++ ".dets").
create_tables(Config) ->
Hash = ets:new(xxx, []),
Tree = ets:new(yyy, [ordered_set]),
Bag = ets:new(yyy, [bag]),
DBag = ets:new(yyy, [duplicate_bag]),
F1 = gen_dets_filename(Config,1),
(catch file:delete(F1)),
{ok,DetsPlain} = dets:open_file(testdets_1,
[{file, F1}]),
F3 = gen_dets_filename(Config,3),
(catch file:delete(F3)),
{ok,DetsBag} = dets:open_file(testdets_3,
[{file, F3},{type, bag}]),
F4 = gen_dets_filename(Config,4),
(catch file:delete(F4)),
{ok,DetsDBag} = dets:open_file(testdets_4,
[{file, F4},{type, duplicate_bag}]),
[{ets,Hash}, {ets,Tree}, {ets,Bag}, {ets,DBag},
{dets, DetsPlain}, {dets, DetsBag}, {dets, DetsDBag}].
gen_key(N,list) ->
[N,N+1,N+2];
gen_key(N,tuple) ->
{N,N+1,N+2};
gen_key(N,complex) ->
{[N,N+1],N+2}.
gen_fun(N) ->
fun() ->
N
end.
gen_bin(N) ->
list_to_binary(integer_to_list(N)).
gen_object(N,Type) ->
L = integer_to_list(N),
A = list_to_atom("a" ++ L),
{gen_key(N,Type), L, A, gen_fun(N), gen_bin(N)}.
gen_object1(N,Type) ->
L = integer_to_list(N),
A = list_to_atom("a" ++ L),
{gen_key(N,Type), A, L, gen_fun(N), gen_bin(N)}.
fill_table(_,0,_) ->
ok;
fill_table({Mod,Tab},N,Type) ->
Obj1 = gen_object1(N,Type),
Obj = gen_object(N,Type),
Mod:insert(Tab, Obj1),
case Mod:info(Tab,type) of
bag ->
Mod:insert(Tab, Obj);
duplicate_bag ->
Mod:insert(Tab, Obj),
Mod:insert(Tab, Obj1);
_ ->
ok
end,
fill_table({Mod,Tab},N-1,Type).
table_size(Tab) ->
15 *table_factor(Tab).
build_tables(Config,Type) ->
L = create_tables(Config),
?dbgformat("Tables: ~p~n",[L]),
lists:foreach(fun(TD) ->
fill_table(TD,table_size(TD),Type)
end,
L),
L.
destroy_tables([]) ->
ok;
destroy_tables([{ets,Tab}|T]) ->
ets:delete(Tab),
destroy_tables(T);
destroy_tables([{dets,Tab}|T]) ->
dets:close(Tab),
destroy_tables(T).
init_random(Config) ->
WriteDir = ReadDir = proplists:get_value(priv_dir,Config),
(catch file:make_dir(WriteDir)),
Seed = case file:consult(filename:join([ReadDir,
"preset_random_seed2.txt"])) of
{ok,[X]} ->
X;
_ ->
rand:seed(exsplus),
rand:export_seed()
end,
rand:seed(Seed),
{ok, F} = file:open(filename:join([WriteDir, "last_random_seed2.txt"]),
[write]),
io:format(F,"~p. ~n",[Seed]),
file:close(F),
ok.
create_random_key(N,Type) ->
gen_key(rand:uniform(N),Type).
create_pb_key(N,list) ->
X = rand:uniform(N),
case rand:uniform(4) of
3 -> {[X, X+1, '_'], fun([Z,Z1,P1]) ->
[Z,Z1,P1] =:= [X,X+1,P1] end};
2 -> {[X, '_', '_'], fun([Z,P1,P2]) -> [Z,P1,P2] =:= [X,P1,P2] end};
1 -> {[X, X+1, '$1'], fun([Z,Z1,P1]) ->
[Z,Z1,P1] =:= [X,X+1,P1] end};
_ -> {[X, '$1', '$2'], fun([Z,P1,P2]) -> [Z,P1,P2] =:= [X,P1,P2] end}
end;
create_pb_key(N, tuple) ->
X = rand:uniform(N),
case rand:uniform(2) of
1 -> {{X, X+1, '$1'},fun({Z,Z1,P1}) -> {Z,Z1,P1} =:= {X,X+1,P1} end};
_ -> {{X, '$1', '$2'},fun({Z,P1,P2}) -> {Z,P1,P2} =:= {X,P1,P2} end}
end;
create_pb_key(N, complex) ->
X = rand:uniform(N),
case rand:uniform(2) of
1 -> {{[X, X+1], '$1'}, fun({[Z,Z1],P1}) ->
{[Z,Z1],P1} =:= {[X,X+1],P1} end};
_ -> {{[X, '$1'], '$2'},fun({[Z,P1],P2}) ->
{[Z,P1],P2} =:= {[X,P1],P2} end}
end.
table_foldl(_Fun, Acc,{_Mod,_Tab},'$end_of_table') ->
Acc;
table_foldl(Fun, Acc,{Mod,Tab},Key) ->
Objs = Mod:lookup(Tab,Key),
Acc2 = lists:foldl(Fun,Acc,Objs),
?dbgformat("Objs: ~p, Acc2: ~p~n",[Objs,Acc2]),
table_foldl(Fun, Acc2, {Mod,Tab}, Mod:next(Tab,Key)).
table_foldl(Fun, Acc,{Mod,Tab}) ->
table_foldl(Fun, Acc,{Mod,Tab},Mod:first(Tab)).
chunked_select(Mod,Tab,MS,0) ->
Mod:select(Tab,MS);
chunked_select(Mod,Tab,MS,Chunk) when Chunk > 0->
do_chunk_select(Mod, Mod:select(Tab,MS,Chunk),[]);
chunked_select(Mod,Tab,MS,Chunk) when Chunk < 0->
case Mod of
ets ->
do_chunk_select_reverse(Mod,
Mod:select_reverse(Tab,MS,-Chunk),[]);
_ ->
chunked_select(Mod,Tab,MS,-Chunk)
end.
do_chunk_select_reverse(_Mod, '$end_of_table',Acc) ->
%% OK, all this reversing is only needed for ordered_set, but
%% this is only testcases, right?
erlang:display(did_chunked_select_reverse),
Acc;
do_chunk_select_reverse(Mod, {L,C},Acc) ->
NewAcc = lists:reverse(L)++Acc,
do_chunk_select(Mod, Mod:select(C), NewAcc).
do_chunk_select(_Mod, '$end_of_table',Acc) ->
%% OK, all this reversing is only needed for ordered_set, but
%% this is only testcases, right?
lists:reverse(Acc);
do_chunk_select(Mod, {L,C},Acc) ->
NewAcc = lists:reverse(L)++Acc,
do_chunk_select(Mod, Mod:select(C), NewAcc).
cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2) ->
cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2, 0).
cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2, ChunkSize) ->
MSRes = lists:sort(chunked_select(Mod,Tab,MS,ChunkSize)),
FunRes0 = table_foldl(Fun1,[],{Mod,Tab}),
FunRes = case Fun2 of
F when is_function(F) ->
FunRes1 = table_foldl(F,[],{Mod,Tab}),
lists:merge(FunRes0,FunRes1);
[] ->
lists:sort(FunRes0)
end,
case MSRes =:= FunRes of
true ->
true;
false ->
?fmt("Match_spec result differs from fun result:~n",[]),
?fmt("Parameters: ~p,~p,~p,~p~n",
[{Mod,Tab}, MS, Fun1, Fun2]),
?fmt("Match_spec Result: ~p~n", [MSRes]),
?fmt("Fun Result: ~p~n", [FunRes]),
Info = (catch Mod:info(Tab)),
?fmt("Table info:~p~n", [Info]),
{'EXIT', {hej, ST}} = (catch erlang:error(hej)),
?fmt("Stack backtrace: ~p~n", [ST]),
erlang:error(badmatch)
end.
do_n(0,_) -> ok;
do_n(N,Fun) ->
Fun(),
do_n(N-1,Fun).
%%
%% We want some misses too, so pretend the tables are slightly
%% larger than they really are.
%%
num_els(Tab) ->
16 * table_factor(Tab).
test() ->
do_return_values(),
do_test([]).
do_test(Config) ->
init_random(Config),
whitebox(),
lists:foreach(fun(Type) ->
Tabs = build_tables(Config,Type),
basic_key(Tabs,Type),
?fmt("basic_key done for type ~w~n",[Type]),
basic_pb_key(Tabs,Type),
?fmt("basic_pb_key done for type ~w~n",[Type]),
double_pb_key(Tabs,Type),
?fmt("double_pb_key done for type ~w~n",[Type]),
multi_key(Tabs,Type),
?fmt("multi_key done for type ~w~n",[Type]),
multi_mixed_key(Tabs,Type),
?fmt("multi_mixed_key done for type ~w~n",
[Type]),
destroy_tables(Tabs)
end,
[tuple, list, complex]),
ok.
basic_key(Tabs,Type) ->
Fun = fun() ->
lists:map(fun(Tab) ->
Key =
create_random_key(num_els(Tab),Type),
MS =
[{{Key,'_','_','_','_'},[],['$_']}],
MF = fun({Key0,A,B,F,Bi},Acc) ->
case Key =:= Key0 of
true ->
[{Key0,A,B,F,Bi} |
Acc];
_ ->
Acc
end
end,
cmp_ms_to_fun(Tab,MS,MF,[])
end,
Tabs)
end,
do_n(50,Fun),
ok.
basic_pb_key(Tabs,Type) ->
InnerFun = fun(Tab) ->
{Key,KeyFun} =
create_pb_key(num_els(Tab),Type),
MS = [{{Key,'_','_','_','_'},[],['$_']}],
MF = fun({Key0,A,B,F,Bi},Acc) ->
case KeyFun(Key0) of
true ->
[{Key0,A,B,F,Bi} |
Acc];
_ ->
Acc
end
end,
cmp_ms_to_fun(Tab,MS,MF,[])
end,
{Etses, Detses} = split_by_type(Tabs),
FunEts = fun() ->
lists:foreach(InnerFun,
Etses)
end,
FunDets = fun() ->
lists:foreach(InnerFun,
Detses)
end,
do_n(table_factor(hd(Etses)) div 2,FunEts),
do_n(10,FunDets),
ok.
double_pb_key(Tabs,Type) ->
InnerFun = fun(Tab) ->
{KeyA,KeyFunA} =
create_pb_key(num_els(Tab),Type),
{KeyB,KeyFunB} =
create_pb_key(num_els(Tab),Type),
MS = [{{KeyA,'_','_','_','_'},[],['$_']},
{{KeyB,'_','_','_','_'},[],['$_']}],
?dbgformat("Tab: ~p, MS: ~p~n",
[Tab,MS]),
MF = fun({Key0,A,B,F,Bi},Acc) ->
case KeyFunA(Key0) of
true ->
?dbgformat
("FunMatched:"
" ~p~n",
[{Key0,A,
B,F,Bi}]),
[{Key0,A,B,F,Bi} |
Acc];
_ ->
case KeyFunB(Key0) of
true ->
?dbgformat
("Fun"
"Matched:"
" ~p~n",
[{Key0,A,
B,F,
Bi}]),
[{Key0,A,B,
F,Bi} |
Acc];
_ ->
Acc
end
end
end,
cmp_ms_to_fun(Tab,MS,MF,[])
end,
{Etses, Detses} = split_by_type(Tabs),
FunEts = fun() ->
lists:foreach(InnerFun,
Etses)
end,
FunDets = fun() ->
lists:foreach(InnerFun,
Detses)
end,
do_n(table_factor(hd(Etses)) div 2,FunEts),
do_n(10,FunDets),
ok.
multi_key(Tabs,Type) ->
Fun = fun() ->
lists:map(fun(Tab) ->
KeyA =
create_random_key(num_els(Tab),Type),
KeyB =
create_random_key(num_els(Tab),Type),
KeyC =
create_random_key(num_els(Tab),Type),
KeyD =
create_random_key(num_els(Tab),Type),
KeyE =
create_random_key(num_els(Tab),Type),
KeyF =
create_random_key(num_els(Tab),Type),
KeyG =
create_random_key(num_els(Tab),Type),
KeyH =
create_random_key(num_els(Tab),Type),
KeyI =
create_random_key(num_els(Tab),Type),
KeyJ =
create_random_key(num_els(Tab),Type),
KeyK =
create_random_key(num_els(Tab),Type),
KeyL =
create_random_key(num_els(Tab),Type),
MS = [{{KeyA,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyB,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyC,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyD,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyE,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyF,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyG,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyH,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyI,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyJ,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyK,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyL,'$1','_','$2','_'},[],
[{{'$1','$2'}}]}
],
?dbgformat("Tab: ~p, MS: ~p~n",
[Tab,MS]),
MF = fun({Key0,A,_B,F,_Bi},Acc) ->
case Key0 of
KeyA ->
[ {A,F} |
Acc];
KeyB ->
[ {A,F} |
Acc];
KeyC ->
[ {A,F} |
Acc];
KeyD ->
[ {A,F} |
Acc];
KeyE ->
[ {A,F} |
Acc];
KeyF ->
[ {A,F} |
Acc];
KeyG ->
[ {A,F} |
Acc];
KeyH ->
[ {A,F} |
Acc];
KeyI ->
[ {A,F} |
Acc];
KeyJ ->
[ {A,F} |
Acc];
KeyK ->
[ {A,F} |
Acc];
KeyL ->
[ {A,F} |
Acc];
_ ->
Acc
end
end,
cmp_ms_to_fun(Tab,MS,MF,[])
end,
Tabs)
end,
do_n(33,Fun),
ok.
multi_mixed_key(Tabs,Type) ->
InnerFun = fun(Tab) ->
KeyA =
create_random_key(num_els(Tab),Type),
KeyB =
create_random_key(num_els(Tab),Type),
KeyC =
create_random_key(num_els(Tab),Type),
KeyD =
create_random_key(num_els(Tab),Type),
{KeyE, FunE} =
create_pb_key(num_els(Tab),Type),
KeyF =
create_random_key(num_els(Tab),Type),
{KeyG, FunG} =
create_pb_key(num_els(Tab),Type),
KeyH =
create_random_key(num_els(Tab),Type),
KeyI =
create_random_key(num_els(Tab),Type),
{KeyJ, FunJ} =
create_pb_key(num_els(Tab),Type),
KeyK =
create_random_key(num_els(Tab),Type),
KeyL =
create_random_key(num_els(Tab),Type),
MS = [{{KeyA,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyB,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyC,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyD,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyE,'$100','_','$200','_'},[],
[{{'$100','$200'}}]},
{{KeyF,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyG,'$100','_','$200','_'},[],
[{{'$100','$200'}}]},
{{KeyH,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyI,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyJ,'$100','_','$200','_'},[],
[{{'$100','$200'}}]},
{{KeyK,'$1','_','$2','_'},[],
[{{'$1','$2'}}]},
{{KeyL,'$1','_','$2','_'},[],
[{{'$1','$2'}}]}
],
?dbgformat("Tab: ~p, MS: ~p~n",
[Tab,MS]),
MF = fun({Key0,A,_B,F,_Bi},Acc) ->
case Key0 of
KeyA ->
[ {A,F} |
Acc];
KeyB ->
[ {A,F} |
Acc];
KeyC ->
[ {A,F} |
Acc];
KeyD ->
[ {A,F} |
Acc];
KeyF ->
[ {A,F} |
Acc];
KeyH ->
[ {A,F} |
Acc];
KeyI ->
[ {A,F} |
Acc];
KeyK ->
[ {A,F} |
Acc];
KeyL ->
[ {A,F} |
Acc];
Else ->
case FunE(Else) or
FunG(Else) or
FunJ(Else) of
true ->
[ {A,F} |
Acc];
_ ->
Acc
end
end
end,
cmp_ms_to_fun(Tab,MS,MF,[]),
case Tab of
{ets,_} ->
cmp_ms_to_fun(Tab,MS,MF,[],1),
cmp_ms_to_fun(Tab,MS,MF,[],10),
cmp_ms_to_fun(Tab,MS,MF,[],1000000),
cmp_ms_to_fun(Tab,MS,MF,[],-1),
cmp_ms_to_fun(Tab,MS,MF,[],-10),
cmp_ms_to_fun(Tab,MS,MF,[],-1000000);
_ ->
ok
end
end,
{Etses, Detses} = split_by_type(Tabs),
FunEts = fun() ->
lists:foreach(InnerFun,
Etses)
end,
FunDets = fun() ->
lists:foreach(InnerFun,
Detses)
end,
do_n(table_factor(hd(Etses)) div 2,FunEts),
do_n(table_factor(hd(Detses)) div 2,FunDets),
ok.
split_by_type(List) ->
split_by_type(List,[],[]).
split_by_type([],AccEts,AccDets) ->
{AccEts,AccDets};
split_by_type([{dets,Tab}|T],AccEts,AccDets) ->
split_by_type(T,AccEts,[{dets,Tab}|AccDets]);
split_by_type([{ets,Tab}|T],AccEts,AccDets) ->
split_by_type(T,[{ets,Tab}|AccEts],AccDets).
whitebox() ->
ets:new(xxx,[named_table, ordered_set]),
ets:new(yyy,[named_table]),
E = fun(0,_)->ok;
(N,F) ->
ets:insert(xxx,{N,N rem 10}),
ets:insert(yyy,{N,N rem 10}),
F(N-1,F)
end,
E(10000,E),
G = fun(F,C,A) ->
case ets:select(C) of
{L,C2} ->
F(F,C2,A+length(L));
'$end_of_table' ->
A
end
end,
H=fun({L,C}) ->
G(G,C,length(L))
end,
1 = H(ets:select(xxx,[{{'$1','$2'},[{'<','$1',2}],['$_']}],7)),
10000 = H(ets:select(xxx,[{{'$1','$2'},[],['$_']}],1)),
1 = H(ets:select(yyy,[{{'$1','$2'},[{'<','$1',2}],['$_']}],7)),
10000 = H(ets:select(yyy,[{{'$1','$2'},[],['$_']}],1)),
{[{5,5}],_} = ets:select(xxx,[{{5,'$2'},[],['$_']}],1),
{[{5,5}],_} = ets:select(yyy,[{{5,'$2'},[],['$_']}],1),
I = fun(_,0) ->
ok;
(I,N) ->
10000 =
H(ets:select(xxx,[{{'$1','$2'},[],['$_']}],N)),
I(I,N-1)
end,
I(I,2000),
J = fun(F,C,A) ->
case ets:select(C) of
{L,C2} ->
F(F,C2,lists:reverse(L)++A);
'$end_of_table' ->
lists:reverse(A)
end
end,
K = fun({L,C}) ->
J(J,C,lists:reverse(L))
end,
M = fun(_, _, 0) ->
ok;
(F, What, N) ->
What =
K(ets:select(xxx,[{{'$1','$2'},[],['$_']}],N)),
F(F, What, N-1)
end,
N = fun(HM) ->
What = ets:select(xxx,[{{'$1','$2'},[],['$_']}]),
What = lists:sort(What),
M(M, What, HM)
end,
N(2000),
ets:delete(xxx),
ets:delete(yyy).
do_return_values() ->
T = ets:new(xxx,[ordered_set]),
U = ets:new(xxx,[]),
'$end_of_table' = ets:select(T,[{'_',[],['$_']}],1),
'$end_of_table' = ets:select(U,[{'_',[],['$_']}],1),
ets:insert(T,{ett,1}),
ets:insert(U,{ett,1}),
{[{ett,1}],C1} = ets:select(T,[{'_',[],['$_']}],1),
'$end_of_table' = ets:select(C1),
{[{ett,1}],C2} = ets:select(U,[{'_',[],['$_']}],1),
'$end_of_table' = ets:select(C2),
{[{ett,1}],C3} = ets:select(T,[{'_',[],['$_']}],2),
'$end_of_table' = ets:select(C3),
{[{ett,1}],C4} = ets:select(U,[{'_',[],['$_']}],2),
'$end_of_table' = ets:select(C4),
E = fun(0,_)->ok;
(N,F) ->
ets:insert(T,{N,N rem 10}),
ets:insert(U,{N,N rem 10}),
F(N-1,F)
end,
E(10000,E),
'$end_of_table' = ets:select(T,[{{hej, hopp},[],['$_']}],1),
'$end_of_table' = ets:select(U,[{{hej,hopp},[],['$_']}],1),
{[{ett,1}],CC1} = ets:select(T,[{{'$1','_'},[{is_atom, '$1'}],
['$_']}],1),
'$end_of_table' = ets:select(CC1),
{[{ett,1}],CC2} = ets:select(U,[{{'$1','_'},[{is_atom, '$1'}],
['$_']}],1),
'$end_of_table' = ets:select(CC2),
{[{ett,1}],CC3} = ets:select(T,[{{'$1','_'},[{is_atom, '$1'}],
['$_']}],2),
'$end_of_table' = ets:select(CC3),
{[{ett,1}],CC4} = ets:select(U,[{{'$1','_'},[{is_atom, '$1'}],
['$_']}],2),
'$end_of_table' = ets:select(CC4),
ets:delete(T),
ets:delete(U),
V = ets:new(xxx,[{keypos, 4}]),
X = ets:new(xxx,[ordered_set, {keypos, 4}]),
ets:insert(V,{1,1,1,ett}),
ets:insert(X,{1,1,1,ett}),
'$end_of_table' = ets:select(V,[{{1,1,1},[],['$_']}],1),
'$end_of_table' = ets:select(X,[{{1,1,1},[],['$_']}],1),
ets:delete(V),
ets:delete(X),
ok.