aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/src/ets.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/src/ets.erl')
-rw-r--r--lib/stdlib/src/ets.erl1269
1 files changed, 1269 insertions, 0 deletions
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
new file mode 100644
index 0000000000..9f84e3639f
--- /dev/null
+++ b/lib/stdlib/src/ets.erl
@@ -0,0 +1,1269 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-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%
+%%
+-module(ets).
+
+%% Interface to the Term store BIF's
+%% ets == Erlang Term Store
+
+-export([file2tab/1,
+ file2tab/2,
+ filter/3,
+ foldl/3, foldr/3,
+ match_delete/2,
+ tab2file/2,
+ tab2file/3,
+ tabfile_info/1,
+ from_dets/2,
+ to_dets/2,
+ init_table/2,
+ test_ms/2,
+ tab2list/1,
+ table/1,
+ table/2,
+ fun2ms/1,
+ match_spec_run/2,
+ repair_continuation/2]).
+
+-export([i/0, i/1, i/2, i/3]).
+
+%%------------------------------------------------------------------------------
+
+-type tab() :: atom() | tid().
+
+-type ext_info() :: 'md5sum' | 'object_count'.
+-type protection() :: 'private' | 'protected' | 'public'.
+-type type() :: 'bag' | 'duplicate_bag' | 'ordered_set' | 'set'.
+
+-type table_info() :: {'name', atom()}
+ | {'type', type()}
+ | {'protection', protection()}
+ | {'named_table', boolean()}
+ | {'keypos', non_neg_integer()}
+ | {'size', non_neg_integer()}
+ | {'extended_info', [ext_info()]}
+ | {'version', {non_neg_integer(), non_neg_integer()}}.
+
+%% these ones are also defined in erl_bif_types
+-type match_pattern() :: atom() | tuple().
+-type match_specs() :: [{match_pattern(), [_], [_]}].
+
+%%------------------------------------------------------------------------------
+
+%% The following functions used to be found in this module, but
+%% are now BIFs (i.e. implemented in C).
+%%
+%% all/0
+%% new/2
+%% delete/1
+%% delete/2
+%% first/1
+%% info/1
+%% info/2
+%% safe_fixtable/2
+%% lookup/2
+%% lookup_element/3
+%% insert/2
+%% is_compiled_ms/1
+%% last/1
+%% next/2
+%% prev/2
+%% rename/2
+%% slot/2
+%% match/1
+%% match/2
+%% match/3
+%% match_object/1
+%% match_object/2
+%% match_object/3
+%% match_spec_compile/1
+%% match_spec_run_r/3
+%% select/1
+%% select/2
+%% select/3
+%% select_reverse/1
+%% select_reverse/2
+%% select_reverse/3
+%% select_delete/2
+%% update_counter/3
+%%
+
+-opaque comp_match_spec() :: any(). %% this one is REALLY opaque
+
+-spec match_spec_run([tuple()], comp_match_spec()) -> [term()].
+
+match_spec_run(List, CompiledMS) ->
+ lists:reverse(ets:match_spec_run_r(List, CompiledMS, [])).
+
+-type continuation() :: '$end_of_table'
+ | {tab(),integer(),integer(),binary(),list(),integer()}
+ | {tab(),_,_,integer(),binary(),list(),integer(),integer()}.
+
+-spec repair_continuation(continuation(), match_specs()) -> continuation().
+
+%% $end_of_table is an allowed continuation in ets...
+repair_continuation('$end_of_table', _) ->
+ '$end_of_table';
+%% ordered_set
+repair_continuation(Untouched = {Table,Lastkey,EndCondition,N2,Bin,L2,N3,N4}, MS)
+ when %% (is_atom(Table) or is_integer(Table)),
+ is_integer(N2),
+ byte_size(Bin) =:= 0,
+ is_list(L2),
+ is_integer(N3),
+ is_integer(N4) ->
+ case ets:is_compiled_ms(Bin) of
+ true ->
+ Untouched;
+ false ->
+ {Table,Lastkey,EndCondition,N2,ets:match_spec_compile(MS),L2,N3,N4}
+ end;
+%% set/bag/duplicate_bag
+repair_continuation(Untouched = {Table,N1,N2,Bin,L,N3}, MS)
+ when %% (is_atom(Table) or is_integer(Table)),
+ is_integer(N1),
+ is_integer(N2),
+ byte_size(Bin) =:= 0,
+ is_list(L),
+ is_integer(N3) ->
+ case ets:is_compiled_ms(Bin) of
+ true ->
+ Untouched;
+ false ->
+ {Table,N1,N2,ets:match_spec_compile(MS),L,N3}
+ end.
+
+-spec fun2ms(function()) -> match_specs().
+
+fun2ms(ShellFun) when is_function(ShellFun) ->
+ %% Check that this is really a shell fun...
+ case erl_eval:fun_data(ShellFun) of
+ {fun_data,ImportList,Clauses} ->
+ case ms_transform:transform_from_shell(
+ ?MODULE,Clauses,ImportList) of
+ {error,[{_,[{_,_,Code}|_]}|_],_} ->
+ io:format("Error: ~s~n",
+ [ms_transform:format_error(Code)]),
+ {error,transform_error};
+ Else ->
+ Else
+ end;
+ false ->
+ exit({badarg,{?MODULE,fun2ms,
+ [function,called,with,real,'fun',
+ should,be,transformed,with,
+ parse_transform,'or',called,with,
+ a,'fun',generated,in,the,
+ shell]}})
+ end.
+
+-spec foldl(fun((_, term()) -> term()), term(), tab()) -> term().
+
+foldl(F, Accu, T) ->
+ ets:safe_fixtable(T, true),
+ First = ets:first(T),
+ try
+ do_foldl(F, Accu, First, T)
+ after
+ ets:safe_fixtable(T, false)
+ end.
+
+do_foldl(F, Accu0, Key, T) ->
+ case Key of
+ '$end_of_table' ->
+ Accu0;
+ _ ->
+ do_foldl(F,
+ lists:foldl(F, Accu0, ets:lookup(T, Key)),
+ ets:next(T, Key), T)
+ end.
+
+-spec foldr(fun((_, term()) -> term()), term(), tab()) -> term().
+
+foldr(F, Accu, T) ->
+ ets:safe_fixtable(T, true),
+ Last = ets:last(T),
+ try
+ do_foldr(F, Accu, Last, T)
+ after
+ ets:safe_fixtable(T, false)
+ end.
+
+do_foldr(F, Accu0, Key, T) ->
+ case Key of
+ '$end_of_table' ->
+ Accu0;
+ _ ->
+ do_foldr(F,
+ lists:foldr(F, Accu0, ets:lookup(T, Key)),
+ ets:prev(T, Key), T)
+ end.
+
+-spec from_dets(tab(), dets:tab_name()) -> 'true'.
+
+from_dets(EtsTable, DetsTable) ->
+ case (catch dets:to_ets(DetsTable, EtsTable)) of
+ {error, Reason} ->
+ erlang:error(Reason, [EtsTable,DetsTable]);
+ {'EXIT', {Reason1, _Stack1}} ->
+ erlang:error(Reason1,[EtsTable,DetsTable]);
+ {'EXIT', EReason} ->
+ erlang:error(EReason,[EtsTable,DetsTable]);
+ EtsTable ->
+ true;
+ Unexpected -> %% Dets bug?
+ erlang:error(Unexpected,[EtsTable,DetsTable])
+ end.
+
+-spec to_dets(tab(), dets:tab_name()) -> tab().
+
+to_dets(EtsTable, DetsTable) ->
+ case (catch dets:from_ets(DetsTable, EtsTable)) of
+ {error, Reason} ->
+ erlang:error(Reason, [EtsTable,DetsTable]);
+ {'EXIT', {Reason1, _Stack1}} ->
+ erlang:error(Reason1,[EtsTable,DetsTable]);
+ {'EXIT', EReason} ->
+ erlang:error(EReason,[EtsTable,DetsTable]);
+ ok ->
+ DetsTable;
+ Unexpected -> %% Dets bug?
+ erlang:error(Unexpected,[EtsTable,DetsTable])
+ end.
+
+-spec test_ms(tuple(), match_specs()) ->
+ {'ok', term()} | {'error', [{'warning'|'error', string()}]}.
+
+test_ms(Term, MS) ->
+ case erlang:match_spec_test(Term, MS, table) of
+ {ok, Result, _Flags, _Messages} ->
+ {ok, Result};
+ {error, _Errors} = Error ->
+ Error
+ end.
+
+-spec init_table(tab(), fun(('read' | 'close') -> term())) -> 'true'.
+
+init_table(Table, Fun) ->
+ ets:delete_all_objects(Table),
+ init_table_continue(Table, Fun(read)).
+
+init_table_continue(_Table, end_of_input) ->
+ true;
+init_table_continue(Table, {List, Fun}) when is_list(List), is_function(Fun) ->
+ case (catch init_table_sub(Table, List)) of
+ {'EXIT', Reason} ->
+ (catch Fun(close)),
+ exit(Reason);
+ true ->
+ init_table_continue(Table, Fun(read))
+ end;
+init_table_continue(_Table, Error) ->
+ exit(Error).
+
+init_table_sub(_Table, []) ->
+ true;
+init_table_sub(Table, [H|T]) ->
+ ets:insert(Table, H),
+ init_table_sub(Table, T).
+
+-spec match_delete(tab(), match_pattern()) -> 'true'.
+
+match_delete(Table, Pattern) ->
+ ets:select_delete(Table, [{Pattern,[],[true]}]),
+ true.
+
+%% Produce a list of tuples from a table
+
+-spec tab2list(tab()) -> [tuple()].
+
+tab2list(T) ->
+ ets:match_object(T, '_').
+
+-spec filter(tab(), function(), [term()]) -> [term()].
+
+filter(Tn, F, A) when is_atom(Tn) ; is_integer(Tn) ->
+ do_filter(Tn, ets:first(Tn), F, A, []).
+
+do_filter(_Tab, '$end_of_table', _, _, Ack) ->
+ Ack;
+do_filter(Tab, Key, F, A, Ack) ->
+ case apply(F, [ets:lookup(Tab, Key)|A]) of
+ false ->
+ do_filter(Tab, ets:next(Tab, Key), F, A, Ack);
+ true ->
+ Ack2 = ets:lookup(Tab, Key) ++ Ack,
+ do_filter(Tab, ets:next(Tab, Key), F, A, Ack2);
+ {true, Value} ->
+ do_filter(Tab, ets:next(Tab, Key), F, A, [Value|Ack])
+ end.
+
+
+%% Dump a table to a file using the disk_log facility
+
+%% Options := [Option]
+%% Option := {extended_info,[ExtInfo]}
+%% ExtInfo := object_count | md5sum
+
+-define(MAJOR_F2T_VERSION,1).
+-define(MINOR_F2T_VERSION,0).
+
+-record(filetab_options,
+ {
+ object_count = false :: boolean(),
+ md5sum = false :: boolean()
+ }).
+
+-type fname() :: string() | atom().
+-type t2f_option() :: {'extended_info', [ext_info()]}.
+
+-spec tab2file(tab(), fname()) -> 'ok' | {'error', term()}.
+
+tab2file(Tab, File) ->
+ tab2file(Tab, File, []).
+
+-spec tab2file(tab(), fname(), [t2f_option()]) -> 'ok' | {'error', term()}.
+
+tab2file(Tab, File, Options) ->
+ try
+ {ok, FtOptions} = parse_ft_options(Options),
+ file:delete(File),
+ case file:read_file_info(File) of
+ {error, enoent} -> ok;
+ _ -> throw(eaccess)
+ end,
+ Name = make_ref(),
+ case disk_log:open([{name, Name}, {file, File}]) of
+ {ok, Name} ->
+ ok;
+ {error, Reason} ->
+ throw(Reason)
+ end,
+ try
+ Info0 = case ets:info(Tab) of
+ undefined ->
+ %% erlang:error(badarg, [Tab, File, Options]);
+ throw(badtab);
+ I ->
+ I
+ end,
+ Info = [list_to_tuple(Info0 ++
+ [{major_version,?MAJOR_F2T_VERSION},
+ {minor_version,?MINOR_F2T_VERSION},
+ {extended_info,
+ ft_options_to_list(FtOptions)}])],
+ {LogFun, InitState} =
+ case FtOptions#filetab_options.md5sum of
+ true ->
+ {fun(Oldstate,Termlist) ->
+ {NewState,BinList} =
+ md5terms(Oldstate,Termlist),
+ disk_log:blog_terms(Name,BinList),
+ NewState
+ end,
+ erlang:md5_init()};
+ false ->
+ {fun(_,Termlist) ->
+ disk_log:log_terms(Name,Termlist),
+ true
+ end,
+ true}
+ end,
+ ets:safe_fixtable(Tab,true),
+ {NewState1,Num} = try
+ NewState = LogFun(InitState,Info),
+ dump_file(
+ ets:select(Tab,[{'_',[],['$_']}],100),
+ LogFun, NewState, 0)
+ after
+ (catch ets:safe_fixtable(Tab,false))
+ end,
+ EndInfo =
+ case FtOptions#filetab_options.object_count of
+ true ->
+ [{count,Num}];
+ false ->
+ []
+ end ++
+ case FtOptions#filetab_options.md5sum of
+ true ->
+ [{md5,erlang:md5_final(NewState1)}];
+ false ->
+ []
+ end,
+ case EndInfo of
+ [] ->
+ ok;
+ List ->
+ LogFun(NewState1,[['$end_of_table',List]])
+ end,
+ disk_log:close(Name)
+ catch
+ throw:TReason ->
+ disk_log:close(Name),
+ file:delete(File),
+ throw(TReason);
+ exit:ExReason ->
+ disk_log:close(Name),
+ file:delete(File),
+ exit(ExReason);
+ error:ErReason ->
+ disk_log:close(Name),
+ file:delete(File),
+ erlang:raise(error,ErReason,erlang:get_stacktrace())
+ end
+ catch
+ throw:TReason2 ->
+ {error,TReason2};
+ exit:ExReason2 ->
+ {error,ExReason2}
+ end.
+
+dump_file('$end_of_table', _LogFun, State, Num) ->
+ {State,Num};
+dump_file({Terms, Context}, LogFun, State, Num) ->
+ Count = length(Terms),
+ NewState = LogFun(State, Terms),
+ dump_file(ets:select(Context), LogFun, NewState, Num + Count).
+
+ft_options_to_list(#filetab_options{md5sum = MD5, object_count = PS}) ->
+ case PS of
+ true ->
+ [object_count];
+ _ ->
+ []
+ end ++
+ case MD5 of
+ true ->
+ [md5sum];
+ _ ->
+ []
+ end.
+
+md5terms(State, []) ->
+ {State, []};
+md5terms(State, [H|T]) ->
+ B = term_to_binary(H),
+ NewState = erlang:md5_update(State, B),
+ {FinState, TL} = md5terms(NewState, T),
+ {FinState, [B|TL]}.
+
+parse_ft_options(Options) when is_list(Options) ->
+ {Opt,Rest} = case (catch lists:keytake(extended_info,1,Options)) of
+ false ->
+ {[],Options};
+ {value,{extended_info,L},R} when is_list(L) ->
+ {L,R}
+ end,
+ case Rest of
+ [] ->
+ parse_ft_info_options(#filetab_options{}, Opt);
+ Other ->
+ throw({unknown_option, Other})
+ end;
+parse_ft_options(Malformed) ->
+ throw({malformed_option, Malformed}).
+
+parse_ft_info_options(FtOpt,[]) ->
+ {ok,FtOpt};
+parse_ft_info_options(FtOpt,[object_count | T]) ->
+ parse_ft_info_options(FtOpt#filetab_options{object_count = true}, T);
+parse_ft_info_options(FtOpt,[md5sum | T]) ->
+ parse_ft_info_options(FtOpt#filetab_options{md5sum = true}, T);
+parse_ft_info_options(_,[Unexpected | _]) ->
+ throw({unknown_option,[{extended_info,[Unexpected]}]});
+parse_ft_info_options(_,Malformed) ->
+ throw({malformed_option,Malformed}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Read a dumped file from disk and create a corresponding table
+%% Opts := [Opt]
+%% Opt := {verify,boolean()}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-type f2t_option() :: {'verify', boolean()}.
+
+-spec file2tab(fname()) -> {'ok', tab()} | {'error', term()}.
+
+file2tab(File) ->
+ file2tab(File, []).
+
+-spec file2tab(fname(), [f2t_option()]) -> {'ok', tab()} | {'error', term()}.
+
+file2tab(File, Opts) ->
+ try
+ {ok,Verify} = parse_f2t_opts(Opts,false),
+ Name = make_ref(),
+ {ok, Major, Minor, FtOptions, MD5State, FullHeader, DLContext} =
+ case disk_log:open([{name, Name},
+ {file, File},
+ {mode, read_only}]) of
+ {ok, Name} ->
+ get_header_data(Name,Verify);
+ {repaired, Name, _,_} -> %Uh? cannot happen?
+ case Verify of
+ true ->
+ disk_log:close(Name),
+ throw(badfile);
+ false ->
+ get_header_data(Name,Verify)
+ end;
+ {error, Other1} ->
+ throw({read_error, Other1});
+ Other2 ->
+ throw(Other2)
+ end,
+ try
+ if
+ Major > ?MAJOR_F2T_VERSION ->
+ throw({unsupported_file_version,{Major,Minor}});
+ true ->
+ ok
+ end,
+ {ok, Tab, HeadCount} = create_tab(FullHeader),
+ StrippedOptions =
+ case Verify of
+ true ->
+ FtOptions;
+ false ->
+ #filetab_options{}
+ end,
+ {ReadFun,InitState} =
+ case StrippedOptions#filetab_options.md5sum of
+ true ->
+ {fun({OldMD5State,OldCount,_OL,ODLContext} = OS) ->
+ case wrap_bchunk(Name,ODLContext,100,Verify) of
+ eof ->
+ {OS,[]};
+ {NDLContext,Blist} ->
+ {Termlist, NewMD5State,
+ NewCount,NewLast} =
+ md5_and_convert(Blist,
+ OldMD5State,
+ OldCount),
+ {{NewMD5State, NewCount,
+ NewLast,NDLContext},
+ Termlist}
+ end
+ end,
+ {MD5State,0,[],DLContext}};
+ false ->
+ {fun({_,OldCount,_OL,ODLContext} = OS) ->
+ case wrap_chunk(Name,ODLContext,100,Verify) of
+ eof ->
+ {OS,[]};
+ {NDLContext,List} ->
+ {NewLast,NewCount,NewList} =
+ scan_for_endinfo(List, OldCount),
+ {{false,NewCount,NewLast,NDLContext},
+ NewList}
+ end
+ end,
+ {false,0,[],DLContext}}
+ end,
+ try
+ do_read_and_verify(ReadFun,InitState,Tab,
+ StrippedOptions,HeadCount,Verify)
+ catch
+ throw:TReason ->
+ ets:delete(Tab),
+ throw(TReason);
+ exit:ExReason ->
+ ets:delete(Tab),
+ exit(ExReason);
+ error:ErReason ->
+ ets:delete(Tab),
+ erlang:raise(error,ErReason,erlang:get_stacktrace())
+ end
+ after
+ disk_log:close(Name)
+ end
+ catch
+ throw:TReason2 ->
+ {error,TReason2};
+ exit:ExReason2 ->
+ {error,ExReason2}
+ end.
+
+do_read_and_verify(ReadFun,InitState,Tab,FtOptions,HeadCount,Verify) ->
+ case load_table(ReadFun,InitState,Tab) of
+ {ok,{_,FinalCount,[],_}} ->
+ case {FtOptions#filetab_options.md5sum,
+ FtOptions#filetab_options.object_count} of
+ {false,false} ->
+ case Verify of
+ false ->
+ ok;
+ true ->
+ case FinalCount of
+ HeadCount ->
+ ok;
+ _ ->
+ throw(invalid_object_count)
+ end
+ end;
+ _ ->
+ throw(badfile)
+ end,
+ {ok,Tab};
+ {ok,{FinalMD5State,FinalCount,['$end_of_table',LastInfo],_}} ->
+ ECount = case lists:keysearch(count,1,LastInfo) of
+ {value,{count,N}} ->
+ N;
+ _ ->
+ false
+ end,
+ EMD5 = case lists:keysearch(md5,1,LastInfo) of
+ {value,{md5,M}} ->
+ M;
+ _ ->
+ false
+ end,
+ case FtOptions#filetab_options.md5sum of
+ true ->
+ case erlang:md5_final(FinalMD5State) of
+ EMD5 ->
+ ok;
+ _MD5MisM ->
+ throw(checksum_error)
+ end;
+ false ->
+ ok
+ end,
+ case FtOptions#filetab_options.object_count of
+ true ->
+ case FinalCount of
+ ECount ->
+ ok;
+ _Other ->
+ throw(invalid_object_count)
+ end;
+ false ->
+ %% Only use header count if no extended info
+ %% at all is present and verification is requested.
+ case {Verify,FtOptions#filetab_options.md5sum} of
+ {true,false} ->
+ case FinalCount of
+ HeadCount ->
+ ok;
+ _Other2 ->
+ throw(invalid_object_count)
+ end;
+ _ ->
+ ok
+ end
+ end,
+ {ok,Tab}
+ end.
+
+parse_f2t_opts([],Verify) ->
+ {ok,Verify};
+parse_f2t_opts([{verify, true}|T],_OV) ->
+ parse_f2t_opts(T,true);
+parse_f2t_opts([{verify,false}|T],OV) ->
+ parse_f2t_opts(T,OV);
+parse_f2t_opts([Unexpected|_],_) ->
+ throw({unknown_option,Unexpected});
+parse_f2t_opts(Malformed,_) ->
+ throw({malformed_option,Malformed}).
+
+count_mandatory([]) ->
+ 0;
+count_mandatory([{Tag,_}|T]) when Tag =:= name;
+ Tag =:= type;
+ Tag =:= protection;
+ Tag =:= named_table;
+ Tag =:= keypos;
+ Tag =:= size ->
+ 1+count_mandatory(T);
+count_mandatory([_|T]) ->
+ count_mandatory(T).
+
+verify_header_mandatory(L) ->
+ count_mandatory(L) =:= 6.
+
+wrap_bchunk(Name,C,N,true) ->
+ case disk_log:bchunk(Name,C,N) of
+ {_,_,X} when X > 0 ->
+ throw(badfile);
+ {NC,Bin,_} ->
+ {NC,Bin};
+ Y ->
+ Y
+ end;
+wrap_bchunk(Name,C,N,false) ->
+ case disk_log:bchunk(Name,C,N) of
+ {NC,Bin,_} ->
+ {NC,Bin};
+ Y ->
+ Y
+ end.
+
+wrap_chunk(Name,C,N,true) ->
+ case disk_log:chunk(Name,C,N) of
+ {_,_,X} when X > 0 ->
+ throw(badfile);
+ {NC,TL,_} ->
+ {NC,TL};
+ Y ->
+ Y
+ end;
+wrap_chunk(Name,C,N,false) ->
+ case disk_log:chunk(Name,C,N) of
+ {NC,TL,_} ->
+ {NC,TL};
+ Y ->
+ Y
+ end.
+
+get_header_data(Name,true) ->
+ case wrap_bchunk(Name,start,1,true) of
+ {C,[Bin]} when is_binary(Bin) ->
+ T = binary_to_term(Bin),
+ case T of
+ Tup when is_tuple(Tup) ->
+ L = tuple_to_list(Tup),
+ case verify_header_mandatory(L) of
+ false ->
+ throw(badfile);
+ true ->
+ Major = case lists:keysearch(major,1,L) of
+ {value,{major,Maj}} ->
+ Maj;
+ _ ->
+ 0
+ end,
+ Minor = case lists:keysearch(minor,1,L) of
+ {value,{minor,Min}} ->
+ Min;
+ _ ->
+ 0
+ end,
+ FtOptions =
+ case lists:keysearch(extended_info,1,L) of
+ {value,{extended_info,I}}
+ when is_list(I) ->
+ #filetab_options
+ {
+ object_count =
+ lists:member(object_count,I),
+ md5sum =
+ lists:member(md5sum,I)
+ };
+ _ ->
+ #filetab_options{}
+ end,
+ MD5Initial =
+ case FtOptions#filetab_options.md5sum of
+ true ->
+ X = erlang:md5_init(),
+ erlang:md5_update(X,Bin);
+ false ->
+ false
+ end,
+ {ok, Major, Minor, FtOptions, MD5Initial, L, C}
+ end;
+ _X ->
+ throw(badfile)
+ end;
+ _Y ->
+ throw(badfile)
+ end;
+
+get_header_data(Name, false) ->
+ case wrap_chunk(Name,start,1,false) of
+ {C,[Tup]} when is_tuple(Tup) ->
+ L = tuple_to_list(Tup),
+ case verify_header_mandatory(L) of
+ false ->
+ throw(badfile);
+ true ->
+ Major = case lists:keysearch(major_version,1,L) of
+ {value,{major_version,Maj}} ->
+ Maj;
+ _ ->
+ 0
+ end,
+ Minor = case lists:keysearch(minor_version,1,L) of
+ {value,{minor_version,Min}} ->
+ Min;
+ _ ->
+ 0
+ end,
+ FtOptions =
+ case lists:keysearch(extended_info,1,L) of
+ {value,{extended_info,I}}
+ when is_list(I) ->
+ #filetab_options
+ {
+ object_count =
+ lists:member(object_count,I),
+ md5sum =
+ lists:member(md5sum,I)
+ };
+ _ ->
+ #filetab_options{}
+ end,
+ {ok, Major, Minor, FtOptions, false, L, C}
+ end;
+ _ ->
+ throw(badfile)
+ end.
+
+md5_and_convert([],MD5State,Count) ->
+ {[],MD5State,Count,[]};
+md5_and_convert([H|T],MD5State,Count) when is_binary(H) ->
+ case (catch binary_to_term(H)) of
+ {'EXIT', _} ->
+ md5_and_convert(T,MD5State,Count);
+ ['$end_of_table',Dat] ->
+ {[],MD5State,Count,['$end_of_table',Dat]};
+ Term ->
+ X = erlang:md5_update(MD5State,H),
+ {Rest,NewMD5,NewCount,NewLast} = md5_and_convert(T,X,Count+1),
+ {[Term | Rest],NewMD5,NewCount,NewLast}
+ end.
+scan_for_endinfo([],Count) ->
+ {[],Count,[]};
+scan_for_endinfo([['$end_of_table',Dat]],Count) ->
+ {['$end_of_table',Dat],Count,[]};
+scan_for_endinfo([Term|T],Count) ->
+ {NewLast,NCount,Rest} = scan_for_endinfo(T,Count+1),
+ {NewLast,NCount,[Term | Rest]}.
+
+load_table(ReadFun, State, Tab) ->
+ {NewState,NewData} = ReadFun(State),
+ case NewData of
+ [] ->
+ {ok,NewState};
+ List ->
+ ets:insert(Tab,List),
+ load_table(ReadFun,NewState,Tab)
+ end.
+
+create_tab(I) ->
+ {value, {name, Name}} = lists:keysearch(name, 1, I),
+ {value, {type, Type}} = lists:keysearch(type, 1, I),
+ {value, {protection, P}} = lists:keysearch(protection, 1, I),
+ {value, {named_table, Val}} = lists:keysearch(named_table, 1, I),
+ {value, {keypos, Kp}} = lists:keysearch(keypos, 1, I),
+ {value, {size, Sz}} = lists:keysearch(size, 1, I),
+ try
+ Tab = ets:new(Name, [Type, P, {keypos, Kp} | named_table(Val)]),
+ {ok, Tab, Sz}
+ catch
+ _:_ ->
+ throw(cannot_create_table)
+ end.
+
+named_table(true) -> [named_table];
+named_table(false) -> [].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% tabfile_info/1 reads the head information in an ets table dumped to
+%% disk by means of file2tab and returns a list of the relevant table
+%% information
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec tabfile_info(fname()) -> {'ok', [table_info()]} | {'error', term()}.
+
+tabfile_info(File) when is_list(File) ; is_atom(File) ->
+ try
+ Name = make_ref(),
+ {ok, Major, Minor, _FtOptions, _MD5State, FullHeader, _DLContext} =
+ case disk_log:open([{name, Name},
+ {file, File},
+ {mode, read_only}]) of
+ {ok, Name} ->
+ get_header_data(Name,false);
+ {repaired, Name, _,_} -> %Uh? cannot happen?
+ get_header_data(Name,false);
+ {error, Other1} ->
+ throw({read_error, Other1});
+ Other2 ->
+ throw(Other2)
+ end,
+ disk_log:close(Name),
+ {value, N} = lists:keysearch(name, 1, FullHeader),
+ {value, Type} = lists:keysearch(type, 1, FullHeader),
+ {value, P} = lists:keysearch(protection, 1, FullHeader),
+ {value, Val} = lists:keysearch(named_table, 1, FullHeader),
+ {value, Kp} = lists:keysearch(keypos, 1, FullHeader),
+ {value, Sz} = lists:keysearch(size, 1, FullHeader),
+ Ei = case lists:keysearch(extended_info, 1, FullHeader) of
+ {value, Ei0} -> Ei0;
+ _ -> {extended_info, []}
+ end,
+ {ok, [N,Type,P,Val,Kp,Sz,Ei,{version,{Major,Minor}}]}
+ catch
+ throw:TReason ->
+ {error,TReason};
+ exit:ExReason ->
+ {error,ExReason}
+ end.
+
+-type qlc__query_handle() :: term(). %% XXX: belongs in 'qlc'
+
+-type num_objects() :: 'default' | pos_integer().
+-type trav_method() :: 'first_next' | 'last_prev'
+ | 'select' | {'select', match_specs()}.
+-type table_option() :: {'n_objects', num_objects()}
+ | {'traverse', trav_method()}.
+
+-spec table(tab()) -> qlc__query_handle().
+
+table(Tab) ->
+ table(Tab, []).
+
+-spec table(tab(), table_option() | [table_option()]) -> qlc__query_handle().
+
+table(Tab, Opts) ->
+ case options(Opts, [traverse, n_objects]) of
+ {badarg,_} ->
+ erlang:error(badarg, [Tab, Opts]);
+ [[Traverse, NObjs], QlcOptions] ->
+ TF = case Traverse of
+ first_next ->
+ fun() -> qlc_next(Tab, ets:first(Tab)) end;
+ last_prev ->
+ fun() -> qlc_prev(Tab, ets:last(Tab)) end;
+ select ->
+ fun(MS) -> qlc_select(ets:select(Tab, MS, NObjs)) end;
+ {select, MS} ->
+ fun() -> qlc_select(ets:select(Tab, MS, NObjs)) end
+ end,
+ PreFun = fun(_) -> ets:safe_fixtable(Tab, true) end,
+ PostFun = fun() -> ets:safe_fixtable(Tab, false) end,
+ InfoFun = fun(Tag) -> table_info(Tab, Tag) end,
+ KeyEquality = case ets:info(Tab, type) of
+ ordered_set -> '==';
+ _ -> '=:='
+ end,
+ LookupFun =
+ case Traverse of
+ {select, _MS} ->
+ undefined;
+ _ ->
+ fun(_Pos, [K]) ->
+ ets:lookup(Tab, K);
+ (_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K)
+ end, Ks)
+ end
+ end,
+ FormatFun =
+ fun({all, _NElements, _ElementFun}) ->
+ As = [Tab | [Opts || _ <- [[]], Opts =/= []]],
+ {?MODULE, table, As};
+ ({match_spec, MS}) ->
+ {?MODULE, table,
+ [Tab, [{traverse, {select, MS}} |
+ listify(Opts)]]};
+ ({lookup, _KeyPos, [Value], _NElements, ElementFun}) ->
+ io_lib:format("~w:lookup(~w, ~w)",
+ [?MODULE, Tab, ElementFun(Value)]);
+ ({lookup, _KeyPos, Values, _NElements, ElementFun}) ->
+ Vals = [ElementFun(V) || V <- Values],
+ io_lib:format("lists:flatmap(fun(V) -> "
+ "~w:lookup(~w, V) end, ~w)",
+ [?MODULE, Tab, Vals])
+ end,
+ qlc:table(TF, [{pre_fun, PreFun}, {post_fun, PostFun},
+ {info_fun, InfoFun}, {format_fun, FormatFun},
+ {key_equality, KeyEquality},
+ {lookup_fun, LookupFun}] ++ QlcOptions)
+ end.
+
+table_info(Tab, num_of_objects) ->
+ ets:info(Tab, size);
+table_info(Tab, keypos) ->
+ ets:info(Tab, keypos);
+table_info(Tab, is_unique_objects) ->
+ ets:info(Tab, type) =/= duplicate_bag;
+table_info(Tab, is_sorted_key) ->
+ ets:info(Tab, type) =:= ordered_set;
+table_info(_Tab, _) ->
+ undefined.
+
+qlc_next(_Tab, '$end_of_table') ->
+ [];
+qlc_next(Tab, Key) ->
+ ets:lookup(Tab, Key) ++ fun() -> qlc_next(Tab, ets:next(Tab, Key)) end.
+
+qlc_prev(_Tab, '$end_of_table') ->
+ [];
+qlc_prev(Tab, Key) ->
+ ets:lookup(Tab, Key) ++ fun() -> qlc_prev(Tab, ets:prev(Tab, Key)) end.
+
+qlc_select('$end_of_table') ->
+ [];
+qlc_select({Objects, Cont}) ->
+ Objects ++ fun() -> qlc_select(ets:select(Cont)) end.
+
+options(Options, Keys) when is_list(Options) ->
+ options(Options, Keys, []);
+options(Option, Keys) ->
+ options([Option], Keys, []).
+
+options(Options, [Key | Keys], L) when is_list(Options) ->
+ V = case lists:keysearch(Key, 1, Options) of
+ {value, {n_objects, default}} ->
+ {ok, default_option(Key)};
+ {value, {n_objects, NObjs}} when is_integer(NObjs),
+ NObjs >= 1 ->
+ {ok, NObjs};
+ {value, {traverse, select}} ->
+ {ok, select};
+ {value, {traverse, {select, MS}}} ->
+ {ok, {select, MS}};
+ {value, {traverse, first_next}} ->
+ {ok, first_next};
+ {value, {traverse, last_prev}} ->
+ {ok, last_prev};
+ {value, {Key, _}} ->
+ badarg;
+ false ->
+ Default = default_option(Key),
+ {ok, Default}
+ end,
+ case V of
+ badarg ->
+ {badarg, Key};
+ {ok,Value} ->
+ NewOptions = lists:keydelete(Key, 1, Options),
+ options(NewOptions, Keys, [Value | L])
+ end;
+options(Options, [], L) ->
+ [lists:reverse(L), Options].
+
+default_option(traverse) -> select;
+default_option(n_objects) -> 100.
+
+listify(L) when is_list(L) ->
+ L;
+listify(T) ->
+ [T].
+
+%% End of table/2.
+
+%% Print info about all tabs on the tty
+-spec i() -> 'ok'.
+
+i() ->
+ hform('id', 'name', 'type', 'size', 'mem', 'owner'),
+ io:format(" -------------------------------------"
+ "---------------------------------------\n"),
+ lists:foreach(fun prinfo/1, tabs()),
+ ok.
+
+tabs() ->
+ lists:sort(ets:all()).
+
+prinfo(Tab) ->
+ case catch prinfo2(Tab) of
+ {'EXIT', _} ->
+ io:format("~-10s ... unreadable \n", [to_string(Tab)]);
+ ok ->
+ ok
+ end.
+prinfo2(Tab) ->
+ Name = ets:info(Tab, name),
+ Type = ets:info(Tab, type),
+ Size = ets:info(Tab, size),
+ Mem = ets:info(Tab, memory),
+ Owner = ets:info(Tab, owner),
+ hform(Tab, Name, Type, Size, Mem, is_reg(Owner)).
+
+is_reg(Owner) ->
+ case process_info(Owner, registered_name) of
+ {registered_name, Name} -> Name;
+ _ -> Owner
+ end.
+
+%%% Arndt: this code used to truncate over-sized fields. Now it
+%%% pushes the remaining entries to the right instead, rather than
+%%% losing information.
+hform(A0, B0, C0, D0, E0, F0) ->
+ [A,B,C,D,E,F] = [to_string(T) || T <- [A0,B0,C0,D0,E0,F0]],
+ A1 = pad_right(A, 15),
+ B1 = pad_right(B, 17),
+ C1 = pad_right(C, 5),
+ D1 = pad_right(D, 6),
+ E1 = pad_right(E, 8),
+ %% no need to pad the last entry on the line
+ io:format(" ~s ~s ~s ~s ~s ~s\n", [A1,B1,C1,D1,E1,F]).
+
+pad_right(String, Len) ->
+ if
+ length(String) >= Len ->
+ String;
+ true ->
+ [Space] = " ",
+ String ++ lists:duplicate(Len - length(String), Space)
+ end.
+
+to_string(X) ->
+ lists:flatten(io_lib:format("~p", [X])).
+
+%% view a specific table
+-spec i(tab()) -> 'ok'.
+
+i(Tab) ->
+ i(Tab, 40).
+
+-spec i(tab(), pos_integer()) -> 'ok'.
+
+i(Tab, Height) ->
+ i(Tab, Height, 80).
+
+-spec i(tab(), pos_integer(), pos_integer()) -> 'ok'.
+
+i(Tab, Height, Width) ->
+ First = ets:first(Tab),
+ display_items(Height, Width, Tab, First, 1, 1).
+
+display_items(Height, Width, Tab, '$end_of_table', Turn, Opos) ->
+ P = 'EOT (q)uit (p)Digits (k)ill /Regexp -->',
+ choice(Height, Width, P, eot, Tab, '$end_of_table', Turn, Opos);
+display_items(Height, Width, Tab, Key, Turn, Opos) when Turn < Height ->
+ do_display(Height, Width, Tab, Key, Turn, Opos);
+display_items(Height, Width, Tab, Key, Turn, Opos) when Turn >= Height ->
+ P = '(c)ontinue (q)uit (p)Digits (k)ill /Regexp -->',
+ choice(Height, Width, P, normal, Tab, Key, Turn, Opos).
+
+choice(Height, Width, P, Mode, Tab, Key, Turn, Opos) ->
+ case get_line(P, "c\n") of
+ "c\n" when Mode =:= normal ->
+ do_display(Height, Width, Tab, Key, 1, Opos);
+ "c\n" when is_tuple(Mode), element(1, Mode) =:= re ->
+ {re, Re} = Mode,
+ re_search(Height, Width, Tab, Key, Re, 1, Opos);
+ "q\n" ->
+ ok;
+ "k\n" ->
+ ets:delete(Tab),
+ ok;
+ [$p|Digs] ->
+ catch case catch list_to_integer(nonl(Digs)) of
+ {'EXIT', _} ->
+ io:put_chars("Bad digits\n");
+ Number when Mode =:= normal ->
+ print_number(Tab, ets:first(Tab), Number);
+ Number when Mode =:= eot ->
+ print_number(Tab, ets:first(Tab), Number);
+ Number -> %% regexp
+ {re, Re} = Mode,
+ print_re_num(Tab, ets:first(Tab), Number, Re)
+ end,
+ choice(Height, Width, P, Mode, Tab, Key, Turn, Opos);
+ [$/|Regexp] -> %% from regexp
+ case re:compile(nonl(Regexp)) of
+ {ok,Re} ->
+ re_search(Height, Width, Tab, ets:first(Tab), Re, 1, 1);
+ {error,{ErrorString,_Pos}} ->
+ io:format("~s\n", [ErrorString]),
+ choice(Height, Width, P, Mode, Tab, Key, Turn, Opos)
+ end;
+ _ ->
+ choice(Height, Width, P, Mode, Tab, Key, Turn, Opos)
+ end.
+
+get_line(P, Default) ->
+ case io:get_line(P) of
+ "\n" ->
+ Default;
+ L ->
+ L
+ end.
+
+nonl(S) -> string:strip(S, right, $\n).
+
+print_number(Tab, Key, Num) ->
+ Os = ets:lookup(Tab, Key),
+ Len = length(Os),
+ if
+ (Num - Len) < 1 ->
+ O = lists:nth(Num, Os),
+ io:format("~p~n", [O]); %% use ppterm here instead
+ true ->
+ print_number(Tab, ets:next(Tab, Key), Num - Len)
+ end.
+
+do_display(Height, Width, Tab, Key, Turn, Opos) ->
+ Objs = ets:lookup(Tab, Key),
+ do_display_items(Height, Width, Objs, Opos),
+ Len = length(Objs),
+ display_items(Height, Width, Tab, ets:next(Tab, Key), Turn+Len, Opos+Len).
+
+do_display_items(Height, Width, [Obj|Tail], Opos) ->
+ do_display_item(Height, Width, Obj, Opos),
+ do_display_items(Height, Width, Tail, Opos+1);
+do_display_items(_Height, _Width, [], Opos) ->
+ Opos.
+
+do_display_item(_Height, Width, I, Opos) ->
+ L = to_string(I),
+ L2 = if
+ length(L) > Width - 8 ->
+ string:substr(L, 1, Width-13) ++ " ...";
+ true ->
+ L
+ end,
+ io:format("<~-4w> ~s~n", [Opos,L2]).
+
+re_search(Height, Width, Tab, '$end_of_table', Re, Turn, Opos) ->
+ P = 'EOT (q)uit (p)Digits (k)ill /Regexp -->',
+ choice(Height, Width, P, {re, Re}, Tab, '$end_of_table', Turn, Opos);
+re_search(Height, Width, Tab, Key, Re, Turn, Opos) when Turn < Height ->
+ re_display(Height, Width, Tab, Key, ets:lookup(Tab, Key), Re, Turn, Opos);
+re_search(Height, Width, Tab, Key, Re, Turn, Opos) ->
+ P = '(c)ontinue (q)uit (p)Digits (k)ill /Regexp -->',
+ choice(Height, Width, P, {re, Re}, Tab, Key, Turn, Opos).
+
+re_display(Height, Width, Tab, Key, [], Re, Turn, Opos) ->
+ re_search(Height, Width, Tab, ets:next(Tab, Key), Re, Turn, Opos);
+re_display(Height, Width, Tab, Key, [H|T], Re, Turn, Opos) ->
+ Str = to_string(H),
+ case re:run(Str, Re, [{capture,none}]) of
+ match ->
+ do_display_item(Height, Width, H, Opos),
+ re_display(Height, Width, Tab, Key, T, Re, Turn+1, Opos+1);
+ nomatch ->
+ re_display(Height, Width, Tab, Key, T, Re, Turn, Opos)
+ end.
+
+print_re_num(_,'$end_of_table',_,_) -> ok;
+print_re_num(Tab, Key, Num, Re) ->
+ Os = re_match(ets:lookup(Tab, Key), Re),
+ Len = length(Os),
+ if
+ (Num - Len) < 1 ->
+ O = lists:nth(Num, Os),
+ io:format("~p~n", [O]); %% use ppterm here instead
+ true ->
+ print_re_num(Tab, ets:next(Tab, Key), Num - Len, Re)
+ end.
+
+re_match([], _) -> [];
+re_match([H|T], Re) ->
+ case re:run(to_string(H), Re, [{capture,none}]) of
+ match ->
+ [H|re_match(T,Re)];
+ nomatch ->
+ re_match(T, Re)
+ end.