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