1 /* Copyright (C) 2000-2009 Thomas Bopp, Thorsten Hampel, Ludger Merkens,
2 * Robert Rosendahl, Daniel Büse
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * $Id: database.pike,v 1.7 2010/01/27 12:42:33 astra Exp $
20 inherit "/base/serialize.pike";
23 #include <attributes.h>
30 #include <exception.h>
32 #include <configure.h>
33 class database : public serialize.pike{
40 #define MODULE_SECURITY _Server->get_module("security")
41 #define MODULE_USERS _Server->get_module("users")
43 #define PROXY "/kernel/proxy.pike"
45 private mapping(string:int) mCurrMaxID;
46 private mapping(int:object ) mProxyLookup;
47 private Thread.Mutex loadMutex = Thread.Mutex();
48 private Thread.Mutex serializeMutex = Thread.Mutex();
49 private Thread.Mutex createMutex = Thread.Mutex();
50 private Thread.Mutex updateMutex = Thread.Mutex();
51 private Thread.Mutex idMutex = Thread.Mutex();
52 private Thread.Mutex lowSaveMutex = Thread.Mutex();
54 private object oSaveQueue;
55 private object preSaveQueue;
56 private mapping mSaveCount;
57 private mapping mSaveIndex;
58 private int iSaves, iSkips;
60 private object oTlDbHandle;
61 private object oDbHandle;
62 private Thread.Local dbHandlesLocal;
63 private array dbHandles;
64 private object tSaveDemon;
65 private object tPreSaveDemon;
66 private object oDemonDbHandle;
67 private object oModules;
69 private Stdio.File lostData;
70 private array tReaderThreads;
71 private object tWriterThread;
72 private Thread.Queue readQueue;
73 private Thread.Queue writeQueue;
74 private Thread.Queue globalQueue;
80 #define CHECKISO(s, mime, obj) if (search(s, mime)>0) { obj->restore_attr_data(mime, DOC_ENCODING); werror(" %s",mime); }
81 #define SAVESTORE(ident, index) ((ident||"") + "#" + (index||""))
83 private mapping oModuleCache;
84 private Calendar.Calendar cal = Calendar.ISO->set_language("german");
87 object SqlHandle(string dbconnect)
92 sscanf(dbconnect, "%s://%*s", dbtype);
96 sqlHandle = new("dbadaptors/mysql.pike", dbconnect);
99 sqlHandle = new("dbadaptors/postgres.pike", dbconnect);
108 private int log_save_queue_modulo = 0;
110 class SqlReadRecord {
112 int iMaxRecNbr; // record number
114 int iNextRecNbr; // record number current
116 function restore; // function if record needs to be restored
117 Thread.Fifo contFifo;
118 Thread.Mutex fullMutex;
123 int check_timeout() {
124 if ( objectp(dbfile) ) {
125 int t = time() - dbfile->get_last_access();
126 if ( !objectp(contFifo) )
138 * return a thread-local (valid) db-handle
141 * @return the database handle
146 if (this_thread() == tSaveDemon) // give saveDemon its own handle
147 return oDemonDbHandle;
149 #ifdef USE_LOCAL_SQLCONNECT
150 if (!objectp(dbHandlesLocal))
151 dbHandlesLocal = thread_local();
154 // everybody else gets the same shared handle
155 if (!objectp(oDbHandle)) {
156 oDbHandle = SqlHandle(STEAM_DB_CONNECT);
157 if (!validate_db_handle(oDbHandle))
158 setup_sTeam_tables(oDbHandle);
159 #ifdef USE_LOCAL_SQLCONNECT
160 // if oDbHandle is not set this is the initial startup
161 dbHandlesLocal->set(oDbHandle);
165 #ifdef USE_LOCAL_SQLCONNECT
166 object handle = dbHandlesLocal->get();
167 if (!objectp(handle)) {
168 // this might fail later on!
170 handle = SqlHandle(STEAM_DB_CONNECT);
171 dbHandles += ({ handle });
175 FATAL("Failed to create database handle for Thread - using main handle!");
178 dbHandlesLocal->set(handle);
181 // FATAL(cal->Second()->format_nice()+": database handle requested.");
182 return dbHandlesLocal->get();
190 object get_db_handle()
192 if ( _Server->is_module(CALLER) )
194 error("Unauthorized call to get_db_handle!");
198 * mimick object id for serialization etc.
199 * @return ID_DATABASE from database.h
200 * @see object.get_object_id
202 final int get_object_id()
208 private void db_execute(string db_query)
210 db()->big_query(db_query);
217 return preSaveQueue->size() + oSaveQueue->size();
220 array get_save_queue_size()
222 return ({ preSaveQueue->size(), oSaveQueue->size() });
227 * Set logging of the save queue size. If bigger than 0, then the save daemon
228 * will output the size of the save queue every "modulo" elements. You can
229 * switch this off again by setting this value to 0.
231 * @param modulo the save daemon will output the save queue size whenever it
232 * is a multiple of this value
233 * @return the previously set modulo
235 int log_save_queue ( int modulo )
237 int tmp = log_save_queue_modulo;
238 log_save_queue_modulo = modulo;
243 * demon function to store pending object saves to the database.
244 * This function is started as a thread and waits for objects entering
245 * a queue to save them to the database.
251 void database_save_demon()
253 MESSAGE("DATABASE SAVE DEMON ENABLED");
257 // if oSaveQueue contains an integer the server has been stopped
258 job = oSaveQueue->read();
263 [proxy, ident, index] = job;
265 low_save_object(proxy, ident, index);
267 else if (stringp(job)) {
272 int save_size = oSaveQueue->size();
273 if ( save_size > 0 &&
274 (log_save_queue_modulo > 0) &&
275 (save_size % log_save_queue_modulo == 0) ) {
276 MESSAGE( "database: %d items left to save...", save_size );
277 werror( "database: %d items left to save...\n", save_size );
281 FATAL("/**************** database_save_demon *************/\n"+
286 MESSAGE("DATABASE SAVE DEMON EXITED!");
291 private void emergency_save()
294 FATAL("Emergency SAVE of " + get_save_size() + " items ....\n");
295 while (preSaveQueue->size() > 0) {
296 job = preSaveQueue->read();
298 low_save_object(job[0], job[1], job[2], 1);
300 while (oSaveQueue->size() > 0) {
301 job = oSaveQueue->read();
302 // save object in kill mode!
304 low_save_object(job[0], job[1], job[2], 1);
311 * wait_for_db_lock waits until all pending database writes are done, and
312 * afterwards aquires the save_demon lock, thus stopping the demon. Destruct
313 * the resulting object to release the save demon again.
316 * @return the key object
317 * @see Thread.Mutex->lock
319 void wait_for_db_lock()
321 int sz = get_save_size();
324 MESSAGE("Save Demon is %O status=%d", tSaveDemon,
325 objectp(tSaveDemon) ? tSaveDemon->status() : "not available");
327 MESSAGE("LowSave Lock currently held by %O %O",
328 lowSaveMutex->current_locking_key() || "none",
329 lowSaveMutex->current_locking_thread() || "none");
331 if (!objectp(tSaveDemon) || tSaveDemon->status() != Thread.THREAD_RUNNING) {
332 if (tSaveDemon->status() == Thread.THREAD_EXITED)
333 MESSAGE("Save demon EXITED!!!");
334 else if (tSaveDemon->status() == Thread.THREAD_NOT_STARTED)
335 MESSAGE("Save demon thread was not yet started!");
340 MESSAGE("Waiting to finish SAVE");
343 int size = get_save_size();
356 MESSAGE_START("Waiting for Database Save Demon to stop");
357 oSaveQueue->write(1); // stop database_save_demon
359 while(tSaveDemon->status() != Thread.THREAD_EXITED && cnt < 10) {
362 oSaveQueue->write(1);
363 oSaveQueue->signal();
372 * constructor for database.pike
373 * - starts thread to keep objects persistent
374 * - enables commands in database
380 // first check for lost data, etc.
382 mProxyLookup = ([ ]);
384 oSaveQueue = Thread.Queue();
385 preSaveQueue = Thread.Queue();
386 oTlDbHandle = thread_local();
393 int get_saves() { return iSaves; }
394 int get_skips() { return iSkips; }
399 _Persistence->register("mysql", this_object());
402 object enable_modules()
404 if ( CALLER != _Server )
405 error("Unauthorized Call to enable_modules!");
408 tSaveDemon = thread_create(database_save_demon);
409 tPreSaveDemon = thread_create(database_manager);
411 tReaderThreads = ({ });
412 readQueue = Thread.Queue();
413 writeQueue = Thread.Queue();
414 globalQueue = Thread.Queue();
415 for ( int i = 0; i < 1; i++ )
416 tReaderThreads += ({ thread_create(db_reader) });
417 tWriterThread = thread_create(db_writer);
419 oModules = ((program)"/modules/modules.pike")();
420 oModuleCache = ([ "modules": oModules ]);
422 oDemonDbHandle = SqlHandle(STEAM_DB_CONNECT);
423 int x=validate_db_handle(oDemonDbHandle);
425 setup_sTeam_tables(oDemonDbHandle);
427 add_new_tables(oDemonDbHandle);
429 check_journaling(oDemonDbHandle);
430 check_tables(oDemonDbHandle);
431 check_database_updates(oDemonDbHandle);
435 //#define DBREAD(l, args...) werror("%O"+l+"\n", Thread.this_thread(), args)
436 #define DBREAD(l, args ...)
439 void add_record(object record)
441 readQueue->write(record);
448 SqlReadRecord record;
449 Sql.sql_result odbData;
453 DBREAD("Waiting for queue...");
456 record = readQueue->read();
457 DBREAD("Jobs in readQueue = %d", readQueue->size());
458 if ( record->check_timeout() && !record->stopRead ) {
459 odbData = db()->big_query
460 ( "select rec_data,rec_order from doc_data"+
461 " where doc_id ="+record->iID+
462 " and rec_order >="+record->iNextRecNbr+
463 " and rec_order < "+(record->iNextRecNbr+READ_ONCE)+
464 " order by rec_order" );
465 DBREAD("Queueing read result for %d job=%d",record->iID,record->myId);
466 while ( fetch_line=odbData->fetch_row() ) {
467 if ( record->contFifo->size() > 100 ) {
470 record->contFifo->write(db()->unescape_blob(fetch_line[0]));
471 record->iNextRecNbr= (int)fetch_line[1] +1;
473 DBREAD("next=%d, last=%d", record->iNextRecNbr, record->iMaxRecNbr);
474 if ( record->iNextRecNbr > 0 &&
475 record->iNextRecNbr <= record->iMaxRecNbr)
477 DBREAD("Continue reading...\n");
478 object mlock = record->fullMutex->lock();
479 if ( record->contFifo->size() > 100 )
480 record->restore = add_record;
482 readQueue->write(record); // further reading
486 DBREAD("Read finished...\n");
487 record->contFifo->write(0);
494 FATAL("Error while reading from database: %O", err);
496 DBREAD("finished read on %d", record->iID);
497 record->contFifo->write(0);
506 array job = writeQueue->read();
507 function|string data;
509 [id, iNextRecNbr,data] = job;
511 string line = "insert into doc_data values('"+
512 db()->escape_blob(data)+"', "+ id +", "+iNextRecNbr+")";
513 mixed err = catch{db()->big_query(line);};
515 FATAL("Fatal error while writting FILE into database: %O\n%O", err[0], err[1]);
518 else if (functionp(data)) {
527 object read_from_database(int id, int nextID, int maxID, object dbfile)
529 SqlReadRecord record = SqlReadRecord();
531 record->dbfile = dbfile;
532 record->iNextRecNbr = nextID;
533 record->iMaxRecNbr = maxID;
534 record->contFifo = Thread.Fifo();
535 record->fullMutex = Thread.Mutex();
536 readQueue->write(record);
537 globalQueue->write(record);
541 void write_into_database(int id, int iRecNr, string|function data)
543 if ( object_program(CALLER) != (program)"/kernel/db_file" )
544 error("No Access to write into Database!");
545 writeQueue->write(({id, iRecNr, data}));
548 int check_save_demon()
551 if ( CALLER != _Server )
552 error("Unauthorized Call to check_save_demon() !");
554 int status = tSaveDemon->status();
555 //werror(ctime(time())+" Checking Database SAVE DEMON\n");
557 FATAL("----- DATABASE SAVE DEMON restarted ! ---");
558 tSaveDemon = thread_create(database_save_demon);
560 status = tPreSaveDemon->status();
562 FATAL("----- DATABASE MANAGER DEMON restarted ! ---");
563 tSaveDemon = thread_create(database_manager);
566 foreach(dbHandles, object handle)
569 oDemonDbHandle->keep();
571 if ( objectp(globalQueue) ) {
572 int sz = globalQueue->size();
575 object record = globalQueue->try_read();
576 // record has restore function set for 15 minutes (timeout)
577 // this means the record is also not in the readQueue
578 if ( objectp(record) ) {
579 if ( functionp(record->restore) && !record->check_timeout() ) {
580 object dbfile = record->dbfile;
581 destruct(record->contFifo);
582 record->contFifo = 0;
584 destruct(dbfile); // make sure everything is gone and freed
587 globalQueue->write(record); // keep
594 void register_transient(array obs)
596 ASSERTINFO(CALLER==MODULE_SECURITY || CALLER== this_object(),
597 "Invalid CALLER at register_transient()");
601 mProxyLookup[obj->get_object_id()] = obj;
607 * set_variable is used to store database internal values. e.g. the last
608 * object ID, the last document ID, as well as object ID of modules etc.
609 * @param name - the name of the variable to store
610 * @param int value - the value
613 void set_variable(string name, int value)
615 if(sizeof(db()->query("SELECT var FROM variables WHERE var='"+name+"'")))
617 db()->big_query("UPDATE variables SET value='"+value+
618 "' WHERE var='"+name+"'" );
622 db()->big_query("INSERT into variables values('"+name+"','"+value+"')");
627 * get_variable reads a value stored by set_variable
628 * @param name - the name used by set_variable
629 * @returns int - value previously stored under given name
632 int get_variable(string name)
635 res = db()->big_query("select value from variables where "+
637 if (objectp(res) && res->num_rows())
638 return (int) res->fetch_row()[0];
644 * reads the currently used max ID from the database and given table
645 * and increments. for performance reasons this ID is cached.
647 * @param int db - database to connect to
648 * @param string table - table to choose
649 * @return int - the calculated ID
650 * @see free_last_db_id
654 int create_new_database_id(string table)
656 object lock = idMutex->lock(2);
658 if (!mCurrMaxID[table]) {
663 result = get_variable(table);
667 query = sprintf("select max(doc_id) from %s",table);
668 res = db()->big_query(query);
669 result = (int) res->fetch_row()[0];
672 query = sprintf("select max(ob_id) from %s",table);
673 res = db()->big_query(query);
674 result = max((int) res->fetch_row()[0], 1);
677 mCurrMaxID[table] = result;
679 mCurrMaxID[table] += 1;
680 // MESSAGE("Created new database ID"+(int) mCurrMaxID[table]);
681 set_variable(table, mCurrMaxID[table]);
684 FATAL("Error whilte creating database ID: %O\n%O", err[0], err[1]);
686 return mCurrMaxID[table];
692 * called in case, a newly created database id is obsolete,
693 * usually called to handle an error occuring in further handling
695 * @param int db - Database to connect to
696 * @param string table - table choosen
698 * @see create_new_databas_id()
700 void free_last_db_id(string table)
706 * creates a new persistent sTeam object.
708 * @param string prog (the class to clone)
709 * @return proxy and id for object
710 * note that proxy creation implies creation of associated object.
711 * @see kernel.proxy.create, register_user
713 mixed new_object(object obj, string prog_name)
717 // check for valid object has to be added
718 // create database ID
720 if ( CALLER != _Persistence )
721 error("Only Persistence Module is allowed to get in here !");
723 int id = obj->get_object_id();
726 ASSERTINFO((p=mProxyLookup[id])->get_object_id() == id,
727 "Attempt to reregister object in database!");
731 object lock = createMutex->lock(); // make sure creation is save
735 new_db_id = create_new_database_id("ob_class");
737 p = new(PROXY, new_db_id, obj );
738 if (!objectp(p->get_object())) // error occured during creation
740 free_last_db_id("ob_class");
744 // insert the newly created Object into the database
745 if (prog_name!="-") {
746 if ( search(prog_name, "classes/") == 0 ) {
747 // something is wrong here - when does this happen ??
748 prog_name = "/" + prog_name; // use absolute path for classes
749 FATAL("database.new_object: Warning - incorrect program name !");
751 Sql.sql_result res1 = db()->big_query(
752 sprintf("insert into ob_class (ob_id, ob_class) values "+
753 "(%d, '%s')", new_db_id, prog_name)
755 mProxyLookup[new_db_id] = p;
760 FATAL("database.new_object: failed to create object\n %O", err);
765 return ({ new_db_id, p});
769 * permanently destroys an object from the database.
770 * @param object represented by (proxy) to delete
774 bool delete_object(object p)
776 if ( CALLER!=_Persistence &&
778 werror("caller of delete_object() is %O\n", CALLER);
779 THROW("Illegal call to database.delete_object", E_ACCESS);
785 private bool do_delete(object p)
788 int iOID = p->get_object_id();
789 db()->query("delete from ob_data where ob_id = "+iOID);
790 db()->query("delete from ob_class where ob_id = "+iOID);
791 proxy = mProxyLookup[iOID];
792 if ( objectp(proxy) )
793 catch(proxy->set_status(PSTAT_DELETED));
794 m_delete(mProxyLookup, iOID);
802 void fail_unserialize(int iOID, string sData, mapping mData, mapping oldData)
804 FATAL("MISMATCH in unserialize %d\n%s\nNEW=%O\n\nOLD=%O\n", iOID, sData,
813 * load and restore values of an object with given Object ID
815 * @return 0, object deleted
816 * @return 1, object failed to compile
817 * @return 2, objects class deleted
818 * @return 3, object fails to load
822 int|object load_object(object proxy, int|object iOID)
830 if ( CALLER != _Persistence )
831 error("Unable to load objects directly !");
835 Sql.sql_result res = db()->big_query(
836 sprintf("select ob_class from ob_class where ob_id = %d", iOID)
838 mixed line = res->fetch_row();
851 if ( (pos = search(sClass, "DB:#")) >= 0 ) {
852 sClass = "/"+sClass[pos..];
853 if (search(sClass, ".pike")==-1)
856 o = new(sClass, proxy);
859 if (!objectp(o)) // somehow failed to load file
862 _Server->add_error(time(), catched);
863 string pikeClass = sClass;
864 sscanf(pikeClass, "%s.pike", pikeClass);
865 if (!master()->master_file_stat(pikeClass+".pike")) {
868 FATAL("Error loading object %d (%s)\n%s", iOID, sClass,
869 master()->describe_backtrace(catched));
871 return 1; // class exists but failes to compile
873 proxy->set_steam_obj(o); // o is the real thing - no proxy!
877 res = db()->big_query(
878 sprintf("select ob_ident, ob_attr, ob_data from ob_data where "+
879 "ob_id = %d", iOID));
881 mapping mStorage = get_storage_handlers(o);
882 if ( !mappingp(mStorage) || sizeof(mStorage) == 0 ) {
883 proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
884 FATAL("Setting UNSERIALIZE on %O - no storage map!\n%O", proxy,
886 FATAL("OBJECT is %O", o);
889 mapping mIndexedStorage = ([]);
891 foreach(indices(mStorage), string _ident) // precreate indexed idents
892 if (mStorage[_ident][2])
893 mIndexedStorage[_ident]=([]);
895 while (line = res->fetch_row())
897 [sIdent, sAttrib, sData] = line;
899 #if !constant(steamtools.unserialize) || !USE_CSERIALIZE
900 mData = unserialize(sData); // second arg is "this_object()"
902 mixed sErr = catch(mData = steamtools.unserialize(sData, find_object));
904 FATAL("Failed to load object - error in unserialize: %O\nData:%O",
907 #if constant(check_equal)
909 mapping oldData = unserialize(sData);
910 if ( !check_equal(mData, oldData) ) {
911 call(fail_unserialize, 1, iOID, sData, mData, oldData);
919 FATAL("While loading ("+iOID+","+sClass+"):\n%O\n%O\n%O",
923 proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
924 werror("Setting UNSERIALIZE on %O - error while loading!!\n", proxy);
929 if ( !mIndexedStorage[sIdent] ) {
930 FATAL("WARNING: Missing Storage %s in %d, %s, attrib=%O",
931 sIdent, iOID, sClass, sAttrib);
934 mIndexedStorage[sIdent][sAttrib]=mData;
936 else if ( !mStorage[sIdent] ) {
937 FATAL("No storage handler %s defined in %d (%s).\ndata=%O",
938 sIdent, iOID, sClass, mData); // INFO ?!
943 if ( proxy->is_loaded() ) {
944 error(sprintf("Fatal error: already loaded %O\n", proxy));
946 mStorage[sIdent][1](mData); // actual function call
949 FATAL("Error while loading (%d, %s)\n"+
950 "Error while calling storage handler %s: %O\n"+
951 "Full Storage:%O\nData: %O",
959 foreach(indices(mIndexedStorage), string _ident)
960 mStorage[_ident][1](mIndexedStorage[_ident]); // prepared call
966 FATAL("POST load failure: %O\n%O", err[0], err[1]);
971 mapping get_storage_handlers(object o)
974 FATAL("Getting storage handlers for %O", o);
976 mapping storage = _Persistence->get_storage_handlers(o);
982 mixed call_storage_handler(function f, mixed ... params)
984 if ( CALLER != _Persistence )
985 error("Database:: call_storage_handler(): Unauthorized call !");
986 mixed res = f(@params);
990 int get_class_id(string classname)
993 case "/classes/Object":
995 case "/classes/User":
996 return CLASS_OBJECT + CLASS_CONTAINER + CLASS_USER;
997 case "/classes/Group":
998 return CLASS_GROUP + CLASS_OBJECT;
999 case "/classes/Container":
1000 return CLASS_OBJECT + CLASS_CONTAINER;
1001 case "/classes/Room":
1002 return CLASS_OBJECT + CLASS_CONTAINER + CLASS_ROOM;
1003 case "/classes/Link":
1004 return CLASS_OBJECT + CLASS_LINK;
1005 case "/classes/Exit":
1006 return CLASS_OBJECT + CLASS_LINK+CLASS_EXIT;
1007 case "/classes/Document":
1008 return CLASS_OBJECT + CLASS_DOCUMENT;
1009 case "/classes/Messageboard":
1010 return CLASS_MESSAGEBOARD + CLASS_OBJECT;
1011 case "/classes/Drawing":
1012 return CLASS_DRAWING + CLASS_OBJECT;
1013 case "/classes/Date":
1014 return CLASS_OBJECT + CLASS_DATE;
1015 case "/classes/DocXSL":
1016 return CLASS_OBJECT + CLASS_DOCUMENT + CLASS_DOCXSL;
1017 case "/classes/DocHTML":
1018 return CLASS_OBJECT + CLASS_DOCUMENT + CLASS_DOCHTML;
1024 string get_class_string(int classid)
1026 for (int i=0;i<32;i++)
1027 if ( classid & (1<<i))
1032 return "/classes/Room";
1034 return "/classes/User";
1035 case CLASS_CONTAINER:
1036 return "/classes/Container";
1037 case CLASS_DOCUMENT:
1038 return "/classes/Document";
1040 return "/classes/Group";
1042 return "/classes/Link";
1044 return "/classes/Exit";
1045 case CLASS_MESSAGEBOARD:
1046 return "/classes/Messageboard";
1048 return "/classes/Drawing";
1050 return "/classes/Object";
1054 * find an object from the global object cache or retreive it from the
1057 * @param int - iOID ( object ID from object to find )
1058 * @return object (proxy associated with object)
1061 final object find_object(int|string iOID)
1065 if ( stringp(iOID) )
1066 return _Server->get_module("filepath:tree")->path_to_object(iOID);
1069 THROW("Wrong argument to find_object() - expected integer!",E_ERROR);
1071 if ( iOID == 0 ) return 0;
1072 if ( iOID == 1 ) return this_object();
1074 if ( objectp(p = mProxyLookup[iOID]) )
1079 res = db()->big_query(sprintf("select ob_class from ob_class"+
1080 " where ob_id = %d", iOID));
1082 if (!objectp(res) || res->num_rows()==0)
1085 // create an empty proxy to avoid recursive loading of objects
1086 array row = res->fetch_row();
1087 p = new(PROXY, iOID, UNDEFINED, get_class_id(row[0]));
1088 mProxyLookup[iOID] = p;
1094 * The function is called to set a flag in an object for saving.
1095 * Additionally the functions triggers the global EVENT_REQ_SAVE event.
1099 void require_save(object proxy, void|string ident, void|string index, void|int action, void|array args)
1101 if (proxy && proxy->status()>=PSTAT_SAVE_OK) {
1102 preSaveQueue->write( ({ proxy, ident, index }) );
1107 void database_manager()
1114 [proxy, ident, index] = preSaveQueue->read();
1115 save_object(proxy, ident, index);
1123 * callback-function called to indicate that an object has been modified
1124 * in memory and needs to be saved to the database.
1126 * @param object p - (proxy) object to be saved
1133 save_object(object proxy, void|string ident, void|string index)
1135 if ( !objectp(proxy) )
1138 string savestore = SAVESTORE(ident,index);
1140 Thread.MutexKey low=lowSaveMutex->lock();
1142 if (!mappingp(mSaveIndex[proxy]))
1143 mSaveIndex[proxy] = ([ ]);
1145 if (mSaveIndex[proxy][savestore] == 1) {
1151 if (proxy->status() == PSTAT_SAVE_OK) {
1152 proxy->set_status(PSTAT_SAVE_PENDING);
1153 if ( !mSaveCount[proxy] )
1154 mSaveCount[proxy] = 0;
1157 mSaveIndex[proxy][savestore] = 1;
1158 mSaveCount[proxy] = mSaveCount[proxy] + 1;
1161 FATAL("Failure in save_object(): %O\n%O", err[0], err[1]);
1165 oSaveQueue->write(({proxy, ident, index }));
1171 * quote and check for maximum length of serialized data.
1172 * @param string data - string to handle
1173 * @param object o - object saved (for error reporting)
1174 * @param string ident - ident block (for error reporting)
1178 quote_data(mixed data, object o, string ident, function quoter, int|void utf8)
1181 data = copy_value(data);
1183 sdata = serialize(data, "utf-8");
1186 #if constant(steamtools.serialize) && USE_CSERIALIZE
1187 sdata = steamtools.serialize(data);
1189 string verifydata = serialize(data);
1190 if ( sdata != verifydata ) {
1191 // unable to verify mappings: index is not the same
1192 if ( (mappingp(data) && strlen(sdata) != strlen(verifydata)) || !mappingp(data) )
1193 FATAL("Failed to verify quoted data, steamtools.serialize returns %O, should be %O\ndata=%O", sdata, verifydata, data);
1198 sdata = serialize(data);
1202 if (strlen(sdata)> 16777215)
1203 FATAL("!!! FATAL - data truncated inserting %d bytes for %s block %s",
1205 (objectp(o) ? "broken" : o->get_identifier()),
1207 return quoter(sdata);
1213 * generate a "mysql-specific" replace statement for saving data according
1214 * @param object o - object to save data from
1215 * @param mapping storage - storage_data to access
1216 * @param string|void ident - optional arg to limit to ident
1217 * @param string|void index - optional arg to limit to index
1218 * @return the mysql statement
1222 prepare_save_statement(object o, mapping storage,
1223 string|void ident, string|void index)
1225 int oid = o->get_object_id();
1226 mapping statements = ([]);
1227 // in case you change the behavoir below - remember to change
1228 // behaviour in prepeare_clear_statement also
1230 arrayp(storage[ident]) ? ({ ident }) : indices(storage);
1232 string sClass = master()->describe_program(object_program(o->get_object()));
1233 function db_quote_data = db()->quote;
1234 foreach(idents, string _ident)
1236 if (!arrayp(storage[_ident]))
1238 FATAL("missing storage handler for _ident %O\n", _ident);
1239 FATAL("prepare_save_statement object=%O, storage=%O, "+
1240 "ident=%O, index=%O\n", o, storage, ident, index);
1242 else if (storage[_ident][2]) // an indexed data-storage
1244 if (zero_type(index))
1246 mapping mData = storage[_ident][0](); // retrieve all
1247 foreach(indices(mData), mixed _index)
1249 if (!stringp(_index))
1251 data = quote_data(mData[_index], o, _ident,
1253 statements[_ident+_index] =
1254 sprintf("(%d,'%s','%s','%s')",
1255 oid, _ident, db_quote_data(_index),
1261 if (_ident != "user" && index!="UserPassword")
1262 data = quote_data(storage[_ident][0](index), o, _ident,
1265 data = quote_data(storage[_ident][0](index), o, _ident,
1266 db_quote_data); // never convert user pw
1268 statements[_ident+index] =
1269 sprintf("(%d,'%s','%s','%s')",
1270 oid, _ident, db_quote_data(index), data);
1274 else // the usual unindexed data-storage
1276 data = quote_data(storage[_ident][0](), o, "all", db_quote_data);
1277 statements[_ident] =
1278 sprintf("(%d,'%s','%s','%s')",
1279 oid, _ident, "", data);
1282 return values(statements);
1289 * generate a delete statement that will clear all entries according to
1290 * the data that will be saved.
1291 * @see prepare_save_statement
1295 prepare_clear_statement(object o, mapping storage,
1296 string|void ident, string|void index, string|void tb)
1301 if (ident=="0" || index=="0")
1302 FATAL("strange call to prepare_clear_statement \n%s\n",
1303 describe_backtrace(backtrace()));
1305 if (!storage[ident]) ident =0; // better save then sorry - wrong ident
1306 // invoces a full save.
1308 return sprintf("delete from %s where ob_id='%d' and "+
1309 "ob_ident='%s' and ob_attr='%s'",
1310 tb, o->get_object_id(), ident, index);
1312 return sprintf("delete from %s where ob_id='%d' and "+
1313 "ob_ident='%s'", tb, o->get_object_id(), ident);
1315 return sprintf("delete from %s where ob_id='%d'",
1316 tb, o->get_object_id());
1323 * low level database function to store a given (proxy) object into the
1324 * database immediately.
1326 * @param object proxy - the object to be saved
1332 low_save_object(object p, string|void ident,string|void index,int|void killed)
1336 int stat = p->status();
1337 // saved twice while waiting
1338 if (stat==PSTAT_DISK || stat==PSTAT_DELETED || stat==PSTAT_FAIL_DELETED)
1340 m_delete(mSaveCount, p);
1341 m_delete(mSaveIndex, p);
1342 return; // low is local so this will unlock also
1345 ASSERTINFO(!objectp(MODULE_SECURITY) ||
1346 MODULE_SECURITY->valid_object(p),
1347 sprintf("invalid object in database.save_object: %O",p));
1349 if (p->status() < PSTAT_SAVE_OK)
1351 FATAL("DBSAVEDEMON ->broken instance not saved(%d, %s, status=%s)",
1353 master()->describe_object(p->get_object()),
1354 PSTAT(p->status()));
1358 if ( !p->is_loaded() ) {
1359 FATAL("DBSAVEDEMON ->trying to save an object that was not "+
1360 "previously loaded !!!!!\nObject ID="+p->get_object_id()+"\n");
1364 if (p->status()<PSTAT_SAVE_OK)
1365 THROW("Invalid proxy status for object:"+
1366 p->get_object_id()+"("+p->status()+")", E_MEMORY);
1367 mapping storage = get_storage_handlers(p);
1368 if ( !mappingp(storage) )
1369 THROW("Corrupted data_storage in "+master()->stupid_describe(p), E_MEMORY);
1371 if (master()->describe_program(object_program(p->get_object()))=="-")
1372 return; // temporary objects like executer
1375 prepare_save_statement(p, storage, ident, index );
1377 ASSERTINFO(sizeof(sStatements)!=0,
1378 sprintf("trying to insert empty data into object %d class %s",
1380 master()->describe_program(object_program(p->get_object()))));
1384 Thread.MutexKey low=lowSaveMutex->lock();
1389 if ( !mappingp(mSaveIndex[p]) ) {
1390 FATAL("Save index not mapped in %O, SaveCount = %O\n",
1394 mSaveIndex[p][SAVESTORE(ident, index)] = 0;
1396 if ( mSaveCount[p] <= 0 ) {
1397 m_delete(mSaveCount, p);
1398 m_delete(mSaveIndex, p);
1399 p->set_status(PSTAT_SAVE_OK);
1403 FATAL("Error in low_save_object(): %O\n%O", err[0], err[1]);
1405 destruct(low); // status set, so unlock
1410 // remove from ob_data
1411 db()->big_query("BEGIN;");
1412 mixed delete_err = catch {
1413 s = prepare_clear_statement(p, storage, ident, index, "ob_data");
1417 FATAL("FATAL in save-demon, deletion statement %s failed\n%O:%O\n",
1418 s, delete_err[0], delete_err[1]);
1421 s = db()->create_insert_statement(sStatements);
1423 db()->big_query("COMMIT;");
1427 FATAL("FATAL - Error in save-demon ------------\n%s\n---------!!!",
1428 master()->describe_backtrace(err));
1429 if ( objectp(lostData) )
1430 catch(lostData->write(sprintf("%d: %s--\n\n", p->get_object_id(),
1431 (sStatements*"\n")+"\n")));
1434 err = catch(update_classtable(p, index));
1436 FATAL("ERROR IN SAVE-DEMON (Updating classtable): %O\n%O\n",
1444 string db_get_path(int oid)
1446 string path = db_get_attribute(oid, OBJ_PATH);
1448 if ( !zero_type(path) )
1451 object env = db_get_attribute(oid, "Environment");
1452 if ( !objectp(env) ) {
1453 object obj = find_object(oid);
1455 if ( !(obj->get_object_class() & CLASS_ROOM) )
1457 else if ( !objectp(_ROOTROOM) )
1460 path = _FILEPATH->object_to_filename(obj);
1462 if ( obj->status() == PSTAT_SAVE_OK || obj->status() == PSTAT_DISK ) {
1463 string query = sprintf("INSERT into ob_data values (%d,'%s','%s','%s')",
1464 oid, "attrib", OBJ_PATH,
1465 oDemonDbHandle->quote(serialize(path)));
1466 oDemonDbHandle->query(query);
1470 path = db_get_path(env->get_object_id());
1471 path = path + (path != "/"?"/":"") + db_get_attribute(oid, OBJ_NAME);
1473 string query = sprintf("INSERT into ob_data values (%d,'%s','%s','%s')",
1474 oid, "attrib", OBJ_PATH,
1475 oDemonDbHandle->quote(serialize(path)));
1476 oDemonDbHandle->query(query);
1485 mixed db_get_attribute(int oid, string attribute)
1488 q = "select ob_data from ob_data where ob_attr='"+attribute+
1489 "' and ob_id='"+ oid+"'";
1490 object data = oDemonDbHandle->big_query(q);
1491 if ( objectp(data) && data->num_rows() > 0 )
1492 return unserialize(data->fetch_row()[0]);
1499 void update_classtableobject(int oid, void|object obj)
1501 object lock = updateMutex->lock(2); // only lock for current thread
1504 string name = db_get_attribute(oid, OBJ_NAME) || "";
1505 string desc = db_get_attribute(oid, OBJ_DESC) || "";
1506 array keywords = db_get_attribute(oid, OBJ_KEYWORDS);
1507 if ( !arrayp(keywords) )
1509 keywords += ({ name, desc });
1510 string mimetype = db_get_attribute(oid, DOC_MIME_TYPE) || "";
1511 mixed versionof = db_get_attribute(oid, OBJ_VERSIONOF) || "";
1512 if ( objectp(versionof) )
1513 versionof = versionof->get_object_id();
1515 string query = "UPDATE ob_class SET"+
1516 " obkeywords='"+oDemonDbHandle->quote(keywords*" ")+
1517 "', obname='"+oDemonDbHandle->quote(name)+
1518 "', obdescription='"+oDemonDbHandle->quote(desc)+
1519 "', obmimetype='"+oDemonDbHandle->quote(mimetype)+
1520 "', obversionof='" + versionof + "' WHERE ob_id='"+oid+"'";
1521 oDemonDbHandle->big_query(query);
1525 FATAL("Error while updating class index %O\n%O", err[0], err[1]);
1533 void update_classtable(object p, void|string index)
1535 if (stringp(index)) {
1536 if (index == OBJ_NAME || index == OBJ_DESC || index == DOC_MIME_TYPE ||
1537 index == OBJ_KEYWORDS || index == OBJ_VERSIONOF)
1538 update_classtableobject(p->get_object_id());
1541 update_classtableobject(p->get_object_id(), p);
1547 * Change the class of an object in the database. Drop the object to
1548 * get an object with the modified class.
1550 * @param object doc - change class for this document
1551 * @param string classfile - the new class
1553 int change_object_class(object doc, string classfile)
1555 if ( CALLER != _Persistence )
1556 steam_error("Illegal call to database.change_class() !");
1558 if ( !functionp(doc->get_object_id) )
1559 steam_error("database.change_class: object is no valid steam object!");
1560 int id = doc->get_object_id();
1562 classfile = CLASS_PATH + classfile; // only from classes directory
1563 MESSAGE("Changing Document class of %d to %s", id, classfile);
1564 db()->query("delete from ob_class where ob_id='" + id+"'");
1565 db()->query("insert into ob_class (ob_id, ob_class) values (%d, '%s')",
1572 * register an module with its name
1573 * e.g. register_module("users", new("/modules/users"));
1575 * @param string - a unique name to register with this module.
1576 * @param object module - the module object to register
1577 * @param void|string source - a source directory for package installations
1578 * @return (object-id|0)
1579 * @see /kernel/db_mapping, /kernel/secure_mapping
1581 int register_module(string oname, object module, void|string source)
1584 string version = "";
1586 //FATAL(sprintf("register module %s with %O source %O", oname, module, source));
1587 if ( CALLER != _Server &&
1588 !MODULE_SECURITY->access_register_module(0, 0, CALLER) )
1589 THROW("Unauthorized call to register_module() !", E_ACCESS);
1592 int imod = get_variable("#" + oname);
1596 mod = find_object(imod); // get old module
1597 if ( objectp(mod) && mod->status() >= 0 &&
1598 mod->status() != PSTAT_DELETED)
1600 object e = master()->getErrorContainer();
1601 master()->set_inhibit_compile_errors(e);
1602 realObject = mod->get_object();
1603 master()->set_inhibit_compile_errors(0);
1606 FATAL("Failed to compile update instance, re-installing");
1611 if ( objectp(realObject) && functionp(realObject->version) &&
1612 functionp(realObject->upgrade) )
1614 FATAL("Found previously registered version of module !");
1615 if ( objectp(module) && module->get_object() != realObject )
1616 THROW("Trying to register a previously registered module.",
1619 version = realObject->get_version();
1621 mixed erg = master()->upgrade(object_program(realObject));
1622 if (!intp(erg) || erg<0)
1625 THROW(erg, backtrace());
1628 FATAL("New version of "+oname+" doesn't implement old "+
1629 "versions interface");
1630 master()->upgrade(object_program(mod->get_object()),1);
1635 else if ( !objectp(module) )
1637 // module is in the /modules directory.
1638 object e = master()->getErrorContainer();
1639 master()->set_inhibit_compile_errors(e);
1640 module = new("/modules/"+oname+".pike");
1641 master()->set_inhibit_compile_errors(0);
1644 FATAL("Failed to compile new instance - throwing");
1645 THROW("Failed to load module\n"+e->get()+"\n"+
1646 e->get_warnings(), backtrace());
1650 MESSAGE("Installing module %s ...", oname);
1651 if ( !stringp(source) )
1654 if ( module->get_object_class() & CLASS_PACKAGE ) {
1656 if ( module->install(source, version) == 0 )
1657 error(sprintf("Failed to install module %s !", oname));
1659 MESSAGE("Installation of module %s succeeded.", oname);
1661 _Server->register_module(module);
1663 _Server->run_global_event(EVENT_REGISTER_MODULE, PHASE_NOTIFY,
1664 this_object(), ({ module }) );
1665 LOG_DB("event is run");
1666 if ( objectp(module) )
1668 set_variable("#"+oname, module->get_object_id());
1669 _Server->register_module(module);
1670 return module->get_object_id();
1676 * Check if a database handle is connected to a properly setup database.
1678 * @param Sql.Sql handle - the handle to check
1679 * @return 1 - old format
1680 * @return 2 - new format
1681 * @see setup_sTeam_tables
1683 int validate_db_handle(object handle)
1685 multiset tables = (<>);
1686 array aTables = handle->list_tables();
1688 foreach(aTables, string table)
1689 tables[table] = true;
1690 if (tables["objects"] && tables["doc_data"])
1692 if (tables["ob_class"] && tables["ob_data"] && tables["doc_data"])
1697 void add_database_update(object handle, string name)
1699 handle->query("insert into database_updates values(\""+name+"\")");
1705 int is_database_update(object handle, string name)
1707 object result = handle->big_query("select * from database_updates where "
1708 + "(database_update='" + name + "')");
1709 if ( !objectp(result) || result->num_rows() == 0 )
1717 int check_database_updates(object handle)
1719 if ( search(handle->list_tables(), "database_updates") == -1 ) {
1720 handle->query("create table database_updates (database_update char(128))");
1723 // depending objects: from attribute to data storage
1724 if ( !is_database_update( handle, "depending_objects_data_store" ) ) {
1725 MESSAGE( "Database update: converting depending objects" );
1726 werror( "Database update: converting depending objects\n" );
1727 // replace OBJ_DEPENDING_OBJECTS with DependingObjects:
1728 object res = oDemonDbHandle->big_query( "select * from ob_data where "
1729 + "(ob_ident='attrib' and ob_attr='OBJ_DEPENDING_OBJECTS')" );
1731 if ( objectp(res) ) {
1732 for ( int i=0; i<res->num_rows(); i++ ) {
1733 mixed row = res->fetch_row();
1734 if ( !arrayp(row) || sizeof(row) < 4 ) continue;
1738 foreach ( rows, mixed row ) {
1739 mixed dbres = oDemonDbHandle->big_query( "select ob_id from ob_data "
1740 + "where (ob_id=" + row[0] + " and ob_ident='data' and "
1741 + "ob_attr='DependingObjects')" );
1742 if ( !objectp(dbres) || dbres->num_rows() < 1 )
1743 oDemonDbHandle->big_query( "insert into ob_data "
1744 + "(ob_id,ob_ident,ob_attr,ob_data) values("
1745 + row[0] + ",'data','DependingObjects','" + row[3] + "')" );
1746 dbres = oDemonDbHandle->big_query( "select ob_id from ob_data where "
1747 + "(ob_id=" + row[0] + " and ob_ident='data' and "
1748 + "ob_attr='DependingObjects')" );
1749 if ( objectp(dbres) && dbres->num_rows() > 0 ) {
1750 oDemonDbHandle->big_query( "delete from ob_data where (ob_id="
1751 + row[0] + " and ob_ident='attrib' and "
1752 + "ob_attr='OBJ_DEPENDING_OBJECTS')" );
1755 // replace OBJ_DEPENDING_ON with DependingOn:
1756 res = oDemonDbHandle->big_query( "select * from ob_data where "
1757 + "(ob_ident='attrib' and ob_attr='OBJ_DEPENDING_ON')" );
1759 if ( objectp(res) ) {
1760 for ( int i=0; i<res->num_rows(); i++ ) {
1761 mixed row = res->fetch_row();
1762 if ( !arrayp(row) || sizeof(row) < 4 ) continue;
1766 foreach ( rows, mixed row ) {
1767 mixed dbres = oDemonDbHandle->big_query( "select ob_id from ob_data "
1768 + "where (ob_id=" + row[0] + " and ob_ident='data' and "
1769 + "ob_attr='DependingOn')" );
1770 if ( !objectp(dbres) || dbres->num_rows() < 1 )
1771 oDemonDbHandle->big_query( "insert into ob_data "
1772 + "(ob_id,ob_ident,ob_attr,ob_data) values("
1773 + row[0] + ",'data','DependingOn','" + row[3] + "')" );
1774 dbres = oDemonDbHandle->big_query( "select ob_id from ob_data where "
1775 + "(ob_id=" + row[0] + " and ob_ident='data' and "
1776 + "ob_attr='DependingOn')" );
1777 if ( objectp(dbres) && dbres->num_rows() > 0 ) {
1778 oDemonDbHandle->big_query( "delete from ob_data where (ob_id="
1779 + row[0] + " and ob_ident='attrib' and "
1780 + "ob_attr='OBJ_DEPENDING_ON')" );
1783 // check whether there are still OBJ_DEPENDING_* attributes:
1784 res = oDemonDbHandle->big_query( "select * from ob_data where "
1785 + "(ob_ident='attrib' and (ob_attr='OBJ_DEPENDING_OOBJECTS' or "
1786 + "ob_attr='OBJ_DEPENDING_ON'))" );
1787 if ( objectp(res) && res->num_rows() == 0 ) {
1788 add_database_update( handle, "depending_objects_data_store" );
1789 werror( "Database update: finished depending objects update.\n" );
1790 MESSAGE( "Database update: finished depending objects update." );
1793 werror( "Database update: errors occurred, there are still %O "
1794 + "depending object entries. Will run update again on next "
1795 + "server restart.\n", res->num_rows() );
1796 MESSAGE( "Database update: errors occurred, there are still %O "
1797 + "depending object entries. Will run update again on next "
1798 + "server restart.", res->num_rows() );
1807 object dbupdates = _Server->get_update("database");
1808 if ( !objectp(dbupdates) ) {
1809 dbupdates = get_factory(CLASS_CONTAINER)->execute((["name":"database"]));
1810 _Server->add_update(dbupdates);
1812 mapping result = oDemonDbHandle->check_updates(dbupdates,
1813 update_classtableobject);
1814 foreach(indices(result), string updateIdx) {
1815 object update = get_factory(CLASS_DOCUMENT)->execute((["name":updateIdx,
1816 "mimetype":"text/plain"]));
1817 update->set_content(result[updateIdx]);
1818 update->move(dbupdates);
1825 void check_tables(object handle)
1827 handle->check_tables();
1833 void check_journaling(Sql.Sql handle)
1835 mixed row, res, err;
1837 string lost = Stdio.read_file("/tmp/lost_data."+BRAND_NAME);
1838 lostData = Stdio.File("/tmp/lost_data."+BRAND_NAME, "wct");
1839 if ( stringp(lost) && strlen(lost) > 0 ) {
1840 array lostlines = lost / "--\n\n";
1841 foreach(lostlines, string ll) {
1842 werror("LOST DATA: Restoring %s\n", ll);
1843 MESSAGE("LOST DATA: Restore %s", ll);
1845 string ident, attr, val, rest;
1846 if ( sscanf(ll, "%d: %s", oid, ll) != 2 )
1848 while ( sscanf(ll, "(%*s,%s,%s,%s)\n%s", ident, attr, val, rest) >= 3 ) {
1849 werror("values are %O %O %O %O\n", oid, ident, attr, val);
1851 res = handle->query(sprintf("select ob_data from ob_data where ob_id='%d' and ob_ident=%s and ob_attr=%s", oid, ident, attr));
1852 row = res->fetch_row();
1853 if ( sizeof(row) > 0 ) {
1854 handle->query(sprintf("update ob_data SET ob_data=%s where ob_id='%d' and ob_ident=%s and ob_attr=%s", val, oid, ident, attr));
1855 werror("updated!\n");
1858 handle->query(sprintf("insert into ob_data values (%d,%s,%s,%s)",
1859 oid, ident, attr, val));
1864 FATAL("Failed to restore data: %O", err);
1866 if ( stringp(rest) || strlen(rest) > 1 )
1878 void add_new_tables(Sql.Sql handle) {
1879 MESSAGE("adding new format tables\n");
1880 MESSAGE("adding ob_class ");
1882 handle->query("drop table if exists ob_class");
1883 handle->query("drop table if exists ob_data");
1885 handle->query("create table ob_class ("+
1886 " ob_id int primary key, "+
1887 " ob_class char(128) "+
1890 MESSAGE("adding ob_data ");
1891 handle->query("create table ob_data ("+
1893 " ob_ident char(15),"+
1894 " ob_attr char(50), "+
1895 " ob_data mediumtext,"+
1896 " unique(ob_id, ob_ident, ob_attr),"+
1897 " index i_attr (ob_attr),"+
1898 " index i_ident (ob_ident),"+
1899 " index i_attrdata (ob_attr, ob_data(80))"+
1902 handle->query("create table ob_journaling ("+
1904 " ob_ident char(15),"+
1905 " ob_attr char(50), "+
1906 " ob_data mediumtext,"+
1907 " unique(ob_id, ob_ident, ob_attr),"+
1908 " index i_attr (ob_attr)"+
1915 * set up the base sTeam tables to create an empty database.
1920 int setup_sTeam_tables(object handle)
1922 /* make sure no old tables exist and delete them properly */
1923 MESSAGE("Checking for old tables.\n");
1925 array res = handle->list_tables();
1928 foreach(res, string table)
1930 MESSAGE(sprintf("dropping (%s)\n",table));
1931 handle->big_query("drop table "+table);
1935 MESSAGE("no old tables found");
1937 MESSAGE("CREATING NEW BASE TABLES:");
1938 handle->create_tables();
1940 res = handle->list_tables();
1942 FATAL("\nFATAL: failed to create base tables");
1946 MESSAGE("\nPOST CHECK retrieves: ");
1947 foreach(res, string table)
1954 * create and return a new instance of db_file
1956 * @param int iContentID - 0|ID of a given Content
1957 * @return the db_file-handle
1961 object new_db_file_handle(int iContentID, string mode)
1963 return new("/kernel/db_file.pike", iContentID, mode);
1967 * connect_db_file, connect a /kernel/db_file instance with the database
1968 * calculate new content id if none given.
1971 * @return function db()
1973 final mixed connect_db_file(int id)
1975 if ( object_program(CALLER) != (program)"/kernel/db_file.pike" )
1976 steam_error("Security Error: Failed to connect db file !");
1977 return ({ db, (id==0 ? create_new_database_id("doc_data") : id)});
1981 * valid_db_mapping - check if an object pretending to be an db_mapping
1982 * really inherits /kernel/db_mapping and thus is a trusted program
1983 * @param m - object inheriting db_mapping
1984 * @return (TRUE|FALSE)
1985 * @see connect_db_mapping
1988 private bool valid_db_mapping(object m)
1990 if ( Program.inherits(object_program(m),
1991 (program)"/kernel/db_mapping.pike") ||
1992 Program.inherits(object_program(m),
1993 (program)"/kernel/db_n_one.pike") ||
1994 Program.inherits(object_program(m),
1995 (program)"/kernel/db_n_n.pike") ||
1996 Program.inherits(object_program(m),
1997 (program)"/kernel/db_searching.pike"))
2005 * connect_mapping, connect a /kernel/db_mapping instance with the database
2007 * @return a pair ({ function db, string tablename })
2009 final mixed connect_db_mapping()
2011 if (!(valid_db_mapping(CALLER)))
2013 FATAL("illegal access %s from %O\n",CALLINGFUNCTION, CALLER);
2014 THROW("illegal access to database ", E_ACCESS);
2017 // hack to allow the modules table to be a member of _Database
2019 sDbTable = CALLER->get_table_name();
2023 THROW(sprintf("Invalid tablename [%s] in module '%s'\n",sDbTable,
2024 master()->describe_program(CALLERPROGRAM)), E_ERROR);
2025 return ({ db, sDbTable });
2028 string get_identifier() { return "database"; }
2029 string _sprintf() { return "database"; }
2030 string describe() { return "database"; }
2031 int get_object_class() { return CLASS_DATABASE; }
2032 int status() { return PSTAT_SAVE_OK; }
2038 * get_objects_by_class()
2039 * mainly for maintenance reasons, retreive all objects matching a given
2040 * class name, or program
2041 * @param class (string|object|int) - the class to compare with
2042 * @return array all objects found in the database
2043 * throws on access violation. (ROLE_READ_ALL required)
2045 final array get_objects_by_class(string|program|int mClass)
2053 if (objectp(security=MODULE_SECURITY) && objectp(lookup_user("root")) ) {
2054 if ( !_Server->is_a_factory(CALLER) )
2055 security->check_access(0, this_user(), 0, ROLE_READ_ALL, false);
2058 if ( intp(mClass) ) {
2059 mixed factory = _Server->get_factory(mClass);
2060 if ( !objectp(factory) )
2062 if ( !stringp(CLASS_PATH) || !stringp(factory->get_class_name()) )
2064 mClass = CLASS_PATH + factory->get_class_name();
2067 if (programp(mClass))
2068 sClass = master()->describe_program(mClass);
2072 res = db()->big_query("select ob_id from ob_class where ob_class='"+
2075 aObjects = allocate((sz=res->num_rows()));
2079 aObjects[i]=find_object((int)res->fetch_row()[0]);
2087 * mainly for maintenance reasons
2088 * @return array all objects found in the database
2089 * throws on access violation. (ROLE_READ_ALL required)
2091 final array get_all_objects()
2093 if ( !_Server->is_a_factory(CALLER) )
2094 THROW("Illegal attempt to call database.get_all_objects !", E_ACCESS);
2095 return low_get_all_objects();
2099 private array low_get_all_objects()
2105 res = db()->big_query("select ob_id from ob_class where ob_class !='-'");
2106 aObjects = allocate((sz=res->num_rows()));
2110 aObjects[i]=find_object((int)res->fetch_row()[0]);
2119 * loads all objects from the database, makes sure each object really loads
2120 * and calls the function given as "visitor" with consecutive with each object.
2121 * @param function visitor
2123 * @see get_all_objects
2124 * @see get_all_objects_like
2125 * @caveats Because this function makes sure an object is properly loaded
2126 * when passing it to function "visitor", you won't
2127 * notice the existence of objects currently not loading.
2129 final void visit_all_objects(function visitor, mixed ... args)
2131 FATAL("visit_all_objects not yet converted to new database format");
2133 Sql.sql_result res = db()->big_query("select ob_id,ob_class from ob_class");
2138 FATAL("Number of objects found:"+res->num_rows());
2139 for (i=0;i<res->num_rows();i++)
2141 mixed erg = res->fetch_row();
2142 oid = (int) erg[0]; // wrong casting with
2143 oclass = erg[1]; // [oid, oclass] = res->fetch_row()
2145 if (oclass[0]=='/') // some heuristics to avoid nonsene classes
2147 p = find_object((int)oid); // get the proxy
2148 catch{p->get_object();}; // force to load the object
2149 if (p->status() > PSTAT_DISK) // positive stati mean object loaded
2156 * Check for a list of objects, if they really exist in the database
2158 * @param objects - the list of object to be checked
2159 * @return a list of those objects, which really exist.
2160 * @see get_not_existing
2162 array get_existing(array ids)
2166 string query = "select ob_id from ob_class where ob_id in (";
2169 if (!ids || !sizeof(ids))
2171 for (i=0,sz=sizeof(ids)-1;i<sz;i++)
2174 res = db()->big_query(query);
2176 result = allocate((sz=res->num_rows()));
2178 result[i]=(int) res->fetch_row()[0];
2184 * Get a list of the not-existing objects.
2186 * @param objects - the list of objects to be checked
2187 * @return a list of objects that are not existing
2190 array get_not_existing(array ids)
2192 return ids - get_existing(ids);
2195 object get_environment() { return 0; }
2196 object get_acquire() { return 0; }
2198 mapping get_xml_data()
2200 return ([ "configs":({_Server->get_configs, XML_NORMAL}), ]);
2204 * clears lost content records from the doc_data table, used for the
2205 * db_file emulation. This function is purely for maintainance reasons, and
2206 * should be obsolete, since we hope no content records will get lost
2209 * @returns a debug string containing the number of deleted doc_id's
2211 string clear_lost_content()
2214 LOG("getting doc_ids");
2215 Sql.sql_result res = h->big_query("select distinct doc_id from doc_data");
2216 array doc_ids = allocate(res->num_rows());
2217 for(int i=0;i<sizeof(doc_ids);i++)
2218 doc_ids[i]=(int)res->fetch_row()[0];
2220 FATAL("deleting '-' files");
2221 h->big_query("delete from objects where ob_class='-'");
2222 FATAL("getting all objects");
2223 res = h->big_query("select ob_id from ob_class");
2224 int oid; object p; mixed a;
2225 while (a = res->fetch_row())
2228 if (p=find_object(oid))
2230 FATAL("accessing object"+oid);
2232 catch{try=p->get_object();};
2234 Program.inherits(object_program(try),
2235 (program)"/base/content"))
2237 FATAL("content "+p->get_content_id()+" is in use");
2238 doc_ids -= ({ p->get_content_id() });
2243 FATAL("number of doc_ids to be deleted is:"+sizeof(doc_ids));
2245 foreach (doc_ids, int did)
2247 h->big_query("delete from doc_data where doc_id = "+did);
2248 FATAL("deleting doc_id"+did);
2250 FATAL("calling optimize");
2251 h->big_query("optimize table doc_data");
2252 return "deleted "+sizeof(doc_ids)+"lost contents";
2255 object lookup (string identifier)
2257 return get_module("objects")->lookup(identifier);
2260 object lookup_user (string identifier)
2262 return get_module("users")->get_user(identifier);
2265 object lookup_group (string identifier)
2267 return get_module("groups")->get_group(identifier);
2270 int supported_classes () { return CLASS_ALL; }
2273 * Searches for users in the persistence layer.
2275 * @param terms a mapping ([ attrib : value ]) where attrib can be "firstname",
2276 * "lastname", "login" or "email" and value is the text ot search for in the
2277 * attribute. If the values contain wildcards, specify the wildcard character
2278 * in the wildcard param.
2279 * @param any true: return all users that match at least one of the terms
2280 * ("or"), false: return all users that match all of the terms ("and").
2281 * @param wildcard a string containing the wildcard used in the search term
2282 * values, or 0 (or unspecified) if no wildcards are used
2283 * @return an array of user names (not objects) of matching users
2285 array search_users ( mapping terms, bool any, string|void wildcard ) {
2287 if ( stringp(wildcard) && sizeof(wildcard) > 0 ) eq = " like ";
2289 array queries = ({ });
2290 foreach ( indices(terms), mixed attr ) {
2291 mixed value = terms[ attr ];
2292 if ( !stringp(attr) || sizeof(attr)<1 ||
2293 !stringp(value) || sizeof(value)<1 )
2300 // additional strings might be mapped to column names...
2302 default : continue; // don't make invalid queries
2304 if ( stringp(wildcard) && sizeof(wildcard) > 0 )
2305 value = replace( value, wildcard, "%" );
2306 queries += ({ attr + eq + "'" + db()->quote(value) + "'" });
2308 string op = " and ";
2309 if ( any ) op = " or ";
2310 mixed where = queries * op;
2311 if ( !stringp(where) || sizeof(where) == 0 ) {
2312 werror( "database(%s): search_users called with invalid search terms: %O "
2313 + "(any: %O, wildcard: %O)\n",
2314 Calendar.Second(time())->format_time(), terms, any, wildcard );
2318 Sql.sql_result res = db()->big_query( "select distinct ob_id from " +
2319 "i_userlookup where " + where );
2322 if ( !objectp(res) )
2325 array result = ({ });
2326 while ( row = res->fetch_row() ) {
2327 object user = find_object( (int)row[0] );
2328 if ( objectp(user) )
2329 result += ({ user->get_identifier() });
2337 * Searches for groups in the persistence layer.
2339 * @param terms a mapping ([ attrib : value ]) where attrib can be "name"
2340 * and value is the text ot search for in the attribute.
2341 * If the values contain wildcards, specify the wildcard character in the
2343 * @param any true: return all groups that match at least one of the terms
2344 * ("or"), false: return all groups that match all of the terms ("and").
2345 * @param wildcard a string containing the wildcard used in the search term
2346 * values, or 0 (or unspecified) if no wildcards are used
2347 * @return an array of group names (not objects) of matching groups
2349 array search_groups ( mapping terms, bool any, string|void wildcard ) {
2351 if ( stringp(wildcard) && sizeof(wildcard) > 0 ) eq = " like ";
2353 array queries = ({ });
2354 foreach ( indices(terms), mixed attr ) {
2355 mixed value = terms[ attr ];
2356 if ( !stringp(attr) || sizeof(attr)<1 ||
2357 !stringp(value) || sizeof(value)<1 )
2360 case "name" : attr = "k"; break;
2361 // additional strings might be mapped to column names...
2362 default : continue; // don't make invalid queries
2364 if ( stringp(wildcard) && sizeof(wildcard) > 0 )
2365 value = replace( value, wildcard, "%" );
2366 queries += ({ attr + eq + "'" + db()->quote(value) + "'" });
2368 string op = " and ";
2369 if ( any ) op = " or ";
2370 mixed where = queries * op;
2371 if ( !stringp(where) || sizeof(where) == 0 ) {
2372 werror( "database(%s): search_groups called with invalid search terms: %O "
2373 + "(any: %O, wildcard: %O)\n",
2374 Calendar.Second(time())->format_time(), terms, any, wildcard );
2378 Sql.sql_result res = db()->big_query( "select distinct v from i_groups " +
2382 if ( !objectp(res) )
2385 array result = ({ });
2386 while ( row = res->fetch_row() ) {
2388 if ( sscanf( row[0], "%%%d", ob_id ) > 0 ) {
2389 object group = find_object( ob_id );
2390 if ( objectp(group) )
2391 result += ({ group->get_identifier() });