35
36:- module(rdf_persistency,
37 [ rdf_attach_db/2, 38 rdf_detach_db/0, 39 rdf_current_db/1, 40 rdf_persistency/2, 41 rdf_flush_journals/1, 42 rdf_persistency_property/1, 43 rdf_journal_file/2, 44 rdf_snapshot_file/2, 45 rdf_db_to_file/2 46 ]). 47:- use_module(library(semweb/rdf_db),
48 [ rdf_graph/1, rdf_unload_graph/1, rdf_statistics/1,
49 rdf_load_db/1, rdf_retractall/4, rdf_create_graph/1,
50 rdf_assert/4, rdf_update/5, rdf_monitor/2, rdf/4,
51 rdf_save_db/2, rdf_atom_md5/3, rdf_current_ns/2,
52 rdf_register_ns/3
53 ]). 54
55:- autoload(library(apply),[maplist/2,maplist/3,partition/4,exclude/3]). 56:- use_module(library(debug),[debug/3]). 57:- autoload(library(error),
58 [permission_error/3,must_be/2,domain_error/2]). 59:- autoload(library(filesex),
60 [directory_file_path/3,make_directory_path/1]). 61:- autoload(library(lists),[select/3,append/3]). 62:- autoload(library(option),[option/2,option/3]). 63:- autoload(library(readutil),[read_file_to_terms/3]). 64:- autoload(library(socket),[gethostname/1]). 65:- autoload(library(thread),[concurrent/3]). 66:- autoload(library(uri),[uri_encoded/3]). 67
99
100:- volatile
101 rdf_directory/1,
102 rdf_lock/2,
103 rdf_option/1,
104 source_journal_fd/2,
105 file_base_db/2. 106:- dynamic
107 rdf_directory/1, 108 rdf_lock/2, 109 rdf_option/1, 110 source_journal_fd/2, 111 file_base_db/2. 112
113:- meta_predicate
114 no_agc(0). 115
116:- predicate_options(rdf_attach_db/2, 2,
117 [ access(oneof([read_write,read_only])),
118 concurrency(positive_integer),
119 max_open_journals(positive_integer),
120 silent(oneof([true,false,brief])),
121 log_nested_transactions(boolean)
122 ]). 123
171
172rdf_attach_db(DirSpec, Options) :-
173 option(access(read_only), Options),
174 !,
175 absolute_file_name(DirSpec,
176 Directory,
177 [ access(read),
178 file_type(directory)
179 ]),
180 rdf_attach_db_ro(Directory, Options).
181rdf_attach_db(DirSpec, Options) :-
182 option(access(read_write), Options),
183 !,
184 rdf_attach_db_rw(DirSpec, Options).
185rdf_attach_db(DirSpec, Options) :-
186 absolute_file_name(DirSpec,
187 Directory,
188 [ access(exist),
189 file_type(directory),
190 file_errors(fail)
191 ]),
192 !,
193 ( access_file(Directory, write)
194 -> catch(rdf_attach_db_rw(Directory, Options), E, true),
195 ( var(E)
196 -> true
197 ; E = error(permission_error(lock, rdf_db, _), _)
198 -> print_message(warning, E),
199 print_message(warning, rdf(read_only)),
200 rdf_attach_db(DirSpec, [access(read_only)|Options])
201 ; throw(E)
202 )
203 ; print_message(warning,
204 error(permission_error(write, directory, Directory))),
205 print_message(warning, rdf(read_only)),
206 rdf_attach_db_ro(Directory, Options)
207 ).
208rdf_attach_db(DirSpec, Options) :-
209 catch(rdf_attach_db_rw(DirSpec, Options), E, true),
210 ( var(E)
211 -> true
212 ; print_message(warning, E),
213 print_message(warning, rdf(read_only)),
214 rdf_attach_db(DirSpec, [access(read_only)|Options])
215 ).
216
217
218rdf_attach_db_rw(DirSpec, Options) :-
219 absolute_file_name(DirSpec,
220 Directory,
221 [ access(write),
222 file_type(directory),
223 file_errors(fail)
224 ]),
225 !,
226 ( rdf_directory(Directory)
227 -> true 228 ; rdf_detach_db,
229 mkdir(Directory),
230 lock_db(Directory),
231 assert(rdf_directory(Directory)),
232 assert_options(Options),
233 stop_monitor, 234 no_agc(load_db),
235 at_halt(rdf_detach_db),
236 start_monitor
237 ).
238rdf_attach_db_rw(DirSpec, Options) :-
239 absolute_file_name(DirSpec,
240 Directory,
241 [ solutions(all)
242 ]),
243 ( exists_directory(Directory)
244 -> access_file(Directory, write)
245 ; catch(make_directory(Directory), _, fail)
246 ),
247 !,
248 rdf_attach_db(Directory, Options).
249rdf_attach_db_rw(DirSpec, _) :- 250 absolute_file_name(DirSpec, 251 Directory,
252 [ access(exist),
253 file_type(directory)
254 ]),
255 permission_error(write, directory, Directory).
256
260
261rdf_attach_db_ro(Directory, Options) :-
262 rdf_detach_db,
263 assert(rdf_directory(Directory)),
264 assert_options(Options),
265 stop_monitor, 266 no_agc(load_db).
267
268
269assert_options([]).
270assert_options([H|T]) :-
271 ( option_type(H, Check)
272 -> Check,
273 assert(rdf_option(H))
274 ; true 275 ),
276 assert_options(T).
277
278option_type(concurrency(X), must_be(positive_integer, X)).
279option_type(max_open_journals(X), must_be(positive_integer, X)).
280option_type(directory_levels(X), must_be(positive_integer, X)).
281option_type(silent(X), must_be(oneof([true,false,brief]), X)).
282option_type(log_nested_transactions(X), must_be(boolean, X)).
283option_type(access(X), must_be(oneof([read_write,
284 read_only]), X)).
285
286
298
299rdf_persistency_property(Property) :-
300 var(Property),
301 !,
302 rdf_persistency_property_(Property).
303rdf_persistency_property(Property) :-
304 rdf_persistency_property_(Property),
305 !.
306
307rdf_persistency_property_(Property) :-
308 rdf_option(Property).
309rdf_persistency_property_(directory(Dir)) :-
310 rdf_directory(Dir).
311
317
318no_agc(Goal) :-
319 setup_call_cleanup(
320 push_prolog_flag(agc_margin, 0),
321 Goal,
322 pop_prolog_flag(agc_margin)).
323
324
330
331rdf_detach_db :-
332 debug(halt, 'Detaching RDF database', []),
333 stop_monitor,
334 close_journals,
335 ( retract(rdf_directory(Dir))
336 -> debug(halt, 'DB Directory: ~w', [Dir]),
337 save_prefixes(Dir),
338 retractall(rdf_option(_)),
339 retractall(source_journal_fd(_,_)),
340 unlock_db(Dir)
341 ; true
342 ).
343
344
348
349rdf_current_db(Directory) :-
350 rdf_directory(Dir),
351 !,
352 Dir = Directory.
353
354
365
366rdf_flush_journals(Options) :-
367 option(graph(Graph), Options, _),
368 forall(rdf_graph(Graph),
369 rdf_flush_journal(Graph, Options)).
370
371rdf_flush_journal(Graph, Options) :-
372 db_files(Graph, _SnapshotFile, JournalFile),
373 db_file(JournalFile, File),
374 ( \+ exists_file(File)
375 -> true
376 ; memberchk(min_size(KB), Options),
377 size_file(File, Size),
378 Size / 1024 < KB
379 -> true
380 ; create_db(Graph)
381 ).
382
383 386
392
393load_db :-
394 rdf_directory(Dir),
395 concurrency(Jobs),
396 cpu_stat_key(Jobs, StatKey),
397 get_time(Wall0),
398 statistics(StatKey, T0),
399 load_prefixes(Dir),
400 verbosity(Silent),
401 find_dbs(Dir, Graphs, SnapShots, Journals),
402 length(Graphs, GraphCount),
403 maplist(rdf_unload_graph, Graphs),
404 rdf_statistics(triples(Triples0)),
405 load_sources(snapshots, SnapShots, Silent, Jobs),
406 load_sources(journals, Journals, Silent, Jobs),
407 rdf_statistics(triples(Triples1)),
408 statistics(StatKey, T1),
409 get_time(Wall1),
410 T is T1 - T0,
411 Wall is Wall1 - Wall0,
412 Triples = Triples1 - Triples0,
413 message_level(Silent, Level),
414 print_message(Level, rdf(restore(attached(GraphCount, Triples, T/Wall)))).
415
416load_sources(_, [], _, _) :- !.
417load_sources(Type, Sources, Silent, Jobs) :-
418 length(Sources, Count),
419 RunJobs is min(Count, Jobs),
420 print_message(informational, rdf(restoring(Type, Count, RunJobs))),
421 make_goals(Sources, Silent, 1, Count, Goals),
422 concurrent(RunJobs, Goals, []).
423
424
426
427make_goals([], _, _, _, []).
428make_goals([DB|T0], Silent, I, Total,
429 [load_source(DB, Silent, I, Total)|T]) :-
430 I2 is I + 1,
431 make_goals(T0, Silent, I2, Total, T).
432
433verbosity(Silent) :-
434 rdf_option(silent(Silent)),
435 !.
436verbosity(Silent) :-
437 current_prolog_flag(verbose, silent),
438 !,
439 Silent = true.
440verbosity(brief).
441
442
446
447concurrency(Jobs) :-
448 rdf_option(concurrency(Jobs)),
449 !.
450concurrency(Jobs) :-
451 current_prolog_flag(cpu_count, Jobs),
452 Jobs > 0,
453 !.
454concurrency(1).
455
456cpu_stat_key(1, cputime) :- !.
457cpu_stat_key(_, process_cputime).
458
459
468
469find_dbs(Dir, Graphs, SnapBySize, JournalBySize) :-
470 directory_files(Dir, Files),
471 phrase(scan_db_files(Files, Dir, '.', 0), Scanned),
472 maplist(db_graph, Scanned, UnsortedGraphs),
473 sort(UnsortedGraphs, Graphs),
474 ( consider_reindex_db(Dir, Graphs, Scanned)
475 -> find_dbs(Dir, Graphs, SnapBySize, JournalBySize)
476 ; partition(db_is_snapshot, Scanned, Snapshots, Journals),
477 sort(Snapshots, SnapBySize),
478 sort(Journals, JournalBySize)
479 ).
480
481consider_reindex_db(Dir, Graphs, Scanned) :-
482 length(Graphs, Count),
483 Count > 0,
484 DepthNeeded is floor(log(Count)/log(256)),
485 ( maplist(depth_db(DepthNow), Scanned)
486 -> ( DepthNeeded > DepthNow
487 -> true
488 ; retractall(rdf_option(directory_levels(_))),
489 assertz(rdf_option(directory_levels(DepthNow))),
490 fail
491 )
492 ; true
493 ),
494 reindex_db(Dir, DepthNeeded).
495
496db_is_snapshot(Term) :-
497 arg(2, Term, trp).
498
499db_graph(Term, DB) :-
500 arg(3, Term, DB).
501
502db_file_name(Term, File) :-
503 arg(4, Term, File).
504
505depth_db(Depth, DB) :-
506 arg(5, DB, Depth).
507
512
513scan_db_files([], _, _, _) -->
514 [].
515scan_db_files([Nofollow|T], Dir, Prefix, Depth) -->
516 { nofollow(Nofollow) },
517 !,
518 scan_db_files(T, Dir, Prefix, Depth).
519scan_db_files([File|T], Dir, Prefix, Depth) -->
520 { file_name_extension(Base, Ext, File),
521 db_extension(Ext),
522 !,
523 rdf_db_to_file(DB, Base),
524 directory_file_path(Prefix, File, DBFile),
525 directory_file_path(Dir, DBFile, AbsFile),
526 size_file(AbsFile, Size)
527 },
528 [ db(Size, Ext, DB, AbsFile, Depth) ],
529 scan_db_files(T, Dir, Prefix, Depth).
530scan_db_files([D|T], Dir, Prefix, Depth) -->
531 { directory_file_path(Prefix, D, SubD),
532 directory_file_path(Dir, SubD, AbsD),
533 exists_directory(AbsD),
534 \+ read_link(AbsD, _, _), 535 !,
536 directory_files(AbsD, SubFiles),
537 SubDepth is Depth + 1
538 },
539 scan_db_files(SubFiles, Dir, SubD, SubDepth),
540 scan_db_files(T, Dir, Prefix, Depth).
541scan_db_files([_|T], Dir, Prefix, Depth) -->
542 scan_db_files(T, Dir, Prefix, Depth).
543
544nofollow(.).
545nofollow(..).
546
547db_extension(trp).
548db_extension(jrn).
549
550:- public load_source/4. 551
552load_source(DB, Silent, Nth, Total) :-
553 db_file_name(DB, File),
554 db_graph(DB, Graph),
555 message_level(Silent, Level),
556 graph_triple_count(Graph, Count0),
557 statistics(cputime, T0),
558 ( db_is_snapshot(DB)
559 -> print_message(Level, rdf(restore(Silent, snapshot(Graph, File)))),
560 rdf_load_db(File)
561 ; print_message(Level, rdf(restore(Silent, journal(Graph, File)))),
562 load_journal(File, Graph)
563 ),
564 statistics(cputime, T1),
565 T is T1 - T0,
566 graph_triple_count(Graph, Count1),
567 Count is Count1 - Count0,
568 print_message(Level, rdf(restore(Silent,
569 done(Graph, T, Count, Nth, Total)))).
570
571
572graph_triple_count(Graph, Count) :-
573 rdf_statistics(triples_by_graph(Graph, Count)),
574 !.
575graph_triple_count(_, 0).
576
577
582
583attach_graph(Graph, Options) :-
584 ( option(silent(true), Options)
585 -> Level = silent
586 ; Level = informational
587 ),
588 db_files(Graph, SnapshotFile, JournalFile),
589 rdf_retractall(_,_,_,Graph),
590 statistics(cputime, T0),
591 print_message(Level, rdf(restore(Silent, Graph))),
592 db_file(SnapshotFile, AbsSnapShot),
593 ( exists_file(AbsSnapShot)
594 -> print_message(Level, rdf(restore(Silent, snapshot(SnapshotFile)))),
595 rdf_load_db(AbsSnapShot)
596 ; true
597 ),
598 ( exists_db(JournalFile)
599 -> print_message(Level, rdf(restore(Silent, journal(JournalFile)))),
600 load_journal(JournalFile, Graph)
601 ; true
602 ),
603 statistics(cputime, T1),
604 T is T1 - T0,
605 ( rdf_statistics(triples_by_graph(Graph, Count))
606 -> true
607 ; Count = 0
608 ),
609 print_message(Level, rdf(restore(Silent,
610 done(Graph, T, Count)))).
611
612message_level(true, silent) :- !.
613message_level(_, informational).
614
615
616 619
624
625load_journal(File, DB) :-
626 rdf_create_graph(DB),
627 setup_call_cleanup(
628 open(File, read, In, [encoding(utf8)]),
629 ( read(In, T0),
630 process_journal(T0, In, DB)
631 ),
632 close(In)).
633
634process_journal(end_of_file, _, _) :- !.
635process_journal(Term, In, DB) :-
636 ( process_journal_term(Term, DB)
637 -> true
638 ; throw(error(type_error(journal_term, Term), _))
639 ),
640 read(In, T2),
641 process_journal(T2, In, DB).
642
643process_journal_term(assert(S,P,O), DB) :-
644 rdf_assert(S,P,O,DB).
645process_journal_term(assert(S,P,O,Line), DB) :-
646 rdf_assert(S,P,O,DB:Line).
647process_journal_term(retract(S,P,O), DB) :-
648 rdf_retractall(S,P,O,DB).
649process_journal_term(retract(S,P,O,Line), DB) :-
650 rdf_retractall(S,P,O,DB:Line).
651process_journal_term(update(S,P,O,Action), DB) :-
652 ( rdf_update(S,P,O,DB, Action)
653 -> true
654 ; print_message(warning, rdf(update_failed(S,P,O,Action)))
655 ).
656process_journal_term(start(_), _). 657process_journal_term(end(_), _).
658process_journal_term(begin(_), _). 659process_journal_term(end, _).
660process_journal_term(begin(_,_,_,_), _). 661process_journal_term(end(_,_,_), _).
662
663
664 667
668:- dynamic
669 blocked_db/2, 670 transaction_message/3, 671 transaction_db/3. 672
677
678rdf_persistency(DB, Bool) :-
679 must_be(atom, DB),
680 must_be(boolean, Bool),
681 fail.
682rdf_persistency(DB, false) :-
683 !,
684 ( blocked_db(DB, persistency)
685 -> true
686 ; assert(blocked_db(DB, persistency)),
687 delete_db(DB)
688 ).
689rdf_persistency(DB, true) :-
690 ( retract(blocked_db(DB, persistency))
691 -> create_db(DB)
692 ; true
693 ).
694
698
699:- multifile
700 rdf_db:property_of_graph/2. 701
702rdf_db:property_of_graph(persistent(State), Graph) :-
703 ( blocked_db(Graph, persistency)
704 -> State = false
705 ; State = true
706 ).
707
708
714
715start_monitor :-
716 rdf_monitor(monitor,
717 [ -assert(load)
718 ]).
719stop_monitor :-
720 rdf_monitor(monitor,
721 [ -all
722 ]).
723
730
731monitor(Msg) :-
732 debug(monitor, 'Monitor: ~p~n', [Msg]),
733 fail.
734monitor(assert(S,P,O,DB:Line)) :-
735 !,
736 \+ blocked_db(DB, _),
737 journal_fd(DB, Fd),
738 open_transaction(DB, Fd),
739 format(Fd, '~q.~n', [assert(S,P,O,Line)]),
740 sync_journal(DB, Fd).
741monitor(assert(S,P,O,DB)) :-
742 \+ blocked_db(DB, _),
743 journal_fd(DB, Fd),
744 open_transaction(DB, Fd),
745 format(Fd, '~q.~n', [assert(S,P,O)]),
746 sync_journal(DB, Fd).
747monitor(retract(S,P,O,DB:Line)) :-
748 !,
749 \+ blocked_db(DB, _),
750 journal_fd(DB, Fd),
751 open_transaction(DB, Fd),
752 format(Fd, '~q.~n', [retract(S,P,O,Line)]),
753 sync_journal(DB, Fd).
754monitor(retract(S,P,O,DB)) :-
755 \+ blocked_db(DB, _),
756 journal_fd(DB, Fd),
757 open_transaction(DB, Fd),
758 format(Fd, '~q.~n', [retract(S,P,O)]),
759 sync_journal(DB, Fd).
760monitor(update(S,P,O,DB:Line,Action)) :-
761 !,
762 \+ blocked_db(DB, _),
763 ( Action = graph(NewDB)
764 -> monitor(assert(S,P,O,NewDB)),
765 monitor(retract(S,P,O,DB:Line))
766 ; journal_fd(DB, Fd),
767 format(Fd, '~q.~n', [update(S,P,O,Action)]),
768 sync_journal(DB, Fd)
769 ).
770monitor(update(S,P,O,DB,Action)) :-
771 \+ blocked_db(DB, _),
772 ( Action = graph(NewDB)
773 -> monitor(assert(S,P,O,NewDB)),
774 monitor(retract(S,P,O,DB))
775 ; journal_fd(DB, Fd),
776 open_transaction(DB, Fd),
777 format(Fd, '~q.~n', [update(S,P,O,Action)]),
778 sync_journal(DB, Fd)
779 ).
780monitor(load(BE, _DumpFileURI)) :-
781 ( BE = end(Graphs)
782 -> sync_loaded_graphs(Graphs)
783 ; true
784 ).
785monitor(create_graph(Graph)) :-
786 \+ blocked_db(Graph, _),
787 journal_fd(Graph, Fd),
788 open_transaction(Graph, Fd),
789 sync_journal(Graph, Fd).
790monitor(reset) :-
791 forall(rdf_graph(Graph), delete_db(Graph)).
792 793
794monitor(transaction(BE, Id)) :-
795 monitor_transaction(Id, BE).
796
797monitor_transaction(load_journal(DB), begin(_)) :-
798 !,
799 assert(blocked_db(DB, journal)).
800monitor_transaction(load_journal(DB), end(_)) :-
801 !,
802 retractall(blocked_db(DB, journal)).
803
804monitor_transaction(parse(URI), begin(_)) :-
805 !,
806 ( blocked_db(URI, persistency)
807 -> true
808 ; assert(blocked_db(URI, parse))
809 ).
810monitor_transaction(parse(URI), end(_)) :-
811 !,
812 ( retract(blocked_db(URI, parse))
813 -> create_db(URI)
814 ; true
815 ).
816monitor_transaction(unload(DB), begin(_)) :-
817 !,
818 ( blocked_db(DB, persistency)
819 -> true
820 ; assert(blocked_db(DB, unload))
821 ).
822monitor_transaction(unload(DB), end(_)) :-
823 !,
824 ( retract(blocked_db(DB, unload))
825 -> delete_db(DB)
826 ; true
827 ).
828monitor_transaction(log(Msg), begin(N)) :-
829 !,
830 check_nested(N),
831 get_time(Time),
832 asserta(transaction_message(N, Time, Msg)).
833monitor_transaction(log(_), end(N)) :-
834 check_nested(N),
835 retract(transaction_message(N, _, _)),
836 !,
837 findall(DB:Id, retract(transaction_db(N, DB, Id)), DBs),
838 end_transactions(DBs, N).
839monitor_transaction(log(Msg, DB), begin(N)) :-
840 !,
841 check_nested(N),
842 get_time(Time),
843 asserta(transaction_message(N, Time, Msg)),
844 journal_fd(DB, Fd),
845 open_transaction(DB, Fd).
846monitor_transaction(log(Msg, _DB), end(N)) :-
847 monitor_transaction(log(Msg), end(N)).
848
849
855
856check_nested(0) :- !.
857check_nested(_) :-
858 rdf_option(log_nested_transactions(true)).
859
860
868
869open_transaction(DB, Fd) :-
870 transaction_message(N, Time, Msg),
871 !,
872 ( transaction_db(N, DB, _)
873 -> true
874 ; next_transaction_id(DB, Id),
875 assert(transaction_db(N, DB, Id)),
876 RoundedTime is round(Time*100)/100,
877 format(Fd, '~q.~n', [begin(Id, N, RoundedTime, Msg)])
878 ).
879open_transaction(_,_).
880
881
889
890:- dynamic
891 current_transaction_id/2. 892
893next_transaction_id(DB, Id) :-
894 retract(current_transaction_id(DB, Last)),
895 !,
896 Id is Last + 1,
897 assert(current_transaction_id(DB, Id)).
898next_transaction_id(DB, Id) :-
899 db_files(DB, _, Journal),
900 exists_file(Journal),
901 !,
902 size_file(Journal, Size),
903 open_db(Journal, read, In, []),
904 call_cleanup(iterative_expand(In, Size, Last), close(In)),
905 Id is Last + 1,
906 assert(current_transaction_id(DB, Id)).
907next_transaction_id(DB, 1) :-
908 assert(current_transaction_id(DB, 1)).
909
910iterative_expand(_, 0, 0) :- !.
911iterative_expand(In, Size, Last) :- 912 Max is floor(log(Size)/log(2)),
913 between(10, Max, Step),
914 Offset is -(1<<Step),
915 seek(In, Offset, eof, _),
916 skip(In, 10), 917 read(In, T0),
918 last_transaction_id(T0, In, 0, Last),
919 Last > 0,
920 !.
921iterative_expand(In, _, Last) :- 922 seek(In, 0, bof, _),
923 read(In, T0),
924 last_transaction_id(T0, In, 0, Last).
925
926last_transaction_id(end_of_file, _, Last, Last) :- !.
927last_transaction_id(end(Id, _, _), In, _, Last) :-
928 read(In, T1),
929 last_transaction_id(T1, In, Id, Last).
930last_transaction_id(_, In, Id, Last) :-
931 read(In, T1),
932 last_transaction_id(T1, In, Id, Last).
933
934
946
947end_transactions(DBs, N) :-
948 end_transactions(DBs, DBs, N).
949
950end_transactions([], _, _).
951end_transactions([DB:Id|T], DBs, N) :-
952 journal_fd(DB, Fd),
953 once(select(DB:Id, DBs, Others)),
954 format(Fd, 'end(~q, ~q, ~q).~n', [Id, N, Others]),
955 sync_journal(DB, Fd),
956 end_transactions(T, DBs, N).
957
958
963
964sync_loaded_graphs(Graphs) :-
965 maplist(create_db, Graphs).
966
967
968 971
979
980journal_fd(DB, Fd) :-
981 source_journal_fd(DB, Fd),
982 !.
983journal_fd(DB, Fd) :-
984 with_mutex(rdf_journal_file,
985 journal_fd_(DB, Out)),
986 Fd = Out.
987
988journal_fd_(DB, Fd) :-
989 source_journal_fd(DB, Fd),
990 !.
991journal_fd_(DB, Fd) :-
992 limit_fd_pool,
993 db_files(DB, _Snapshot, Journal),
994 open_db(Journal, append, Fd,
995 [ close_on_abort(false)
996 ]),
997 time_stamp(Now),
998 format(Fd, '~q.~n', [start([time(Now)])]),
999 assert(source_journal_fd(DB, Fd)). 1000
1007
1008limit_fd_pool :-
1009 predicate_property(source_journal_fd(_, _), number_of_clauses(N)),
1010 !,
1011 ( rdf_option(max_open_journals(Max))
1012 -> true
1013 ; Max = 10
1014 ),
1015 Close is N - Max,
1016 forall(between(1, Close, _),
1017 close_oldest_journal).
1018limit_fd_pool.
1019
1020close_oldest_journal :-
1021 source_journal_fd(DB, _Fd),
1022 !,
1023 debug(rdf_persistency, 'Closing old journal for ~q', [DB]),
1024 close_journal(DB).
1025close_oldest_journal.
1026
1027
1033
1034sync_journal(DB, _) :-
1035 transaction_db(_, DB, _),
1036 !.
1037sync_journal(_, Fd) :-
1038 flush_output(Fd).
1039
1043
1044close_journal(DB) :-
1045 with_mutex(rdf_journal_file,
1046 close_journal_(DB)).
1047
1048close_journal_(DB) :-
1049 ( retract(source_journal_fd(DB, Fd))
1050 -> time_stamp(Now),
1051 format(Fd, '~q.~n', [end([time(Now)])]),
1052 close(Fd, [force(true)])
1053 ; true
1054 ).
1055
1059
1060close_journals :-
1061 forall(source_journal_fd(DB, _),
1062 catch(close_journal(DB), E,
1063 print_message(error, E))).
1064
1069
1070create_db(Graph) :-
1071 \+ rdf(_,_,_,Graph),
1072 !,
1073 debug(rdf_persistency, 'Deleting empty Graph ~w', [Graph]),
1074 delete_db(Graph).
1075create_db(Graph) :-
1076 debug(rdf_persistency, 'Saving Graph ~w', [Graph]),
1077 close_journal(Graph),
1078 db_abs_files(Graph, Snapshot, Journal),
1079 atom_concat(Snapshot, '.new', NewSnapshot),
1080 ( catch(( create_directory_levels(Snapshot),
1081 rdf_save_db(NewSnapshot, Graph)
1082 ), Error,
1083 ( print_message(warning, Error),
1084 fail
1085 ))
1086 -> ( exists_file(Journal)
1087 -> delete_file(Journal)
1088 ; true
1089 ),
1090 rename_file(NewSnapshot, Snapshot),
1091 debug(rdf_persistency, 'Saved Graph ~w', [Graph])
1092 ; catch(delete_file(NewSnapshot), _, true)
1093 ).
1094
1095
1099
1100delete_db(DB) :-
1101 with_mutex(rdf_journal_file,
1102 delete_db_(DB)).
1103
1104delete_db_(DB) :-
1105 close_journal_(DB),
1106 db_abs_files(DB, Snapshot, Journal),
1107 !,
1108 ( exists_file(Journal)
1109 -> delete_file(Journal)
1110 ; true
1111 ),
1112 ( exists_file(Snapshot)
1113 -> delete_file(Snapshot)
1114 ; true
1115 ).
1116delete_db_(_).
1117
1118 1121
1125
1126lock_db(Dir) :-
1127 lockfile(Dir, File),
1128 catch(open(File, update, Out, [lock(write), wait(false)]),
1129 error(permission_error(Access, _, _), _),
1130 locked_error(Access, Dir)),
1131 ( current_prolog_flag(pid, PID)
1132 -> true
1133 ; PID = 0 1134 ),
1135 time_stamp(Now),
1136 gethostname(Host),
1137 format(Out, '/* RDF Database is in use */~n~n', []),
1138 format(Out, '~q.~n', [ locked([ time(Now),
1139 pid(PID),
1140 host(Host)
1141 ])
1142 ]),
1143 flush_output(Out),
1144 set_end_of_stream(Out),
1145 assert(rdf_lock(Dir, lock(Out, File))),
1146 at_halt(unlock_db(Dir)).
1147
1148locked_error(lock, Dir) :-
1149 lockfile(Dir, File),
1150 ( catch(read_file_to_terms(File, Terms, []), _, fail),
1151 Terms = [locked(Args)]
1152 -> Context = rdf_locked(Args)
1153 ; Context = context(_, 'Database is in use')
1154 ),
1155 throw(error(permission_error(lock, rdf_db, Dir), Context)).
1156locked_error(open, Dir) :-
1157 throw(error(permission_error(lock, rdf_db, Dir),
1158 context(_, 'Lock file cannot be opened'))).
1159
1162
1163unlock_db(Dir) :-
1164 retract(rdf_lock(Dir, lock(Out, File))),
1165 !,
1166 unlock_db(Out, File).
1167unlock_db(_).
1168
1169unlock_db(Out, File) :-
1170 close(Out),
1171 delete_file(File).
1172
1173 1176
1177lockfile(Dir, LockFile) :-
1178 atomic_list_concat([Dir, /, lock], LockFile).
1179
1180directory_levels(Levels) :-
1181 rdf_option(directory_levels(Levels)),
1182 !.
1183directory_levels(2).
1184
1185db_file(Base, File) :-
1186 rdf_directory(Dir),
1187 directory_levels(Levels),
1188 db_file(Dir, Base, Levels, File).
1189
1190db_file(Dir, Base, Levels, File) :-
1191 dir_levels(Base, Levels, Segments, [Base]),
1192 atomic_list_concat([Dir|Segments], /, File).
1193
1194open_db(Base, Mode, Stream, Options) :-
1195 db_file(Base, File),
1196 create_directory_levels(File),
1197 open(File, Mode, Stream, [encoding(utf8)|Options]).
1198
1199create_directory_levels(_File) :-
1200 rdf_option(directory_levels(0)),
1201 !.
1202create_directory_levels(File) :-
1203 file_directory_name(File, Dir),
1204 make_directory_path(Dir).
1205
1206exists_db(Base) :-
1207 db_file(Base, File),
1208 exists_file(File).
1209
1214
1215dir_levels(_, 0, Segments, Segments) :- !.
1216dir_levels(File, Levels, Segments, Tail) :-
1217 rdf_atom_md5(File, 1, Hash),
1218 create_dir_levels(Levels, 0, Hash, Segments, Tail).
1219
1220create_dir_levels(0, _, _, Segments, Segments) :- !.
1221create_dir_levels(N, S, Hash, [S1|Segments0], Tail) :-
1222 sub_atom(Hash, S, 2, _, S1),
1223 S2 is S+2,
1224 N2 is N-1,
1225 create_dir_levels(N2, S2, Hash, Segments0, Tail).
1226
1227
1235
1236db_files(DB, Snapshot, Journal) :-
1237 nonvar(DB),
1238 !,
1239 rdf_db_to_file(DB, Base),
1240 atom_concat(Base, '.trp', Snapshot),
1241 atom_concat(Base, '.jrn', Journal).
1242db_files(DB, Snapshot, Journal) :-
1243 nonvar(Snapshot),
1244 !,
1245 atom_concat(Base, '.trp', Snapshot),
1246 atom_concat(Base, '.jrn', Journal),
1247 rdf_db_to_file(DB, Base).
1248db_files(DB, Snapshot, Journal) :-
1249 nonvar(Journal),
1250 !,
1251 atom_concat(Base, '.jrn', Journal),
1252 atom_concat(Base, '.trp', Snapshot),
1253 rdf_db_to_file(DB, Base).
1254
1255db_abs_files(DB, Snapshot, Journal) :-
1256 db_files(DB, Snapshot0, Journal0),
1257 db_file(Snapshot0, Snapshot),
1258 db_file(Journal0, Journal).
1259
1260
1265
1266rdf_journal_file(Graph, Journal) :-
1267 ( var(Graph)
1268 -> rdf_graph(Graph)
1269 ; true
1270 ),
1271 db_abs_files(Graph, _Snapshot, Journal),
1272 exists_file(Journal).
1273
1274
1279
1280rdf_snapshot_file(Graph, Snapshot) :-
1281 ( var(Graph)
1282 -> rdf_graph(Graph) 1283 ; true
1284 ),
1285 db_abs_files(Graph, Snapshot, _Journal),
1286 exists_file(Snapshot).
1287
1288
1297
1298rdf_db_to_file(DB, File) :-
1299 file_base_db(File, DB),
1300 !.
1301rdf_db_to_file(DB, File) :-
1302 url_to_filename(DB, File),
1303 assert(file_base_db(File, DB)).
1304
1315
1316url_to_filename(URL, FileName) :-
1317 atomic(URL),
1318 !,
1319 atom_codes(URL, Codes),
1320 phrase(url_encode(EncCodes), Codes),
1321 atom_codes(FileName, EncCodes).
1322url_to_filename(URL, FileName) :-
1323 uri_encoded(path, URL, FileName).
1324
1325url_encode([0'+|T]) -->
1326 " ",
1327 !,
1328 url_encode(T).
1329url_encode([C|T]) -->
1330 alphanum(C),
1331 !,
1332 url_encode(T).
1333url_encode([C|T]) -->
1334 no_enc_extra(C),
1335 !,
1336 url_encode(T).
1337url_encode(Enc) -->
1338 ( "\r\n"
1339 ; "\n"
1340 ),
1341 !,
1342 { string_codes("%0D%0A", Codes),
1343 append(Codes, T, Enc)
1344 },
1345 url_encode(T).
1346url_encode([]) -->
1347 eos,
1348 !.
1349url_encode([0'%,D1,D2|T]) -->
1350 [C],
1351 { Dv1 is (C>>4 /\ 0xf),
1352 Dv2 is (C /\ 0xf),
1353 code_type(D1, xdigit(Dv1)),
1354 code_type(D2, xdigit(Dv2))
1355 },
1356 url_encode(T).
1357
1358eos([], []).
1359
1360alphanum(C) -->
1361 [C],
1362 { C < 128, 1363 code_type(C, alnum)
1364 }.
1365
(0'_) --> "_".
1367
1368
1369 1372
1376
1377reindex_db(Dir, Levels) :-
1378 directory_files(Dir, Files),
1379 reindex_files(Files, Dir, '.', 0, Levels),
1380 remove_empty_directories(Files, Dir).
1381
1382reindex_files([], _, _, _, _).
1383reindex_files([Nofollow|Files], Dir, Prefix, CLevel, Levels) :-
1384 nofollow(Nofollow),
1385 !,
1386 reindex_files(Files, Dir, Prefix, CLevel, Levels).
1387reindex_files([File|Files], Dir, Prefix, CLevel, Levels) :-
1388 CLevel \== Levels,
1389 file_name_extension(_Base, Ext, File),
1390 db_extension(Ext),
1391 !,
1392 directory_file_path(Prefix, File, DBFile),
1393 directory_file_path(Dir, DBFile, OldPath),
1394 db_file(Dir, File, Levels, NewPath),
1395 debug(rdf_persistency, 'Rename ~q --> ~q', [OldPath, NewPath]),
1396 file_directory_name(NewPath, NewDir),
1397 make_directory_path(NewDir),
1398 rename_file(OldPath, NewPath),
1399 reindex_files(Files, Dir, Prefix, CLevel, Levels).
1400reindex_files([D|Files], Dir, Prefix, CLevel, Levels) :-
1401 directory_file_path(Prefix, D, SubD),
1402 directory_file_path(Dir, SubD, AbsD),
1403 exists_directory(AbsD),
1404 \+ read_link(AbsD, _, _), 1405 !,
1406 directory_files(AbsD, SubFiles),
1407 CLevel2 is CLevel + 1,
1408 reindex_files(SubFiles, Dir, SubD, CLevel2, Levels),
1409 reindex_files(Files, Dir, Prefix, CLevel, Levels).
1410reindex_files([_|Files], Dir, Prefix, CLevel, Levels) :-
1411 reindex_files(Files, Dir, Prefix, CLevel, Levels).
1412
1413
1414remove_empty_directories([], _).
1415remove_empty_directories([File|Files], Dir) :-
1416 \+ nofollow(File),
1417 directory_file_path(Dir, File, Path),
1418 exists_directory(Path),
1419 \+ read_link(Path, _, _),
1420 !,
1421 directory_files(Path, Content),
1422 exclude(nofollow, Content, RealContent),
1423 ( RealContent == []
1424 -> debug(rdf_persistency, 'Remove empty dir ~q', [Path]),
1425 delete_directory(Path)
1426 ; remove_empty_directories(RealContent, Path)
1427 ),
1428 remove_empty_directories(Files, Dir).
1429remove_empty_directories([_|Files], Dir) :-
1430 remove_empty_directories(Files, Dir).
1431
1432
1433 1436
1437save_prefixes(Dir) :-
1438 atomic_list_concat([Dir, /, 'prefixes.db'], PrefixFile),
1439 setup_call_cleanup(open(PrefixFile, write, Out, [encoding(utf8)]),
1440 write_prefixes(Out),
1441 close(Out)).
1442
1443write_prefixes(Out) :-
1444 format(Out, '% Snapshot of defined RDF prefixes~n~n', []),
1445 forall(rdf_current_ns(Alias, URI),
1446 format(Out, 'prefix(~q, ~q).~n', [Alias, URI])).
1447
1455
1456load_prefixes(Dir) :-
1457 atomic_list_concat([Dir, /, 'prefixes.db'], PrefixFile),
1458 ( exists_file(PrefixFile)
1459 -> setup_call_cleanup(open(PrefixFile, read, In, [encoding(utf8)]),
1460 read_prefixes(In),
1461 close(In))
1462 ; true
1463 ).
1464
1465read_prefixes(Stream) :-
1466 read_term(Stream, T0, []),
1467 read_prefixes(T0, Stream).
1468
1469read_prefixes(end_of_file, _) :- !.
1470read_prefixes(prefix(Alias, URI), Stream) :-
1471 !,
1472 must_be(atom, Alias),
1473 must_be(atom, URI),
1474 catch(rdf_register_ns(Alias, URI, []), E,
1475 print_message(warning, E)),
1476 read_term(Stream, T, []),
1477 read_prefixes(T, Stream).
1478read_prefixes(Term, _) :-
1479 domain_error(prefix_term, Term).
1480
1481
1482 1485
1489
1490mkdir(Directory) :-
1491 exists_directory(Directory),
1492 !.
1493mkdir(Directory) :-
1494 make_directory(Directory).
1495
1499
1500time_stamp(Int) :-
1501 get_time(Now),
1502 Int is round(Now).
1503
1504
1505 1508
1509:- multifile
1510 prolog:message/3,
1511 prolog:message_context/3. 1512
1513prolog:message(rdf(Term)) -->
1514 message(Term).
1515
1516message(restoring(Type, Count, Jobs)) -->
1517 [ 'Restoring ~D ~w using ~D concurrent workers'-[Count, Type, Jobs] ].
1518message(restore(attached(Graphs, Triples, Time/Wall))) -->
1519 { catch(Percent is round(100*Time/Wall), _, Percent = 0) },
1520 [ 'Loaded ~D graphs (~D triples) in ~2f sec. (~d% CPU = ~2f sec.)'-
1521 [Graphs, Triples, Wall, Percent, Time] ].
1523message(restore(true, Action)) -->
1524 !,
1525 silent_message(Action).
1526message(restore(brief, Action)) -->
1527 !,
1528 brief_message(Action).
1529message(restore(_, Graph)) -->
1530 [ 'Restoring ~p ... '-[Graph], flush ].
1531message(restore(_, snapshot(_))) -->
1532 [ at_same_line, '(snapshot) '-[], flush ].
1533message(restore(_, journal(_))) -->
1534 [ at_same_line, '(journal) '-[], flush ].
1535message(restore(_, done(_, Time, Count))) -->
1536 [ at_same_line, '~D triples in ~2f sec.'-[Count, Time] ].
1538message(restore(_, snapshot(G, _))) -->
1539 [ 'Restoring ~p\t(snapshot)'-[G], flush ].
1540message(restore(_, journal(G, _))) -->
1541 [ 'Restoring ~p\t(journal)'-[G], flush ].
1542message(restore(_, done(_, Time, Count))) -->
1543 [ at_same_line, '~D triples in ~2f sec.'-[Count, Time] ].
1545message(update_failed(S,P,O,Action)) -->
1546 [ 'Failed to update <~p ~p ~p> with ~p'-[S,P,O,Action] ].
1548message(reindex(Count, Depth)) -->
1549 [ 'Restructuring database with ~d levels (~D graphs)'-[Depth, Count] ].
1550message(reindex(Depth)) -->
1551 [ 'Fixing database directory structure (~d levels)'-[Depth] ].
1552message(read_only) -->
1553 [ 'Cannot write persistent store; continuing in read-only mode.', nl,
1554 'All changes to the RDF store will be lost if this process terminates.'
1555 ].
1556
1557silent_message(_Action) --> [].
1558
1559brief_message(done(Graph, _Time, _Count, Nth, Total)) -->
1560 { file_base_name(Graph, Base) },
1561 [ at_same_line,
1562 '\r~p~`.t ~D of ~D graphs~72|'-[Base, Nth, Total],
1563 flush
1564 ].
1565brief_message(_) --> [].
1566
1567
1568prolog:message_context(rdf_locked(Args)) -->
1569 { memberchk(time(Time), Args),
1570 memberchk(pid(Pid), Args),
1571 format_time(string(S), '%+', Time)
1572 },
1573 [ nl,
1574 'locked at ~s by process id ~w'-[S,Pid]
1575 ]