View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2009-2020, VU University, Amsterdam
    7                              CWI, Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(persistency,
   37          [ (persistent)/1,             % +Declarations
   38            current_persistent_predicate/1, % :PI
   39
   40            db_attach/2,                % :File, +Options
   41            db_detach/0,
   42            db_attached/1,              % :File
   43
   44            db_sync/1,                  % :What
   45            db_sync_all/1,              % +What
   46
   47            op(1150, fx, (persistent))
   48          ]).   49:- autoload(library(aggregate),[aggregate_all/3]).   50:- use_module(library(debug),[debug/3]).   51:- autoload(library(error),
   52	    [ instantiation_error/1,
   53	      must_be/2,
   54	      permission_error/3,
   55	      existence_error/2
   56	    ]).   57:- autoload(library(option),[option/3]).   58
   59
   60:- predicate_options(db_attach/2, 2,
   61                     [ sync(oneof([close,flush,none]))
   62                     ]).   63
   64/** <module> Provide persistent dynamic predicates
   65
   66This module provides simple persistent storage   for one or more dynamic
   67predicates. A database is always associated with a module. A module that
   68wishes to maintain a database must declare  the terms that can be placed
   69in the database using the directive persistent/1.
   70
   71The persistent/1 expands each declaration into four predicates:
   72
   73 - name(Arg, ...)
   74 - assert_name(Arg, ...)
   75 - retract_name(Arg, ...)
   76 - retractall_name(Arg, ...)
   77
   78As mentioned, a database can  only  be   accessed  from  within a single
   79module. This limitation is on purpose,  forcing   the  user to provide a
   80proper API for accessing the shared persistent data.
   81
   82This module requires  the  same   thread-synchronization  as  the normal
   83Prolog database. This implies that if  each individual assert or retract
   84takes the database from one consistent state  to the next, no additional
   85locking is required. If more than   one elementary database operation is
   86required to get from one consistent state to the next, both updating and
   87querying the database must be locked using with_mutex/2.
   88
   89Below is a simple example, where adding a  user does not need locking as
   90it is a single _assert_, while modifying  a user requires both a retract
   91and assert and thus needs to be locked.
   92
   93```
   94:- module(user_db,
   95          [ attach_user_db/1,           % +File
   96            current_user_role/2,        % ?User, ?Role
   97            add_user/2,                 % +User, +Role
   98            set_user_role/2             % +User, +Role
   99          ]).
  100:- use_module(library(persistency)).
  101
  102:- persistent
  103        user_role(name:atom, role:oneof([user,administrator])).
  104
  105attach_user_db(File) :-
  106        db_attach(File, []).
  107
  108%%      current_user_role(+Name, -Role) is semidet.
  109
  110current_user_role(Name, Role) :-
  111        with_mutex(user_db, user_role(Name, Role)).
  112
  113add_user(Name, Role) :-
  114        assert_user_role(Name, Role).
  115
  116set_user_role(Name, Role) :-
  117        user_role(Name, Role), !.
  118set_user_role(Name, Role) :-
  119        with_mutex(user_db,
  120                   (  retractall_user_role(Name, _),
  121                      assert_user_role(Name, Role))).
  122```
  123
  124@tbd    Provide type safety while loading
  125@tbd    Thread safety must now be provided at the user-level. Can we
  126        provide generic thread safety?  Basically, this means that we
  127        must wrap all exported predicates.  That might better be done
  128        outside this library.
  129@tbd    Transaction management?
  130@tbd    Should assert_<name> only assert if the database does not
  131        contain a variant?
  132@tbd	Since we have prolog_listen/2, we could use direct assert/1 and
  133	retract/1 and use the system hooks to deal with the updates.
  134*/
  135
  136:- meta_predicate
  137    db_attach(:, +),
  138    db_attached(:),
  139    db_sync(:),
  140    current_persistent_predicate(:).  141:- module_transparent
  142    db_detach/0.  143
  144
  145                 /*******************************
  146                 *              DB              *
  147                 *******************************/
  148
  149:- dynamic
  150    db_file/5,                      % Module, File, Created, Modified, EndPos
  151    db_stream/2,                    % Module, Stream
  152    db_dirty/2,                     % Module, Deleted
  153    db_option/2.                    % Module, Name(Value)
  154
  155:- volatile
  156    db_stream/2.  157
  158:- multifile
  159    (persistent)/3,                 % Module, Generic, Term
  160    prolog:generated_predicate/1.  161
  162
  163                 /*******************************
  164                 *         DECLARATIONS         *
  165                 *******************************/
  166
  167%!  persistent(+Spec)
  168%
  169%   Declare dynamic database terms. Declarations appear in a
  170%   directive and have the following format:
  171%
  172%   ==
  173%   :- persistent
  174%           <callable>,
  175%           <callable>,
  176%           ...
  177%   ==
  178%
  179%   Each specification is a callable term, following the conventions
  180%   of library(record), where each argument is of the form
  181%
  182%           name:type
  183%
  184%   Types are defined by library(error).
  185
  186persistent(Spec) :-
  187    throw(error(context_error(nodirective, persistent(Spec)), _)).
  188
  189compile_persistent(Var, _, _) -->
  190    { var(Var),
  191      !,
  192      instantiation_error(Var)
  193    }.
  194compile_persistent(M:Spec, _, LoadModule) -->
  195    !,
  196    compile_persistent(Spec, M, LoadModule).
  197compile_persistent((A,B), Module, LoadModule) -->
  198    !,
  199    compile_persistent(A, Module, LoadModule),
  200    compile_persistent(B, Module, LoadModule).
  201compile_persistent(Term, Module, LoadModule) -->
  202    { functor(Term, Name, Arity),           % Validates Term as callable
  203      functor(Generic, Name, Arity),
  204      qualify(Module, LoadModule, Name/Arity, Dynamic)
  205    },
  206    [ :- dynamic(Dynamic),
  207
  208      persistency:persistent(Module, Generic, Term)
  209    ],
  210    assert_clause(asserta, Term, Module, LoadModule),
  211    assert_clause(assert,  Term, Module, LoadModule),
  212    retract_clause(Term, Module, LoadModule),
  213    retractall_clause(Term, Module, LoadModule).
  214
  215assert_clause(Where, Term, Module, LoadModule) -->
  216    { functor(Term, Name, Arity),
  217      atomic_list_concat([Where,'_', Name], PredName),
  218      length(Args, Arity),
  219      Head =.. [PredName|Args],
  220      Assert =.. [Name|Args],
  221      type_checkers(Args, 1, Term, Check),
  222      atom_concat(db_, Where, DBActionName),
  223      DBAction =.. [DBActionName, Module:Assert],
  224      qualify(Module, LoadModule, Head, QHead),
  225      Clause = (QHead :- Check, persistency:DBAction)
  226    },
  227    [ Clause ].
  228
  229type_checkers([], _, _, true).
  230type_checkers([A0|AL], I, Spec, Check) :-
  231    arg(I, Spec, ArgSpec),
  232    (   ArgSpec = _Name:Type,
  233        nonvar(Type),
  234        Type \== any
  235    ->  Check = (must_be(Type, A0),More)
  236    ;   More = Check
  237    ),
  238    I2 is I + 1,
  239    type_checkers(AL, I2, Spec, More).
  240
  241retract_clause(Term, Module, LoadModule) -->
  242    { functor(Term, Name, Arity),
  243      atom_concat(retract_, Name, PredName),
  244      length(Args, Arity),
  245      Head =.. [PredName|Args],
  246      Retract =.. [Name|Args],
  247      qualify(Module, LoadModule, Head, QHead),
  248      Clause = (QHead :- persistency:db_retract(Module:Retract))
  249    },
  250    [ Clause ].
  251
  252retractall_clause(Term, Module, LoadModule) -->
  253    { functor(Term, Name, Arity),
  254      atom_concat(retractall_, Name, PredName),
  255      length(Args, Arity),
  256      Head =.. [PredName|Args],
  257      Retract =.. [Name|Args],
  258      qualify(Module, LoadModule, Head, QHead),
  259      Clause = (QHead :- persistency:db_retractall(Module:Retract))
  260    },
  261    [ Clause ].
  262
  263qualify(Module, Module, Head, Head) :- !.
  264qualify(Module, _LoadModule, Head, Module:Head).
  265
  266
  267:- multifile
  268    system:term_expansion/2.  269
  270system:term_expansion((:- persistent(Spec)), Clauses) :-
  271    prolog_load_context(module, Module),
  272    phrase(compile_persistent(Spec, Module, Module), Clauses).
  273
  274
  275%!  current_persistent_predicate(:PI) is nondet.
  276%
  277%   True if PI is a predicate that provides access to the persistent
  278%   database DB.
  279
  280current_persistent_predicate(M:PName/Arity) :-
  281    persistency:persistent(M, Generic, _),
  282    functor(Generic, Name, Arity),
  283    (   Name = PName
  284    ;   atom_concat(assert_, Name, PName)
  285    ;   atom_concat(retract_, Name, PName)
  286    ;   atom_concat(retractall_, Name, PName)
  287    ).
  288
  289prolog:generated_predicate(PI) :-
  290    current_persistent_predicate(PI).
  291
  292
  293                 /*******************************
  294                 *            ATTACH            *
  295                 *******************************/
  296
  297%!  db_attach(:File, +Options)
  298%
  299%   Use File as persistent database for  the calling module. The calling
  300%   module must defined persistent/1  to   declare  the  database terms.
  301%   Defined options:
  302%
  303%     - sync(+Sync)
  304%       One of =close= (close journal after write), =flush=
  305%       (default, flush journal after write) or =none=
  306%       (handle as fully buffered stream).
  307%
  308%   If File is already attached  this   operation  may change the `sync`
  309%   behaviour.
  310
  311db_attach(Module:File, Options) :-
  312    db_set_options(Module, Options),
  313    db_attach_file(Module, File).
  314
  315db_set_options(Module, Options) :-
  316    option(sync(Sync), Options, flush),
  317    must_be(oneof([close,flush,none]), Sync),
  318    (   db_option(Module, sync(Sync))
  319    ->  true
  320    ;   retractall(db_option(Module, _)),
  321        assert(db_option(Module, sync(Sync)))
  322    ).
  323
  324db_attach_file(Module, File) :-
  325    db_file(Module, Old, _, _, _),         % we already have a db
  326    !,
  327    (   Old == File
  328    ->  (   db_stream(Module, Stream)
  329        ->  sync(Module, Stream)
  330        ;   true
  331        )
  332    ;   permission_error(attach, db, File)
  333    ).
  334db_attach_file(Module, File) :-
  335    db_load(Module, File),
  336    !.
  337db_attach_file(Module, File) :-
  338    assert(db_file(Module, File, 0, 0, 0)).
  339
  340db_load(Module, File) :-
  341    retractall(db_file(Module, _, _, _, _)),
  342    debug(db, 'Loading database ~w', [File]),
  343    catch(setup_call_cleanup(
  344              open(File, read, In, [encoding(utf8)]),
  345              load_db_end(In, Module, Created, EndPos),
  346              close(In)),
  347          error(existence_error(source_sink, File), _), fail),
  348    debug(db, 'Loaded ~w', [File]),
  349    time_file(File, Modified),
  350    assert(db_file(Module, File, Created, Modified, EndPos)).
  351
  352db_load_incremental(Module, File) :-
  353    db_file(Module, File, Created, _, EndPos0),
  354    setup_call_cleanup(
  355        ( open(File, read, In, [encoding(utf8)]),
  356          read_action(In, created(Created0)),
  357          set_stream_position(In, EndPos0)
  358        ),
  359        ( Created0 == Created,
  360          debug(db, 'Incremental load from ~p', [EndPos0]),
  361          load_db_end(In, Module, _Created, EndPos)
  362        ),
  363        close(In)),
  364    debug(db, 'Updated ~w', [File]),
  365    time_file(File, Modified),
  366    retractall(db_file(Module, File, Created, _, _)),
  367    assert(db_file(Module, File, Created, Modified, EndPos)).
  368
  369load_db_end(In, Module, Created, End) :-
  370    read_action(In, T0),
  371    (   T0 = created(Created)
  372    ->  read_action(In, T1)
  373    ;   T1 = T0,
  374        Created = 0
  375    ),
  376    load_db(T1, In, Module),
  377    stream_property(In, position(End)).
  378
  379load_db(end_of_file, _, _) :- !.
  380load_db(assert(Term), In, Module) :-
  381    persistent(Module, Term, _Types),
  382    !,
  383    assert(Module:Term),
  384    read_action(In, T1),
  385    load_db(T1, In, Module).
  386load_db(asserta(Term), In, Module) :-
  387    persistent(Module, Term, _Types),
  388    !,
  389    asserta(Module:Term),
  390    read_action(In, T1),
  391    load_db(T1, In, Module).
  392load_db(retractall(Term, Count), In, Module) :-
  393    persistent(Module, Term, _Types),
  394    !,
  395    retractall(Module:Term),
  396    set_dirty(Module, Count),
  397    read_action(In, T1),
  398    load_db(T1, In, Module).
  399load_db(retract(Term), In, Module) :-
  400    persistent(Module, Term, _Types),
  401    !,
  402    (   retract(Module:Term)
  403    ->  set_dirty(Module, 1)
  404    ;   true
  405    ),
  406    read_action(In, T1),
  407    load_db(T1, In, Module).
  408load_db(Term, In, Module) :-
  409    print_message(error, illegal_term(Term)),
  410    read_action(In, T1),
  411    load_db(T1, In, Module).
  412
  413db_clean(Module) :-
  414    retractall(db_dirty(Module, _)),
  415    (   persistent(Module, Term, _Types),
  416        retractall(Module:Term),
  417        fail
  418    ;   true
  419    ).
  420
  421%!  db_size(+Module, -Terms) is det.
  422%
  423%   Terms is the total number of terms in the DB for Module.
  424
  425db_size(Module, Total) :-
  426    aggregate_all(sum(Count), persistent_size(Module, Count), Total).
  427
  428persistent_size(Module, Count) :-
  429    persistent(Module, Term, _Types),
  430    predicate_property(Module:Term, number_of_clauses(Count)).
  431
  432%!  db_attached(:File) is semidet.
  433%
  434%   True if the context module attached to the persistent database File.
  435
  436db_attached(Module:File) :-
  437    db_file(Module, File, _Created, _Modified, _EndPos).
  438
  439%!  db_assert(:Term) is det.
  440%
  441%   Assert Term into the database  and   record  it for persistency.
  442%   Note that if the on-disk file  has   been  modified  it is first
  443%   reloaded.
  444
  445:- public
  446    db_assert/1,
  447    db_asserta/1,
  448    db_retractall/1,
  449    db_retract/1.  450
  451db_assert(Term)     :- with_mutex('$persistency', db_assert_sync(Term)).
  452db_asserta(Term)    :- with_mutex('$persistency', db_asserta_sync(Term)).
  453db_retract(Term)    :- with_mutex('$persistency', db_retract_sync(Term)).
  454db_retractall(Term) :- with_mutex('$persistency', db_retractall_sync(Term)).
  455
  456db_assert_sync(Module:Term) :-
  457    assert(Module:Term),
  458    persistent(Module, assert(Term)).
  459
  460db_asserta_sync(Module:Term) :-
  461    asserta(Module:Term),
  462    persistent(Module, asserta(Term)).
  463
  464persistent(Module, Action) :-
  465    (   db_stream(Module, Stream)
  466    ->  true
  467    ;   db_file(Module, File, _Created, _Modified, _EndPos)
  468    ->  db_sync(Module, update),            % Is this correct?
  469        db_open_file(File, append, Stream),
  470        assert(db_stream(Module, Stream))
  471    ;   existence_error(db_file, Module)
  472    ),
  473    write_action(Stream, Action),
  474    sync(Module, Stream).
  475
  476db_open_file(File, Mode, Stream) :-
  477    open(File, Mode, Stream,
  478         [ close_on_abort(false),
  479           encoding(utf8),
  480           lock(write)
  481         ]),
  482    (   size_file(File, 0)
  483    ->  get_time(Now),
  484        write_action(Stream, created(Now))
  485    ;   true
  486    ).
  487
  488
  489%!  db_detach is det.
  490%
  491%   Detach persistency from  the  calling   module  and  delete  all
  492%   persistent clauses from the Prolog database.  Note that the file
  493%   is not affected. After  this  operation   another  file  may  be
  494%   attached,  providing  it   satisfies    the   same   persistency
  495%   declaration.
  496
  497db_detach :-
  498    context_module(Module),
  499    db_sync(Module:detach),
  500    db_clean(Module).
  501
  502
  503%!  sync(+Module, +Stream) is det.
  504%
  505%   Synchronise journal after a write.   Using  =close=, the journal
  506%   file is closed, making it easier   to  edit the file externally.
  507%   Using =flush= flushes the stream  but   does  not close it. This
  508%   provides better performance. Using  =none=,   the  stream is not
  509%   even flushed. This makes the journal   sensitive to crashes, but
  510%   much faster.
  511
  512sync(Module, Stream) :-
  513    db_option(Module, sync(Sync)),
  514    (   Sync == close
  515    ->  db_sync(Module, close)
  516    ;   Sync == flush
  517    ->  flush_output(Stream)
  518    ;   true
  519    ).
  520
  521read_action(Stream, Action) :-
  522    read_term(Stream, Action, [module(db)]).
  523
  524write_action(Stream, Action) :-
  525    \+ \+ ( numbervars(Action, 0, _, [singletons(true)]),
  526            format(Stream, '~W.~n',
  527                   [ Action,
  528                     [ quoted(true),
  529                       numbervars(true),
  530                       module(db)
  531                     ]
  532                   ])
  533          ).
  534
  535%!  db_retractall(:Term) is det.
  536%
  537%   Retract all matching facts and do the   same in the database. If
  538%   Term is unbound, persistent/1 from the   calling  module is used as
  539%   generator.
  540
  541db_retractall_sync(Module:Term) :-
  542    (   var(Term)
  543    ->  forall(persistent(Module, Term, _Types),
  544               db_retractall(Module:Term))
  545    ;   State = count(0),
  546        (   retract(Module:Term),
  547            arg(1, State, C0),
  548            C1 is C0+1,
  549            nb_setarg(1, State, C1),
  550            fail
  551        ;   arg(1, State, Count)
  552        ),
  553        (   Count > 0
  554        ->  set_dirty(Module, Count),
  555            persistent(Module, retractall(Term, Count))
  556        ;   true
  557        )
  558    ).
  559
  560
  561%!  db_retract(:Term) is nondet.
  562%
  563%   Retract terms from the database one-by-one.
  564
  565db_retract_sync(Module:Term) :-
  566    (   var(Term)
  567    ->  instantiation_error(Term)
  568    ;   retract(Module:Term),
  569        set_dirty(Module, 1),
  570        persistent(Module, retract(Term))
  571    ).
  572
  573
  574set_dirty(_, 0) :- !.
  575set_dirty(Module, Count) :-
  576    (   retract(db_dirty(Module, C0))
  577    ->  true
  578    ;   C0 = 0
  579    ),
  580    C1 is C0 + Count,
  581    assert(db_dirty(Module, C1)).
  582
  583%!  db_sync(:What)
  584%
  585%   Synchronise database with the associated file.  What is one of:
  586%
  587%     * reload
  588%     Database is reloaded from file if the file was modified
  589%     since loaded.
  590%     * update
  591%     As `reload`, but use incremental loading if possible.
  592%     This allows for two processes to examine the same database
  593%     file, where one writes the database and the other periodycally
  594%     calls db_sync(update) to follow the modified data.
  595%     * gc
  596%     Database was re-written, deleting all retractall
  597%     statements.  This is the same as gc(50).
  598%     * gc(Percentage)
  599%     GC DB if the number of deleted terms is greater than the given
  600%     percentage of the total number of terms.
  601%     * gc(always)
  602%     GC DB without checking the percentage.
  603%     * close
  604%     Database stream was closed
  605%     * detach
  606%     Remove all registered persistency for the calling module
  607%     * nop
  608%     No-operation performed
  609%
  610%   With unbound What, db_sync/1 reloads  the   database  if  it was
  611%   modified on disk, gc it if it  is   dirty  and close it if it is
  612%   opened.
  613
  614db_sync(Module:What) :-
  615    db_sync(Module, What).
  616
  617
  618db_sync(Module, reload) :-
  619    \+ db_stream(Module, _),                % not open
  620    db_file(Module, File, _Created, ModifiedWhenLoaded, _EndPos),
  621    catch(time_file(File, Modified), _, fail),
  622    Modified > ModifiedWhenLoaded,         % Externally modified
  623    !,
  624    debug(db, 'Database ~w was externally modified; reloading', [File]),
  625    !,
  626    (   catch(db_load_incremental(Module, File),
  627              E,
  628              ( print_message(warning, E), fail ))
  629    ->  true
  630    ;   db_clean(Module),
  631        db_load(Module, File)
  632    ).
  633db_sync(Module, gc) :-
  634    !,
  635    db_sync(Module, gc(50)).
  636db_sync(Module, gc(When)) :-
  637    (   When == always
  638    ->  true
  639    ;   db_dirty(Module, Dirty),
  640        db_size(Module, Total),
  641        (   Total > 0
  642        ->  Perc is (100*Dirty)/Total,
  643            Perc > When
  644        ;   Dirty > 0
  645        )
  646    ),
  647    !,
  648    db_sync(Module, close),
  649    db_file(Module, File, _, Modified, _),
  650    atom_concat(File, '.new', NewFile),
  651    debug(db, 'Database ~w is dirty; cleaning', [File]),
  652    get_time(Created),
  653    catch(setup_call_cleanup(
  654              db_open_file(NewFile, write, Out),
  655              (   persistent(Module, Term, _Types),
  656                  call(Module:Term),
  657                  write_action(Out, assert(Term)),
  658                  fail
  659              ;   stream_property(Out, position(EndPos))
  660              ),
  661              close(Out)),
  662          Error,
  663          ( catch(delete_file(NewFile),_,fail),
  664            throw(Error))),
  665    retractall(db_file(Module, File, _, Modified, _)),
  666    rename_file(NewFile, File),
  667    time_file(File, NewModified),
  668    assert(db_file(Module, File, Created, NewModified, EndPos)).
  669db_sync(Module, close) :-
  670    retract(db_stream(Module, Stream)),
  671    !,
  672    db_file(Module, File, Created, _, _),
  673    debug(db, 'Database ~w is open; closing', [File]),
  674    stream_property(Stream, position(EndPos)),
  675    close(Stream),
  676    time_file(File, Modified),
  677    retractall(db_file(Module, File, _, _, _)),
  678    assert(db_file(Module, File, Created, Modified, EndPos)).
  679db_sync(Module, Action) :-
  680    Action == detach,
  681    !,
  682    (   retract(db_stream(Module, Stream))
  683    ->  close(Stream)
  684    ;   true
  685    ),
  686    retractall(db_file(Module, _, _, _, _)),
  687    retractall(db_dirty(Module, _)),
  688    retractall(db_option(Module, _)).
  689db_sync(_, nop) :- !.
  690db_sync(_, _).
  691
  692
  693%!  db_sync_all(+What)
  694%
  695%   Sync all registered databases.
  696
  697db_sync_all(What) :-
  698    must_be(oneof([reload,gc,gc(_),close]), What),
  699    forall(db_file(Module, _, _, _, _),
  700           db_sync(Module:What)).
  701
  702
  703                 /*******************************
  704                 *             CLOSE            *
  705                 *******************************/
  706
  707close_dbs :-
  708    forall(retract(db_stream(_Module, Stream)),
  709           close(Stream)).
  710
  711:- at_halt(close_dbs).