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