%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2000-2010. 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% %% -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]). -define(fmt(A,B),io:format(A,B)). -else. -include("test_server.hrl"). -define(fmt(A,B),test_server:format(A,B)). -endif. -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/1,select_test/1,init_per_testcase/2, fin_per_testcase/2, return_values/1]). init_per_testcase(_Case, Config) when is_list(Config) -> ?line Dog=test_server:timetrap(test_server:seconds(1200)), [{watchdog, Dog}|Config]. fin_per_testcase(_Case, Config) -> Dog=?config(watchdog, Config), test_server:timetrap_cancel(Dog), ok. all(doc) -> ["Test ets:select"]; all(suite) -> [return_values, select_test]. select_test(suite) -> []; select_test(doc) -> ["Tests select in numerous ways"]; select_test(Config) when is_list(Config) -> do_test(Config). return_values(suite) -> []; return_values(doc) -> ["Tests 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(?config(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 = ?config(priv_dir,Config), (catch file:make_dir(WriteDir)), Seed = case file:consult(filename:join([ReadDir, "preset_random_seed2.txt"])) of {ok,[X]} -> X; _ -> {A,B,C} = erlang:now(), random:seed(A,B,C), get(random_seed) end, put(random_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(random:uniform(N),Type). create_pb_key(N,list) -> X = random:uniform(N), case random: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 = random:uniform(N), case random: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 = random:uniform(N), case random: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) -> ?line Key = create_random_key(num_els(Tab),Type), ?line MS = [{{Key,'_','_','_','_'},[],['$_']}], MF = fun({Key0,A,B,F,Bi},Acc) -> case Key =:= Key0 of true -> [{Key0,A,B,F,Bi} | Acc]; _ -> Acc end end, ?line cmp_ms_to_fun(Tab,MS,MF,[]) end, Tabs) end, ?line do_n(50,Fun), ok. basic_pb_key(Tabs,Type) -> InnerFun = fun(Tab) -> ?line {Key,KeyFun} = create_pb_key(num_els(Tab),Type), ?line MS = [{{Key,'_','_','_','_'},[],['$_']}], MF = fun({Key0,A,B,F,Bi},Acc) -> case KeyFun(Key0) of true -> [{Key0,A,B,F,Bi} | Acc]; _ -> Acc end end, ?line cmp_ms_to_fun(Tab,MS,MF,[]) end, ?line {Etses, Detses} = split_by_type(Tabs), ?line FunEts = fun() -> ?line lists:foreach(InnerFun, Etses) end, ?line FunDets = fun() -> ?line lists:foreach(InnerFun, Detses) end, ?line do_n(table_factor(hd(Etses)) div 2,FunEts), ?line do_n(10,FunDets), ok. double_pb_key(Tabs,Type) -> InnerFun = fun(Tab) -> ?line {KeyA,KeyFunA} = create_pb_key(num_els(Tab),Type), ?line {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, ?line cmp_ms_to_fun(Tab,MS,MF,[]) end, ?line {Etses, Detses} = split_by_type(Tabs), ?line FunEts = fun() -> ?line lists:foreach(InnerFun, Etses) end, ?line FunDets = fun() -> ?line lists:foreach(InnerFun, Detses) end, ?line do_n(table_factor(hd(Etses)) div 2,FunEts), ?line do_n(10,FunDets), ok. multi_key(Tabs,Type) -> Fun = fun() -> lists:map(fun(Tab) -> ?line KeyA = create_random_key(num_els(Tab),Type), ?line KeyB = create_random_key(num_els(Tab),Type), ?line KeyC = create_random_key(num_els(Tab),Type), ?line KeyD = create_random_key(num_els(Tab),Type), ?line KeyE = create_random_key(num_els(Tab),Type), ?line KeyF = create_random_key(num_els(Tab),Type), ?line KeyG = create_random_key(num_els(Tab),Type), ?line KeyH = create_random_key(num_els(Tab),Type), ?line KeyI = create_random_key(num_els(Tab),Type), ?line KeyJ = create_random_key(num_els(Tab),Type), ?line KeyK = create_random_key(num_els(Tab),Type), ?line 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, ?line cmp_ms_to_fun(Tab,MS,MF,[]) end, Tabs) end, ?line do_n(33,Fun), ok. multi_mixed_key(Tabs,Type) -> InnerFun = fun(Tab) -> ?line KeyA = create_random_key(num_els(Tab),Type), ?line KeyB = create_random_key(num_els(Tab),Type), ?line KeyC = create_random_key(num_els(Tab),Type), ?line KeyD = create_random_key(num_els(Tab),Type), ?line {KeyE, FunE} = create_pb_key(num_els(Tab),Type), ?line KeyF = create_random_key(num_els(Tab),Type), ?line {KeyG, FunG} = create_pb_key(num_els(Tab),Type), ?line KeyH = create_random_key(num_els(Tab),Type), ?line KeyI = create_random_key(num_els(Tab),Type), ?line {KeyJ, FunJ} = create_pb_key(num_els(Tab),Type), ?line KeyK = create_random_key(num_els(Tab),Type), ?line 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, ?line cmp_ms_to_fun(Tab,MS,MF,[]), ?line case Tab of {ets,_} -> ?line cmp_ms_to_fun(Tab,MS,MF,[],1), ?line cmp_ms_to_fun(Tab,MS,MF,[],10), ?line cmp_ms_to_fun(Tab,MS,MF,[],1000000), ?line cmp_ms_to_fun(Tab,MS,MF,[],-1), ?line cmp_ms_to_fun(Tab,MS,MF,[],-10), ?line cmp_ms_to_fun(Tab,MS,MF,[],-1000000); _ -> ok end end, ?line {Etses, Detses} = split_by_type(Tabs), ?line FunEts = fun() -> ?line lists:foreach(InnerFun, Etses) end, ?line FunDets = fun() -> ?line lists:foreach(InnerFun, Detses) end, ?line do_n(table_factor(hd(Etses)) div 2,FunEts), ?line 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() -> ?line ets:new(xxx,[named_table, ordered_set]), ?line ets:new(yyy,[named_table]), ?line E = fun(0,_)->ok; (N,F) -> ?line ets:insert(xxx,{N,N rem 10}), ?line ets:insert(yyy,{N,N rem 10}), F(N-1,F) end, ?line E(10000,E), ?line G = fun(F,C,A) -> ?line case ets:select(C) of {L,C2} -> ?line F(F,C2,A+length(L)); '$end_of_table' -> ?line A end end, ?line H=fun({L,C}) -> ?line G(G,C,length(L)) end, ?line 1 = H(ets:select(xxx,[{{'$1','$2'},[{'<','$1',2}],['$_']}],7)), ?line 10000 = H(ets:select(xxx,[{{'$1','$2'},[],['$_']}],1)), ?line 1 = H(ets:select(yyy,[{{'$1','$2'},[{'<','$1',2}],['$_']}],7)), ?line 10000 = H(ets:select(yyy,[{{'$1','$2'},[],['$_']}],1)), ?line {[{5,5}],_} = ets:select(xxx,[{{5,'$2'},[],['$_']}],1), ?line {[{5,5}],_} = ets:select(yyy,[{{5,'$2'},[],['$_']}],1), ?line I = fun(_,0) -> ok; (I,N) -> ?line 10000 = H(ets:select(xxx,[{{'$1','$2'},[],['$_']}],N)), I(I,N-1) end, ?line I(I,2000), ?line J = fun(F,C,A) -> ?line case ets:select(C) of {L,C2} -> ?line F(F,C2,lists:reverse(L)++A); '$end_of_table' -> ?line lists:reverse(A) end end, ?line K = fun({L,C}) -> ?line J(J,C,lists:reverse(L)) end, ?line M = fun(_, _, 0) -> ok; (F, What, N) -> ?line What = K(ets:select(xxx,[{{'$1','$2'},[],['$_']}],N)), F(F, What, N-1) end, ?line N = fun(HM) -> ?line What = ets:select(xxx,[{{'$1','$2'},[],['$_']}]), ?line What = lists:sort(What), M(M, What, HM) end, ?line N(2000), ?line ets:delete(xxx), ?line ets:delete(yyy). do_return_values() -> ?line T = ets:new(xxx,[ordered_set]), ?line U = ets:new(xxx,[]), ?line '$end_of_table' = ets:select(T,[{'_',[],['$_']}],1), ?line '$end_of_table' = ets:select(U,[{'_',[],['$_']}],1), ?line ets:insert(T,{ett,1}), ?line ets:insert(U,{ett,1}), ?line {[{ett,1}],C1} = ets:select(T,[{'_',[],['$_']}],1), ?line '$end_of_table' = ets:select(C1), ?line {[{ett,1}],C2} = ets:select(U,[{'_',[],['$_']}],1), ?line '$end_of_table' = ets:select(C2), ?line {[{ett,1}],C3} = ets:select(T,[{'_',[],['$_']}],2), ?line '$end_of_table' = ets:select(C3), ?line {[{ett,1}],C4} = ets:select(U,[{'_',[],['$_']}],2), ?line '$end_of_table' = ets:select(C4), ?line E = fun(0,_)->ok; (N,F) -> ?line ets:insert(T,{N,N rem 10}), ?line ets:insert(U,{N,N rem 10}), F(N-1,F) end, ?line E(10000,E), ?line '$end_of_table' = ets:select(T,[{{hej, hopp},[],['$_']}],1), ?line '$end_of_table' = ets:select(U,[{{hej,hopp},[],['$_']}],1), ?line {[{ett,1}],CC1} = ets:select(T,[{{'$1','_'},[{is_atom, '$1'}], ['$_']}],1), ?line '$end_of_table' = ets:select(CC1), ?line {[{ett,1}],CC2} = ets:select(U,[{{'$1','_'},[{is_atom, '$1'}], ['$_']}],1), ?line '$end_of_table' = ets:select(CC2), ?line {[{ett,1}],CC3} = ets:select(T,[{{'$1','_'},[{is_atom, '$1'}], ['$_']}],2), ?line '$end_of_table' = ets:select(CC3), ?line {[{ett,1}],CC4} = ets:select(U,[{{'$1','_'},[{is_atom, '$1'}], ['$_']}],2), ?line '$end_of_table' = ets:select(CC4), ?line ets:delete(T), ?line ets:delete(U), ?line V = ets:new(xxx,[{keypos, 4}]), ?line X = ets:new(xxx,[ordered_set, {keypos, 4}]), ?line ets:insert(V,{1,1,1,ett}), ?line ets:insert(X,{1,1,1,ett}), ?line '$end_of_table' = ets:select(V,[{{1,1,1},[],['$_']}],1), ?line '$end_of_table' = ets:select(X,[{{1,1,1},[],['$_']}],1), ?line ets:delete(V), ?line ets:delete(X), ok.