1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(ets).
21
22%% Interface to the Term store BIF's
23%% ets == Erlang Term Store
24
25-export([file2tab/1,
26	 file2tab/2,
27	 filter/3,
28	 foldl/3, foldr/3,
29	 match_delete/2,
30	 tab2file/2,
31	 tab2file/3,
32	 tabfile_info/1,
33	 from_dets/2,
34	 to_dets/2,
35	 init_table/2,
36	 test_ms/2,
37	 tab2list/1,
38         table/1,
39         table/2,
40	 fun2ms/1,
41	 match_spec_run/2,
42	 repair_continuation/2]).
43
44-export([i/0, i/1, i/2, i/3]).
45
46-export_type([tab/0, tid/0, match_spec/0, comp_match_spec/0, match_pattern/0]).
47
48%%-----------------------------------------------------------------------------
49
50-type access()     :: public | protected | private.
51-type tab()        :: atom() | tid().
52-type type()       :: set | ordered_set | bag | duplicate_bag.
53-type continuation() :: '$end_of_table'
54                      | {tab(),integer(),integer(),comp_match_spec(),list(),integer()}
55                      | {tab(),_,_,integer(),comp_match_spec(),list(),integer(),integer()}.
56
57-opaque tid()      :: reference().
58
59-type match_pattern() :: atom() | tuple().
60-type match_spec()    :: [{match_pattern(), [_], [_]}].
61
62%%-----------------------------------------------------------------------------
63
64%%% BIFs
65
66-export([all/0, delete/1, delete/2, delete_all_objects/1,
67         delete_object/2, first/1, give_away/3, info/1, info/2,
68         insert/2, insert_new/2, is_compiled_ms/1, last/1, lookup/2,
69         lookup_element/3, match/1, match/2, match/3, match_object/1,
70         match_object/2, match_object/3, match_spec_compile/1,
71         match_spec_run_r/3, member/2, new/2, next/2, prev/2,
72         rename/2, safe_fixtable/2, select/1, select/2, select/3,
73         select_count/2, select_delete/2, select_replace/2, select_reverse/1,
74         select_reverse/2, select_reverse/3, setopts/2, slot/2,
75         take/2,
76         update_counter/3, update_counter/4, update_element/3,
77         whereis/1]).
78
79%% internal exports
80-export([internal_request_all/0,
81         internal_delete_all/2,
82         internal_select_delete/2]).
83
84-spec all() -> [Tab] when
85      Tab :: tab().
86
87all() ->
88    receive_all(ets:internal_request_all(),
89                erlang:system_info(schedulers),
90                []).
91
92receive_all(_Ref, 0, All) ->
93    All;
94receive_all(Ref, N, All) ->
95    receive
96        {Ref, SchedAll} ->
97            receive_all(Ref, N-1, SchedAll ++ All)
98    end.
99
100-spec internal_request_all() -> reference().
101
102internal_request_all() ->
103    erlang:nif_error(undef).
104
105-spec delete(Tab) -> true when
106      Tab :: tab().
107
108delete(_) ->
109    erlang:nif_error(undef).
110
111-spec delete(Tab, Key) -> true when
112      Tab :: tab(),
113      Key :: term().
114
115delete(_, _) ->
116    erlang:nif_error(undef).
117
118-spec delete_all_objects(Tab) -> true when
119      Tab :: tab().
120
121delete_all_objects(Tab) ->
122    _ = ets:internal_delete_all(Tab, undefined),
123    true.
124
125-spec internal_delete_all(Tab, undefined) -> NumDeleted when
126      Tab :: tab(),
127      NumDeleted :: non_neg_integer().
128
129internal_delete_all(_, _) ->
130    erlang:nif_error(undef).
131
132-spec delete_object(Tab, Object) -> true when
133      Tab :: tab(),
134      Object :: tuple().
135
136delete_object(_, _) ->
137    erlang:nif_error(undef).
138
139-spec first(Tab) -> Key | '$end_of_table' when
140      Tab :: tab(),
141      Key :: term().
142
143first(_) ->
144    erlang:nif_error(undef).
145
146-spec give_away(Tab, Pid, GiftData) -> true when
147      Tab :: tab(),
148      Pid :: pid(),
149      GiftData :: term().
150
151give_away(_, _, _) ->
152    erlang:nif_error(undef).
153
154-spec info(Tab) -> InfoList | undefined when
155      Tab :: tab(),
156      InfoList :: [InfoTuple],
157      InfoTuple :: {compressed, boolean()}
158                 | {heir, pid() | none}
159                 | {id, tid()}
160                 | {keypos, pos_integer()}
161                 | {memory, non_neg_integer()}
162                 | {name, atom()}
163                 | {named_table, boolean()}
164                 | {node, node()}
165                 | {owner, pid()}
166                 | {protection, access()}
167                 | {size, non_neg_integer()}
168                 | {type, type()}
169		 | {write_concurrency, boolean()}
170		 | {read_concurrency, boolean()}.
171
172info(_) ->
173    erlang:nif_error(undef).
174
175-spec info(Tab, Item) -> Value | undefined when
176      Tab :: tab(),
177      Item :: binary | compressed | fixed | heir | id | keypos | memory
178            | name | named_table | node | owner | protection
179            | safe_fixed | safe_fixed_monotonic_time | size | stats | type
180	    | write_concurrency | read_concurrency,
181      Value :: term().
182
183info(_, _) ->
184    erlang:nif_error(undef).
185
186-spec insert(Tab, ObjectOrObjects) -> true when
187      Tab :: tab(),
188      ObjectOrObjects :: tuple() | [tuple()].
189
190insert(_, _) ->
191    erlang:nif_error(undef).
192
193-spec insert_new(Tab, ObjectOrObjects) -> boolean() when
194      Tab :: tab(),
195      ObjectOrObjects :: tuple() | [tuple()].
196
197insert_new(_, _) ->
198    erlang:nif_error(undef).
199
200-spec is_compiled_ms(Term) -> boolean() when
201      Term :: term().
202
203is_compiled_ms(_) ->
204    erlang:nif_error(undef).
205
206-spec last(Tab) -> Key | '$end_of_table' when
207      Tab :: tab(),
208      Key :: term().
209
210last(_) ->
211    erlang:nif_error(undef).
212
213-spec lookup(Tab, Key) -> [Object] when
214      Tab :: tab(),
215      Key :: term(),
216      Object :: tuple().
217
218lookup(_, _) ->
219    erlang:nif_error(undef).
220
221-spec lookup_element(Tab, Key, Pos) -> Elem when
222      Tab :: tab(),
223      Key :: term(),
224      Pos :: pos_integer(),
225      Elem :: term() | [term()].
226
227lookup_element(_, _, _) ->
228    erlang:nif_error(undef).
229
230-spec match(Tab, Pattern) -> [Match] when
231      Tab :: tab(),
232      Pattern :: match_pattern(),
233      Match :: [term()].
234
235match(_, _) ->
236    erlang:nif_error(undef).
237
238-spec match(Tab, Pattern, Limit) -> {[Match], Continuation} |
239                                       '$end_of_table'  when
240      Tab :: tab(),
241      Pattern :: match_pattern(),
242      Limit :: pos_integer(),
243      Match :: [term()],
244      Continuation :: continuation().
245
246match(_, _, _) ->
247    erlang:nif_error(undef).
248
249-spec match(Continuation) -> {[Match], Continuation} |
250                                '$end_of_table'  when
251      Match :: [term()],
252      Continuation :: continuation().
253
254match(_) ->
255    erlang:nif_error(undef).
256
257-spec match_object(Tab, Pattern) -> [Object] when
258      Tab :: tab(),
259      Pattern :: match_pattern(),
260      Object :: tuple().
261
262match_object(_, _) ->
263    erlang:nif_error(undef).
264
265-spec match_object(Tab, Pattern, Limit) -> {[Object], Continuation} |
266                                           '$end_of_table' when
267      Tab :: tab(),
268      Pattern :: match_pattern(),
269      Limit :: pos_integer(),
270      Object :: tuple(),
271      Continuation :: continuation().
272
273match_object(_, _, _) ->
274    erlang:nif_error(undef).
275
276-spec match_object(Continuation) -> {[Object], Continuation} |
277                                    '$end_of_table' when
278      Object :: tuple(),
279      Continuation :: continuation().
280
281match_object(_) ->
282    erlang:nif_error(undef).
283
284-spec match_spec_compile(MatchSpec) -> CompiledMatchSpec when
285      MatchSpec :: match_spec(),
286      CompiledMatchSpec :: comp_match_spec().
287
288match_spec_compile(_) ->
289    erlang:nif_error(undef).
290
291-spec match_spec_run_r(List, CompiledMatchSpec, list()) -> list() when
292      List :: [term()],
293      CompiledMatchSpec :: comp_match_spec().
294
295match_spec_run_r(_, _, _) ->
296    erlang:nif_error(undef).
297
298-spec member(Tab, Key) -> boolean() when
299      Tab :: tab(),
300      Key :: term().
301
302member(_, _) ->
303    erlang:nif_error(undef).
304
305-spec new(Name, Options) -> tid() | atom() when
306      Name :: atom(),
307      Options :: [Option],
308      Option :: Type | Access | named_table | {keypos,Pos}
309              | {heir, Pid :: pid(), HeirData} | {heir, none} | Tweaks,
310      Type :: type(),
311      Access :: access(),
312      Tweaks :: {write_concurrency, boolean()}
313              | {read_concurrency, boolean()}
314              | compressed,
315      Pos :: pos_integer(),
316      HeirData :: term().
317
318new(_, _) ->
319    erlang:nif_error(undef).
320
321-spec next(Tab, Key1) -> Key2 | '$end_of_table' when
322      Tab :: tab(),
323      Key1 :: term(),
324      Key2 :: term().
325
326next(_, _) ->
327    erlang:nif_error(undef).
328
329-spec prev(Tab, Key1) -> Key2 | '$end_of_table' when
330      Tab :: tab(),
331      Key1 :: term(),
332      Key2 :: term().
333
334prev(_, _) ->
335    erlang:nif_error(undef).
336
337%% Shadowed by erl_bif_types: ets:rename/2
338-spec rename(Tab, Name) -> Name when
339      Tab :: tab(),
340      Name :: atom().
341
342rename(_, _) ->
343    erlang:nif_error(undef).
344
345-spec safe_fixtable(Tab, Fix) -> true when
346      Tab :: tab(),
347      Fix :: boolean().
348
349safe_fixtable(_, _) ->
350    erlang:nif_error(undef).
351
352-spec select(Tab, MatchSpec) -> [Match] when
353      Tab :: tab(),
354      MatchSpec :: match_spec(),
355      Match :: term().
356
357select(_, _) ->
358    erlang:nif_error(undef).
359
360-spec select(Tab, MatchSpec, Limit) -> {[Match],Continuation} |
361                                       '$end_of_table' when
362      Tab :: tab(),
363      MatchSpec :: match_spec(),
364      Limit :: pos_integer(),
365      Match :: term(),
366      Continuation :: continuation().
367
368select(_, _, _) ->
369    erlang:nif_error(undef).
370
371-spec select(Continuation) -> {[Match],Continuation} | '$end_of_table' when
372      Match :: term(),
373      Continuation :: continuation().
374
375select(_) ->
376    erlang:nif_error(undef).
377
378-spec select_count(Tab, MatchSpec) -> NumMatched when
379      Tab :: tab(),
380      MatchSpec :: match_spec(),
381      NumMatched :: non_neg_integer().
382
383select_count(_, _) ->
384    erlang:nif_error(undef).
385
386-spec select_delete(Tab, MatchSpec) -> NumDeleted when
387      Tab :: tab(),
388      MatchSpec :: match_spec(),
389      NumDeleted :: non_neg_integer().
390
391select_delete(Tab, [{'_',[],[true]}]) ->
392    ets:internal_delete_all(Tab, undefined);
393select_delete(Tab, MatchSpec) ->
394    ets:internal_select_delete(Tab, MatchSpec).
395
396-spec internal_select_delete(Tab, MatchSpec) -> NumDeleted when
397      Tab :: tab(),
398      MatchSpec :: match_spec(),
399      NumDeleted :: non_neg_integer().
400
401internal_select_delete(_, _) ->
402    erlang:nif_error(undef).
403
404-spec select_replace(Tab, MatchSpec) -> NumReplaced when
405      Tab :: tab(),
406      MatchSpec :: match_spec(),
407      NumReplaced :: non_neg_integer().
408
409select_replace(_, _) ->
410    erlang:nif_error(undef).
411
412-spec select_reverse(Tab, MatchSpec) -> [Match] when
413      Tab :: tab(),
414      MatchSpec :: match_spec(),
415      Match :: term().
416
417select_reverse(_, _) ->
418    erlang:nif_error(undef).
419
420-spec select_reverse(Tab, MatchSpec, Limit) -> {[Match],Continuation} |
421                                               '$end_of_table' when
422      Tab :: tab(),
423      MatchSpec :: match_spec(),
424      Limit :: pos_integer(),
425      Match :: term(),
426      Continuation :: continuation().
427
428select_reverse(_, _, _) ->
429    erlang:nif_error(undef).
430
431-spec select_reverse(Continuation) -> {[Match],Continuation} |
432                                      '$end_of_table' when
433      Continuation :: continuation(),
434      Match :: term().
435
436select_reverse(_) ->
437    erlang:nif_error(undef).
438
439-spec setopts(Tab, Opts) -> true when
440      Tab :: tab(),
441      Opts :: Opt | [Opt],
442      Opt :: {heir, pid(), HeirData} | {heir,none},
443      HeirData :: term().
444
445setopts(_, _) ->
446    erlang:nif_error(undef).
447
448-spec slot(Tab, I) -> [Object] | '$end_of_table' when
449      Tab :: tab(),
450      I :: non_neg_integer(),
451      Object :: tuple().
452
453slot(_, _) ->
454    erlang:nif_error(undef).
455
456-spec take(Tab, Key) -> [Object] when
457      Tab :: tab(),
458      Key :: term(),
459      Object :: tuple().
460
461take(_, _) ->
462    erlang:nif_error(undef).
463
464-spec update_counter(Tab, Key, UpdateOp) -> Result when
465      Tab :: tab(),
466      Key :: term(),
467      UpdateOp :: {Pos, Incr} | {Pos, Incr, Threshold, SetValue},
468      Pos :: integer(),
469      Incr :: integer(),
470      Threshold :: integer(),
471      SetValue :: integer(),
472      Result :: integer();
473                       (Tab, Key, [UpdateOp]) -> [Result] when
474      Tab :: tab(),
475      Key :: term(),
476      UpdateOp :: {Pos, Incr} | {Pos, Incr, Threshold, SetValue},
477      Pos :: integer(),
478      Incr :: integer(),
479      Threshold :: integer(),
480      SetValue :: integer(),
481      Result :: integer();
482                       (Tab, Key, Incr) -> Result when
483      Tab :: tab(),
484      Key :: term(),
485      Incr :: integer(),
486      Result :: integer().
487
488update_counter(_, _, _) ->
489    erlang:nif_error(undef).
490
491-spec update_counter(Tab, Key, UpdateOp, Default) -> Result when
492                        Tab :: tab(),
493                        Key :: term(),
494                        UpdateOp :: {Pos, Incr}
495                                  | {Pos, Incr, Threshold, SetValue},
496                        Pos :: integer(),
497                        Incr :: integer(),
498                        Threshold :: integer(),
499                        SetValue :: integer(),
500                        Result :: integer(),
501                        Default :: tuple();
502                    (Tab, Key, [UpdateOp], Default) -> [Result] when
503                        Tab :: tab(),
504                        Key :: term(),
505                        UpdateOp :: {Pos, Incr}
506                                  | {Pos, Incr, Threshold, SetValue},
507                        Pos :: integer(),
508                        Incr :: integer(),
509                        Threshold :: integer(),
510                        SetValue :: integer(),
511                        Result :: integer(),
512                        Default :: tuple();
513                    (Tab, Key, Incr, Default) -> Result when
514                        Tab :: tab(),
515                        Key :: term(),
516                        Incr :: integer(),
517                        Result :: integer(),
518                        Default :: tuple().
519
520update_counter(_, _, _, _) ->
521    erlang:nif_error(undef).
522
523-spec update_element(Tab, Key, ElementSpec :: {Pos, Value}) -> boolean() when
524      Tab :: tab(),
525      Key :: term(),
526      Pos :: pos_integer(),
527      Value :: term();
528                       (Tab, Key, ElementSpec :: [{Pos, Value}]) -> boolean() when
529      Tab :: tab(),
530      Key :: term(),
531      Pos :: pos_integer(),
532      Value :: term().
533
534update_element(_, _, _) ->
535    erlang:nif_error(undef).
536
537-spec whereis(TableName) -> tid() | undefined when
538    TableName :: atom().
539whereis(_) ->
540    erlang:nif_error(undef).
541
542%%% End of BIFs
543
544-opaque comp_match_spec() :: reference().
545
546-spec match_spec_run(List, CompiledMatchSpec) -> list() when
547      List :: [term()],
548      CompiledMatchSpec :: comp_match_spec().
549
550match_spec_run(List, CompiledMS) ->
551    lists:reverse(ets:match_spec_run_r(List, CompiledMS, [])).
552
553-spec repair_continuation(Continuation, MatchSpec) -> Continuation when
554      Continuation :: continuation(),
555      MatchSpec :: match_spec().
556
557%% $end_of_table is an allowed continuation in ets...
558repair_continuation('$end_of_table', _) ->
559    '$end_of_table';
560%% ordered_set
561repair_continuation(Untouched = {Table,Lastkey,EndCondition,N2,MSRef,L2,N3,N4}, MS)
562  when %% (is_atom(Table) or is_integer(Table)),
563       is_integer(N2),
564      %% is_reference(MSRef),
565       is_list(L2),
566       is_integer(N3),
567       is_integer(N4) ->
568    case ets:is_compiled_ms(MSRef) of
569	true ->
570	    Untouched;
571	false ->
572	    {Table,Lastkey,EndCondition,N2,ets:match_spec_compile(MS),L2,N3,N4}
573    end;
574%% set/bag/duplicate_bag
575repair_continuation(Untouched = {Table,N1,N2,MSRef,L,N3}, MS)
576  when %% (is_atom(Table) or is_integer(Table)),
577       is_integer(N1),
578       is_integer(N2),
579      %% is_reference(MSRef),
580       is_list(L),
581       is_integer(N3) ->
582    case ets:is_compiled_ms(MSRef) of
583	true ->
584	    Untouched;
585	false ->
586	    {Table,N1,N2,ets:match_spec_compile(MS),L,N3}
587    end.
588
589-spec fun2ms(LiteralFun) -> MatchSpec when
590      LiteralFun :: function(),
591      MatchSpec :: match_spec().
592
593fun2ms(ShellFun) when is_function(ShellFun) ->
594    %% Check that this is really a shell fun...
595    case erl_eval:fun_data(ShellFun) of
596        {fun_data,ImportList,Clauses} ->
597            case ms_transform:transform_from_shell(
598                   ?MODULE,Clauses,ImportList) of
599                {error,[{_,[{_,_,Code}|_]}|_],_} ->
600                    io:format("Error: ~ts~n",
601                              [ms_transform:format_error(Code)]),
602                    {error,transform_error};
603                Else ->
604                    Else
605            end;
606        _ ->
607            exit({badarg,{?MODULE,fun2ms,
608                          [function,called,with,real,'fun',
609                           should,be,transformed,with,
610                           parse_transform,'or',called,with,
611                           a,'fun',generated,in,the,
612                           shell]}})
613    end.
614
615-spec foldl(Function, Acc0, Tab) -> Acc1 when
616      Function :: fun((Element :: term(), AccIn) -> AccOut),
617      Tab :: tab(),
618      Acc0 :: term(),
619      Acc1 :: term(),
620      AccIn :: term(),
621      AccOut :: term().
622
623foldl(F, Accu, T) ->
624    ets:safe_fixtable(T, true),
625    First = ets:first(T),
626    try
627        do_foldl(F, Accu, First, T)
628    after
629	ets:safe_fixtable(T, false)
630    end.
631
632do_foldl(F, Accu0, Key, T) ->
633    case Key of
634	'$end_of_table' ->
635	    Accu0;
636	_ ->
637	    do_foldl(F,
638		     lists:foldl(F, Accu0, ets:lookup(T, Key)),
639		     ets:next(T, Key), T)
640    end.
641
642-spec foldr(Function, Acc0, Tab) -> Acc1 when
643      Function :: fun((Element :: term(), AccIn) -> AccOut),
644      Tab :: tab(),
645      Acc0 :: term(),
646      Acc1 :: term(),
647      AccIn :: term(),
648      AccOut :: term().
649
650foldr(F, Accu, T) ->
651    ets:safe_fixtable(T, true),
652    Last = ets:last(T),
653    try
654        do_foldr(F, Accu, Last, T)
655    after
656        ets:safe_fixtable(T, false)
657    end.
658
659do_foldr(F, Accu0, Key, T) ->
660    case Key of
661	'$end_of_table' ->
662	    Accu0;
663	_ ->
664	    do_foldr(F,
665		     lists:foldr(F, Accu0, ets:lookup(T, Key)),
666		     ets:prev(T, Key), T)
667    end.
668
669-spec from_dets(Tab, DetsTab) -> 'true' when
670      Tab :: tab(),
671      DetsTab :: dets:tab_name().
672
673from_dets(EtsTable, DetsTable) ->
674    case (catch dets:to_ets(DetsTable, EtsTable)) of
675	{error, Reason} ->
676	    erlang:error(Reason, [EtsTable,DetsTable]);
677	{'EXIT', {Reason1, _Stack1}} ->
678	    erlang:error(Reason1,[EtsTable,DetsTable]);
679	{'EXIT', EReason} ->
680	    erlang:error(EReason,[EtsTable,DetsTable]);
681	EtsTable ->
682	    true;
683	Unexpected -> %% Dets bug?
684	    erlang:error(Unexpected,[EtsTable,DetsTable])
685    end.
686
687-spec to_dets(Tab, DetsTab) -> DetsTab when
688      Tab :: tab(),
689      DetsTab :: dets:tab_name().
690
691to_dets(EtsTable, DetsTable) ->
692    case (catch dets:from_ets(DetsTable, EtsTable)) of
693	{error, Reason} ->
694	    erlang:error(Reason, [EtsTable,DetsTable]);
695	{'EXIT', {Reason1, _Stack1}} ->
696	    erlang:error(Reason1,[EtsTable,DetsTable]);
697	{'EXIT', EReason} ->
698	    erlang:error(EReason,[EtsTable,DetsTable]);
699	ok ->
700	    DetsTable;
701	Unexpected -> %% Dets bug?
702	    erlang:error(Unexpected,[EtsTable,DetsTable])
703    end.
704
705-spec test_ms(Tuple, MatchSpec) -> {'ok', Result} | {'error', Errors} when
706      Tuple :: tuple(),
707      MatchSpec :: match_spec(),
708      Result :: term(),
709      Errors :: [{'warning'|'error', string()}].
710
711test_ms(Term, MS) ->
712    case erlang:match_spec_test(Term, MS, table) of
713	{ok, Result, _Flags, _Messages} ->
714	    {ok, Result};
715	{error, _Errors} = Error ->
716	    Error
717    end.
718
719-spec init_table(Tab, InitFun) -> 'true' when
720      Tab :: tab(),
721      InitFun :: fun((Arg) -> Res),
722      Arg :: 'read' | 'close',
723      Res :: 'end_of_input' | {Objects :: [term()], InitFun} | term().
724
725init_table(Table, Fun) ->
726    ets:delete_all_objects(Table),
727    init_table_continue(Table, Fun(read)).
728
729init_table_continue(_Table, end_of_input) ->
730    true;
731init_table_continue(Table, {List, Fun}) when is_list(List), is_function(Fun) ->
732    case (catch init_table_sub(Table, List)) of
733	{'EXIT', Reason} ->
734	    (catch Fun(close)),
735	    exit(Reason);
736	true ->
737	    init_table_continue(Table, Fun(read))
738    end;
739init_table_continue(_Table, Error) ->
740    exit(Error).
741
742init_table_sub(_Table, []) ->
743    true;
744init_table_sub(Table, [H|T]) ->
745    ets:insert(Table, H),
746    init_table_sub(Table, T).
747
748-spec match_delete(Tab, Pattern) -> 'true' when
749      Tab :: tab(),
750      Pattern :: match_pattern().
751
752match_delete(Table, Pattern) ->
753    ets:select_delete(Table, [{Pattern,[],[true]}]),
754    true.
755
756%% Produce a list of tuples from a table
757
758-spec tab2list(Tab) -> [Object] when
759      Tab :: tab(),
760      Object :: tuple().
761
762tab2list(T) ->
763    ets:match_object(T, '_').
764
765-spec filter(tab(), function(), [term()]) -> [term()].
766
767filter(Tn, F, A) when is_atom(Tn) ; is_integer(Tn) ->
768    do_filter(Tn, ets:first(Tn), F, A, []).
769
770do_filter(_Tab, '$end_of_table', _, _, Ack) ->
771    Ack;
772do_filter(Tab, Key, F, A, Ack) ->
773    case apply(F, [ets:lookup(Tab, Key)|A]) of
774	false ->
775	    do_filter(Tab, ets:next(Tab, Key), F, A, Ack);
776	true ->
777            Ack2 = ets:lookup(Tab, Key) ++ Ack,
778	    do_filter(Tab, ets:next(Tab, Key), F, A, Ack2);
779	{true, Value} ->
780	    do_filter(Tab, ets:next(Tab, Key), F, A, [Value|Ack])
781    end.
782
783
784%% Dump a table to a file using the disk_log facility
785
786%% Options := [Option]
787%% Option := {extended_info,[ExtInfo]}
788%% ExtInfo := object_count | md5sum
789
790-define(MAJOR_F2T_VERSION,1).
791-define(MINOR_F2T_VERSION,0).
792
793-record(filetab_options,
794	{
795	  object_count = false :: boolean(),
796	  md5sum       = false :: boolean(),
797	  sync         = false :: boolean()
798	 }).
799
800-spec tab2file(Tab, Filename) -> 'ok' | {'error', Reason} when
801      Tab :: tab(),
802      Filename :: file:name(),
803      Reason :: term().
804
805tab2file(Tab, File) ->
806    tab2file(Tab, File, []).
807
808-spec tab2file(Tab, Filename, Options) -> 'ok' | {'error', Reason} when
809      Tab :: tab(),
810      Filename :: file:name(),
811      Options :: [Option],
812      Option :: {'extended_info', [ExtInfo]} | {'sync', boolean()},
813      ExtInfo :: 'md5sum' | 'object_count',
814      Reason :: term().
815
816tab2file(Tab, File, Options) ->
817    try
818	{ok, FtOptions} = parse_ft_options(Options),
819	_ = file:delete(File),
820	case file:read_file_info(File) of
821	    {error, enoent} -> ok;
822	    _ -> throw(eaccess)
823	end,
824	Name = make_ref(),
825	case disk_log:open([{name, Name}, {file, File}]) of
826	    {ok, Name} ->
827		ok;
828	    {error, Reason} ->
829		throw(Reason)
830	end,
831	try
832	    Info0 = case ets:info(Tab) of
833		       undefined ->
834			   %% erlang:error(badarg, [Tab, File, Options]);
835			   throw(badtab);
836		       I ->
837			   I
838	    end,
839	    Info = [list_to_tuple(Info0 ++
840				  [{major_version,?MAJOR_F2T_VERSION},
841				   {minor_version,?MINOR_F2T_VERSION},
842				   {extended_info,
843				    ft_options_to_list(FtOptions)}])],
844	    {LogFun, InitState} =
845	    case FtOptions#filetab_options.md5sum of
846		true ->
847		    {fun(Oldstate,Termlist) ->
848			     {NewState,BinList} =
849				 md5terms(Oldstate,Termlist),
850                             case disk_log:blog_terms(Name,BinList) of
851                                 ok -> NewState;
852                                 {error, Reason2} -> throw(Reason2)
853                             end
854		     end,
855		     erlang:md5_init()};
856		false ->
857		    {fun(_,Termlist) ->
858                             case disk_log:log_terms(Name,Termlist) of
859                                 ok -> true;
860                                 {error, Reason2} -> throw(Reason2)
861                             end
862		     end,
863		     true}
864	    end,
865	    ets:safe_fixtable(Tab,true),
866	    {NewState1,Num} = try
867				  NewState = LogFun(InitState,Info),
868				  dump_file(
869				      ets:select(Tab,[{'_',[],['$_']}],100),
870				      LogFun, NewState, 0)
871			      after
872				  (catch ets:safe_fixtable(Tab,false))
873			      end,
874	    EndInfo =
875	    case  FtOptions#filetab_options.object_count of
876		true ->
877		    [{count,Num}];
878		false ->
879		    []
880	    end ++
881	    case  FtOptions#filetab_options.md5sum of
882		true ->
883		    [{md5,erlang:md5_final(NewState1)}];
884		false ->
885		    []
886	    end,
887	    case EndInfo of
888		[] ->
889		    ok;
890		List ->
891		    LogFun(NewState1,[['$end_of_table',List]])
892	    end,
893	    case FtOptions#filetab_options.sync of
894	        true ->
895		    case disk_log:sync(Name) of
896		        ok -> ok;
897			{error, Reason2} -> throw(Reason2)
898		    end;
899                false ->
900		    ok
901            end,
902	    disk_log:close(Name)
903	catch
904	    throw:TReason ->
905		_ = disk_log:close(Name),
906		_ = file:delete(File),
907		throw(TReason);
908	    exit:ExReason ->
909		_ = disk_log:close(Name),
910		_ = file:delete(File),
911		exit(ExReason);
912	    error:ErReason:StackTrace ->
913		_ = disk_log:close(Name),
914		_ = file:delete(File),
915	        erlang:raise(error,ErReason,StackTrace)
916	end
917    catch
918	throw:TReason2 ->
919	    {error,TReason2};
920	exit:ExReason2 ->
921	    {error,ExReason2}
922    end.
923
924dump_file('$end_of_table', _LogFun, State, Num) ->
925    {State,Num};
926dump_file({Terms, Context}, LogFun, State, Num) ->
927    Count = length(Terms),
928    NewState = LogFun(State, Terms),
929    dump_file(ets:select(Context), LogFun, NewState, Num + Count).
930
931ft_options_to_list(#filetab_options{md5sum = MD5, object_count = PS}) ->
932    case PS of
933	true ->
934	    [object_count];
935	_ ->
936	    []
937    end ++
938	case MD5 of
939	    true ->
940		[md5sum];
941	    _ ->
942		[]
943	end.
944
945md5terms(State, []) ->
946    {State, []};
947md5terms(State, [H|T]) ->
948    B = term_to_binary(H),
949    NewState = erlang:md5_update(State, B),
950    {FinState, TL} = md5terms(NewState, T),
951    {FinState, [B|TL]}.
952
953parse_ft_options(Options) when is_list(Options) ->
954    {ok, parse_ft_options(Options, #filetab_options{}, false)}.
955
956parse_ft_options([], FtOpt, _) ->
957    FtOpt;
958parse_ft_options([{sync,true} | Rest], FtOpt, EI) ->
959    parse_ft_options(Rest, FtOpt#filetab_options{sync = true}, EI);
960parse_ft_options([{sync,false} | Rest], FtOpt, EI) ->
961    parse_ft_options(Rest, FtOpt, EI);
962parse_ft_options([{extended_info,L} | Rest], FtOpt0, false) ->
963    FtOpt1 = parse_ft_info_options(FtOpt0, L),
964    parse_ft_options(Rest, FtOpt1, true);
965parse_ft_options([Other | _], _, _) ->
966    throw({unknown_option, Other});
967parse_ft_options(Malformed, _, _) ->
968    throw({malformed_option, Malformed}).
969
970parse_ft_info_options(FtOpt,[]) ->
971    FtOpt;
972parse_ft_info_options(FtOpt,[object_count | T]) ->
973    parse_ft_info_options(FtOpt#filetab_options{object_count = true}, T);
974parse_ft_info_options(FtOpt,[md5sum | T]) ->
975    parse_ft_info_options(FtOpt#filetab_options{md5sum = true}, T);
976parse_ft_info_options(_,[Unexpected | _]) ->
977    throw({unknown_option,[{extended_info,[Unexpected]}]});
978parse_ft_info_options(_,Malformed) ->
979    throw({malformed_option,Malformed}).
980
981%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
982%% Read a dumped file from disk and create a corresponding table
983%% Opts := [Opt]
984%% Opt := {verify,boolean()}
985%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
986
987-spec file2tab(Filename) -> {'ok', Tab} | {'error', Reason} when
988      Filename :: file:name(),
989      Tab :: tab(),
990      Reason :: term().
991
992file2tab(File) ->
993    file2tab(File, []).
994
995-spec file2tab(Filename, Options) -> {'ok', Tab} | {'error', Reason} when
996      Filename :: file:name(),
997      Tab :: tab(),
998      Options :: [Option],
999      Option :: {'verify', boolean()},
1000      Reason :: term().
1001
1002file2tab(File, Opts) ->
1003    try
1004	{ok,Verify,TabArg} = parse_f2t_opts(Opts,false,[]),
1005	Name = make_ref(),
1006        {ok, Name} =
1007	    case disk_log:open([{name, Name},
1008				{file, File},
1009				{mode, read_only}]) of
1010		{ok, Name} ->
1011                    {ok, Name};
1012		{repaired, Name, _,_} -> %Uh? cannot happen?
1013		    case Verify of
1014			true ->
1015			    _ = disk_log:close(Name),
1016			    throw(badfile);
1017			false ->
1018                            {ok, Name}
1019		    end;
1020		{error, Other1} ->
1021		    throw({read_error, Other1});
1022		Other2 ->
1023		    throw(Other2)
1024	    end,
1025	{ok, Major, Minor, FtOptions, MD5State, FullHeader, DLContext} =
1026            try get_header_data(Name, Verify)
1027            catch
1028                badfile ->
1029                    _ = disk_log:close(Name),
1030                    throw(badfile)
1031            end,
1032	try
1033	    if
1034		Major > ?MAJOR_F2T_VERSION ->
1035		    throw({unsupported_file_version,{Major,Minor}});
1036		true ->
1037		    ok
1038	    end,
1039	    {ok, Tab, HeadCount} = create_tab(FullHeader, TabArg),
1040	    StrippedOptions =
1041	        case Verify of
1042		    true ->
1043			FtOptions;
1044		    false ->
1045			#filetab_options{}
1046		end,
1047	    {ReadFun,InitState} =
1048	        case StrippedOptions#filetab_options.md5sum of
1049		    true ->
1050			{fun({OldMD5State,OldCount,_OL,ODLContext} = OS) ->
1051				 case wrap_bchunk(Name,ODLContext,100,Verify) of
1052				     eof ->
1053					 {OS,[]};
1054				     {NDLContext,Blist} ->
1055					 {Termlist, NewMD5State,
1056					  NewCount,NewLast} =
1057					     md5_and_convert(Blist,
1058							     OldMD5State,
1059							     OldCount),
1060					 {{NewMD5State, NewCount,
1061					   NewLast,NDLContext},
1062					  Termlist}
1063				 end
1064			 end,
1065			 {MD5State,0,[],DLContext}};
1066		    false ->
1067			{fun({_,OldCount,_OL,ODLContext} = OS) ->
1068				 case wrap_chunk(Name,ODLContext,100,Verify) of
1069				     eof ->
1070					 {OS,[]};
1071				     {NDLContext,List} ->
1072					 {NewLast,NewCount,NewList} =
1073					     scan_for_endinfo(List, OldCount),
1074					 {{false,NewCount,NewLast,NDLContext},
1075					  NewList}
1076				 end
1077			 end,
1078			 {false,0,[],DLContext}}
1079		end,
1080	    try
1081		do_read_and_verify(ReadFun,InitState,Tab,
1082				   StrippedOptions,HeadCount,Verify)
1083	    catch
1084		throw:TReason ->
1085		    ets:delete(Tab),
1086		    throw(TReason);
1087		exit:ExReason ->
1088		    ets:delete(Tab),
1089		    exit(ExReason);
1090		error:ErReason:StackTrace ->
1091		    ets:delete(Tab),
1092		    erlang:raise(error,ErReason,StackTrace)
1093	    end
1094	after
1095	    _ = disk_log:close(Name)
1096	end
1097    catch
1098	throw:TReason2 ->
1099	    {error,TReason2};
1100	exit:ExReason2 ->
1101	    {error,ExReason2}
1102    end.
1103
1104do_read_and_verify(ReadFun,InitState,Tab,FtOptions,HeadCount,Verify) ->
1105    case load_table(ReadFun,InitState,Tab) of
1106	{ok,{_,FinalCount,[],_}} ->
1107	    case {FtOptions#filetab_options.md5sum,
1108		  FtOptions#filetab_options.object_count} of
1109		{false,false} ->
1110		    case Verify of
1111			false ->
1112			    ok;
1113			true ->
1114			    case FinalCount of
1115				HeadCount ->
1116				    ok;
1117				_ ->
1118				    throw(invalid_object_count)
1119			    end
1120		    end;
1121		_ ->
1122		    throw(badfile)
1123	    end,
1124	    {ok,Tab};
1125	{ok,{FinalMD5State,FinalCount,['$end_of_table',LastInfo],_}} ->
1126	    ECount = case lists:keyfind(count,1,LastInfo) of
1127			 {count,N} ->
1128			     N;
1129			 _ ->
1130			     false
1131		     end,
1132	    EMD5 = case lists:keyfind(md5,1,LastInfo) of
1133			 {md5,M} ->
1134			     M;
1135			 _ ->
1136			     false
1137		     end,
1138	    case FtOptions#filetab_options.md5sum of
1139		true ->
1140		    case erlang:md5_final(FinalMD5State) of
1141			EMD5 ->
1142			    ok;
1143			_MD5MisM ->
1144			    throw(checksum_error)
1145		    end;
1146		false ->
1147		    ok
1148	    end,
1149	    case FtOptions#filetab_options.object_count of
1150		true ->
1151		    case FinalCount of
1152			ECount ->
1153			    ok;
1154			_Other ->
1155			    throw(invalid_object_count)
1156		    end;
1157		false ->
1158		    %% Only use header count if no extended info
1159		    %% at all is present and verification is requested.
1160		    case {Verify,FtOptions#filetab_options.md5sum} of
1161			{true,false} ->
1162			    case FinalCount of
1163				HeadCount ->
1164				    ok;
1165				_Other2 ->
1166				     throw(invalid_object_count)
1167			    end;
1168			_ ->
1169			    ok
1170		    end
1171	    end,
1172	    {ok,Tab}
1173    end.
1174
1175parse_f2t_opts([],Verify,Tab) ->
1176    {ok,Verify,Tab};
1177parse_f2t_opts([{verify, true}|T],_OV,Tab) ->
1178    parse_f2t_opts(T,true,Tab);
1179parse_f2t_opts([{verify,false}|T],OV,Tab) ->
1180    parse_f2t_opts(T,OV,Tab);
1181parse_f2t_opts([{table,Tab}|T],OV,[]) ->
1182    parse_f2t_opts(T,OV,Tab);
1183parse_f2t_opts([Unexpected|_],_,_) ->
1184    throw({unknown_option,Unexpected});
1185parse_f2t_opts(Malformed,_,_) ->
1186    throw({malformed_option,Malformed}).
1187
1188count_mandatory([]) ->
1189    0;
1190count_mandatory([{Tag,_}|T]) when Tag =:= name;
1191				  Tag =:= type;
1192				  Tag =:= protection;
1193				  Tag =:= named_table;
1194				  Tag =:= keypos;
1195				  Tag =:= size ->
1196    1+count_mandatory(T);
1197count_mandatory([_|T]) ->
1198    count_mandatory(T).
1199
1200verify_header_mandatory(L) ->
1201    count_mandatory(L) =:= 6.
1202
1203wrap_bchunk(Name,C,N,true) ->
1204    case disk_log:bchunk(Name,C,N) of
1205	{_,_,X} when X > 0 ->
1206	    throw(badfile);
1207	{NC,Bin,_} ->
1208	    {NC,Bin};
1209	Y ->
1210	    Y
1211    end;
1212wrap_bchunk(Name,C,N,false) ->
1213    case disk_log:bchunk(Name,C,N) of
1214	{NC,Bin,_} ->
1215	    {NC,Bin};
1216	Y ->
1217	    Y
1218    end.
1219
1220wrap_chunk(Name,C,N,true) ->
1221    case disk_log:chunk(Name,C,N) of
1222	{_,_,X} when X > 0 ->
1223	    throw(badfile);
1224	{NC,TL,_} ->
1225	    {NC,TL};
1226	Y ->
1227	    Y
1228    end;
1229wrap_chunk(Name,C,N,false) ->
1230    case disk_log:chunk(Name,C,N) of
1231	{NC,TL,_} ->
1232	    {NC,TL};
1233	Y ->
1234	    Y
1235    end.
1236
1237get_header_data(Name,true) ->
1238    case wrap_bchunk(Name,start,1,true) of
1239	{C,[Bin]} when is_binary(Bin) ->
1240	    T = binary_to_term(Bin),
1241	    case T of
1242		Tup when is_tuple(Tup) ->
1243		    L = tuple_to_list(Tup),
1244		    case verify_header_mandatory(L) of
1245			false ->
1246			    throw(badfile);
1247			true ->
1248			    Major = case lists:keyfind(major,1,L) of
1249					{major,Maj} ->
1250					    Maj;
1251					_ ->
1252					    0
1253				    end,
1254			    Minor = case lists:keyfind(minor,1,L) of
1255					{minor,Min} ->
1256					    Min;
1257					_ ->
1258					    0
1259				    end,
1260			    FtOptions =
1261				case lists:keyfind(extended_info,1,L) of
1262				    {extended_info,I} when is_list(I) ->
1263					#filetab_options
1264					    {
1265					    object_count =
1266					      lists:member(object_count,I),
1267					    md5sum =
1268					      lists:member(md5sum,I)
1269					    };
1270				    _ ->
1271					#filetab_options{}
1272				end,
1273			    MD5Initial =
1274				case FtOptions#filetab_options.md5sum of
1275				    true ->
1276					X = erlang:md5_init(),
1277					erlang:md5_update(X,Bin);
1278				    false ->
1279					false
1280				end,
1281			    {ok, Major, Minor, FtOptions, MD5Initial, L, C}
1282		    end;
1283		_X ->
1284		    throw(badfile)
1285	    end;
1286	_Y ->
1287	    throw(badfile)
1288    end;
1289
1290get_header_data(Name, false) ->
1291   case wrap_chunk(Name, start, 1, false) of
1292       {C,[Tup]} when is_tuple(Tup) ->
1293	   L = tuple_to_list(Tup),
1294	   case verify_header_mandatory(L) of
1295	       false ->
1296		   throw(badfile);
1297	       true ->
1298		   Major = case lists:keyfind(major_version, 1, L) of
1299			       {major_version, Maj} ->
1300				   Maj;
1301			       _ ->
1302				   0
1303			   end,
1304		   Minor = case lists:keyfind(minor_version, 1, L) of
1305			       {minor_version, Min} ->
1306				   Min;
1307			       _ ->
1308				   0
1309			   end,
1310		   FtOptions =
1311		       case lists:keyfind(extended_info, 1, L) of
1312			   {extended_info, I} when is_list(I) ->
1313			       #filetab_options
1314					 {
1315					 object_count =
1316					 lists:member(object_count,I),
1317					 md5sum =
1318					 lists:member(md5sum,I)
1319					};
1320			   _ ->
1321			       #filetab_options{}
1322		       end,
1323		   {ok, Major, Minor, FtOptions, false, L, C}
1324	   end;
1325       _ ->
1326	   throw(badfile)
1327    end.
1328
1329md5_and_convert([], MD5State, Count) ->
1330    {[],MD5State,Count,[]};
1331md5_and_convert([H|T], MD5State, Count) when is_binary(H) ->
1332    case (catch binary_to_term(H)) of
1333	{'EXIT', _} ->
1334	    md5_and_convert(T,MD5State,Count);
1335	['$end_of_table',_Dat] = L ->
1336	   {[],MD5State,Count,L};
1337	Term ->
1338	    X = erlang:md5_update(MD5State, H),
1339	    {Rest,NewMD5,NewCount,NewLast} = md5_and_convert(T, X, Count+1),
1340	    {[Term | Rest],NewMD5,NewCount,NewLast}
1341    end.
1342
1343scan_for_endinfo([], Count) ->
1344    {[],Count,[]};
1345scan_for_endinfo([['$end_of_table',Dat]], Count) ->
1346    {['$end_of_table',Dat],Count,[]};
1347scan_for_endinfo([Term|T], Count) ->
1348    {NewLast,NCount,Rest} = scan_for_endinfo(T, Count+1),
1349    {NewLast,NCount,[Term | Rest]}.
1350
1351load_table(ReadFun, State, Tab) ->
1352    {NewState,NewData} = ReadFun(State),
1353    case NewData of
1354	[] ->
1355	    {ok,NewState};
1356	List ->
1357	    ets:insert(Tab, List),
1358	    load_table(ReadFun, NewState, Tab)
1359    end.
1360
1361create_tab(I, TabArg) ->
1362    {name, Name} = lists:keyfind(name, 1, I),
1363    {type, Type} = lists:keyfind(type, 1, I),
1364    {protection, P} = lists:keyfind(protection, 1, I),
1365    {keypos, _Kp} = Keypos = lists:keyfind(keypos, 1, I),
1366    {size, Sz} = lists:keyfind(size, 1, I),
1367    L1 = [Type, P, Keypos],
1368    L2 = case lists:keyfind(named_table, 1, I) of
1369             {named_table, true} -> [named_table | L1];
1370	     {named_table, false} -> L1
1371	 end,
1372    L3 = case lists:keyfind(compressed, 1, I) of
1373	     {compressed, true} -> [compressed | L2];
1374	     {compressed, false} -> L2;
1375	     false -> L2
1376	 end,
1377    L4 = case lists:keyfind(write_concurrency, 1, I) of
1378	     {write_concurrency, _}=Wcc -> [Wcc | L3];
1379	     _ -> L3
1380	 end,
1381    L5 = case lists:keyfind(read_concurrency, 1, I) of
1382	     {read_concurrency, _}=Rcc -> [Rcc | L4];
1383	     false -> L4
1384	 end,
1385    case TabArg of
1386        [] ->
1387	    try
1388		Tab = ets:new(Name, L5),
1389		{ok, Tab, Sz}
1390	    catch _:_ ->
1391		throw(cannot_create_table)
1392            end;
1393        _ ->
1394            {ok, TabArg, Sz}
1395    end.
1396
1397
1398%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1399%% tabfile_info/1 reads the head information in an ets table dumped to
1400%% disk by means of file2tab and returns a list of the relevant table
1401%% information
1402%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1403
1404-spec tabfile_info(Filename) -> {'ok', TableInfo} | {'error', Reason} when
1405      Filename :: file:name(),
1406      TableInfo :: [InfoItem],
1407      InfoItem :: {'name', atom()}
1408                | {'type', Type}
1409                | {'protection', Protection}
1410                | {'named_table', boolean()}
1411                | {'keypos', non_neg_integer()}
1412                | {'size', non_neg_integer()}
1413                | {'extended_info', [ExtInfo]}
1414                | {'version', {Major :: non_neg_integer(),
1415                               Minor :: non_neg_integer()}},
1416      ExtInfo :: 'md5sum' | 'object_count',
1417      Type :: 'bag' | 'duplicate_bag' | 'ordered_set' | 'set',
1418      Protection :: 'private' | 'protected' | 'public',
1419      Reason :: term().
1420
1421tabfile_info(File) when is_list(File) ; is_atom(File) ->
1422    try
1423	Name = make_ref(),
1424        {ok, Name} =
1425	    case disk_log:open([{name, Name},
1426				{file, File},
1427				{mode, read_only}]) of
1428		{ok, Name} ->
1429                    {ok, Name};
1430		{repaired, Name, _,_} -> %Uh? cannot happen?
1431		    {ok, Name};
1432		{error, Other1} ->
1433		    throw({read_error, Other1});
1434		Other2 ->
1435		    throw(Other2)
1436	    end,
1437	{ok, Major, Minor, _FtOptions, _MD5State, FullHeader, _DLContext} =
1438            try get_header_data(Name, false)
1439            catch
1440                badfile ->
1441                    _ = disk_log:close(Name),
1442                    throw(badfile)
1443            end,
1444        case disk_log:close(Name) of
1445            ok -> ok;
1446            {error, Reason} -> throw(Reason)
1447        end,
1448	{value, N} = lists:keysearch(name, 1, FullHeader),
1449	{value, Type} = lists:keysearch(type, 1, FullHeader),
1450	{value, P} = lists:keysearch(protection, 1, FullHeader),
1451	{value, Val} = lists:keysearch(named_table, 1, FullHeader),
1452	{value, Kp} = lists:keysearch(keypos, 1, FullHeader),
1453	{value, Sz} = lists:keysearch(size, 1, FullHeader),
1454	Ei = case lists:keyfind(extended_info, 1, FullHeader) of
1455		 false -> {extended_info, []};
1456		 Ei0 -> Ei0
1457	     end,
1458	{ok, [N,Type,P,Val,Kp,Sz,Ei,{version,{Major,Minor}}]}
1459    catch
1460	throw:TReason ->
1461	    {error,TReason};
1462	exit:ExReason ->
1463	    {error,ExReason}
1464    end.
1465
1466-spec table(Tab) -> QueryHandle when
1467      Tab :: tab(),
1468      QueryHandle :: qlc:query_handle().
1469
1470table(Tab) ->
1471    table(Tab, []).
1472
1473-spec table(Tab, Options) -> QueryHandle when
1474      Tab :: tab(),
1475      QueryHandle :: qlc:query_handle(),
1476      Options :: [Option] | Option,
1477      Option :: {'n_objects', NObjects}
1478              | {'traverse', TraverseMethod},
1479      NObjects :: 'default' | pos_integer(),
1480      TraverseMethod :: 'first_next' | 'last_prev'
1481                      | 'select' | {'select', MatchSpec :: match_spec()}.
1482
1483table(Tab, Opts) ->
1484    case options(Opts, [traverse, n_objects]) of
1485        {badarg,_} ->
1486            erlang:error(badarg, [Tab, Opts]);
1487        [[Traverse, NObjs], QlcOptions] ->
1488            TF = case Traverse of
1489                     first_next ->
1490                         fun() -> qlc_next(Tab, ets:first(Tab)) end;
1491                     last_prev ->
1492                         fun() -> qlc_prev(Tab, ets:last(Tab)) end;
1493                     select ->
1494                         fun(MS) -> qlc_select(ets:select(Tab, MS, NObjs)) end;
1495                     {select, MS} ->
1496                         fun() -> qlc_select(ets:select(Tab, MS, NObjs)) end
1497                 end,
1498            PreFun = fun(_) -> ets:safe_fixtable(Tab, true) end,
1499            PostFun = fun() -> ets:safe_fixtable(Tab, false) end,
1500            InfoFun = fun(Tag) -> table_info(Tab, Tag) end,
1501            KeyEquality = case ets:info(Tab, type) of
1502                              ordered_set -> '==';
1503                              _ -> '=:='
1504                          end,
1505            LookupFun =
1506                case Traverse of
1507                    {select, _MS} ->
1508                        undefined;
1509                    _ ->
1510                        fun(_Pos, [K]) ->
1511                                ets:lookup(Tab, K);
1512                           (_Pos, Ks) ->
1513                                lists:flatmap(fun(K) -> ets:lookup(Tab, K)
1514                                              end, Ks)
1515                        end
1516                end,
1517            FormatFun =
1518                fun({all, _NElements, _ElementFun}) ->
1519                        As = [Tab | [Opts || _ <- [[]], Opts =/= []]],
1520                        {?MODULE, table, As};
1521                   ({match_spec, MS}) ->
1522                        {?MODULE, table,
1523                         [Tab, [{traverse, {select, MS}} |
1524                                listify(Opts)]]};
1525                   ({lookup, _KeyPos, [Value], _NElements, ElementFun}) ->
1526                        io_lib:format("~w:lookup(~w, ~w)",
1527                                      [?MODULE, Tab, ElementFun(Value)]);
1528                   ({lookup, _KeyPos, Values, _NElements, ElementFun}) ->
1529                        Vals = [ElementFun(V) || V <- Values],
1530                        io_lib:format("lists:flatmap(fun(V) -> "
1531                                      "~w:lookup(~w, V) end, ~w)",
1532                                      [?MODULE, Tab, Vals])
1533                end,
1534            qlc:table(TF, [{pre_fun, PreFun}, {post_fun, PostFun},
1535                           {info_fun, InfoFun}, {format_fun, FormatFun},
1536                           {key_equality, KeyEquality},
1537                           {lookup_fun, LookupFun}] ++ QlcOptions)
1538    end.
1539
1540table_info(Tab, num_of_objects) ->
1541    ets:info(Tab, size);
1542table_info(Tab, keypos) ->
1543    ets:info(Tab, keypos);
1544table_info(Tab, is_unique_objects) ->
1545    ets:info(Tab, type) =/= duplicate_bag;
1546table_info(Tab, is_sorted_key) ->
1547    ets:info(Tab, type) =:= ordered_set;
1548table_info(_Tab, _) ->
1549    undefined.
1550
1551qlc_next(_Tab, '$end_of_table') ->
1552    [];
1553qlc_next(Tab, Key) ->
1554    ets:lookup(Tab, Key) ++ fun() -> qlc_next(Tab, ets:next(Tab, Key)) end.
1555
1556qlc_prev(_Tab, '$end_of_table') ->
1557    [];
1558qlc_prev(Tab, Key) ->
1559    ets:lookup(Tab, Key) ++ fun() -> qlc_prev(Tab, ets:prev(Tab, Key)) end.
1560
1561qlc_select('$end_of_table') ->
1562    [];
1563qlc_select({Objects, Cont}) ->
1564    Objects ++ fun() -> qlc_select(ets:select(Cont)) end.
1565
1566options(Options, Keys) when is_list(Options) ->
1567    options(Options, Keys, []);
1568options(Option, Keys) ->
1569    options([Option], Keys, []).
1570
1571options(Options, [Key | Keys], L) when is_list(Options) ->
1572    V = case lists:keyfind(Key, 1, Options) of
1573            {n_objects, default} ->
1574                {ok, default_option(Key)};
1575            {n_objects, NObjs} when is_integer(NObjs), NObjs >= 1 ->
1576                {ok, NObjs};
1577            {traverse, select} ->
1578                {ok, select};
1579            {traverse, {select, _MS} = Select} ->
1580                {ok, Select};
1581            {traverse, first_next} ->
1582                {ok, first_next};
1583            {traverse, last_prev} ->
1584                {ok, last_prev};
1585	    {Key, _} ->
1586		badarg;
1587	    false ->
1588		Default = default_option(Key),
1589		{ok, Default}
1590	end,
1591    case V of
1592	badarg ->
1593	    {badarg, Key};
1594	{ok,Value} ->
1595	    NewOptions = lists:keydelete(Key, 1, Options),
1596	    options(NewOptions, Keys, [Value | L])
1597    end;
1598options(Options, [], L) ->
1599    [lists:reverse(L), Options].
1600
1601default_option(traverse) -> select;
1602default_option(n_objects) -> 100.
1603
1604listify(L) when is_list(L) ->
1605    L;
1606listify(T) ->
1607    [T].
1608
1609%% End of table/2.
1610
1611%% Print info about all tabs on the tty
1612-spec i() -> 'ok'.
1613
1614i() ->
1615    hform('id', 'name', 'type', 'size', 'mem', 'owner'),
1616    io:format(" -------------------------------------"
1617	      "---------------------------------------\n"),
1618    lists:foreach(fun prinfo/1, tabs()),
1619    ok.
1620
1621tabs() ->
1622    lists:sort(ets:all()).
1623
1624prinfo(Tab) ->
1625    case catch prinfo2(Tab) of
1626	{'EXIT', _} ->
1627	    io:format("~-10s ... unreadable \n", [to_string(Tab)]);
1628	ok ->
1629	    ok
1630    end.
1631prinfo2(Tab) ->
1632    Name = ets:info(Tab, name),
1633    Type = ets:info(Tab, type),
1634    Size = ets:info(Tab, size),
1635    Mem = ets:info(Tab, memory),
1636    Owner = ets:info(Tab, owner),
1637    hform(Tab, Name, Type, Size, Mem, is_reg(Owner)).
1638
1639is_reg(Owner) ->
1640    case process_info(Owner, registered_name) of
1641	{registered_name, Name} -> Name;
1642	_ -> Owner
1643    end.
1644
1645%%% Arndt: this code used to truncate over-sized fields. Now it
1646%%% pushes the remaining entries to the right instead, rather than
1647%%% losing information.
1648hform(A0, B0, C0, D0, E0, F0) ->
1649    [A,B,C,D,E,F] = [to_string(T) || T <- [A0,B0,C0,D0,E0,F0]],
1650    A1 = pad_right(A, 15),
1651    B1 = pad_right(B, 17),
1652    C1 = pad_right(C, 5),
1653    D1 = pad_right(D, 6),
1654    E1 = pad_right(E, 8),
1655    %% no need to pad the last entry on the line
1656    io:format(" ~s ~s ~s ~s ~s ~s\n", [A1,B1,C1,D1,E1,F]).
1657
1658pad_right(String, Len) ->
1659    if
1660	length(String) >= Len ->
1661	    String;
1662	true ->
1663	    [Space] = " ",
1664	    String ++ lists:duplicate(Len - length(String), Space)
1665    end.
1666
1667to_string(X) ->
1668    lists:flatten(io_lib:format("~p", [X])).
1669
1670%% view a specific table
1671-spec i(Tab) -> 'ok' when
1672      Tab :: tab().
1673
1674i(Tab) ->
1675    i(Tab, 40).
1676
1677-spec i(tab(), pos_integer()) -> 'ok'.
1678
1679i(Tab, Height) ->
1680    i(Tab, Height, 80).
1681
1682-spec i(tab(), pos_integer(), pos_integer()) -> 'ok'.
1683
1684i(Tab, Height, Width) ->
1685    First = ets:first(Tab),
1686    display_items(Height, Width, Tab, First, 1, 1).
1687
1688display_items(Height, Width, Tab, '$end_of_table', Turn, Opos) ->
1689    P = 'EOT  (q)uit (p)Digits (k)ill /Regexp -->',
1690    choice(Height, Width, P, eot, Tab, '$end_of_table', Turn, Opos);
1691display_items(Height, Width, Tab, Key, Turn, Opos) when Turn < Height ->
1692    do_display(Height, Width, Tab, Key, Turn, Opos);
1693display_items(Height, Width, Tab, Key, Turn, Opos) when Turn >=  Height ->
1694    P = '(c)ontinue (q)uit (p)Digits (k)ill /Regexp -->',
1695    choice(Height, Width, P, normal, Tab, Key, Turn, Opos).
1696
1697choice(Height, Width, P, Mode, Tab, Key, Turn, Opos) ->
1698    case get_line(P, "c\n") of
1699	"c\n" when Mode =:= normal ->
1700	    do_display(Height, Width, Tab, Key, 1, Opos);
1701	"c\n" when is_tuple(Mode), element(1, Mode) =:= re ->
1702	    {re, Re} = Mode,
1703	    re_search(Height, Width, Tab, Key, Re, 1, Opos);
1704	"q\n" ->
1705	    ok;
1706	"k\n" ->
1707	    ets:delete(Tab),
1708	    ok;
1709	[$p|Digs]  ->
1710	    catch case catch list_to_integer(nonl(Digs)) of
1711		      {'EXIT', _} ->
1712			  io:put_chars("Bad digits\n");
1713		      Number when Mode =:= normal ->
1714			  print_number(Tab, ets:first(Tab), Number);
1715		      Number when Mode =:= eot ->
1716			  print_number(Tab, ets:first(Tab), Number);
1717		      Number -> %% regexp
1718			  {re, Re} = Mode,
1719			  print_re_num(Tab, ets:first(Tab), Number, Re)
1720		  end,
1721	    choice(Height, Width, P, Mode, Tab, Key, Turn, Opos);
1722	[$/|Regexp]   -> %% from regexp
1723	    case re:compile(nonl(Regexp),[unicode]) of
1724		{ok,Re} ->
1725		    re_search(Height, Width, Tab, ets:first(Tab), Re, 1, 1);
1726		{error,{ErrorString,_Pos}} ->
1727		    io:format("~ts\n", [ErrorString]),
1728		    choice(Height, Width, P, Mode, Tab, Key, Turn, Opos)
1729	    end;
1730        eof ->
1731            ok;
1732	_  ->
1733	    choice(Height, Width, P, Mode, Tab, Key, Turn, Opos)
1734    end.
1735
1736get_line(P, Default) ->
1737    case line_string(io:get_line(P)) of
1738	"\n" ->
1739	    Default;
1740	L ->
1741	    L
1742    end.
1743
1744%% If the standard input is set to binary mode
1745%% convert it to a list so we can properly match.
1746line_string(Binary) when is_binary(Binary) -> unicode:characters_to_list(Binary);
1747line_string(Other) -> Other.
1748
1749nonl(S) -> string:trim(S, trailing, "$\n").
1750
1751print_number(Tab, Key, Num) ->
1752    Os = ets:lookup(Tab, Key),
1753    Len = length(Os),
1754    if
1755	(Num - Len) < 1 ->
1756	    O = lists:nth(Num, Os),
1757	    io:format("~p~n", [O]); %% use ppterm here instead
1758	true ->
1759	    print_number(Tab, ets:next(Tab, Key), Num - Len)
1760    end.
1761
1762do_display(Height, Width, Tab, Key, Turn, Opos) ->
1763    Objs = ets:lookup(Tab, Key),
1764    do_display_items(Height, Width, Objs, Opos),
1765    Len = length(Objs),
1766    display_items(Height, Width, Tab, ets:next(Tab, Key), Turn+Len, Opos+Len).
1767
1768do_display_items(Height, Width, [Obj|Tail], Opos) ->
1769    do_display_item(Height, Width, Obj, Opos),
1770    do_display_items(Height, Width, Tail, Opos+1);
1771do_display_items(_Height, _Width, [], Opos) ->
1772    Opos.
1773
1774do_display_item(_Height, Width, I, Opos)  ->
1775    L = to_string(I),
1776    L2 = if
1777	     length(L) > Width - 8 ->
1778                 string:slice(L, 0, Width-13) ++ "  ...";
1779	     true ->
1780		 L
1781	 end,
1782    io:format("<~-4w> ~s~n", [Opos,L2]).
1783
1784re_search(Height, Width, Tab, '$end_of_table', Re, Turn, Opos) ->
1785    P = 'EOT  (q)uit (p)Digits (k)ill /Regexp -->',
1786    choice(Height, Width, P, {re, Re}, Tab, '$end_of_table', Turn, Opos);
1787re_search(Height, Width, Tab, Key, Re, Turn, Opos) when Turn < Height ->
1788    re_display(Height, Width, Tab, Key, ets:lookup(Tab, Key), Re, Turn, Opos);
1789re_search(Height, Width, Tab, Key, Re, Turn, Opos)  ->
1790    P = '(c)ontinue (q)uit (p)Digits (k)ill /Regexp -->',
1791    choice(Height, Width, P, {re, Re}, Tab, Key, Turn, Opos).
1792
1793re_display(Height, Width, Tab, Key, [], Re, Turn, Opos) ->
1794    re_search(Height, Width, Tab, ets:next(Tab, Key), Re, Turn, Opos);
1795re_display(Height, Width, Tab, Key, [H|T], Re, Turn, Opos) ->
1796    Str = to_string(H),
1797    case re:run(Str, Re, [{capture,none}]) of
1798	match ->
1799	    do_display_item(Height, Width, H, Opos),
1800	    re_display(Height, Width, Tab, Key, T, Re, Turn+1, Opos+1);
1801	nomatch ->
1802	    re_display(Height, Width, Tab, Key, T, Re, Turn, Opos)
1803    end.
1804
1805print_re_num(_,'$end_of_table',_,_) -> ok;
1806print_re_num(Tab, Key, Num, Re) ->
1807    Os = re_match(ets:lookup(Tab, Key), Re),
1808    Len = length(Os),
1809    if
1810	(Num - Len) < 1 ->
1811	    O = lists:nth(Num, Os),
1812	    io:format("~p~n", [O]); %% use ppterm here instead
1813	true ->
1814	    print_re_num(Tab, ets:next(Tab, Key), Num - Len, Re)
1815    end.
1816
1817re_match([], _) -> [];
1818re_match([H|T], Re) ->
1819    case re:run(to_string(H), Re, [{capture,none}]) of
1820	match ->
1821	    [H|re_match(T,Re)];
1822	nomatch ->
1823	    re_match(T, Re)
1824    end.
1825