1
/* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3
* This program is free software; you can redistribute it and/or modify
4
* it under the terms of the GNU General Public License as published by
5
* the Free Software Foundation; either version 2 of the License, or
6
* (at your option) any later version.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program; if not, write to the Free Software
15
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
* $Id: database.pike,v 1.22 2006/10/09 19:17:47 astra Exp $
20
constant cvs_version="$Id: database.pike,v 1.22 2006/10/09 19:17:47 astra Exp $";
22
inherit "/base/serialize.pike";
23
inherit Thread.Mutex : muBusy;
24
inherit Thread.Mutex : muLowSave;
28
#include <attributes.h>
35
#include <exception.h>
37
#include <configure.h>
39
#define MODULE_SECURITY _Server->get_module("security")
40
#define MODULE_USERS _Server->get_module("users")
42
#define PROXY "/kernel/proxy.pike"
44
private static mapping(int:mapping(string:int)) mCurrMaxID;
45
private static mapping(int:object ) mProxyLookup;
46
private static Thread.Mutex loadMutex = Thread.Mutex();
47
private static Thread.Mutex createMutex = Thread.Mutex();
49
private static int iCacheID;
50
private static object oSQLCache;
51
private static object oSaveQueue;
53
private static object oTlDbHandle;
54
private static object oDbHandle;
55
private static object tSaveDemon;
56
private static object oDemonDbHandle;
57
private static object oModules;
58
private static mapping(string:object) mDbMaps;
60
private static string sDbUser;
61
private static string sDbPassword;
62
private static int idbMappingNaming;
63
private static Stdio.File lostData;
64
private static array(object) tReaderThreads;
65
private static Thread.Queue readQueue;
66
private static Thread.Queue globalQueue;
72
#define CHECKISO(s, mime, obj) if (search(s, mime)>0) { obj->restore_attr_data(mime, DOC_ENCODING); werror(" %s",mime); }
74
private static mapping(string:object) oModuleCache;
75
private static Calendar.Calendar cal = Calendar.ISO->set_language("german");
77
private static int CONVERSION = 0;
79
private static mapping(string:int) mSaveRequests = ([ ]);
81
string generate_request_name(int oid, mixed ident, mixed index)
83
return (string)oid+"|"+(stringp(ident)? ident : "0")+
84
"|" + (stringp(index)? index : "0");
88
int iMaxRecNbr; // record number
90
int iNextRecNbr; // record number current
92
function restore; // function if record needs to be restored
94
Thread.Mutex fullMutex;
100
if ( objectp(dbfile) ) {
101
int t = time() - dbfile->get_last_access();
102
if ( !objectp(contFifo) )
115
private string db_connect;
119
oHandle->big_query("select ob_class from ob_class "+
124
void create(string connect) {
125
db_connect = connect;
126
oHandle = Sql.Sql(db_connect);
129
int|object big_query(object|string q, mixed ... extraargs) {
131
mixed err = catch { res=oHandle->big_query(q, @extraargs); };
134
FATAL(cal->Second()->format_nice()+
135
" Database Error ("+(string)oHandle->error()+")\n"+
136
master()->describe_backtrace(err));
142
array(mapping(string:mixed)) query(object|string q, mixed ... extraargs) {
143
array(mapping(string:mixed)) res;
144
mixed err = catch { res=oHandle->query(q, @extraargs);};
147
FATAL(cal->Second()->format_nice()+
148
" Database Error("+(string)oHandle->error()+")");
150
oHandle = Sql.Sql(db_connect);
151
res = oHandle->query(q, @extraargs);
157
function `->(string fname) {
159
case "query": return query;
160
case "big_query" : return big_query;
161
case "keep": return keep;
163
if ( !objectp(oHandle) )
165
return oHandle[fname];
168
string describe() { return "SqlHandle()"; }
173
* return a thread-local (valid) db-handle
176
* @return the database handle
178
private static Sql.Sql db()
180
if (this_thread() == tSaveDemon) // give saveDemon its own handle
181
return oDemonDbHandle;
183
// everybody else gets the same shared handle
184
if (!objectp(oDbHandle))
186
oDbHandle = SqlHandle(STEAM_DB_CONNECT);
187
if (!validate_db_handle(oDbHandle))
188
setup_sTeam_tables(oDbHandle);
192
// FATAL(cal->Second()->format_nice()+": database handle requested.");
197
* mimick object id for serialization etc.
198
* @return ID_DATABASE from database.h
199
* @see object.get_object_id
200
* @author Ludger Merkens
202
final int get_object_id()
207
private static void db_execute(string db_query)
209
db()->big_query(db_query);
214
return oSaveQueue->size();
218
* demon function to store pending object saves to the database.
219
* This function is started as a thread and waits for objects entering
220
* a queue to save them to the database.
225
* @author Ludger Merkens
227
void database_save_demon()
229
MESSAGE("DATABASE SAVE DEMON ENABLED");
236
job = oSaveQueue->read();
239
lBusy = muBusy::lock();
247
[proxy, ident, index] = job;
248
low_save_object(proxy, ident, index);
254
if (oSaveQueue->size() == 0) {
259
FATAL("/**************** database_save_demon *************/\n"+
266
* wait_for_db_lock waits until all pending database writes are done, and
267
* afterwards aquires the save_demon lock, thus stopping the demon. Destruct
268
* the resulting object to release the save demon again.
271
* @return the key object
272
* @see Thread.Mutex->lock
273
* @author Ludger Merkens
275
object wait_for_db_lock()
277
return muBusy::lock();
281
* constructor for database.pike
282
* - starts thread to keep objects persistent
283
* - enables commands in database
286
* @author Ludger Merkens
290
// first check for lost data, etc.
292
mProxyLookup = ([ ]);
294
oSaveQueue = Thread.Queue();
295
oTlDbHandle = thread_local();
301
_Persistence->register( "database", this_object() );
304
object enable_modules()
306
// oDemonDbHandle = Sql.Sql(STEAM_DB_CONNECT);
307
tSaveDemon = thread_create(database_save_demon);
308
tReaderThreads = ({ });
309
readQueue = Thread.Queue();
310
globalQueue = Thread.Queue();
311
for ( int i = 0; i < 2; i++ )
312
tReaderThreads += ({ thread_create(db_reader) });
314
oModules = ((program)"/modules/modules.pike")();
315
oModuleCache = ([ "modules": oModules ]);
317
oDemonDbHandle = SqlHandle(STEAM_DB_CONNECT);
318
int x=validate_db_handle(oDemonDbHandle);
320
setup_sTeam_tables(oDemonDbHandle);
322
add_new_tables(oDemonDbHandle);
324
check_journaling(oDemonDbHandle);
328
//#define DBREAD(l, args...) werror("%O"+l+"\n", Thread.this_thread(), args)
329
#define DBREAD(l, args ...)
331
static void add_record(object record)
333
readQueue->write(record);
338
SqlReadRecord record;
339
Sql.sql_result odbData;
343
DBREAD("Waiting for queue...");
346
record = readQueue->read();
347
DBREAD("Jobs in readQueue = %d", readQueue->size());
348
if ( record->check_timeout() && !record->stopRead ) {
349
odbData = db()->big_query
350
( "select rec_data,rec_order from doc_data"+
351
" where doc_id ="+record->iID+
352
" and rec_order >="+record->iNextRecNbr+
353
" and rec_order < "+(record->iNextRecNbr+READ_ONCE)+
354
" order by rec_order" );
355
DBREAD("Queueing read result for %d job=%d",record->iID,record->myId);
356
while ( fetch_line=odbData->fetch_row() ) {
357
if ( record->contFifo->size() > 100 ) {
360
record->contFifo->write(fetch_line[0]);
361
record->iNextRecNbr= (int)fetch_line[1] +1;
363
DBREAD("next=%d, last=%d", record->iNextRecNbr, record->iMaxRecNbr);
364
if ( record->iNextRecNbr > 0 &&
365
record->iNextRecNbr <= record->iMaxRecNbr)
367
DBREAD("Continue reading...\n");
368
object mlock = record->fullMutex->lock();
369
if ( record->contFifo->size() > 100 )
370
record->restore = add_record;
372
readQueue->write(record); // further reading
376
DBREAD("Read finished...\n");
377
record->contFifo->write(0);
385
FATAL("Error while reading from database: %O", err);
387
DBREAD("finished read on %d", record->iID);
388
record->contFifo->write(0);
395
object read_from_database(int id, int nextID, int maxID, object dbfile)
397
SqlReadRecord record = SqlReadRecord();
399
record->dbfile = dbfile;
400
record->iNextRecNbr = nextID;
401
record->iMaxRecNbr = maxID;
402
record->contFifo = Thread.Fifo();
403
record->fullMutex = Thread.Mutex();
404
readQueue->write(record);
405
globalQueue->write(record);
410
int check_save_demon()
412
if ( CALLER != _Server )
413
error( "Unauthorized call to check_save_demon() !" );
415
int status = tSaveDemon->status();
416
//werror(ctime(time())+" Checking Database SAVE DEMON\n");
418
FATAL("----- DATABASE SAVE DEMON restarted ! ---");
419
tSaveDemon = thread_create(database_save_demon);
422
oDemonDbHandle->keep();
424
if ( objectp(globalQueue) ) {
425
int sz = globalQueue->size();
428
object record = globalQueue->read();
429
// record has restore function set for 15 minutes (timeout)
430
// this means the record is also not in the readQueue
431
if ( functionp(record->restore) && !record->check_timeout() ) {
432
object dbfile = record->dbfile;
433
destruct(record->contFifo);
434
record->contFifo = 0;
436
destruct(dbfile); // make sure everything is gone and freed
439
globalQueue->write(record); // keep
446
void register_transient(array(object) obs)
448
ASSERTINFO(CALLER==MODULE_SECURITY || CALLER== this_object(),
449
"Invalid CALLER at register_transient()");
453
mProxyLookup[obj->get_object_id()] = obj;
459
* set_variable is used to store database internal values. e.g. the last
460
* object ID, the last document ID, as well as object ID of modules etc.
461
* @param name - the name of the variable to store
462
* @param int value - the value
463
* @author Ludger Merkens
466
void set_variable(string name, int value)
468
if(sizeof(db()->query("SELECT var FROM variables WHERE var='"+name+"'")))
470
db()->big_query("UPDATE variables SET value='"+value+
471
"' WHERE var='"+name+"'" );
475
db()->big_query("INSERT into variables values('"+name+"','"+value+"')");
480
* get_variable reads a value stored by set_variable
481
* @param name - the name used by set_variable
482
* @returns int - value previously stored under given name
483
* @author Ludger Merkens
486
int get_variable(string name)
489
res = db()->big_query("select value from variables where "+
491
if (objectp(res) && res->num_rows())
492
return (int) res->fetch_row()[0];
498
* reads the currently used max ID from the database and given table
499
* and increments. for performance reasons this ID is cached.
501
* @param int db - database to connect to
502
* @param string table - table to choose
503
* @return int - the calculated ID
504
* @see free_last_db_id
505
* @author Ludger Merkens
508
int create_new_database_id(string table)
510
if (!mCurrMaxID[table])
516
result = get_variable(table);
522
query = sprintf("select max(doc_id) from %s",table);
523
res = db()->big_query(query);
524
result = (int) res->fetch_row()[0];
527
query = sprintf("select max(ob_id) from %s",table);
528
res = db()->big_query(query);
529
result = max((int) res->fetch_row()[0], 1);
532
mCurrMaxID[table] = result;
534
mCurrMaxID[table] += 1;
535
// MESSAGE("Created new database ID"+(int) mCurrMaxID[table]);
536
set_variable(table, mCurrMaxID[table]);
537
return mCurrMaxID[table];
541
* called in case, a newly created database id is obsolete,
542
* usually called to handle an error occuring in further handling
544
* @param int db - Database to connect to
545
* @param string table - table choosen
547
* @see create_new_databas_id()
548
* @author Ludger Merkens
550
void free_last_db_id(string table)
556
* creates a new persistent sTeam object.
558
* @param string prog (the class to clone)
559
* @return proxy and id for object
560
* note that proxy creation implies creation of associated object.
561
* @see kernel.proxy.create, register_user
562
* @author Ludger Merkens
564
mixed new_object(object obj, string prog_name)
567
string sData, sAccess;
569
// check for valid object has to be added
570
// create database ID
572
if ( CALLER != _Persistence )
573
error("Only Persistence Module is allowed to get in here !");
575
int id = obj->get_object_id();
578
ASSERTINFO((p=mProxyLookup[id])->get_object_id() == id,
579
"Attempt to reregister object in database!");
583
object lock = createMutex->lock(); // make sure creation is save
587
new_db_id = create_new_database_id("objects");
589
new_db_id = create_new_database_id("ob_class");
591
p = new(PROXY, new_db_id, obj );
592
if (!objectp(p->get_object())) // error occured during creation
594
free_last_db_id("ob_class");
598
// insert the newly created Object into the database
599
if (prog_name!="-") {
600
Sql.sql_result res = db()->big_query(
601
sprintf("insert into ob_class values(%d,'%s')",
602
new_db_id, prog_name)
604
mProxyLookup[new_db_id] = p;
609
FATAL("database.new_object: failed to create object\n %O", err);
612
return ({ new_db_id, p});
616
* permanently destroys an object from the database.
617
* @param object represented by (proxy) to delete
620
* @author Ludger Merkens
622
bool delete_object(object p)
624
if ( CALLER!=_Persistence &&
625
(!MODULE_SECURITY->valid_object(CALLER) || CALLER->this() != p->this()))
627
werror("caller of delete_object() is %O\n", CALLER);
628
THROW("Illegal call to database.delete_object", E_ACCESS);
633
private bool do_delete(object p)
636
int iOID = p->get_object_id();
637
db()->query("delete from ob_data where ob_id = "+iOID);
638
db()->query("delete from ob_class where ob_id = "+iOID);
639
proxy = mProxyLookup[iOID];
640
if ( objectp(proxy) )
641
catch(proxy->set_status(PSTAT_DELETED));
642
m_delete(mProxyLookup, iOID);
648
get_old_instance(int iOID, string sClass, object proxy)
655
mixed catched = catch {
656
o = new(sClass, proxy);
659
if (!objectp(o)) // somehow failed to load file
662
FATAL("/**** while loading:"+ sClass + "****/\n" +
665
return 1; // class exists but failes to compile
671
* load and restore values of an object with given Object ID
673
* @return 0, object deleted
674
* @return 1, object failed to compile
675
* @return 2, objects class deleted
676
* @return 3, object fails to load
679
* @author Ludger Merkens
681
int|object load_object(object proxy, int|object iOID)
691
if ( CALLER != _Persistence )
692
error("Unable to load objects directly !");
698
Sql.sql_result res = db()->big_query(
699
sprintf("select ob_class, ob_data from objects where ob_id = %d",
701
//werror("select executed\n");
702
mixed line = res->fetch_row();
703
//werror("data fetched\n");
704
if ( !arrayp(line) || sizeof(line)!=2 ) {
705
FATAL(PRINT_BT(({"database.load_object: Failed to load "+
707
(arrayp(line) ? sizeof(line) : "- not found" ),
711
[sClass, sData] = line;
712
if (!objectp(iOID)) {
713
o = get_old_instance(iOID, sClass, proxy);
714
if (objectp(o)) { proxy->set_steam_obj(o); }
720
if ( objectp(o) && functionp(o->get_object_id) )
721
compatibility_load_object(iOID, o, sData);
726
Sql.sql_result res = db()->big_query(
727
sprintf("select ob_class from ob_class where ob_id = %d", iOID)
729
mixed line = res->fetch_row();
732
FATAL("Failed to load object (id %d) - deleted?\n", iOID);
735
ASSERTINFO(arrayp(line), "Failed to load object - database corrupt");
744
if ( (pos = search(sClass, "DB:#")) >= 0 ) {
745
sClass = "/"+sClass[pos..];
747
o = new(sClass, proxy);
750
if (!objectp(o)) // somehow failed to load file
753
_Server->add_error(time(), catched);
754
string pikeClass = sClass;
755
sscanf(pikeClass, "%s.pike", pikeClass);
757
if (!master()->master_file_stat(pikeClass+".pike"))
760
"You may have stale objects in the database of class:"+
764
FATAL("Error loading object %d (%s)\n%s", iOID, sClass,
765
master()->describe_backtrace(catched));
767
return 1; // class exists but failes to compile
770
proxy->set_steam_obj(o); // o is the real thing - no proxy!
774
res = db()->big_query(
775
sprintf("select ob_ident, ob_attr, ob_data from ob_data where "+
776
"ob_id = %d", iOID));
779
FATAL("Warning: Object not set %O", iOID);
781
mapping mStorage = get_storage_handlers(o);
782
if ( !mappingp(mStorage) || equal(mStorage, ([ ])) ) {
783
proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
786
mapping mIndexedStorage = ([]);
788
foreach(indices(mStorage), string _ident) // precreate indexed idents
789
if (mStorage[_ident][2])
790
mIndexedStorage[_ident]=([]);
793
while (line = res->fetch_row())
795
[sIdent, sAttrib, sData] = line;
796
mem += strlen(sData);
798
mData = unserialize(sData); // second arg is "this_object()"
801
FATAL("While loading ("+iOID+","+sClass+"):\n"+ catched[0] +"\n"+
802
master()->print_backtrace(catched));
803
proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
808
if ( !mIndexedStorage[sIdent] ) {
809
FATAL("Missing Storage %s in %d, %s", sIdent, iOID, sClass);
810
proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
813
mIndexedStorage[sIdent][sAttrib]=mData;
815
else if ( !mStorage[sIdent] ) {
816
FATAL("No storage handler %s defined in %d (%s).\ndata=%O",
817
sIdent, iOID, sClass, mData);
822
if ( proxy->is_loaded() ) {
823
error(sprintf("Fatal error: already loaded %O\n", proxy));
825
mStorage[sIdent][1](mData); // actual function call
828
FATAL("Error while loading (%d, %s)\n"+
829
"Error while calling storage handler %s: %O\n"+
830
"Full Storage:%O\nData: %O",
838
foreach(indices(mIndexedStorage), string _ident)
839
mStorage[_ident][1](mIndexedStorage[_ident]); // prepared call
842
o->this()->set_status(PSTAT_SAVE_OK);
848
static mapping get_storage_handlers(object o)
851
FATAL("Getting storage handlers for %O", o);
853
mapping storage = _Persistence->get_storage_handlers(o);
857
mixed call_storage_handler(function f, mixed ... params)
859
if ( CALLER != _Persistence )
860
error("Database:: call_storage_handler(): Unauthorized call !");
861
mixed res = f(@params);
865
mapping convert_attribute_mapping(mapping old_fashion)
867
// temporary for conversion
868
mapping m_conversion = ([
869
101 : "OBJ_OWNER", 102 : "OBJ_NAME", 104 : "OBJ_DESC",
870
105 : "OBJ_ICON", 111 : "OBJ_KEYWORDS", 112 : "OBJ_COMMAND_MAP",
871
113 : "OBJ_POSITION_X", 114 : "OBJ_POSITION_Y", 115 : "OBJ_POSITION_Z",
872
116 : "OBJ_LAST_CHANGED", 119 : "OBJ_CREATION_TIME", "url" : "OBJ_URL",
873
"obj:link_icon" : "OBJ_LINK_ICON", "obj_script" : "OBJ_SCRIPT",
874
"obj_annotations_changed" : "OBJ_ANNOTATIONS_CHANGED",
875
207 : "DOC_TYPE", 208 : "DOC_MIME_TYPE", 213 : "DOC_USER_MODIFIED",
876
214 : "DOC_LAST_MODIFIED", 215 : "DOC_LAST_ACCESSED",
877
216 : "DOC_EXTERN_URL", 217 : "DOC_TIMES_READ",
878
218 : "DOC_IMAGE_ROTATION", 219 : "DOC_IMAGE_THUMBNAIL",
879
220 : "DOC_IMAGE_SIZEX", 221 : "DOC_IMAGE_SIZEY",
880
300 : "CONT_SIZE_X", 301 : "CONT_SIZE_Y", 302 : "CONT_SIZE_Z",
881
303 : "CONT_EXCHANGE_LINKS", "cont:monitor" : "CONT_MONITOR",
882
"cont_last_modified" : "CONT_LAST_MODIFIED",
883
500 : "GROUP_MEMBERSHIP_REQS", 501 : "GROUP_EXITS",
884
502 : "GROUP_MAXSIZE", 503 : "GROUP_MSG_ACCEPT",
885
504 : "GROUP_MAXPENDING", 611 : "USER_ADRESS", 612 : "USER_FULLNAME",
886
613 : "USER_MAILBOX", 614 : "USER_WORKROOM", 615 : "USER_LAST_LOGIN",
887
616 : "USER_EMAIL", 617 : "USER_UMASK", 618 : "USER_MODE",
888
619 : "USER_MODE_MSG", 620 : "USER_LOGOUT_PLACE",
889
621 : "USER_TRASHBIN", 622 : "USER_BOOKMARKROOM",
890
623 : "USER_FORWARD_MSG", 624 : "USER_IRC_PASSWORD",
891
"user_firstname" : "USER_FIRSTNAME", "user_language" : "USER_LANGUAGE",
892
"user_selection" : "USER_SELECTION",
893
"user_favorites" : "USER_FAVOURITES", 700 : "DRAWING_TYPE",
894
701 : "DRAWING_WIDTH", 702 : "DRAWING_HEIGHT", 703 : "DRAWING_COLOR",
895
704 : "DRAWING_THICKNESS", 705 : "DRAWING_FILLED",
896
800 : "GROUP_WORKROOM", 801 : "GROUP_EXCLUSIVE_SUBGROUPS",
897
1000 : "LAB_TUTOR", 1001 : "LAB_SIZE", 1002 : "LAB_ROOM",
898
1003 : "LAB_APPTIME", 1100 : "MAIL_MIMEHEADERS",
899
1101 : "MAIL_IMAPFLAGS"
902
foreach(indices(old_fashion), mixed Attr)
904
if (string newAttr=m_conversion[Attr])
906
if (old_fashion[newAttr]) {
907
FATAL("WarningD conversion - targetname "+
908
"alreay in use - overwriting...");
909
//THROW("CONVERSION FAILED - TERMINATING", E_ERROR);
910
old_fashion[newAttr]=old_fashion[Attr];
911
m_delete(old_fashion, Attr);
914
old_fashion[newAttr]=old_fashion[Attr];
915
m_delete(old_fashion, Attr);
921
FATAL("FAILED conversion - incomplete conversion "+
923
THROW("CONVERSION FAILED - TERMINATING", E_ERROR);
925
else if (!stringp(Attr))
927
FATAL("Warning - strange Attribute name found "+
929
m_delete(old_fashion, Attr);
937
* load and restore values of an object with given Object ID
939
* @return 0, object deleted
940
* @return 1, object failed to compile
941
* @return 2, objects class deleted thus instance deleted
944
* @author Ludger Merkens
946
private static int|object
947
compatibility_load_object(int iOID, object o, string sData)
955
mData = unserialize(sData); // second arg is "this_object()"
958
FATAL("Deserialisation failed (%d,%O)\n%s",
959
iOID, o, master()->describe_backtrace(catched));
962
if ( mappingp(mData) ) {
963
array lines = indices(mData);
964
lines = ({ "restore_content_data", "restore_data"}) +
965
(lines - ({"restore_content_data", "restore_data"}));
966
foreach(lines, string line) {
967
if (line=="restore_data")
972
mapping mAttributes =
973
convert_attribute_mapping(mData[line]["Attributes"]);
974
mapping mAttributeAcquire =
975
convert_attribute_mapping(mData[line]["AttributesAcquire"]);
976
if ( functionp(o->restore_attr_data))
977
o->restore_attr_data(mAttributes);
978
m_delete(mData[line], "Attributes");
979
mData[line]["AttributesAcquire"]= mAttributeAcquire;
980
if ( functionp(o->restore_data) )
981
o->restore_data(mData[line]);
983
if (mData["restore_content_data"]) // it's a document
985
string doctype = o->query_attribute(DOC_MIME_TYPE);
986
if (doctype && strlen(doctype) &&
987
search(doctype, "text")>-1)
989
//werror(" MIME = %s", doctype);
990
string sContent = o->get_content();
991
if (sContent && strlen(sContent))
993
CHECKISO(sContent, "iso-8859-1", o);
994
CHECKISO(sContent, "utf-8", o);
1000
else if (line=="restore_icons" && functionp(o->restore_icons) )
1002
foreach(indices(mData[line]["icons"]), mixed icondx)
1003
o->restore_icons(mData[line]["icons"][icondx],
1004
(stringp(icondx) ? "\""+icondx :
1005
"#"+(string)icondx));
1007
else if (line=="restore_content_data")
1010
o->restore_content_data(mData[line]);
1012
else if (line=="restore_annotations")
1014
o->restore_annotations(mData[line]);
1015
if (mData[line]["Annotates"])
1017
string sContent = o->get_content();
1018
if (!stringp(sContent))
1019
FATAL("\n!!!Missing Content in Annotation %d\n",
1020
o->get_object_id());
1022
//werror("cid = %d\n", o->get_content_id());
1023
object oModifiedBy = o->retrieve_attr_data(DOC_USER_MODIFIED);
1024
o->set_content(string_to_utf8(sContent));
1025
o->restore_attr_data("utf-8", DOC_ENCODING);
1026
o->restore_attr_data(oModifiedBy, DOC_USER_MODIFIED);
1027
//werror("\nannotation content %s -> %s\n",
1028
//sContent, o->get_content());
1034
//werror("\nrestore-data line=%O o[line]=%O\n",line, o[line]);
1035
catched = catch(o[line](mData[line]));
1036
if (arrayp(catched)) {
1037
FATAL("Error in deserialisation (%d, %s):\n%s",
1038
iOID, line, master()->describe_backtrace(catched));
1043
//werror("Loaded "+ o->get_object_id() + " - " + o->get_identifier()+"\n");
1044
mixed err = catch { o->loaded(); };
1049
* find an object from the global object cache or retreive it from the
1052
* @param int - iOID ( object ID from object to find )
1053
* @return object (proxy associated with object)
1055
* @author Ludger Merkens
1057
final object find_object(int|string iOID)
1061
if ( stringp(iOID) )
1062
return _Server->get_module("filepath:tree")->path_to_object(iOID);
1065
THROW("Wrong argument to find_object() - expected integer!",E_ERROR);
1067
if ( iOID == 0 ) return 0;
1068
if ( iOID == 1 ) return this_object();
1070
if ( objectp(p = mProxyLookup[iOID]) )
1075
res = db()->big_query(sprintf("select ob_class, ob_data from "+
1076
"objects where ob_id = %d", iOID));
1078
res = db()->big_query(sprintf("select ob_class from ob_class "+
1079
"where ob_id = %d", iOID));
1081
if (!objectp(res) || res->num_rows()==0)
1086
// cache the query for a following load_object.
1090
if (objectp(oSQLCache))
1091
destruct(oSQLCache);
1095
// create an empty proxy to avoid recursive loading of objects
1096
p = new(PROXY, iOID);
1097
mProxyLookup[iOID] = p;
1102
* The function is called to set a flag in an object for saving.
1103
* Additionally the functions triggers the global EVENT_REQ_SAVE event.
1105
* @author Thomas Bopp (astra@upb.de)
1108
void require_save(object proxy, void|string ident, void|string index)
1113
if (proxy && proxy->status()>=PSTAT_SAVE_OK)
1116
generate_request_name( proxy->get_object_id(), ident, index);
1117
mSaveRequests[request]++;
1118
save_object(proxy, ident, index);
1124
* callback-function called to indicate that an object has been modified
1125
* in memory and needs to be saved to the database.
1127
* @param object p - (proxy) object to be saved
1131
* @author Ludger Merkens
1134
save_object(object proxy, void|string ident, void|string index)
1136
if ( !objectp(proxy) )
1139
if (proxy->status() == PSTAT_SAVE_OK)
1140
proxy->set_status(PSTAT_SAVE_PENDING);
1142
oSaveQueue->write(({proxy, ident, index}));
1146
* quote and check for maximum length of serialized data.
1147
* @param string data - string to handle
1148
* @param object o - object saved (for error reporting)
1149
* @param string ident - ident block (for error reporting)
1150
* @author <a href="mailto:balduin@upb.de">Ludger Merkens</a>
1152
private static string
1153
quote_data(string data, object o, string ident, function quoter, int|void utf8)
1156
data = serialize(data, "utf-8");
1158
data = serialize(data);
1160
if (strlen(data)> 16777215)
1161
FATAL("!!! FATAL - data truncated inserting %d bytes for %s block %s",
1163
(objectp(o) ? "broken" : o->get_identifier()),
1165
return quoter(data);
1169
* generate a "mysql-specific" replace statement for saving data according
1170
* to needs of require_save()
1171
* @param object o - object to save data from
1172
* @param mapping storage - storage_data to access
1173
* @param string|void ident - optional arg to limit to ident
1174
* @param string|void index - optional arg to limit to index
1175
* @return the mysql statement
1176
* @author Ludger Merkens
1178
private static string
1179
prepare_save_statement(object o, mapping storage,
1180
string|void ident, string|void index)
1182
int oid = o->get_object_id();
1183
array statements = ({});
1184
// in case you change the behavoir below - remember to change
1185
// behaviour in prepeare_clear_statement also
1186
array(string) idents =
1187
arrayp(storage[ident]) ? ({ ident }) : indices(storage);
1189
string sClass = master()->describe_program(object_program(o->get_object()));
1190
function db_quote_data = db()->quote;
1191
foreach(idents, string _ident)
1193
if (!arrayp(storage[_ident]))
1195
FATAL("missing storage handler for _ident %O\n", _ident);
1196
FATAL("prepare_save_statement object=%O, storage=%O, "+
1197
"ident=%O, index=%O\n", o, storage, ident, index);
1199
else if (storage[_ident][2]) // an indexed data-storage
1201
//werror("indexed data-storage (ident=%s, indexed=%O, storage=%O)\n",
1202
// _ident, storage[_ident][2], storage[_ident][0]);
1203
if (zero_type(index))
1205
mapping mData = storage[_ident][0](); // retrieve all
1206
foreach(indices(mData), mixed _index)
1208
if (!stringp(_index))
1210
data = quote_data(mData[_index], o, _ident,
1211
db_quote_data, CONVERSION);
1213
({ sprintf("(\"%d\",\"%s\",\"%s\",\"%s\")",
1214
oid, _ident, db_quote_data(_index),
1221
if (_ident != "user" && index!="UserPassword")
1222
data = quote_data(storage[_ident][0](index), o, _ident,
1223
db_quote_data, CONVERSION);
1225
data = quote_data(storage[_ident][0](index), o, _ident,
1226
db_quote_data); // never convert user pw
1229
sprintf("(\"%d\",\"%s\",\"%s\",\"%s\")",
1230
oid, _ident, db_quote_data(index), data)
1235
else // the usual unindexed data-storage
1237
data = quote_data(storage[_ident][0](), o, "all", db_quote_data,
1240
sprintf("(\"%d\",\"%s\",\"%s\",\"%s\")",
1241
oid, _ident, "", data)
1245
return statements*",";
1250
* generate a delete statement that will clear all entries according to
1251
* the data that will be saved.
1252
* @author Ludger Merkens
1253
* @see prepare_save_statement
1255
private static string
1256
prepare_clear_statement(object o, mapping storage,
1257
string|void ident, string|void index, string|void tb)
1262
if (ident=="0" || index=="0")
1263
FATAL("strange call to prepare_clear_statement \n%s\n",
1264
describe_backtrace(backtrace()));
1266
if (!storage[ident]) ident =0; // better save then sorry - wrong ident
1267
// invoces a full save.
1269
return sprintf("delete from %s where ob_id='%d' and "+
1270
"ob_ident='%s' and ob_attr='%s'",
1271
tb, o->get_object_id(), ident, index);
1273
return sprintf("delete from %s where ob_id='%d' and "+
1274
"ob_ident='%s'", tb, o->get_object_id(), ident);
1276
return sprintf("delete from %s where ob_id='%d'",
1277
tb, o->get_object_id());
1282
* low level database function to store a given (proxy) object into the
1283
* database immediately.
1285
* @param object proxy - the object to be saved
1288
* @author Ludger Merkens
1291
low_save_object(object p, string|void ident, string|void index)
1295
LOG_DB("low_save_object:"+p->get_object_id()+"("+PSTAT(p->status())+") "+
1296
(p->status()>0 ? p->get_identifier() : ""));
1298
int stat = p->status();
1299
// saved twice while waiting
1300
if ( stat == PSTAT_DISK || stat == PSTAT_DELETED )
1301
return; // low is local so this will unlock also
1303
//if ( stat == PSTAT_SAVE_OK )
1304
//werror("proxy %O twice in queue (ident=%O,index=%O)\n", p, ident, index);
1305
ASSERTINFO(!objectp(MODULE_SECURITY) ||
1306
MODULE_SECURITY->valid_object(p),
1307
"invalid object in database.save_object");
1309
if (p->status() < PSTAT_SAVE_OK)
1311
FATAL("DBSAVEDEMON ->broken instance not saved(%d, %s, status=%s)",
1313
master()->describe_object(p->get_object()),
1314
PSTAT(p->status()));
1317
if ( !p->is_loaded() ) {
1318
FATAL("DBSAVEDEMON ->trying to save an object that was not previously loaded !!!!!\n"+
1319
"Object ID="+p->get_object_id() + "\n");
1323
Thread.MutexKey low=muLowSave::lock(1);
1325
string request = generate_request_name(p->get_object_id(), ident, index);
1326
mSaveRequests[request]--;
1327
if (mSaveRequests[request]==0)
1328
m_delete(mSaveRequests, request);
1330
if (p->status()<PSTAT_SAVE_OK)
1331
THROW("Invalid proxy status for object:"+
1332
p->get_object_id()+"("+p->status()+")", E_MEMORY);
1334
mapping storage = get_storage_handlers(p);
1335
ASSERTINFO(mappingp(storage),
1336
"Corrupted data_storage in "+master()->stupid_describe(p));
1339
p->set_status(PSTAT_SAVE_OK);
1340
destruct(low); // status set, so unlock
1342
if (master()->describe_program(object_program(p->get_object()))=="-")
1343
return; // temporary objects like executer
1346
prepare_save_statement(p, storage, ident, index );
1348
ASSERTINFO(strlen(sStatement)!=0,
1349
sprintf("trying to insert empty data into object %d class %s",
1351
master()->describe_program(object_program(p->get_object()))));
1359
catch(db()->big_query("insert into ob_journaling values " + sStatement));
1361
// remove from ob_dat
1362
mixed delete_err = catch {
1363
s = prepare_clear_statement(p, storage, ident, index, "ob_data");
1367
FATAL("!!! FATAL in save-demon, deletion statement %s failed\n%O: %O\n",
1368
s, delete_err[0], delete_err[1]);
1371
db()->big_query("insert into ob_data values " + sStatement);
1373
s = prepare_clear_statement(p, storage, ident, index, "ob_journaling");
1374
catch(db()->big_query(s));
1378
FATAL("!!! FATAL - Error in save-demon ------------\n%s\n---------!!!",
1379
master()->describe_backtrace(err));
1380
FATAL("----- Clear Statement is %O\n", s);
1381
if ( objectp(lostData) ) {
1383
lostData->write(sprintf("%d: %s\n\n", p->get_object_id(), sStatement));
1390
* Change the class of an object in the database. Drop the object to
1391
* get an object with the modified class.
1393
* @param object doc - change class for this document
1394
* @param string classfile - the new class
1396
int change_object_class(object doc, string classfile)
1398
if ( CALLER != _Persistence )
1399
steam_error("Illegal call to database.change_class() !");
1401
if ( !functionp(doc->get_object_id) )
1402
steam_error("database.change_class: object is no valid steam object!");
1403
int id = doc->get_object_id();
1405
classfile = CLASS_PATH + classfile; // only from classes directory
1406
db()->query("delete from ob_class where ob_id=" + id);
1407
db()->query("insert into ob_class values("+id+",'"+classfile+"')");
1412
* register an module with its name
1413
* e.g. register_module("users", new("/modules/users"));
1415
* @param string - a unique name to register with this module.
1416
* @param object module - the module object to register
1417
* @param void|string source - a source directory for package installations
1418
* @return (object-id|0)
1419
* @see /kernel/db_mapping, /kernel/secure_mapping
1420
* @author Ludger Merkens
1422
int register_module(string oname, object module, void|string source)
1425
string version = "";
1427
//FATAL(sprintf("register module %s with %O source %O", oname, module, source));
1428
if ( CALLER != _Server &&
1429
!MODULE_SECURITY->access_register_module(0, 0, CALLER) )
1430
THROW("Unauthorized call to register_module() !", E_ACCESS);
1433
int imod = get_variable("#" + oname);
1437
mod = find_object(imod); // get old module
1438
//FATAL(sprintf("Attempting to get object for module %s", oname));
1439
if ( objectp(mod) ) {
1440
object e = master()->getErrorContainer();
1441
master()->set_inhibit_compile_errors(e);
1442
realObject = mod->get_object();
1443
master()->set_inhibit_compile_errors(0);
1446
FATAL("Failed to compile new instance of module '" + oname
1448
THROW("Failed to load module\n"+e->get()+"\n"+
1449
e->get_warnings(), backtrace());
1451
//FATAL(sprintf("module found is %O", realObject));
1454
if ( objectp(realObject) ) {
1455
werror("Found previously registered version of module '" + oname
1457
if ( objectp(module) && module->get_object() != realObject )
1458
THROW("Trying to register previously registered module '"
1459
+ oname +"'.", E_ERROR);
1461
version = realObject->get_version();
1463
mixed erg = master()->upgrade(object_program(realObject));
1464
//FATAL(sprintf("upgrade resulted in %O", erg));
1465
if (!intp(erg) || erg<0)
1468
THROW(erg, backtrace());
1471
FATAL("New version of "+oname+" doesn't implement old "+
1472
"versions interface");
1473
master()->upgrade(object_program(mod->get_object()),1);
1476
//FATAL("Upgrading done !");
1479
else if ( !objectp(module) )
1481
// module is in the /modules directory.
1482
object e = master()->getErrorContainer();
1483
master()->set_inhibit_compile_errors(e);
1484
module = new("/modules/"+oname+".pike");
1485
master()->set_inhibit_compile_errors(0);
1488
FATAL("Failed to compile new instance of module '" + oname
1490
THROW("Failed to load module\n"+e->get()+"\n"+
1491
e->get_warnings(), backtrace());
1495
MESSAGE(sprintf("Installing module %s", oname));
1496
if ( !stringp(source) )
1499
if ( module->get_object_class() & CLASS_PACKAGE ) {
1501
if ( module->install(source, version) == 0 )
1502
error("Failed to install module !");
1504
_Server->register_module(module);
1506
_Server->run_global_event(EVENT_REGISTER_MODULE, PHASE_NOTIFY,
1507
this_object(), ({ module }) );
1508
LOG_DB("event is run");
1509
if ( objectp(module) )
1511
set_variable("#"+oname, module->get_object_id());
1512
_Server->register_module(module);
1513
return module->get_object_id();
1519
* Check if a database handle is connected to a properly setup database.
1521
* @param Sql.Sql handle - the handle to check
1522
* @return 1 - old format
1523
* @return 2 - new format
1524
* @see setup_sTeam_tables
1525
* @author Ludger Merkens
1527
int validate_db_handle(SqlHandle handle)
1529
multiset tables = (<>);
1530
array(string) aTables = handle->list_tables();
1532
foreach(aTables, string table)
1533
tables[table] = true;
1534
if (tables["objects"] && tables["doc_data"])
1536
if (tables["ob_class"] && tables["ob_data"] && tables["doc_data"])
1540
bool check_convert()
1542
multiset tables = (<>);
1543
Sql.Sql handle = SqlHandle(STEAM_DB_CONNECT);
1544
array(string) aTables = handle->list_tables();
1546
foreach(aTables, string table)
1547
tables[table] = true;
1549
// make sure the "var" column of table "variables" is type char(100):
1550
if ( tables["variables"] ) {
1551
Sql.sql_result result = handle->big_query("show columns from variables");
1552
if ( objectp(result) ) {
1553
array row = result->fetch_row();
1554
while ( arrayp(row) ) {
1555
if ( (sizeof(row) >= 2) && (row[0] == "var") && stringp(row[1]) ) {
1556
if ( lower_case(row[1]) != "char(100)" ) {
1557
werror( "Database: setting column \"var\" of table \"variables\""
1558
" to type \"char(100)\" (was: "+row[1]+")...\n" );
1559
MESSAGE( "Database: setting column \"var\" of table \"variables\""
1560
" to type \"char(100)\" (was: "+row[1]+")..." );
1561
handle->big_query( "alter table variables modify var char(100)" );
1565
row = result->fetch_row();
1570
// make sure the max_rows value of ob_data is large enough:
1571
if ( tables["ob_data"] ) {
1572
int DB_MAX_ROWS = 4294967295;
1573
string db_name = basename( STEAM_DB_CONNECT );
1574
if ( ! stringp(db_name) ) db_name = "steam";
1575
Sql.sql_result result = handle->big_query( "show table status from "
1576
+ db_name + " like 'ob_data'" );
1577
array fields = result->fetch_fields();
1578
array row = result->fetch_row();
1579
if ( arrayp(fields) && arrayp(row) ) {
1580
for ( int i=0; i<sizeof(fields); i++ ) {
1581
if ( fields[i]["name"] == "Max_data_length" ) {
1582
int max_rows = DB_MAX_ROWS;
1583
if ( stringp(row[i]) ) sscanf( row[i], "%d", max_rows );
1584
else if ( intp(row[i]) ) max_rows = row[i];
1585
if ( max_rows < DB_MAX_ROWS ) {
1586
werror( "\nDatabase: altering ob_data table to MAX_ROWS = "
1587
+ DB_MAX_ROWS + " (was: " + max_rows + ").\n"
1588
+ "This could take a while...\n" );
1589
MESSAGE( "\nDatabase: altering ob_data table to MAX_ROWS = "
1590
+ DB_MAX_ROWS + " (was: " + max_rows + ").\n"
1591
+ "This could take a while..." );
1592
handle->big_query( "alter table ob_data MAX_ROWS=" + DB_MAX_ROWS );
1600
return tables["objects"];
1604
void set_converting(int i)
1609
int get_converting()
1614
void convert_tables()
1616
if ( CALLER != _Server )
1617
error("Unauthorized call to convert_tables() !");
1618
werror("creating new table format\n");
1619
Sql.Sql handle = SqlHandle(STEAM_DB_CONNECT);
1620
create_tables(handle);
1621
werror("Starting database conversion ...\n");
1622
convert_old_tables(handle);
1623
werror("Database conversion finished sucessfully.\n");
1624
handle->big_query("alter table objects rename objectsold");
1629
* Convert an old database of sTeam Version below 1.5 to new format
1634
convert_old_tables(SqlHandle handle)
1636
string sOid, sOclass, sOdata;
1643
// uncatched ... we want to die in that case.
1645
convert_index_tables(handle, "i_groups");
1646
convert_index_tables(handle, "i_users");
1648
Sql.Sql writehandle = Sql.Sql(STEAM_DB_CONNECT);
1649
Sql.sql_result instream =
1650
handle->big_query("select ob_id, ob_class, ob_data from objects");
1652
while (erg = instream->fetch_row())
1654
[sOid, sOclass, sOdata] = erg;
1655
if (sOclass == "-:15") continue;
1656
if (sOclass == "-") continue;
1657
if (sOclass == "/factories/AnnotationFactory.pike") continue;
1658
if (sOclass == "/modules/keyword_index.pike") continue;
1660
werror("CONV %s(%s):", sOid, sOclass);
1661
p = find_object((int) sOid);
1663
mixed mIdent = p->get_identifier();
1664
werror("(%O)", mIdent);
1665
if (p->status()==PSTAT_SAVE_OK)
1668
convert_keywords(writehandle, o);
1669
sOclass = master()->describe_program(object_program(o));
1671
sprintf("insert into ob_class values(%s,'%s')",
1673
// compatibility_load_object((int)sOid, o, sOdata);
1675
storage = get_storage_handlers(o);
1676
sStatement = prepare_save_statement(p, storage);
1677
werror("save(%O)-", p);
1678
// again we want to die in case of an error --- no catch
1679
string clear = prepare_clear_statement(p, storage);
1681
writehandle->query("replace into ob_data values "+sStatement);
1682
switch ( sOclass ) {
1683
case "/classes/User" : write(" post conversion ");
1686
object oForwardModule = get_module("forward");
1687
if (p->query_attribute(USER_FORWARD_MSG) && stringp(p->query_attribtue(USER_EMAIL))
1688
&&!arrayp(oForwardModule->get_forward(p)))
1690
oForwardModule->add_forward(p, "/"+p->get_identifier());
1691
oForwardModule->add_forward(p, p->query_attribute(USER_EMAIL));
1695
write("failed : %s\n",master()->describe_backtrace(err));
1699
default : werror("ok\n");
1703
werror("Failed to load %s (%O)\n", sOid, p);
1707
private static void convert_keywords(Sql.Sql handle, object o)
1709
if ( !functionp(o->get_object_id) )
1711
Sql.sql_result res =
1712
handle->big_query("select k from mi_keyword_index where v='%"+
1713
o->get_object_id()+"'");
1714
while (mixed key = res->fetch_row())
1716
o->restore_keywords(1, string_to_utf8(key[0]));
1719
private static void convert_index_tables(Sql.Sql handle, string index)
1721
mixed res = handle->query("select k,v from "+ index);
1722
handle->query("delete from "+ index);
1723
foreach(res, mapping line)
1725
handle->query("insert into "+index+" values(%s,%s)",
1726
string_to_utf8(line["k"]), line["v"]);
1730
static void check_journaling(Sql.Sql handle)
1732
mixed row, res, err;
1734
string lost = Stdio.read_file("/tmp/lost_data."+BRAND_NAME);
1735
lostData = Stdio.File("/tmp/lost_data."+BRAND_NAME, "wct");
1736
if ( stringp(lost) && strlen(lost) > 0 ) {
1737
array lostlines = lost / "\n\n";
1738
foreach(lostlines, string ll) {
1739
werror("LOST DATA: Restoring %s\n", ll);
1740
MESSAGE("LOST DATA: Restore %s", ll);
1742
string ident, attr, val, rest;
1743
if ( sscanf(ll, "%d: %s", oid, ll) != 2 )
1745
while ( sscanf(ll, "(%*s,%s,%s,%s)%s", ident, attr, val, rest) >= 3 ) {
1746
werror("values are %O %O %O %O\n", oid, ident, attr, val);
1748
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));
1749
row = res->fetch_row();
1750
if ( sizeof(row) > 0 ) {
1751
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));
1752
werror("updated!\n");
1755
handle->query(sprintf("insert into ob_data values (%d,%s,%s,%s)",
1756
oid, ident, attr, val));
1760
FATAL("Failed to restore data: %O", err);
1762
if ( stringp(rest) || strlen(rest) > 1 )
1769
handle->query("create table if not exists ob_journaling ("+
1770
" ob_id int not null, "+
1771
" ob_ident char(15) not null,"+
1772
" ob_attr char(50), "+
1773
" ob_data mediumtext,"+
1774
" unique(ob_id, ob_ident, ob_attr),"+
1775
" index i_attr (ob_attr)"+
1778
handle->big_query("select ob_id, ob_ident, ob_attr, ob_data from ob_journaling");
1780
if ( !objectp(res) )
1782
while ( row = res->fetch_row() ) {
1783
if ( arrayp(row) ) {
1784
MESSAGE("JOURNAL: Restoring %O", row);
1785
string q = sprintf("insert into ob_data values (%d, '%s', '%s', '%s')",
1786
(int)row[0], row[1], row[2], handle->quote(row[3]));
1787
string c = sprintf("delete from ob_data where ob_id='%d' and ob_ident='%s' and ob_attr='%s'", (int)row[0], row[1], row[2]);
1793
error("Failed to restore journaling table: " + err[0] +
1794
sprintf("\n%O\n%O", err[1], row));
1798
handle->query("delete from ob_journaling where 1");
1801
static void add_new_tables(Sql.Sql handle) {
1802
MESSAGE("adding new format tables\n");
1803
MESSAGE("adding ob_class ");
1805
handle->query("drop table if exists ob_class");
1806
handle->query("drop table if exists ob_data");
1808
handle->query("create table if not exists ob_class ("+
1809
" ob_id int primary key, "+
1810
" ob_class char(128) "+
1813
MESSAGE("adding ob_data ");
1814
handle->query("create table if not exists ob_data ("+
1815
" ob_id int not null, "+
1816
" ob_ident char(15) not null,"+
1817
" ob_attr char(50), "+
1818
" ob_data mediumtext,"+
1819
" unique(ob_id, ob_ident, ob_attr),"+
1820
" index i_attr (ob_attr),"+
1821
" index i_attrdata (ob_attr, ob_data(80))"+
1824
handle->query("create table if not exists ob_journaling ("+
1825
" ob_id int not null, "+
1826
" ob_ident char(15) not null,"+
1827
" ob_attr char(50), "+
1828
" ob_data mediumtext,"+
1829
" unique(ob_id, ob_ident, ob_attr),"+
1830
" index i_attr (ob_attr)"+
1834
static void create_tables(Sql.Sql handle) {
1835
MESSAGE("creating table \"doc_data\" ");
1836
handle->big_query("create table if not exists doc_data ("+
1838
" doc_id int not null, "+
1839
" rec_order int not null, "+
1840
" primary key (doc_id, rec_order)"+
1841
") AVG_ROW_LENGTH=65535 MAX_ROWS=4294967296");
1842
//FIXME: postgres does not support (and probably not even need)
1843
//AVG_ROW_LENGTH and MAX_ROWS in this place
1846
MESSAGE("creating table \"ob_class\" ");
1847
handle->big_query("create table if not exists ob_class ("+
1848
" ob_id int primary key, "+
1849
" ob_class char(128) "+
1852
MESSAGE("creating table \"ob_data\" ");
1853
handle->big_query("create table if not exists ob_data ("+
1854
" ob_id int not null, "+
1855
" ob_ident char(15) not null,"+
1856
" ob_attr char(50), "+
1857
" ob_data mediumtext,"+
1858
" unique(ob_id, ob_ident, ob_attr)"+
1860
MESSAGE("creating table \"variables\" ");
1861
handle->big_query("create table if not exists variables ("+
1862
" var char(100) primary key, "+
1868
* set up the base sTeam tables to create an empty database.
1872
* @author Ludger Merkens
1874
int setup_sTeam_tables(SqlHandle handle)
1876
/* make sure no old tables exist and delete them properly */
1877
MESSAGE("Checking for old tables.\n");
1881
THROW("preliminary sTeam Table setup during CONVERSION!", E_ERROR);
1883
// Sql.Sql_result res = handle->big_query("show tables");
1884
array(string) res = handle->list_tables();
1887
foreach(res, string table)
1889
MESSAGE(sprintf("dropping (%s)\n",table));
1890
handle->big_query("drop table "+table);
1894
MESSAGE("no old tables found");
1896
MESSAGE("CREATING NEW BASE TABLES:");
1897
create_tables(handle);
1899
res = handle->list_tables();
1901
FATAL("\nFATAL: failed to create base tables");
1905
MESSAGE("\nPOST CHECK retrieves: ");
1906
foreach(res, string table)
1913
* create and return a new instance of db_file
1915
* @param int iContentID - 0|ID of a given Content
1916
* @return the db_file-handle
1919
* @author Ludger Merkens
1921
object new_db_file_handle(int iContentID, string mode)
1923
return new("/kernel/db_file.pike", iContentID, mode);
1927
* Check if a given gile handle is valid, eg inherits db_file.pike
1929
* @param object m - the db_file to check
1930
* @return true or false
1931
* @author <a href="mailto:astra@upb.de">Thomas Bopp</a>)
1933
private static bool valid_db_file(object m)
1935
if (Program.inherits(object_program(m), (program)"/kernel/db_file.pike"))
1941
* connect_db_file, connect a /kernel/db_file instance with the database
1942
* calculate new content id if none given.
1945
* @return function db()
1947
final mixed connect_db_file(int id)
1949
// if (!(CALLINGFUNCTION == "get_database_handle" &&
1950
// valid_db_file(CALLER)))
1951
// THROW("illegal access to database ", E_ACCESS);
1952
return ({ db, (id==0 ? create_new_database_id("doc_data") : id)});
1956
* valid_db_mapping - check if an object pretending to be an db_mapping
1957
* really inherits /kernel/db_mapping and thus is a trusted program
1958
* @param m - object inheriting db_mapping
1959
* @return (TRUE|FALSE)
1960
* @see connect_db_mapping
1961
* @author Ludger Merkens
1963
private static bool valid_db_mapping(object m)
1965
if ( Program.inherits(object_program(m),
1966
(program)"/kernel/db_mapping.pike") ||
1967
Program.inherits(object_program(m),
1968
(program)"/kernel/db_n_one.pike") ||
1969
Program.inherits(object_program(m),
1970
(program)"/kernel/db_n_n.pike") ||
1971
Program.inherits(object_program(m),
1972
(program)"/kernel/db_searching.pike"))
1978
* connect_mapping, connect a /kernel/db_mapping instance with the database
1980
* @return a pair ({ function db, string tablename })
1982
final mixed connect_db_mapping()
1984
if (!(valid_db_mapping(CALLER)))
1986
FATAL("illegal access %s from %O\n",CALLINGFUNCTION, CALLER);
1987
THROW("illegal access to database ", E_ACCESS);
1990
// hack to allow the modules table to be a member of _Database
1992
sDbTable = CALLER->get_table_name();
1996
THROW(sprintf("Invalid tablename [%s] in module \"%s\"\n",sDbTable,
1997
master()->describe_program(CALLERPROGRAM)), E_ERROR);
1998
/* if (search(db()->list_tables("i_"+CALLER->get_object_id()),
1999
"i_"+CALLER->get_object_id())!=-1)
2001
werror(sprintf("Detected invalid tablename %s - fix it by running "+
2002
"check_database first - CALLER %s\n",
2003
"i_"+CALLER->get_object_id(),
2004
master()->describe_program(CALLERPROGRAM)));
2007
return ({ db, sDbTable });
2010
string get_identifier() { return "database"; }
2011
string _sprintf() { return "database"; }
2012
string describe() { return "database"; }
2013
int get_object_class() { return CLASS_DATABASE; }
2014
object this() { return this_object(); }
2020
* get_objects_by_class()
2021
* mainly for maintenance reasons, retreive all objects matching a given
2022
* class name, or program
2023
* @param class (string|object|int) - the class to compare with
2024
* @return array(object) all objects found in the database
2025
* throws on access violation. (ROLE_READ_ALL required)
2026
* @autoher Ludger Merkens
2028
final array(object) get_objects_by_class(string|program|int mClass)
2033
array(object) aObjects;
2036
if (security=MODULE_SECURITY)
2037
ASSERTINFO(security->
2038
check_access(0, this_user(), 0, ROLE_READ_ALL, false),
2039
"Illegal access on database.get_all_objects");
2041
if ( intp(mClass) ) {
2042
mixed factory = _Server->get_factory(mClass);
2043
if ( !objectp(factory) )
2045
if ( !stringp(CLASS_PATH) || !stringp(factory->get_class_name()) )
2047
mClass = CLASS_PATH + factory->get_class_name();
2050
if (programp(mClass))
2051
sClass = master()->describe_program(mClass);
2055
res = db()->big_query("select ob_id from ob_class where ob_class='"+
2058
aObjects = allocate((sz=res->num_rows()));
2062
aObjects[i]=find_object((int)res->fetch_row()[0]);
2070
* mainly for maintenance reasons
2071
* @return array(object) all objects found in the database
2072
* throws on access violation. (ROLE_READ_ALL required)
2073
* @autoher Ludger Merkens
2075
final array(object) get_all_objects()
2080
array(object) aObjects;
2083
if ( !_Server->is_a_factory(CALLER) )
2084
THROW("Illegal attempt to call database.get_all_objects !", E_ACCESS);
2086
if (security=MODULE_SECURITY)
2087
ASSERTINFO(security->
2088
check_access(0, this_user(), 0, ROLE_READ_ALL, false),
2089
"Illegal access on database.get_all_objects");
2092
res = db()->big_query("select ob_id from ob_class where ob_class !='-'");
2093
aObjects = allocate((sz=res->num_rows()));
2097
aObjects[i]=find_object((int)res->fetch_row()[0]);
2104
* loads all objects from the database, makes sure each object really loads
2105
* and calls the function given as "visitor" with consecutive with each object.
2106
* @param function visitor
2108
* @author Ludger Merkens
2109
* @see get_all_objects
2110
* @see get_all_objects_like
2111
* @caveats Because this function makes sure an object is properly loaded
2112
* when passing it to function "visitor", you won't
2113
* notice the existence of objects currently not loading.
2115
final void visit_all_objects(function visitor, mixed ... args)
2117
FATAL("visit_all_objects not yet converted to new database format");
2119
Sql.sql_result res = db()->big_query("select ob_id,ob_class from ob_class");
2124
FATAL("Number of objects found:"+res->num_rows());
2125
for (i=0;i<res->num_rows();i++)
2127
mixed erg = res->fetch_row();
2128
oid = (int) erg[0]; // wrong casting with
2129
oclass = erg[1]; // [oid, oclass] = res->fetch_row()
2131
if (oclass[0]=='/') // some heuristics to avoid nonsene classes
2133
p = find_object((int)oid); // get the proxy
2134
catch{p->get_object();}; // force to load the object
2135
if (p->status() > PSTAT_DISK) // positive stati mean object loaded
2142
* Check for a list of objects, if they really exist in the database
2144
* @param objects - the list of object to be checked
2145
* @return a list of those objects, which really exist.
2146
* @author Ludger Merkens
2147
* @see get_not_existing
2149
array(int) get_existing(array(int) ids)
2153
string query = "select ob_id from ob_class where ob_id in (";
2156
if (!ids || !sizeof(ids))
2158
for (i=0,sz=sizeof(ids)-1;i<sz;i++)
2161
res = db()->big_query(query);
2163
result = allocate((sz=res->num_rows()));
2165
result[i]=(int) res->fetch_row()[0];
2171
* Get a list of the not-existing objects.
2173
* @param objects - the list of objects to be checked
2174
* @return a list of objects that are not existing
2175
* @author <a href="mailto:astra@upb.de">Thomas Bopp</a>)
2178
array(int) get_not_existing(array(int) ids)
2180
return ids - get_existing(ids);
2183
object get_environment() { return 0; }
2184
object get_acquire() { return 0; }
2186
mapping get_xml_data()
2188
return ([ "configs":({_Server->get_configs, XML_NORMAL}), ]);
2192
* clears lost content records from the doc_data table, used for the
2193
* db_file emulation. This function is purely for maintainance reasons, and
2194
* should be obsolete, since we hope no content records will get lost
2197
* @returns a debug string containing the number of deleted doc_id's
2199
string clear_lost_content()
2202
LOG("getting doc_ids");
2203
Sql.sql_result res = h->big_query("select distinct doc_id from doc_data");
2204
array(int) doc_ids = allocate(res->num_rows());
2205
for(int i=0;i<sizeof(doc_ids);i++)
2206
doc_ids[i]=(int)res->fetch_row()[0];
2208
FATAL("deleting '-' files");
2209
h->big_query("delete from objects where ob_class='-'");
2210
FATAL("getting all objects");
2211
res = h->big_query("select ob_id from objects");
2212
int oid; object p; mixed a;
2213
while (a = res->fetch_row())
2216
if (p=find_object(oid))
2218
FATAL("accessing object"+oid);
2220
catch{try=p->get_object();};
2222
Program.inherits(object_program(try),
2223
(program)"/base/content"))
2225
FATAL("content "+p->get_content_id()+" is in use");
2226
doc_ids -= ({ p->get_content_id() });
2231
FATAL("number of doc_ids to be deleted is:"+sizeof(doc_ids));
2233
foreach (doc_ids, int did)
2235
h->big_query("delete from doc_data where doc_id = "+did);
2236
FATAL("deleting doc_id"+did);
2238
FATAL("calling optimize");
2239
h->big_query("optimize table doc_data");
2240
return "deleted "+sizeof(doc_ids)+"lost contents";
2243
object lookup (string identifier)
2245
return get_module("objects")->lookup(identifier);
2248
object lookup_user (string identifier)
2250
return get_module("users")->get_user(identifier);
2253
object lookup_group (string identifier)
2255
return get_module("groups")->get_group(identifier);