From b07720e1471ebfa50374c380a28761754323c5cf Mon Sep 17 00:00:00 2001 From: vsc Date: Mon, 20 Feb 2006 12:52:24 +0000 Subject: [PATCH] Christian Thaeter's persistence library git-svn-id: https://yap.svn.sf.net/svnroot/yap/trunk@1547 b08c6af1-5177-4d33-ba66-4b1c6b8b522a --- LGPL/persistence.yap | 309 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 LGPL/persistence.yap diff --git a/LGPL/persistence.yap b/LGPL/persistence.yap new file mode 100644 index 000000000..64d59a43f --- /dev/null +++ b/LGPL/persistence.yap @@ -0,0 +1,309 @@ +/* + persistence.yap - make assertions and retracts persistent + + Copyright (C) 2006, Christian Thaeter + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, contact me. + +*/ + +:- module(persistence, + [ + persistent_open/3, + persistent_close/1, + persistent_assert/1, + persistent_retract/1 + ]). + +:- use_module(library(system),[]). + +:- dynamic(persistent_desc/2). + +/* + persistent_open(PredDesc, File, Opts). + + declare Module:Functor/Arity (Functor/Arity) to be persistent + stored in File's (*.db *.log *log.$PID *.lock *.bak) + + Opts are: + db - use dbfile (flat file containing all persistent predicates) + log - use logfile (logfile with either +(Term) for asserts and -(Term) for retracts) + bak - make backupfiles when regenerating the dbfile + sync - flush data always + ro - readonly, can load locked files, never changes data on disk + wo - (planned) writeonly, implies [log], data is only written to the log and not + asserted into prolog, the database will not be loaded at persistent_open. + conc - (planned) concurrency, extends the locking for multiple readers/single writer locks + trans - (planned) support for transactions (begin/commit/abort) + + Guides: + - if the data mutates a lot, use [db,log]. + - if you mostly append data [log] suffices. + - if the data is not important (can be regenerated) and mostly readonly then [db] is ok. + - when using only [db] you must not forget to persistent_close! + - for extra security against failures add [bak,sync]. + - don't use [bak] if you need to conserve disk space and the database is huge. + - don't use [sync] if you need very fast writes. + - turning all on [db,log,bak,sync] is probably the best, if you are undecided. + - [ro,db] loads only the last saved db file. + - [ro,log] loads the last saved db file if it exists and replays the log. + - note that [ro] will fail if the db is not intact (.bak file present). + + (planned features) + - [wo] is very limited and only useful if you want to log data to a file + - [wo,db] will replay the log at close + - [conc] is useful for shareing data between prolog processes, but this is not a + high performance solution. + - [trans] can improve performance of concurrent access somewhat +*/ +persistent_open(PredDesc, File, Opts) :- + module_goal(PredDesc, Module:Functor/Arity), + atom(Functor), integer(Arity), atom(File), + \+ persistent_desc(Module:Functor/Arity,_), + + atom_concat(File,'.db',DBfile), + assertz(persistent_desc(Module:Functor/Arity,dbfile(DBfile))), + + atom_concat(File,'.bak',Backupfile), + assertz(persistent_desc(Module:Functor/Arity,backupfile(Backupfile))), + + atom_concat(File,'.log',Logfile), + assertz(persistent_desc(Module:Functor/Arity,logfile(Logfile))), + + system:pid(Pid), + assertz(persistent_desc(Module:Functor/Arity,pid(Pid))), + + number_atom(Pid,P), + atom_concat(Logfile,P,Mylogfile), + assertz(persistent_desc(Module:Functor/Arity,mylogfile(Mylogfile))), + + atom_concat(File,'.lock',Lockfile), + assertz(persistent_desc(Module:Functor/Arity,lockfile(Lockfile))), + + persistent_opts_store(Module:Functor/Arity,Opts), + persistent_load(Module:Functor/Arity), + + ( \+ persistent_desc(Module:Functor/Arity, ro), persistent_desc(Module:Functor/Arity, log) + -> open(Logfile, append, Log), + assertz(persistent_desc(Module:Functor/Arity,logstream(Log))) + ; true + ). + +/* + closes the database associated with PredDesc ([Module:]Functor/Arity) +*/ +persistent_close(PredDesc0) :- + module_goal(PredDesc0,PredDesc), + ( persistent_desc(PredDesc, logstream(Log)) + -> close(Log) + ; true + ), + persistent_save(PredDesc), + persistent_desc(PredDesc, backupfile(Backupfile)), + (system:delete_file(Backupfile,[ignore]); true), + persistent_lock_release(PredDesc), + retractall(persistent_desc(PredDesc,_)). + +/* + assert data to the database, this is always an assertz, if you need some ordering, + then store some kind of key within your data. + rules can be asserted too +*/ +persistent_assert(Term) :- + Term = (Head0 :- Body), + module_goal(Head0, Module:Head), + functor(Head, Functor, Arity), + once(persistent_desc(Module:Functor/Arity,_)),!, + ( persistent_desc(Module:Functor/Arity, logstream(Log)) + -> writeq(Log,+(((Module:Head):-Body))), write(Log,'.\n'), + ( persistent_desc(Module:Functor/Arity, sync) + -> flush_output(Log) + ; true + ) + ; true + ), + assertz((Module:Head:-Body)). +persistent_assert(Term0) :- + module_goal(Term0, Module:Term), + functor(Term,Functor,Arity), + once(persistent_desc(Module:Functor/Arity,_)),!, + ( persistent_desc(Module:Functor/Arity,logstream(Log)) + -> writeq(Log,+(Module:Term)), write(Log,'.\n'), + ( persistent_desc(Module:Functor/Arity, sync) + -> flush_output(Log) + ; true + ) + ; true + ), + assertz(Module:Term). + +/* + retract a persistent Term +*/ +persistent_retract(Term0) :- + module_goal(Term0, Module:Term), + functor(Term,Functor,Arity), + once(persistent_desc(Module:Functor/Arity,_)),!, + retract(Module:Term), + ( persistent_desc(Module:Functor/Arity, logstream(Log)) + -> writeq(Log,-(Module:Term)), write(Log,'.\n'), + ( persistent_desc(Module:Functor/Arity, sync) + -> flush_output(Log) + ; true + ) + ; true + ). + +% transaction support for future +persistent_begin. +persistent_commit. +persistent_abort. + + +/* + + PRIVATE PREDICATES, DONT USE THESE + +*/ + +% save all data to a .db file +persistent_save(PredDesc) :- + \+ persistent_desc(PredDesc,ro), + ( persistent_desc(PredDesc,db) + -> persistent_desc(PredDesc,dbfile(DBfile)), + ( + persistent_desc(PredDesc,bak) + -> persistent_desc(PredDesc,backupfile(Backupfile)), + ( system:file_exists(DBfile) + -> system:rename_file(DBfile,Backupfile) + ; true + ) + ; true + ), + open(DBfile, write, S), + persistent_writeall(PredDesc,S), + close(S), + persistent_desc(PredDesc,logfile(Logfile)), + (system:delete_file(Logfile,[ignore]); true) + ; true + ). + +% write all predicates matching Functor/Arity to stream S +persistent_writeall(PredDesc, S) :- + module_goal(PredDesc, Module:Functor/Arity), + functor(Clause, Functor, Arity), + clause(Module:Clause, Body), + ( Body = true + -> writeq(S,Module:Clause) + ; writeq(S,(Module:Clause:-Body)) + ), + write(S,'.\n'), + fail. +persistent_writeall(_,_). + +% load a database, recover logfile, recreate .db +persistent_load(PredDesc) :- + persistent_desc(PredDesc,dbfile(DBfile)), + persistent_desc(PredDesc,backupfile(Backupfile)), + persistent_desc(PredDesc,logfile(Logfile)), + + ( persistent_desc(PredDesc,ro) + -> \+ system:file_exists(Backupfile), + ( system:file_exists(DBfile) + -> persistent_load_file(DBfile) + ; true + ), + ( persistent_desc(PredDesc,log), system:file_exists(Logfile) + -> persistent_load_file(Logfile) + ; true + ) + ; + persistent_lock_exclusive(PredDesc), + ( system:file_exists(Backupfile) + -> system:rename_file(Backupfile, DBfile) + ; true + ), + ( system:file_exists(DBfile) + -> persistent_load_file(DBfile) + ; true + ), + ( system:file_exists(Logfile) + -> persistent_load_file(Logfile), + ( persistent_desc(PredDesc, db) + -> persistent_save(PredDesc) + ; true + ) + ; true + ) + ). + +% load a .db file or replay a .log file +persistent_load_file(File) :- + open(File, read, S), + repeat, + read(S, TermIn), + ( + TermIn == end_of_file, + close(S), + ! + ; + ( + TermIn = +(Term), + assertz(Term) + ; + TermIn = -(Term), + retract(Term) + ; + assertz(TermIn) + ), + fail + ). + +%lock handling, so far only exclusive locks +persistent_lock_exclusive(PredDesc) :- + persistent_desc(PredDesc,lockfile(Lockfile)), + persistent_desc(PredDesc,pid(Pid)), + open(Lockfile, append, Lockappend), + write(Lockappend,lock(write,Pid)),write(Lockappend,'.\n'), + close(Lockappend), + open(Lockfile, read, Lockread), + read(Lockread,LPid), + close(Lockread), + LPid = lock(_,Pid). + +% recover lock +persistent_lock_exclusive(PredDesc) :- + persistent_desc(PredDesc, lockfile(Lockfile)), + open(Lockfile, read, Lockread), + read(Lockread,lock(_,LPid)), + close(Lockread), + \+ catch(kill(LPid,0),_,fail), + (system:delete_file(Lockfile,[ignore]); true), + %system:sleep(1), + persistent_lock_exclusive(PredDesc). + +persistent_lock_release(PredDesc) :- + persistent_lock_exclusive(PredDesc), + persistent_desc(PredDesc,lockfile(Lockfile)), + (system:delete_file(Lockfile,[ignore]); true). + + +persistent_opts_store(_,[]). +persistent_opts_store(PredDesc,[H|T]) :- + assertz(persistent_desc(PredDesc,H)), + persistent_opts_store(PredDesc,T). + +module_goal(Module:Goal,Module:Goal) :- + callable(Goal), nonvar(Module),!. +module_goal(Goal,Module:Goal) :- + callable(Goal), prolog_flag(typein_module,Module).