1/*
2  persistence.yap - make assertions and retracts persistent
3
4  Copyright (C) 2006, Christian Thaeter <chth@gmx.net>
5
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License version 2 as
8  published by the Free Software Foundation.
9
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  GNU General Public License for more details.
14
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, contact me.
17
18*/
19
20:- module(persistence,
21	[
22		persistent_open/3,
23		persistent_close/1,
24		persistent_assert/1,
25		persistent_retract/1
26	]).
27
28:- use_module(library(system),[]).
29
30:- dynamic(persistent_desc/2).
31
32/*
33  persistent_open(PredDesc, File, Opts).
34
35  declare Module:Functor/Arity (Functor/Arity) to be persistent
36  stored in File's (*.db *.log *log.$PID *.lock *.bak)
37
38  Opts are:
39    db     - use dbfile (flat file containing all persistent predicates)
40    log    - use logfile (logfile with either +(Term) for asserts and -(Term) for retracts)
41    bak    - make backupfiles when regenerating the dbfile
42    sync   - flush data always
43    ro     - readonly, can load locked files, never changes data on disk
44    wo     - (planned) writeonly, implies [log], data is only written to the log and not
45             asserted into prolog, the database will not be loaded at persistent_open.
46    conc   - (planned) concurrency, extends the locking for multiple readers/single writer locks
47    trans  - (planned) support for transactions (begin/commit/abort)
48
49  Guides:
50  - if the data mutates a lot, use [db,log].
51  - if you mostly append data [log] suffices.
52  - if the data is not important (can be regenerated) and mostly readonly then [db] is ok.
53  - when using only [db] you must not forget to persistent_close!
54  - for extra security against failures add [bak,sync].
55  - don't use [bak] if you need to conserve disk space and the database is huge.
56  - don't use [sync] if you need very fast writes.
57  - turning all on [db,log,bak,sync] is probably the best, if you are undecided.
58  - [ro,db] loads only the last saved db file.
59  - [ro,log] loads the last saved db file if it exists and replays the log.
60  - note that [ro] will fail if the db is not intact (.bak file present).
61
62  (planned features)
63  - [wo] is very limited and only useful if you want to log data to a file
64  - [wo,db] will replay the log at close
65  - [conc] is useful for shareing data between prolog processes, but this is not a
66    high performance solution.
67  - [trans] can improve performance of concurrent access somewhat
68*/
69persistent_open(PredDesc, File, Opts) :-
70	module_goal(PredDesc, Module:Functor/Arity),
71	atom(Functor), integer(Arity), atom(File),
72	\+ persistent_desc(Module:Functor/Arity,_),
73
74	atom_concat(File,'.db',DBfile),
75	assertz(persistent_desc(Module:Functor/Arity,dbfile(DBfile))),
76
77	atom_concat(File,'.bak',Backupfile),
78	assertz(persistent_desc(Module:Functor/Arity,backupfile(Backupfile))),
79
80        atom_concat(File,'.log',Logfile),
81	assertz(persistent_desc(Module:Functor/Arity,logfile(Logfile))),
82
83        system:pid(Pid),
84	assertz(persistent_desc(Module:Functor/Arity,pid(Pid))),
85
86        number_atom(Pid,P),
87        atom_concat(Logfile,P,Mylogfile),
88	assertz(persistent_desc(Module:Functor/Arity,mylogfile(Mylogfile))),
89
90        atom_concat(File,'.lock',Lockfile),
91	assertz(persistent_desc(Module:Functor/Arity,lockfile(Lockfile))),
92
93        persistent_opts_store(Module:Functor/Arity,Opts),
94	persistent_load(Module:Functor/Arity),
95
96        (       \+ persistent_desc(Module:Functor/Arity, ro), persistent_desc(Module:Functor/Arity, log)
97        ->      open(Logfile, append, Log),
98                assertz(persistent_desc(Module:Functor/Arity,logstream(Log)))
99        ;       true
100        ).
101
102/*
103  closes the database associated with PredDesc ([Module:]Functor/Arity)
104*/
105persistent_close(PredDesc0) :-
106	module_goal(PredDesc0,PredDesc),
107        (       persistent_desc(PredDesc, logstream(Log))
108        ->      close(Log)
109        ;       true
110        ),
111        persistent_save(PredDesc),
112        persistent_desc(PredDesc, backupfile(Backupfile)),
113        (system:delete_file(Backupfile,[ignore]); true),
114        persistent_lock_release(PredDesc),
115        retractall(persistent_desc(PredDesc,_)).
116
117/*
118  assert data to the database, this is always an assertz, if you need some ordering,
119  then store some kind of key within your data.
120  rules can be asserted too
121*/
122persistent_assert(Term) :-
123        Term = (Head0 :- Body),
124	module_goal(Head0, Module:Head),
125        functor(Head, Functor, Arity),
126	once(persistent_desc(Module:Functor/Arity,_)),!,
127        (       persistent_desc(Module:Functor/Arity, logstream(Log))
128        ->      writeq(Log,+(((Module:Head):-Body))), write(Log,'.\n'),
129                (       persistent_desc(Module:Functor/Arity, sync)
130                ->      flush_output(Log)
131                ;       true
132                )
133        ;       true
134        ),
135        assertz((Module:Head:-Body)).
136persistent_assert(Term0) :-
137	module_goal(Term0, Module:Term),
138        functor(Term,Functor,Arity),
139	once(persistent_desc(Module:Functor/Arity,_)),!,
140        (       persistent_desc(Module:Functor/Arity,logstream(Log))
141        ->      writeq(Log,+(Module:Term)), write(Log,'.\n'),
142                (       persistent_desc(Module:Functor/Arity, sync)
143                ->      flush_output(Log)
144                ;       true
145                )
146        ;       true
147        ),
148        assertz(Module:Term).
149
150/*
151  retract a persistent Term
152*/
153persistent_retract(Term0) :-
154	module_goal(Term0, Module:Term),
155        functor(Term,Functor,Arity),
156        once(persistent_desc(Module:Functor/Arity,_)),!,
157        retract(Module:Term),
158        (       persistent_desc(Module:Functor/Arity, logstream(Log))
159        ->      writeq(Log,-(Module:Term)), write(Log,'.\n'),
160                (       persistent_desc(Module:Functor/Arity, sync)
161                ->      flush_output(Log)
162                ;       true
163                )
164        ;       true
165        ).
166
167% transaction support for future
168persistent_begin.
169persistent_commit.
170persistent_abort.
171
172
173/*
174
175  PRIVATE PREDICATES, DONT USE THESE
176
177*/
178
179% save all data to a .db file
180persistent_save(PredDesc) :-
181        \+  persistent_desc(PredDesc,ro),
182	(       persistent_desc(PredDesc,db)
183	->	persistent_desc(PredDesc,dbfile(DBfile)),
184		(
185                        persistent_desc(PredDesc,bak)
186                ->      persistent_desc(PredDesc,backupfile(Backupfile)),
187                        (       system:file_exists(DBfile)
188                        ->      system:rename_file(DBfile,Backupfile)
189                        ;       true
190                        )
191                ;       true
192                ),
193                open(DBfile, write, S),
194                persistent_writeall(PredDesc,S),
195                close(S),
196                persistent_desc(PredDesc,logfile(Logfile)),
197                (system:delete_file(Logfile,[ignore]); true)
198        ;       true
199        ).
200
201% write all predicates matching Functor/Arity to stream S
202persistent_writeall(PredDesc, S) :-
203	module_goal(PredDesc, Module:Functor/Arity),
204        functor(Clause, Functor, Arity),
205        clause(Module:Clause, Body),
206        (       Body = true
207        ->      writeq(S,Module:Clause)
208        ;       writeq(S,(Module:Clause:-Body))
209        ),
210        write(S,'.\n'),
211        fail.
212persistent_writeall(_,_).
213
214% load a database, recover logfile, recreate .db
215persistent_load(PredDesc) :-
216	persistent_desc(PredDesc,dbfile(DBfile)),
217	persistent_desc(PredDesc,backupfile(Backupfile)),
218	persistent_desc(PredDesc,logfile(Logfile)),
219
220        (       persistent_desc(PredDesc,ro)
221        ->      \+ system:file_exists(Backupfile),
222                (       system:file_exists(DBfile)
223                ->      persistent_load_file(DBfile)
224                ;       true
225                ),
226                (       persistent_desc(PredDesc,log), system:file_exists(Logfile)
227                ->      persistent_load_file(Logfile)
228                ;       true
229                )
230        ;
231                persistent_lock_exclusive(PredDesc),
232                (       system:file_exists(Backupfile)
233                ->      system:rename_file(Backupfile, DBfile)
234                ;       true
235                ),
236                (       system:file_exists(DBfile)
237                ->      persistent_load_file(DBfile)
238                ;       true
239                ),
240                (       system:file_exists(Logfile)
241                ->      persistent_load_file(Logfile),
242                        (       persistent_desc(PredDesc, db)
243                        ->      persistent_save(PredDesc)
244                        ;       true
245                        )
246                ;       true
247                )
248        ).
249
250% load a .db file or replay a .log file
251persistent_load_file(File) :-
252        open(File, read, S),
253        repeat,
254        read(S, TermIn),
255        (
256                TermIn == end_of_file,
257                close(S),
258                !
259        ;
260                (
261                        TermIn = +(Term),
262                        assertz(Term)
263                ;
264                        TermIn = -(Term),
265                        retract(Term)
266                ;
267                        assertz(TermIn)
268                ),
269                fail
270        ).
271
272%lock handling, so far only exclusive locks
273persistent_lock_exclusive(PredDesc) :-
274	persistent_desc(PredDesc,lockfile(Lockfile)),
275	persistent_desc(PredDesc,pid(Pid)),
276        open(Lockfile, append, Lockappend),
277        write(Lockappend,lock(write,Pid)),write(Lockappend,'.\n'),
278        close(Lockappend),
279        open(Lockfile, read, Lockread),
280        read(Lockread,LPid),
281        close(Lockread),
282        LPid = lock(_,Pid).
283
284% recover lock
285persistent_lock_exclusive(PredDesc) :-
286	persistent_desc(PredDesc, lockfile(Lockfile)),
287        open(Lockfile, read, Lockread),
288        read(Lockread,lock(_,LPid)),
289        close(Lockread),
290        \+ catch(kill(LPid,0),_,fail),
291        (system:delete_file(Lockfile,[ignore]); true),
292        %system:sleep(1),
293        persistent_lock_exclusive(PredDesc).
294
295persistent_lock_release(PredDesc) :-
296	persistent_lock_exclusive(PredDesc),
297	persistent_desc(PredDesc,lockfile(Lockfile)),
298        (system:delete_file(Lockfile,[ignore]); true).
299
300
301persistent_opts_store(_,[]).
302persistent_opts_store(PredDesc,[H|T]) :-
303	assertz(persistent_desc(PredDesc,H)),
304	persistent_opts_store(PredDesc,T).
305
306module_goal(Module:Goal,Module:Goal) :-
307	callable(Goal), nonvar(Module),!.
308module_goal(Goal,Module:Goal) :-
309	callable(Goal), prolog_flag(typein_module,Module).
310