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