34
35:- module(persistency,
36 [ (persistent)/1, 37 current_persistent_predicate/1, 38
39 db_attach/2, 40 db_detach/0,
41
42 db_sync/1, 43 db_sync_all/1, 44
45 op(1150, fx, (persistent))
46 ]).
47:- use_module(library(debug)).
48:- use_module(library(error)).
49:- use_module(library(aggregate)).
50
51:- predicate_options(db_attach/2, 2,
52 [ sync(oneof([close,flush,none]))
53 ]).
54
115
116:- meta_predicate
117 db_attach(:, +),
118 db_sync(:),
119 current_persistent_predicate(:).
120:- module_transparent
121 db_detach/0.
122
123
124 127
128:- dynamic
129 db_file/5, 130 db_stream/2, 131 db_dirty/2, 132 db_option/2. 133
134:- volatile
135 db_stream/2.
136
137:- multifile
138 (persistent)/3, 139 prolog:generated_predicate/1.
140
141
142 145
164
165persistent(Spec) :-
166 throw(error(context_error(nodirective, persistent(Spec)), _)).
167
168compile_persistent(Var, _, _) -->
169 { var(Var),
170 !,
171 instantiation_error(Var)
172 }.
173compile_persistent(M:Spec, _, LoadModule) -->
174 !,
175 compile_persistent(Spec, M, LoadModule).
176compile_persistent((A,B), Module, LoadModule) -->
177 !,
178 compile_persistent(A, Module, LoadModule),
179 compile_persistent(B, Module, LoadModule).
180compile_persistent(Term, Module, LoadModule) -->
181 { functor(Term, Name, Arity), % Validates Term as callable
182 functor(Generic, Name, Arity),
183 qualify(Module, LoadModule, Name/Arity, Dynamic)
184 },
185 [ :- dynamic(Dynamic),
186
187 persistency:persistent(Module, Generic, Term)
188 ],
189 assert_clause(asserta, Term, Module, LoadModule),
190 assert_clause(assert, Term, Module, LoadModule),
191 retract_clause(Term, Module, LoadModule),
192 retractall_clause(Term, Module, LoadModule).
193
194assert_clause(Where, Term, Module, LoadModule) -->
195 { functor(Term, Name, Arity),
196 atomic_list_concat([Where,'_', Name], PredName),
197 length(Args, Arity),
198 Head =.. [PredName|Args],
199 Assert =.. [Name|Args],
200 type_checkers(Args, 1, Term, Check),
201 atom_concat(db_, Where, DBActionName),
202 DBAction =.. [DBActionName, Module:Assert],
203 qualify(Module, LoadModule, Head, QHead),
204 Clause = (QHead :- Check, persistency:DBAction)
205 },
206 [ Clause ].
207
208type_checkers([], _, _, true).
209type_checkers([A0|AL], I, Spec, Check) :-
210 arg(I, Spec, ArgSpec),
211 ( ArgSpec = _Name:Type,
212 nonvar(Type),
213 Type \== any
214 -> Check = (must_be(Type, A0),More)
215 ; More = Check
216 ),
217 I2 is I + 1,
218 type_checkers(AL, I2, Spec, More).
219
220retract_clause(Term, Module, LoadModule) -->
221 { functor(Term, Name, Arity),
222 atom_concat(retract_, Name, PredName),
223 length(Args, Arity),
224 Head =.. [PredName|Args],
225 Retract =.. [Name|Args],
226 qualify(Module, LoadModule, Head, QHead),
227 Clause = (QHead :- persistency:db_retract(Module:Retract))
228 },
229 [ Clause ].
230
231retractall_clause(Term, Module, LoadModule) -->
232 { functor(Term, Name, Arity),
233 atom_concat(retractall_, Name, PredName),
234 length(Args, Arity),
235 Head =.. [PredName|Args],
236 Retract =.. [Name|Args],
237 qualify(Module, LoadModule, Head, QHead),
238 Clause = (QHead :- persistency:db_retractall(Module:Retract))
239 },
240 [ Clause ].
241
242qualify(Module, Module, Head, Head) :- !.
243qualify(Module, _LoadModule, Head, Module:Head).
244
245
246:- multifile
247 system:term_expansion/2.
248
249system:term_expansion((:- persistent(Spec)), Clauses) :-
250 prolog_load_context(module, Module),
251 phrase(compile_persistent(Spec, Module, Module), Clauses).
252
253
258
259current_persistent_predicate(M:PName/Arity) :-
260 persistency:persistent(M, Generic, _),
261 functor(Generic, Name, Arity),
262 ( Name = PName
263 ; atom_concat(assert_, Name, PName)
264 ; atom_concat(retract_, Name, PName)
265 ; atom_concat(retractall_, Name, PName)
266 ).
267
268prolog:generated_predicate(PI) :-
269 current_persistent_predicate(PI).
270
271
272 275
286
287db_attach(Module:File, Options) :-
288 db_set_options(Module, Options),
289 db_attach_file(Module, File).
290
291db_set_options(Module, Options) :-
292 retractall(db_option(Module, _)),
293 option(sync(Sync), Options, flush),
294 must_be(oneof([close,flush,none]), Sync),
295 assert(db_option(Module, sync(Sync))).
296
297db_attach_file(Module, File) :-
298 db_file(Module, Old, _, _, _), 299 !,
300 ( Old == File
301 -> true
302 ; permission_error(attach, db, File)
303 ).
304db_attach_file(Module, File) :-
305 db_load(Module, File),
306 !.
307db_attach_file(Module, File) :-
308 assert(db_file(Module, File, 0, 0, 0)).
309
310db_load(Module, File) :-
311 retractall(db_file(Module, _, _, _, _)),
312 debug(db, 'Loading database ~w', [File]),
313 catch(setup_call_cleanup(
314 open(File, read, In, [encoding(utf8)]),
315 load_db_end(In, Module, Created, EndPos),
316 close(In)),
317 error(existence_error(source_sink, File), _), fail),
318 debug(db, 'Loaded ~w', [File]),
319 time_file(File, Modified),
320 assert(db_file(Module, File, Created, Modified, EndPos)).
321
322db_load_incremental(Module, File) :-
323 db_file(Module, File, Created, _, EndPos0),
324 setup_call_cleanup(
325 ( open(File, read, In, [encoding(utf8)]),
326 read_action(In, created(Created0)),
327 set_stream_position(In, EndPos0)
328 ),
329 ( Created0 == Created,
330 debug(db, 'Incremental load from ~p', [EndPos0]),
331 load_db_end(In, Module, _Created, EndPos)
332 ),
333 close(In)),
334 debug(db, 'Updated ~w', [File]),
335 time_file(File, Modified),
336 retractall(db_file(Module, File, Created, _, _)),
337 assert(db_file(Module, File, Created, Modified, EndPos)).
338
339load_db_end(In, Module, Created, End) :-
340 read_action(In, T0),
341 ( T0 = created(Created)
342 -> read_action(In, T1)
343 ; T1 = T0,
344 Created = 0
345 ),
346 load_db(T1, In, Module),
347 stream_property(In, position(End)).
348
349load_db(end_of_file, _, _) :- !.
350load_db(assert(Term), In, Module) :-
351 persistent(Module, Term, _Types),
352 !,
353 assert(Module:Term),
354 read_action(In, T1),
355 load_db(T1, In, Module).
356load_db(asserta(Term), In, Module) :-
357 persistent(Module, Term, _Types),
358 !,
359 asserta(Module:Term),
360 read_action(In, T1),
361 load_db(T1, In, Module).
362load_db(retractall(Term, Count), In, Module) :-
363 persistent(Module, Term, _Types),
364 !,
365 retractall(Module:Term),
366 set_dirty(Module, Count),
367 read_action(In, T1),
368 load_db(T1, In, Module).
369load_db(retract(Term), In, Module) :-
370 persistent(Module, Term, _Types),
371 !,
372 ( retract(Module:Term)
373 -> set_dirty(Module, 1)
374 ; true
375 ),
376 read_action(In, T1),
377 load_db(T1, In, Module).
378load_db(Term, In, Module) :-
379 print_message(error, illegal_term(Term)),
380 read_action(In, T1),
381 load_db(T1, In, Module).
382
383db_clean(Module) :-
384 retractall(db_dirty(Module, _)),
385 ( persistent(Module, Term, _Types),
386 retractall(Module:Term),
387 fail
388 ; true
389 ).
390
394
395db_size(Module, Total) :-
396 aggregate_all(sum(Count), persistent_size(Module, Count), Total).
397
398persistent_size(Module, Count) :-
399 persistent(Module, Term, _Types),
400 predicate_property(Module:Term, number_of_clauses(Count)).
401
407
408:- public
409 db_assert/1,
410 db_asserta/1,
411 db_retractall/1,
412 db_retract/1.
413
414db_assert(Module:Term) :-
415 assert(Module:Term),
416 persistent(Module, assert(Term)).
417
418db_asserta(Module:Term) :-
419 asserta(Module:Term),
420 persistent(Module, asserta(Term)).
421
422persistent(Module, Action) :-
423 ( db_stream(Module, Stream)
424 -> true
425 ; db_file(Module, File, _Created, _Modified, _EndPos)
426 -> db_sync(Module, update), % Is this correct?
427 db_open_file(File, append, Stream),
428 assert(db_stream(Module, Stream))
429 ; existence_error(db_file, Module)
430 ),
431 write_action(Stream, Action),
432 sync(Module, Stream).
433
434db_open_file(File, Mode, Stream) :-
435 open(File, Mode, Stream,
436 [ close_on_abort(false),
437 encoding(utf8),
438 lock(write)
439 ]),
440 ( size_file(File, 0)
441 -> get_time(Now),
442 write_action(Stream, created(Now))
443 ; true
444 ).
445
446
454
455db_detach :-
456 context_module(Module),
457 db_sync(Module:detach),
458 db_clean(Module).
459
460
469
470sync(Module, Stream) :-
471 db_option(Module, sync(Sync)),
472 ( Sync == close
473 -> db_sync(Module, close)
474 ; Sync == flush
475 -> flush_output(Stream)
476 ; true
477 ).
478
479read_action(Stream, Action) :-
480 read_term(Stream, Action, [module(db)]).
481
482write_action(Stream, Action) :-
483 \+ \+ ( numbervars(Action, 0, _, [singletons(true)]),
484 format(Stream, '~W.~n',
485 [ Action,
486 [ quoted(true),
487 numbervars(true),
488 module(db)
489 ]
490 ])
491 ).
492
498
499db_retractall(Module:Term) :-
500 ( var(Term)
501 -> forall(persistent(Module, Term, _Types),
502 db_retractall(Module:Term))
503 ; State = count(0),
504 ( retract(Module:Term),
505 arg(1, State, C0),
506 C1 is C0+1,
507 nb_setarg(1, State, C1),
508 fail
509 ; arg(1, State, Count)
510 ),
511 ( Count > 0
512 -> set_dirty(Module, Count),
513 persistent(Module, retractall(Term, Count))
514 ; true
515 )
516 ).
517
518
522
523db_retract(Module:Term) :-
524 ( var(Term)
525 -> instantiation_error(Term)
526 ; retract(Module:Term),
527 set_dirty(Module, 1),
528 persistent(Module, retract(Term))
529 ).
530
531
532set_dirty(_, 0) :- !.
533set_dirty(Module, Count) :-
534 ( retract(db_dirty(Module, C0))
535 -> true
536 ; C0 = 0
537 ),
538 C1 is C0 + Count,
539 assert(db_dirty(Module, C1)).
540
569
570db_sync(Module:What) :-
571 db_sync(Module, What).
572
573
574db_sync(Module, reload) :-
575 \+ db_stream(Module, _), 576 db_file(Module, File, _Created, ModifiedWhenLoaded, _EndPos),
577 catch(time_file(File, Modified), _, fail),
578 Modified > ModifiedWhenLoaded, 579 !,
580 debug(db, 'Database ~w was externally modified; reloading', [File]),
581 !,
582 ( catch(db_load_incremental(Module, File),
583 E,
584 ( print_message(warning, E), fail ))
585 -> true
586 ; db_clean(Module),
587 db_load(Module, File)
588 ).
589db_sync(Module, gc) :-
590 !,
591 db_sync(Module, gc(50)).
592db_sync(Module, gc(When)) :-
593 db_dirty(Module, Dirty),
594 ( When == always
595 -> true
596 ; db_size(Module, Total),
597 ( Total > 0
598 -> Perc is (100*Dirty)/Total,
599 Perc > When
600 ; Dirty > 0
601 )
602 ),
603 !,
604 db_sync(Module, close),
605 db_file(Module, File, _, Modified, _),
606 atom_concat(File, '.new', NewFile),
607 debug(db, 'Database ~w is dirty; cleaning', [File]),
608 get_time(Created),
609 catch(setup_call_cleanup(
610 db_open_file(NewFile, write, Out),
611 ( persistent(Module, Term, _Types),
612 call(Module:Term),
613 write_action(Out, assert(Term)),
614 fail
615 ; stream_property(Out, position(EndPos))
616 ),
617 close(Out)),
618 Error,
619 ( catch(delete_file(NewFile),_,fail),
620 throw(Error))),
621 retractall(db_file(Module, File, _, Modified, _)),
622 rename_file(NewFile, File),
623 time_file(File, NewModified),
624 assert(db_file(Module, File, Created, NewModified, EndPos)).
625db_sync(Module, close) :-
626 retract(db_stream(Module, Stream)),
627 !,
628 db_file(Module, File, Created, _, _),
629 debug(db, 'Database ~w is open; closing', [File]),
630 stream_property(Stream, position(EndPos)),
631 close(Stream),
632 time_file(File, Modified),
633 retractall(db_file(Module, File, _, _, _)),
634 assert(db_file(Module, File, Created, Modified, EndPos)).
635db_sync(Module, Action) :-
636 Action == detach,
637 !,
638 ( retract(db_stream(Module, Stream))
639 -> close(Stream)
640 ; true
641 ),
642 retractall(db_file(Module, _, _, _, _)),
643 retractall(db_dirty(Module, _)),
644 retractall(db_option(Module, _)).
645db_sync(_, nop) :- !.
646db_sync(_, _).
647
648
652
653db_sync_all(What) :-
654 must_be(oneof([reload,gc,gc(_),close]), What),
655 forall(db_file(Module, _, _, _, _),
656 db_sync(Module:What)).
657
658
659 662
663close_dbs :-
664 forall(retract(db_stream(_Module, Stream)),
665 close(Stream)).
666
667:- at_halt(close_dbs).