annotate jamendo/sparql-archived/SeRQL/db.pl @ 27:d95e683fbd35 tip

Enable CORS on urispace redirects as well
author Chris Cannam
date Tue, 20 Feb 2018 14:52:02 +0000
parents df9685986338
children
rev   line source
Chris@0 1 /* $Id$
Chris@0 2
Chris@0 3 Part of SWI-Prolog
Chris@0 4
Chris@0 5 Author: Jan Wielemaker
Chris@0 6 E-mail: jan@swi.psy.uva.nl
Chris@0 7 WWW: http://www.swi-prolog.org
Chris@0 8 Copyright (C): 1985-2004, University of Amsterdam
Chris@0 9
Chris@0 10 This program is free software; you can redistribute it and/or
Chris@0 11 modify it under the terms of the GNU General Public License
Chris@0 12 as published by the Free Software Foundation; either version 2
Chris@0 13 of the License, or (at your option) any later version.
Chris@0 14
Chris@0 15 This program is distributed in the hope that it will be useful,
Chris@0 16 but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 18 GNU General Public License for more details.
Chris@0 19
Chris@0 20 You should have received a copy of the GNU Lesser General Public
Chris@0 21 License along with this library; if not, write to the Free Software
Chris@0 22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Chris@0 23
Chris@0 24 As a special exception, if you link this library with other files,
Chris@0 25 compiled with a Free Software compiler, to produce an executable, this
Chris@0 26 library does not by itself cause the resulting executable to be covered
Chris@0 27 by the GNU General Public License. This exception does not however
Chris@0 28 invalidate any other reasons why the executable file might be covered by
Chris@0 29 the GNU General Public License.
Chris@0 30 */
Chris@0 31
Chris@0 32
Chris@0 33 :- module(db,
Chris@0 34 [ (db_term)/1, % +Declarations
Chris@0 35
Chris@0 36 db_attach/2, % :File, +Options
Chris@0 37
Chris@0 38 db_assert/1, % :Term
Chris@0 39 db_retractall/1, % :Term
Chris@0 40
Chris@0 41 db_sync/1, % :What
Chris@0 42 db_sync_all/1, % +What
Chris@0 43
Chris@0 44 op(1150, fx, (db_term))
Chris@0 45 ]).
Chris@0 46 :- use_module(library(debug)).
Chris@0 47 :- use_module(library(error)).
Chris@0 48
Chris@0 49 /** <module> Provide persistent dynamic predicates
Chris@0 50
Chris@0 51 This module provide simple persistent storage for one or more dynamic
Chris@0 52 predicates. A database is always associated with a module. A module that
Chris@0 53 wishes to maintain a database must declare the terms that can be placed
Chris@0 54 in the database using the predicate db_term(?Term).
Chris@0 55
Chris@0 56 db_term/1 is called by attach_db/1 to initialise all database predicates
Chris@0 57 as dynamic. In this mode it is called as db_term(-Term) and must
Chris@0 58 enumerate all database terms. While loading a database, it is called as
Chris@0 59 db_term(+Term) and must succeed for any valid database term. If it
Chris@0 60 fails, a warning is printed and the term is not asserted into the
Chris@0 61 database.
Chris@0 62
Chris@0 63 Here is a typical example:
Chris@0 64
Chris@0 65 ==
Chris@0 66 db_term(user(_Name, _Group)).
Chris@0 67
Chris@0 68 ...,
Chris@0 69 db_attach('users.db'),
Chris@0 70
Chris@0 71 ...,
Chris@0 72 user(Name, Group),
Chris@0 73
Chris@0 74 ...,
Chris@0 75 db_assert(user('Bob', 'administrator')),
Chris@0 76 ==
Chris@0 77
Chris@0 78 @tbd Deal with amount of `dirtyness'. I.e. only _gc_ if more than
Chris@0 79 _X_ percent is dirty.
Chris@0 80 @tbd Provide db_retract/1?
Chris@0 81 @tbd Type safety
Chris@0 82 @tbd Thread safety
Chris@0 83 @tbd Transaction management?
Chris@0 84
Chris@0 85 @author Jan Wielemaker
Chris@0 86 */
Chris@0 87
Chris@0 88 :- meta_predicate
Chris@0 89 db_attach(:, +),
Chris@0 90 db_assert(:),
Chris@0 91 db_retractall(:),
Chris@0 92 db_sync(:).
Chris@0 93
Chris@0 94
Chris@0 95 /*******************************
Chris@0 96 * DB *
Chris@0 97 *******************************/
Chris@0 98
Chris@0 99 :- dynamic
Chris@0 100 db_file/3, % Module, File, Modified
Chris@0 101 db_stream/2, % Module, Stream
Chris@0 102 db_dirty/1, % Module
Chris@0 103 db_option/2. % Module, Name(Value)
Chris@0 104
Chris@0 105 :- multifile
Chris@0 106 (db_term)/2. % Module, Term
Chris@0 107
Chris@0 108
Chris@0 109 /*******************************
Chris@0 110 * DECLARATIONS *
Chris@0 111 *******************************/
Chris@0 112
Chris@0 113 %% db_term(+Spec)
Chris@0 114 %
Chris@0 115 % Declare dynamic database terms. Declarations appear in a
Chris@0 116 % directive and have the following format:
Chris@0 117 %
Chris@0 118 % ==
Chris@0 119 % :- db_term
Chris@0 120 % <callable>,
Chris@0 121 % <callable>,
Chris@0 122 % ...
Chris@0 123 % ==
Chris@0 124
Chris@0 125 db_term(Spec) :-
Chris@0 126 throw(error(context_error(nodirective, db_term(Spec)), _)).
Chris@0 127
Chris@0 128 compile_db_term(Var) -->
Chris@0 129 { var(Var), !,
Chris@0 130 type_error(callable, Var)
Chris@0 131 }.
Chris@0 132 compile_db_term((A,B)) --> !,
Chris@0 133 compile_db_term(A),
Chris@0 134 compile_db_term(B).
Chris@0 135 compile_db_term(Term) -->
Chris@0 136 { functor(Term, Name, Arity), % Validates Term as callable
Chris@0 137 prolog_load_context(module, Module)
Chris@0 138 },
Chris@0 139 [ :- dynamic(Name/Arity),
Chris@0 140 db:db_term(Module, Term)
Chris@0 141 ].
Chris@0 142
Chris@0 143 :- multifile
Chris@0 144 user:term_expansion/2.
Chris@0 145
Chris@0 146 user:term_expansion((:- db_term(Spec)), Clauses) :-
Chris@0 147 phrase(compile_db_term(Spec), Clauses).
Chris@0 148
Chris@0 149
Chris@0 150 /*******************************
Chris@0 151 * ATTACH *
Chris@0 152 *******************************/
Chris@0 153
Chris@0 154 %% attach_db(:File, +Options)
Chris@0 155 %
Chris@0 156 % Use File as persistent database for the calling module. The
Chris@0 157 % calling module must defined db_term/1 to declare the database
Chris@0 158 % terms. Defined options:
Chris@0 159 %
Chris@0 160 % * sync(+Sync)
Chris@0 161 % One of =close= (close journal after write), =flush=
Chris@0 162 % (default, flush journal after write) or =none=
Chris@0 163 % (handle as fully buffered stream).
Chris@0 164
Chris@0 165 db_attach(Spec, Options) :-
Chris@0 166 strip_module(Spec, Module, File),
Chris@0 167 db_set_options(Module, Options),
Chris@0 168 db_attach_file(Module, File).
Chris@0 169
Chris@0 170 db_set_options(Module, Options) :-
Chris@0 171 retractall(db_option(Module, _)),
Chris@0 172 option(sync(Sync), Options, flush),
Chris@0 173 must_be(oneof([close,flush,none]), Sync),
Chris@0 174 assert(db_option(Module, sync(Sync))).
Chris@0 175
Chris@0 176 db_attach_file(Module, File) :-
Chris@0 177 db_file(Module, Old, _), !, % we already have a db
Chris@0 178 ( Old == File
Chris@0 179 -> true
Chris@0 180 ; permission_error(attach, db, File)
Chris@0 181 ).
Chris@0 182 db_attach_file(Module, File) :-
Chris@0 183 db_load(Module, File), !.
Chris@0 184 db_attach_file(Module, File) :-
Chris@0 185 assert(db_file(Module, File, 0)).
Chris@0 186
Chris@0 187 db_load(Module, File) :-
Chris@0 188 retractall(db_file(Module, _, _)),
Chris@0 189 catch(open(File, read, In, [encoding(utf8)]), _, fail), !,
Chris@0 190 debug(db, 'Loading database ~w', [File]),
Chris@0 191 call_cleanup((read_action(In, T0),
Chris@0 192 load_db(T0, In, Module)),
Chris@0 193 close(In)),
Chris@0 194 debug(db, 'Loaded ~w', [File]),
Chris@0 195 time_file(File, Modified),
Chris@0 196 assert(db_file(Module, File, Modified)).
Chris@0 197
Chris@0 198 load_db(end_of_file, _, _) :- !.
Chris@0 199 load_db(assert(Term), In, Module) :-
Chris@0 200 db_term(Module, Term), !,
Chris@0 201 assert(Module:Term),
Chris@0 202 read_action(In, T1),
Chris@0 203 load_db(T1, In, Module).
Chris@0 204 load_db(retractall(Term), In, Module) :-
Chris@0 205 db_term(Module, Term), !,
Chris@0 206 retractall(Module:Term),
Chris@0 207 set_dirty(Module),
Chris@0 208 read_action(In, T1),
Chris@0 209 load_db(T1, In, Module).
Chris@0 210 load_db(Term, In, Module) :-
Chris@0 211 print_message(error, illegal_term(Term)),
Chris@0 212 read_action(In, T1),
Chris@0 213 load_db(T1, In, Module).
Chris@0 214
Chris@0 215 db_clean(Module) :-
Chris@0 216 retractall(db_dirty(Module)),
Chris@0 217 ( db_term(Module, Term),
Chris@0 218 retractall(Module:Term),
Chris@0 219 fail
Chris@0 220 ; true
Chris@0 221 ).
Chris@0 222
Chris@0 223 %% db_assert(:Term) is det.
Chris@0 224 %
Chris@0 225 % Assert Term into the database and record it for persistency.
Chris@0 226 % Note that if the on-disk file has been modified it is first
Chris@0 227 % reloaded.
Chris@0 228
Chris@0 229 db_assert(Spec) :-
Chris@0 230 strip_module(Spec, Module, Term),
Chris@0 231 assert(Module:Term),
Chris@0 232 persistent(Module, assert(Term)).
Chris@0 233
Chris@0 234 persistent(Module, Action) :-
Chris@0 235 ( db_stream(Module, Stream)
Chris@0 236 -> true
Chris@0 237 ; db_file(Module, File, _Modified)
Chris@0 238 -> db_sync(Module, reload), % Is this correct?
Chris@0 239 open(File, append, Stream,
Chris@0 240 [ close_on_abort(false),
Chris@0 241 encoding(utf8),
Chris@0 242 lock(write)
Chris@0 243 ]),
Chris@0 244 assert(db_stream(Module, Stream))
Chris@0 245 ; existence_error(db_file, Module)
Chris@0 246 ),
Chris@0 247 write_action(Stream, Action),
Chris@0 248 sync(Module, Stream).
Chris@0 249
Chris@0 250 %% sync(+Module, +Stream) is det.
Chris@0 251 %
Chris@0 252 % Synchronise journal after a write. Using =close=, the journal
Chris@0 253 % file is closed, making it easier to edit the file externally.
Chris@0 254 % Using =flush= flushes the stream but does not close it. This
Chris@0 255 % provides better performance. Using =none=, the stream is not
Chris@0 256 % even flushed. This makes the journal sensitive to crashes, but
Chris@0 257 % much faster.
Chris@0 258
Chris@0 259 sync(Module, Stream) :-
Chris@0 260 db_option(Module, sync(Sync)),
Chris@0 261 ( Sync == close
Chris@0 262 -> db_sync(Module, close)
Chris@0 263 ; Sync == flush
Chris@0 264 -> flush_output(Stream)
Chris@0 265 ; true
Chris@0 266 ).
Chris@0 267
Chris@0 268 read_action(Stream, Action) :-
Chris@0 269 read_term(Stream, Action, [module(db)]).
Chris@0 270
Chris@0 271 write_action(Stream, Action) :-
Chris@0 272 \+ \+ ( numbervars(Action, 0, _, [singletons(true)]),
Chris@0 273 format(Stream, '~W.~n',
Chris@0 274 [ Action,
Chris@0 275 [ quoted(true),
Chris@0 276 numbervars(true),
Chris@0 277 module(db)
Chris@0 278 ]
Chris@0 279 ])
Chris@0 280 ).
Chris@0 281
Chris@0 282 %% db_retractall(:Term) is det.
Chris@0 283 %
Chris@0 284 % Retract all matching facts and do the same in the database. If
Chris@0 285 % Term is unbound, db_term/1 from the calling module is used as
Chris@0 286 % generator.
Chris@0 287 %
Chris@0 288 % @tbd Only flag dirty if clauses are deleted.
Chris@0 289
Chris@0 290 db_retractall(Spec) :-
Chris@0 291 strip_module(Spec, Module, Term),
Chris@0 292 ( var(Term)
Chris@0 293 -> forall(db_term(Module, Term),
Chris@0 294 db_retractall(Module:Term))
Chris@0 295 ; retractall(Module:Term),
Chris@0 296 set_dirty(Module),
Chris@0 297 persistent(Module, retractall(Term))
Chris@0 298 ).
Chris@0 299
Chris@0 300
Chris@0 301 set_dirty(Module) :-
Chris@0 302 ( db_dirty(Module)
Chris@0 303 -> true
Chris@0 304 ; assert(db_dirty(Module))
Chris@0 305 ).
Chris@0 306
Chris@0 307 %% db_sync(?What)
Chris@0 308 %
Chris@0 309 % Synchronise database with the associated file. What is one of:
Chris@0 310 %
Chris@0 311 % * reload
Chris@0 312 % Database is reloaded from file
Chris@0 313 % * gc
Chris@0 314 % Database was re-written, deleting all retractall
Chris@0 315 % statements.
Chris@0 316 % * close
Chris@0 317 % Database stream was closed
Chris@0 318 % * nop
Chris@0 319 % No-operation performed
Chris@0 320 %
Chris@0 321 % With unbound What, db_sync/1 will reload the database if it was
Chris@0 322 % modified on disk, gc it if it is dirty and close it if it is
Chris@0 323 % opened.
Chris@0 324
Chris@0 325 db_sync(Spec) :-
Chris@0 326 strip_module(Spec, Module, What),
Chris@0 327 db_sync(Module, What).
Chris@0 328
Chris@0 329
Chris@0 330 db_sync(Module, reload) :-
Chris@0 331 \+ db_stream(Module, _), % not open
Chris@0 332 db_file(Module, File, ModifiedWhenLoaded),
Chris@0 333 catch(time_file(File, Modified), _, fail),
Chris@0 334 Modified > ModifiedWhenLoaded, !, % Externally modified
Chris@0 335 debug(db, 'Database ~w was externally modified; reloading', [File]),
Chris@0 336 db_clean(Module),
Chris@0 337 db_load(Module, File).
Chris@0 338 db_sync(Module, gc) :-
Chris@0 339 db_dirty(Module), !,
Chris@0 340 db_sync(Module, close),
Chris@0 341 db_file(Module, File, Modified),
Chris@0 342 atom_concat(File, '.new', NewFile),
Chris@0 343 debug(db, 'Database ~w is dirty; cleaning', [File]),
Chris@0 344 open(NewFile, write, Out, [encoding(utf8)]),
Chris@0 345 ( db_term(Module, Term),
Chris@0 346 Module:Term,
Chris@0 347 write_action(Out, assert(Term)),
Chris@0 348 fail
Chris@0 349 ; true
Chris@0 350 ),
Chris@0 351 close(Out),
Chris@0 352 retractall(db_file(Module, File, Modified)),
Chris@0 353 rename_file(NewFile, File),
Chris@0 354 time_file(File, NewModified),
Chris@0 355 assert(db_file(Module, File, NewModified)).
Chris@0 356 db_sync(Module, close) :-
Chris@0 357 retract(db_stream(Module, Stream)), !,
Chris@0 358 db_file(Module, File, _),
Chris@0 359 debug(db, 'Database ~w is open; closing', [File]),
Chris@0 360 close(Stream),
Chris@0 361 time_file(File, Modified),
Chris@0 362 retractall(db_file(Module, File, _)),
Chris@0 363 assert(db_file(Module, File, Modified)).
Chris@0 364 db_sync(_, nop) :- !.
Chris@0 365 db_sync(_, _).
Chris@0 366
Chris@0 367
Chris@0 368 %% db_sync_all(+What)
Chris@0 369 %
Chris@0 370 % Sync all registered databases.
Chris@0 371
Chris@0 372 db_sync_all(What) :-
Chris@0 373 must_be(oneof([reload,gc,close]), What),
Chris@0 374 forall(db_file(Module, _, _),
Chris@0 375 db_sync(Module:What)).
Chris@0 376
Chris@0 377
Chris@0 378 /*******************************
Chris@0 379 * CLOSE *
Chris@0 380 *******************************/
Chris@0 381
Chris@0 382 close_dbs :-
Chris@0 383 forall(db_stream(_Module, Stream),
Chris@0 384 close(Stream)).
Chris@0 385
Chris@0 386 :- at_halt(close_dbs).
Chris@0 387
Chris@0 388