database._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2009 Thomas Bopp, Thorsten Hampel, Ludger Merkens,
2  * Robert Rosendahl, Daniel Büse
3  *
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.
8  *
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.
13  *
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
17  *
18  * $Id: database.pike,v 1.7 2010/01/27 12:42:33 astra Exp $
19  */
20 inherit "/base/serialize.pike";
21 #include <macros.h>
22 #include <assert.h>
23 #include <attributes.h>
24 #include <database.h>
25 #include <config.h>
26 #include <classes.h>
27 #include <access.h>
28 #include <roles.h>
29 #include <events.h>
30 #include <exception.h>
31 #include <types.h>
32 #include <configure.h>
33 class database : public serialize.pike{
34 public:
35 
36 
37 
38 
39 
40 #define MODULE_SECURITY _Server->get_module("security")
41 #define MODULE_USERS _Server->get_module("users")
42 
43 #define PROXY "/kernel/proxy.pike"
44 
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();
53 
54 private object oSaveQueue;
55 private object preSaveQueue;
56 private mapping mSaveCount;
57 private mapping mSaveIndex;
58 private int iSaves, iSkips;
59 
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;
68 
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;
75 
76 #define DBM_UNDEF 0
77 #define DBM_ID 1
78 #define DBM_NAME 2
79 
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||""))
82 
83 private mapping oModuleCache;
84 private Calendar.Calendar cal = Calendar.ISO->set_language("german");
85 
86 protected:
87  object SqlHandle(string dbconnect)
88 {
89  object sqlHandle;
90  string dbtype;
91 
92  sscanf(dbconnect, "%s://%*s", dbtype);
93 
94  switch ( dbtype ) {
95  case "mysql":
96  sqlHandle = new("dbadaptors/mysql.pike", dbconnect);
97  break;
98  case "postgres":
99  sqlHandle = new("dbadaptors/postgres.pike", dbconnect);
100  break;
101  }
102  return sqlHandle;
103 }
104 
105 public:
106 
107 
108 private int log_save_queue_modulo = 0;
109 
110 class SqlReadRecord {
111 public:
112  int iMaxRecNbr; // record number
113  int iID; // doc_it
114  int iNextRecNbr; // record number current
115  object dbfile;
116  function restore; // function if record needs to be restored
117  Thread.Fifo contFifo;
118  Thread.Mutex fullMutex;
119 
120  int stopRead = 0;
121  int myId = time();
122 
123  int check_timeout() {
124  if ( objectp(dbfile) ) {
125  int t = time() - dbfile->get_last_access();
126  if ( !objectp(contFifo) )
127  return 0;
128  if ( t > 600 )
129  return 0;
130  return 1;
131  }
132  return 0;
133  }
134 }
135 
136 
137 /**
138  * return a thread-local (valid) db-handle
139  *
140  * @param none
141  * @return the database handle
142  */
143 private:
144 private Sql.Sql db()
145 {
146  if (this_thread() == tSaveDemon) // give saveDemon its own handle
147  return oDemonDbHandle;
148 
149 #ifdef USE_LOCAL_SQLCONNECT
150  if (!objectp(dbHandlesLocal))
151  dbHandlesLocal = thread_local();
152 #endif
153 
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);
162 #endif
163  }
164 
165 #ifdef USE_LOCAL_SQLCONNECT
166  object handle = dbHandlesLocal->get();
167  if (!objectp(handle)) {
168  // this might fail later on!
169  mixed err = catch {
170  handle = SqlHandle(STEAM_DB_CONNECT);
171  dbHandles += ({ handle });
172  };
173  if (err) {
174  handle = oDbHandle;
175  FATAL("Failed to create database handle for Thread - using main handle!");
176  }
177 
178  dbHandlesLocal->set(handle);
179  }
180 
181  // FATAL(cal->Second()->format_nice()+": database handle requested.");
182  return dbHandlesLocal->get();
183 #else
184  return oDbHandle;
185 #endif
186 }
187 
188 public:
189 
190 object get_db_handle()
191 {
192  if ( _Server->is_module(CALLER) )
193  return db();
194  error("Unauthorized call to get_db_handle!");
195 }
196 
197 /**
198  * mimick object id for serialization etc.
199  * @return ID_DATABASE from database.h
200  * @see object.get_object_id
201  */
202 final int get_object_id()
203 {
204  return ID_DATABASE;
205 }
206 
207 private:
208 private void db_execute(string db_query)
209 {
210  db()->big_query(db_query);
211 }
212 
213 public:
214 
215 int get_save_size()
216 {
217  return preSaveQueue->size() + oSaveQueue->size();
218 }
219 
220 array get_save_queue_size()
221 {
222  return ({ preSaveQueue->size(), oSaveQueue->size() });
223 }
224 
225 
226 /**
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.
230  *
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
234  */
235 int log_save_queue ( int modulo )
236 {
237  int tmp = log_save_queue_modulo;
238  log_save_queue_modulo = modulo;
239  return tmp;
240 }
241 
242 /**
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.
246  *
247  * @param nothing
248  * @return void
249  * @see save_object
250  */
251 void database_save_demon()
252 {
253  MESSAGE("DATABASE SAVE DEMON ENABLED");
254  mixed job = "";
255 
256  while(!intp(job)) {
257  // if oSaveQueue contains an integer the server has been stopped
258  job = oSaveQueue->read();
259  mixed cerr = catch {
260  if (arrayp(job)) {
261  object proxy;
262  string ident, index;
263  [proxy, ident, index] = job;
264 
265  low_save_object(proxy, ident, index);
266  }
267  else if (stringp(job)) {
268  if (stringp(job))
269  db_execute(job);
270  }
271  };
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 );
278  }
279 
280  if ( cerr ) {
281  FATAL("/**************** database_save_demon *************/\n"+
282  PRINT_BT(cerr));
283  }
284  }
285  MESSAGE_END("");
286  MESSAGE("DATABASE SAVE DEMON EXITED!");
287 }
288 
289 
290 private:
291 private void emergency_save()
292 {
293  mixed job;
294  FATAL("Emergency SAVE of " + get_save_size() + " items ....\n");
295  while (preSaveQueue->size() > 0) {
296  job = preSaveQueue->read();
297  if (arrayp(job))
298  low_save_object(job[0], job[1], job[2], 1);
299  }
300  while (oSaveQueue->size() > 0) {
301  job = oSaveQueue->read();
302  // save object in kill mode!
303  if (arrayp(job))
304  low_save_object(job[0], job[1], job[2], 1);
305  }
306 }
307 
308 public:
309 
310 /**
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.
314  *
315  * @param nothing
316  * @return the key object
317  * @see Thread.Mutex->lock
318  */
319 void wait_for_db_lock()
320 {
321  int sz = get_save_size();
322  int cnt = 0;
323 
324  MESSAGE("Save Demon is %O status=%d", tSaveDemon,
325  objectp(tSaveDemon) ? tSaveDemon->status() : "not available");
326 
327  MESSAGE("LowSave Lock currently held by %O %O",
328  lowSaveMutex->current_locking_key() || "none",
329  lowSaveMutex->current_locking_thread() || "none");
330 
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!");
336  emergency_save();
337  return;
338  }
339 
340  MESSAGE("Waiting to finish SAVE");
341  while ( sz > 0 ) {
342  write(".");
343  int size = get_save_size();
344  if ( size == sz )
345  cnt++;
346  else
347  cnt=0;
348  if ( cnt > 20 ) {
349  // nothing saved ?!
350  emergency_save();
351  }
352  sz = size;
353  sleep(2);
354  }
355 
356  MESSAGE_START("Waiting for Database Save Demon to stop");
357  oSaveQueue->write(1); // stop database_save_demon
358  cnt = 0;
359  while(tSaveDemon->status() != Thread.THREAD_EXITED && cnt < 10) {
360  MESSAGE_APPEND(".");
361  cnt++;
362  oSaveQueue->write(1);
363  oSaveQueue->signal();
364  sleep(1);
365  }
366  MESSAGE_END("");
367 
368  return;
369 }
370 
371 /**
372  * constructor for database.pike
373  * - starts thread to keep objects persistent
374  * - enables commands in database
375  * @param none
376  * @return void
377  */
378 void create()
379 {
380  // first check for lost data, etc.
381 
382  mProxyLookup = ([ ]);
383  mCurrMaxID = ([ ]);
384  oSaveQueue = Thread.Queue();
385  preSaveQueue = Thread.Queue();
386  oTlDbHandle = thread_local();
387  mSaveCount = ([ ]);
388  mSaveIndex = ([ ]);
389  iSaves = 0;
390  iSkips = 0;
391 }
392 
393 int get_saves() { return iSaves; }
394 int get_skips() { return iSkips; }
395 
396 
397 void init()
398 {
399  _Persistence->register("mysql", this_object());
400 }
401 
402 object enable_modules()
403 {
404  if ( CALLER != _Server )
405  error("Unauthorized Call to enable_modules!");
406 
407  dbHandles = ({ });
408  tSaveDemon = thread_create(database_save_demon);
409  tPreSaveDemon = thread_create(database_manager);
410 
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);
418 
419  oModules = ((program)"/modules/modules.pike")();
420  oModuleCache = ([ "modules": oModules ]);
421 
422  oDemonDbHandle = SqlHandle(STEAM_DB_CONNECT);
423  int x=validate_db_handle(oDemonDbHandle);
424  if (x==0)
425  setup_sTeam_tables(oDemonDbHandle);
426  else if (x==1)
427  add_new_tables(oDemonDbHandle);
428 
429  check_journaling(oDemonDbHandle);
430  check_tables(oDemonDbHandle);
431  check_database_updates(oDemonDbHandle);
432  return oModules;
433 }
434 
435 //#define DBREAD(l, args...) werror("%O"+l+"\n", Thread.this_thread(), args)
436 #define DBREAD(l, args ...)
437 
438 protected:
439  void add_record(object record)
440 {
441  readQueue->write(record);
442 }
443 
444 public:
445 
446 void db_reader()
447 {
448  SqlReadRecord record;
449  Sql.sql_result odbData;
450  array fetch_line;
451 
452  while ( 1 ) {
453  DBREAD("Waiting for queue...");
454 
455  mixed err = catch {
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 ) {
468  break;
469  }
470  record->contFifo->write(db()->unescape_blob(fetch_line[0]));
471  record->iNextRecNbr= (int)fetch_line[1] +1;
472  }
473  DBREAD("next=%d, last=%d", record->iNextRecNbr, record->iMaxRecNbr);
474  if ( record->iNextRecNbr > 0 &&
475  record->iNextRecNbr <= record->iMaxRecNbr)
476  {
477  DBREAD("Continue reading...\n");
478  object mlock = record->fullMutex->lock();
479  if ( record->contFifo->size() > 100 )
480  record->restore = add_record;
481  else
482  readQueue->write(record); // further reading
483  destruct(mlock);
484  }
485  else {
486  DBREAD("Read finished...\n");
487  record->contFifo->write(0);
488  }
489  }
490  else
491  destruct(record);
492  };
493  if ( err ) {
494  FATAL("Error while reading from database: %O", err);
495  catch {
496  DBREAD("finished read on %d", record->iID);
497  record->contFifo->write(0);
498  };
499  }
500  }
501 }
502 
503 protected:
504  void db_writer() {
505  while ( 1 ) {
506  array job = writeQueue->read();
507  function|string data;
508  int id, iNextRecNbr;
509  [id, iNextRecNbr,data] = job;
510  if (stringp(data)) {
511  string line = "insert into doc_data values('"+
512  db()->escape_blob(data)+"', "+ id +", "+iNextRecNbr+")";
513  mixed err = catch{db()->big_query(line);};
514  if (err) {
515  FATAL("Fatal error while writting FILE into database: %O\n%O", err[0], err[1]);
516  }
517  }
518  else if (functionp(data)) {
519  catch(data());
520  }
521  }
522 }
523 
524 public:
525 
526 
527 object read_from_database(int id, int nextID, int maxID, object dbfile)
528 {
529  SqlReadRecord record = SqlReadRecord();
530  record->iID = id;
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);
538  return record;
539 }
540 
541 void write_into_database(int id, int iRecNr, string|function data)
542 {
543  if ( object_program(CALLER) != (program)"/kernel/db_file" )
544  error("No Access to write into Database!");
545  writeQueue->write(({id, iRecNr, data}));
546 }
547 
548 int check_save_demon()
549 {
550 
551  if ( CALLER != _Server )
552  error("Unauthorized Call to check_save_demon() !");
553 
554  int status = tSaveDemon->status();
555  //werror(ctime(time())+" Checking Database SAVE DEMON\n");
556  if ( status != 0 ) {
557  FATAL("----- DATABASE SAVE DEMON restarted ! ---");
558  tSaveDemon = thread_create(database_save_demon);
559  }
560  status = tPreSaveDemon->status();
561  if ( status != 0 ) {
562  FATAL("----- DATABASE MANAGER DEMON restarted ! ---");
563  tSaveDemon = thread_create(database_manager);
564  }
565 
566  foreach(dbHandles, object handle)
567  handle->keep();
568  oDbHandle->keep();
569  oDemonDbHandle->keep();
570 
571  if ( objectp(globalQueue) ) {
572  int sz = globalQueue->size();
573  while ( sz > 0 ) {
574  sz--;
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;
583  destruct(record);
584  destruct(dbfile); // make sure everything is gone and freed
585  }
586  else
587  globalQueue->write(record); // keep
588  }
589  }
590  }
591  return status;
592 }
593 
594 void register_transient(array obs)
595 {
596  ASSERTINFO(CALLER==MODULE_SECURITY || CALLER== this_object(),
597  "Invalid CALLER at register_transient()");
598  object obj;
599  foreach (obs, obj) {
600  if (objectp(obj))
601  mProxyLookup[obj->get_object_id()] = obj;
602  }
603 }
604 
605 
606 /**
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
611  * @see get_variable
612  */
613 void set_variable(string name, int value)
614 {
615  if(sizeof(db()->query("SELECT var FROM variables WHERE var='"+name+"'")))
616  {
617  db()->big_query("UPDATE variables SET value='"+value+
618  "' WHERE var='"+name+"'" );
619  }
620  else
621  {
622  db()->big_query("INSERT into variables values('"+name+"','"+value+"')");
623  }
624 }
625 
626 /**
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
630  * @see set_variable
631  */
632 int get_variable(string name)
633 {
634  object res;
635  res = db()->big_query("select value from variables where "+
636  "var ='"+name+"'");
637  if (objectp(res) && res->num_rows())
638  return (int) res->fetch_row()[0];
639 
640  return 0;
641 }
642 
643 /**
644  * reads the currently used max ID from the database and given table
645  * and increments. for performance reasons this ID is cached.
646  *
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
651  */
652 private:
653 private
654 int create_new_database_id(string table)
655 {
656  object lock = idMutex->lock(2);
657  mixed err = catch {
658  if (!mCurrMaxID[table]) {
659  string query;
660  int result;
661  Sql.sql_result res;
662 
663  result = get_variable(table);
664  if (!result) {
665  switch(table) {
666  case "doc_data" :
667  query = sprintf("select max(doc_id) from %s",table);
668  res = db()->big_query(query);
669  result = (int) res->fetch_row()[0];
670  break;
671  case "ob_class":
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);
675  }
676  }
677  mCurrMaxID[table] = result;
678  }
679  mCurrMaxID[table] += 1;
680  // MESSAGE("Created new database ID"+(int) mCurrMaxID[table]);
681  set_variable(table, mCurrMaxID[table]);
682  };
683  if ( err != 0 )
684  FATAL("Error whilte creating database ID: %O\n%O", err[0], err[1]);
685  destruct(lock);
686  return mCurrMaxID[table];
687 }
688 
689 public:
690 
691 /**
692  * called in case, a newly created database id is obsolete,
693  * usually called to handle an error occuring in further handling
694  *
695  * @param int db - Database to connect to
696  * @param string table - table choosen
697  * @return void
698  * @see create_new_databas_id()
699  */
700 void free_last_db_id(string table)
701 {
702  mCurrMaxID[table]--;
703 }
704 
705 /**
706  * creates a new persistent sTeam object.
707  *
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
712  */
713 mixed new_object(object obj, string prog_name)
714 {
715  int new_db_id;
716  object p;
717  // check for valid object has to be added
718  // create database ID
719 
720  if ( CALLER != _Persistence )
721  error("Only Persistence Module is allowed to get in here !");
722 
723  int id = obj->get_object_id();
724  if ( id )
725  {
726  ASSERTINFO((p=mProxyLookup[id])->get_object_id() == id,
727  "Attempt to reregister object in database!");
728  return ({ id, p });
729  }
730 
731  object lock = createMutex->lock(); // make sure creation is save
732 
733  mixed err = catch {
734 
735  new_db_id = create_new_database_id("ob_class");
736 
737  p = new(PROXY, new_db_id, obj );
738  if (!objectp(p->get_object())) // error occured during creation
739  {
740  free_last_db_id("ob_class");
741  destruct(p);
742  }
743 
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 !");
750  }
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)
754  );
755  mProxyLookup[new_db_id] = p;
756  save_object(p, 0);
757  }
758  };
759  if ( err ) {
760  FATAL("database.new_object: failed to create object\n %O", err);
761  }
762 
763  destruct(lock);
764 
765  return ({ new_db_id, p});
766 }
767 
768 /**
769  * permanently destroys an object from the database.
770  * @param object represented by (proxy) to delete
771  * @return (0|1)
772  * @see new_object
773  */
774 bool delete_object(object p)
775 {
776  if ( CALLER!=_Persistence &&
777  {
778  werror("caller of delete_object() is %O\n", CALLER);
779  THROW("Illegal call to database.delete_object", E_ACCESS);
780  }
781  return do_delete(p);
782 }
783 
784 private:
785 private bool do_delete(object p)
786 {
787  object proxy;
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);
795 
796  return 1;
797 }
798 
799 public:
800 
801 protected:
802  void fail_unserialize(int iOID, string sData, mapping mData, mapping oldData)
803 {
804  FATAL("MISMATCH in unserialize %d\n%s\nNEW=%O\n\nOLD=%O\n", iOID, sData,
805  mData,
806  oldData);
807 }
808 
809 public:
810 
811 
812 /**
813  * load and restore values of an object with given Object ID
814  * @param int OID
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
819  * @return the object
820  * @see
821  */
822 int|object load_object(object proxy, int|object iOID)
823 {
824  string sClass;
825  string sIdent;
826  string sAttrib;
827  string sData;
828  object o;
829 
830  if ( CALLER != _Persistence )
831  error("Unable to load objects directly !");
832 
833  mixed catched;
834 
835  Sql.sql_result res = db()->big_query(
836  sprintf("select ob_class from ob_class where ob_id = %d", iOID)
837  );
838  mixed line = res->fetch_row();
839  // object deleted?!
840  if (!arrayp(line))
841  return 0;
842 
843  if (objectp(iOID)) {
844  o = iOID;
845  }
846  else
847  {
848  catched = catch {
849  sClass = line[0];
850  int pos;
851  if ( (pos = search(sClass, "DB:#")) >= 0 ) {
852  sClass = "/"+sClass[pos..];
853  if (search(sClass, ".pike")==-1)
854  sClass += ".pike";
855  }
856  o = new(sClass, proxy);
857  };
858 
859  if (!objectp(o)) // somehow failed to load file
860  {
861  if ( catched ) {
862  _Server->add_error(time(), catched);
863  string pikeClass = sClass;
864  sscanf(pikeClass, "%s.pike", pikeClass);
865  if (!master()->master_file_stat(pikeClass+".pike")) {
866  return 2;
867  }
868  FATAL("Error loading object %d (%s)\n%s", iOID, sClass,
869  master()->describe_backtrace(catched));
870  }
871  return 1; // class exists but failes to compile
872  }
873  proxy->set_steam_obj(o); // o is the real thing - no proxy!
874  }
875  mapping mData;
876 
877  res = db()->big_query(
878  sprintf("select ob_ident, ob_attr, ob_data from ob_data where "+
879  "ob_id = %d", iOID));
880 
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,
885  backtrace());
886  FATAL("OBJECT is %O", o);
887  return 3;
888  }
889  mapping mIndexedStorage = ([]);
890 
891  foreach(indices(mStorage), string _ident) // precreate indexed idents
892  if (mStorage[_ident][2])
893  mIndexedStorage[_ident]=([]);
894 
895  while (line = res->fetch_row())
896  {
897  [sIdent, sAttrib, sData] = line;
898  catched = catch {
899 #if !constant(steamtools.unserialize) || !USE_CSERIALIZE
900  mData = unserialize(sData); // second arg is "this_object()"
901 #else
902  mixed sErr = catch(mData = steamtools.unserialize(sData, find_object));
903  if ( sErr ) {
904  FATAL("Failed to load object - error in unserialize: %O\nData:%O",
905  sErr[0], sData);
906  }
907 #if constant(check_equal)
908 #ifdef VERIFY_CMOD
909  mapping oldData = unserialize(sData);
910  if ( !check_equal(mData, oldData) ) {
911  call(fail_unserialize, 1, iOID, sData, mData, oldData);
912  //exit(1);
913  }
914 #endif
915 #endif
916 #endif
917  };
918  if ( catched ) {
919  FATAL("While loading ("+iOID+","+sClass+"):\n%O\n%O\n%O",
920  sData,
921  catched[0],
922  catched[1]);
923  proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
924  werror("Setting UNSERIALIZE on %O - error while loading!!\n", proxy);
925  return 3;
926  }
927  if ( sAttrib != "")
928  {
929  if ( !mIndexedStorage[sIdent] ) {
930  FATAL("WARNING: Missing Storage %s in %d, %s, attrib=%O",
931  sIdent, iOID, sClass, sAttrib);
932  continue;
933  }
934  mIndexedStorage[sIdent][sAttrib]=mData;
935  }
936  else if ( !mStorage[sIdent] ) {
937  FATAL("No storage handler %s defined in %d (%s).\ndata=%O",
938  sIdent, iOID, sClass, mData); // INFO ?!
939  }
940  else
941  {
942  catched = catch {
943  if ( proxy->is_loaded() ) {
944  error(sprintf("Fatal error: already loaded %O\n", proxy));
945  }
946  mStorage[sIdent][1](mData); // actual function call
947  };
948  if ( catched ) {
949  FATAL("Error while loading (%d, %s)\n"+
950  "Error while calling storage handler %s: %O\n"+
951  "Full Storage:%O\nData: %O",
952  iOID, sClass,
953  sIdent, catched,
954  mStorage, mData);
955  }
956  }
957  }
958 
959  foreach(indices(mIndexedStorage), string _ident)
960  mStorage[_ident][1](mIndexedStorage[_ident]); // prepared call
961 
962  mixed err = catch {
963  o->loaded();
964  };
965  if ( err != 0 )
966  FATAL("POST load failure: %O\n%O", err[0], err[1]);
967  return o;
968 }
969 
970 protected:
971  mapping get_storage_handlers(object o)
972 {
973  if ( !objectp(o) )
974  FATAL("Getting storage handlers for %O", o);
975 
976  mapping storage = _Persistence->get_storage_handlers(o);
977  return storage;
978 }
979 
980 public:
981 
982 mixed call_storage_handler(function f, mixed ... params)
983 {
984  if ( CALLER != _Persistence )
985  error("Database:: call_storage_handler(): Unauthorized call !");
986  mixed res = f(@params);
987  return res;
988 }
989 
990 int get_class_id(string classname)
991 {
992  switch(classname) {
993  case "/classes/Object":
994  return CLASS_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;
1019  default:
1020  return 0;
1021  }
1022 }
1023 
1024 string get_class_string(int classid)
1025 {
1026  for (int i=0;i<32;i++)
1027  if ( classid & (1<<i))
1028  classid = (1<<i);
1029 
1030  switch(classid) {
1031  case CLASS_ROOM:
1032  return "/classes/Room";
1033  case CLASS_USER:
1034  return "/classes/User";
1035  case CLASS_CONTAINER:
1036  return "/classes/Container";
1037  case CLASS_DOCUMENT:
1038  return "/classes/Document";
1039  case CLASS_GROUP:
1040  return "/classes/Group";
1041  case CLASS_LINK:
1042  return "/classes/Link";
1043  case CLASS_EXIT:
1044  return "/classes/Exit";
1045  case CLASS_MESSAGEBOARD:
1046  return "/classes/Messageboard";
1047  case CLASS_DRAWING:
1048  return "/classes/Drawing";
1049  }
1050  return "/classes/Object";
1051 }
1052 
1053 /**
1054  * find an object from the global object cache or retreive it from the
1055  * database.
1056  *
1057  * @param int - iOID ( object ID from object to find )
1058  * @return object (proxy associated with object)
1059  * @see load_object
1060  */
1061 final object find_object(int|string iOID)
1062 {
1063  object p;
1064 
1065  if ( stringp(iOID) )
1066  return _Server->get_module("filepath:tree")->path_to_object(iOID);
1067 
1068  if ( !intp(iOID) )
1069  THROW("Wrong argument to find_object() - expected integer!",E_ERROR);
1070 
1071  if ( iOID == 0 ) return 0;
1072  if ( iOID == 1 ) return this_object();
1073 
1074  if ( objectp(p = mProxyLookup[iOID]) )
1075  return p;
1076 
1077  Sql.sql_result res;
1078 
1079  res = db()->big_query(sprintf("select ob_class from ob_class"+
1080  " where ob_id = %d", iOID));
1081 
1082  if (!objectp(res) || res->num_rows()==0)
1083  return 0;
1084 
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;
1089 
1090  return p;
1091 }
1092 
1093 /**
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.
1096  *
1097  * @see save_object
1098  */
1099 void require_save(object proxy, void|string ident, void|string index, void|int action, void|array args)
1100 {
1101  if (proxy && proxy->status()>=PSTAT_SAVE_OK) {
1102  preSaveQueue->write( ({ proxy, ident, index }) );
1103  }
1104 }
1105 
1106 protected:
1107  void database_manager()
1108 {
1109  while ( 1 ) {
1110  object proxy;
1111  string ident;
1112  string index;
1113 
1114  [proxy, ident, index] = preSaveQueue->read();
1115  save_object(proxy, ident, index);
1116  }
1117 }
1118 
1119 public:
1120 
1121 
1122 /**
1123  * callback-function called to indicate that an object has been modified
1124  * in memory and needs to be saved to the database.
1125  *
1126  * @param object p - (proxy) object to be saved
1127  * @return void
1128  * @see load_object
1129  * @see find_object
1130  */
1131 protected:
1132  void
1133 save_object(object proxy, void|string ident, void|string index)
1134 {
1135  if ( !objectp(proxy) )
1136  return;
1137 
1138  string savestore = SAVESTORE(ident,index);
1139 
1140  Thread.MutexKey low=lowSaveMutex->lock();
1141  mixed err = catch {
1142  if (!mappingp(mSaveIndex[proxy]))
1143  mSaveIndex[proxy] = ([ ]);
1144 
1145  if (mSaveIndex[proxy][savestore] == 1) {
1146  iSkips++;
1147  destruct(low);
1148  return;
1149  }
1150 
1151  if (proxy->status() == PSTAT_SAVE_OK) {
1152  proxy->set_status(PSTAT_SAVE_PENDING);
1153  if ( !mSaveCount[proxy] )
1154  mSaveCount[proxy] = 0;
1155  }
1156 
1157  mSaveIndex[proxy][savestore] = 1;
1158  mSaveCount[proxy] = mSaveCount[proxy] + 1;
1159  };
1160  if (err) {
1161  FATAL("Failure in save_object(): %O\n%O", err[0], err[1]);
1162  }
1163  destruct(low);
1164 
1165  oSaveQueue->write(({proxy, ident, index }));
1166 }
1167 
1168 public:
1169 
1170 /**
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)
1175  */
1176 private:
1177 private string
1178 quote_data(mixed data, object o, string ident, function quoter, int|void utf8)
1179 {
1180  string sdata;
1181  data = copy_value(data);
1182  if (utf8) {
1183  sdata = serialize(data, "utf-8");
1184  }
1185  else {
1186 #if constant(steamtools.serialize) && USE_CSERIALIZE
1187  sdata = steamtools.serialize(data);
1188 #ifdef VERIFY_CMOD
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);
1194  sdata = verifydata;
1195  }
1196 #endif
1197 #else
1198  sdata = serialize(data);
1199 #endif
1200  }
1201 
1202  if (strlen(sdata)> 16777215)
1203  FATAL("!!! FATAL - data truncated inserting %d bytes for %s block %s",
1204  strlen(data),
1205  (objectp(o) ? "broken" : o->get_identifier()),
1206  ident);
1207  return quoter(sdata);
1208 }
1209 
1210 public:
1211 
1212 /**
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
1219  */
1220 private:
1221 private array
1222 prepare_save_statement(object o, mapping storage,
1223  string|void ident, string|void index)
1224 {
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
1229  array idents =
1230  arrayp(storage[ident]) ? ({ ident }) : indices(storage);
1231  string data;
1232  string sClass = master()->describe_program(object_program(o->get_object()));
1233  function db_quote_data = db()->quote;
1234  foreach(idents, string _ident)
1235  {
1236  if (!arrayp(storage[_ident]))
1237  {
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);
1241  }
1242  else if (storage[_ident][2]) // an indexed data-storage
1243  {
1244  if (zero_type(index))
1245  {
1246  mapping mData = storage[_ident][0](); // retrieve all
1247  foreach(indices(mData), mixed _index)
1248  {
1249  if (!stringp(_index))
1250  continue;
1251  data = quote_data(mData[_index], o, _ident,
1252  db_quote_data);
1253  statements[_ident+_index] =
1254  sprintf("(%d,'%s','%s','%s')",
1255  oid, _ident, db_quote_data(_index),
1256  data );
1257  }
1258  }
1259  else
1260  {
1261  if (_ident != "user" && index!="UserPassword")
1262  data = quote_data(storage[_ident][0](index), o, _ident,
1263  db_quote_data);
1264  else
1265  data = quote_data(storage[_ident][0](index), o, _ident,
1266  db_quote_data); // never convert user pw
1267  // to utf8
1268  statements[_ident+index] =
1269  sprintf("(%d,'%s','%s','%s')",
1270  oid, _ident, db_quote_data(index), data);
1271  }
1272 
1273  }
1274  else // the usual unindexed data-storage
1275  {
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);
1280  }
1281  }
1282  return values(statements);
1283 }
1284 
1285 public:
1286 
1287 
1288 /**
1289  * generate a delete statement that will clear all entries according to
1290  * the data that will be saved.
1291  * @see prepare_save_statement
1292  */
1293 private:
1294 private string
1295 prepare_clear_statement(object o, mapping storage,
1296  string|void ident, string|void index, string|void tb)
1297 {
1298  if ( !stringp(tb) )
1299  tb = "ob_data";
1300 
1301  if (ident=="0" || index=="0")
1302  FATAL("strange call to prepare_clear_statement \n%s\n",
1303  describe_backtrace(backtrace()));
1304 
1305  if (!storage[ident]) ident =0; // better save then sorry - wrong ident
1306  // invoces a full save.
1307  if (ident && index)
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);
1311  else if (ident)
1312  return sprintf("delete from %s where ob_id='%d' and "+
1313  "ob_ident='%s'", tb, o->get_object_id(), ident);
1314  else
1315  return sprintf("delete from %s where ob_id='%d'",
1316  tb, o->get_object_id());
1317 
1318 }
1319 
1320 public:
1321 
1322 /**
1323  * low level database function to store a given (proxy) object into the
1324  * database immediately.
1325  *
1326  * @param object proxy - the object to be saved
1327  * @return void
1328  * @see save_object
1329  */
1330 private:
1331 private void
1332 low_save_object(object p, string|void ident,string|void index,int|void killed)
1333 {
1334  array sStatements;
1335 
1336  int stat = p->status();
1337  // saved twice while waiting
1338  if (stat==PSTAT_DISK || stat==PSTAT_DELETED || stat==PSTAT_FAIL_DELETED)
1339  {
1340  m_delete(mSaveCount, p);
1341  m_delete(mSaveIndex, p);
1342  return; // low is local so this will unlock also
1343  }
1344 
1345  ASSERTINFO(!objectp(MODULE_SECURITY) ||
1346  MODULE_SECURITY->valid_object(p),
1347  sprintf("invalid object in database.save_object: %O",p));
1348 
1349  if (p->status() < PSTAT_SAVE_OK)
1350  {
1351  FATAL("DBSAVEDEMON ->broken instance not saved(%d, %s, status=%s)",
1352  p->get_object_id(),
1353  master()->describe_object(p->get_object()),
1354  PSTAT(p->status()));
1355  return;
1356  }
1357 
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");
1361  return;
1362  }
1363 
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);
1370 
1371  if (master()->describe_program(object_program(p->get_object()))=="-")
1372  return; // temporary objects like executer
1373 
1374  sStatements =
1375  prepare_save_statement(p, storage, ident, index );
1376 
1377  ASSERTINFO(sizeof(sStatements)!=0,
1378  sprintf("trying to insert empty data into object %d class %s",
1379  p->get_object_id(),
1380  master()->describe_program(object_program(p->get_object()))));
1381 
1382  mixed err;
1383  if (!killed) {
1384  Thread.MutexKey low=lowSaveMutex->lock();
1385  err = catch {
1386  mSaveCount[p]--;
1387 
1388  iSaves++;
1389  if ( !mappingp(mSaveIndex[p]) ) {
1390  FATAL("Save index not mapped in %O, SaveCount = %O\n",
1391  p, mSaveCount[p]);
1392  }
1393  else {
1394  mSaveIndex[p][SAVESTORE(ident, index)] = 0;
1395  }
1396  if ( mSaveCount[p] <= 0 ) {
1397  m_delete(mSaveCount, p);
1398  m_delete(mSaveIndex, p);
1399  p->set_status(PSTAT_SAVE_OK);
1400  }
1401  };
1402  if (err) {
1403  FATAL("Error in low_save_object(): %O\n%O", err[0], err[1]);
1404  }
1405  destruct(low); // status set, so unlock
1406  }
1407 
1408  string s;
1409  err = catch {
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");
1414  db()->big_query(s);
1415  };
1416  if ( delete_err )
1417  FATAL("FATAL in save-demon, deletion statement %s failed\n%O:%O\n",
1418  s, delete_err[0], delete_err[1]);
1419 
1420  // add new value
1421  s = db()->create_insert_statement(sStatements);
1422  db()->big_query(s);
1423  db()->big_query("COMMIT;");
1424  };
1425  if ( err )
1426  {
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")));
1432  }
1433 
1434  err = catch(update_classtable(p, index));
1435  if ( err ) {
1436  FATAL("ERROR IN SAVE-DEMON (Updating classtable): %O\n%O\n",
1437  err[0], err[1]);
1438  }
1439 }
1440 
1441 public:
1442 
1443 protected:
1444  string db_get_path(int oid)
1445 {
1446  string path = db_get_attribute(oid, OBJ_PATH);
1447 
1448  if ( !zero_type(path) )
1449  return path;
1450 
1451  object env = db_get_attribute(oid, "Environment");
1452  if ( !objectp(env) ) {
1453  object obj = find_object(oid);
1454 
1455  if ( !(obj->get_object_class() & CLASS_ROOM) )
1456  path = "";
1457  else if ( !objectp(_ROOTROOM) )
1458  path = "";
1459  else
1460  path = _FILEPATH->object_to_filename(obj);
1461 
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);
1467  }
1468  return path;
1469  }
1470  path = db_get_path(env->get_object_id());
1471  path = path + (path != "/"?"/":"") + db_get_attribute(oid, OBJ_NAME);
1472 
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);
1477 
1478  return path;
1479 }
1480 
1481 public:
1482 
1483 
1484 protected:
1485  mixed db_get_attribute(int oid, string attribute)
1486 {
1487  string q;
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]);
1493  return UNDEFINED;
1494 }
1495 
1496 public:
1497 
1498 protected:
1499  void update_classtableobject(int oid, void|object obj)
1500 {
1501  object lock = updateMutex->lock(2); // only lock for current thread
1502 
1503  mixed err = catch {
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) )
1508  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();
1514 
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);
1522  };
1523  destruct(lock);
1524  if ( err ) {
1525  FATAL("Error while updating class index %O\n%O", err[0], err[1]);
1526  throw(err);
1527  }
1528 }
1529 
1530 public:
1531 
1532 protected:
1533  void update_classtable(object p, void|string index)
1534 {
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());
1539  }
1540  else
1541  update_classtableobject(p->get_object_id(), p);
1542 }
1543 
1544 public:
1545 
1546 /**
1547  * Change the class of an object in the database. Drop the object to
1548  * get an object with the modified class.
1549  *
1550  * @param object doc - change class for this document
1551  * @param string classfile - the new class
1552  */
1553 int change_object_class(object doc, string classfile)
1554 {
1555  if ( CALLER != _Persistence )
1556  steam_error("Illegal call to database.change_class() !");
1557 
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();
1561 
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')",
1566  id, classfile);
1567  return 1;
1568 }
1569 
1570 
1571 /**
1572  * register an module with its name
1573  * e.g. register_module("users", new("/modules/users"));
1574  *
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
1580  */
1581 int register_module(string oname, object module, void|string source)
1582 {
1583  object realObject;
1584  string version = "";
1585 
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);
1590 
1591  object mod;
1592  int imod = get_variable("#" + oname);
1593 
1594  if ( imod > 0 )
1595  {
1596  mod = find_object(imod); // get old module
1597  if ( objectp(mod) && mod->status() >= 0 &&
1598  mod->status() != PSTAT_DELETED)
1599  {
1600  object e = master()->getErrorContainer();
1601  master()->set_inhibit_compile_errors(e);
1602  realObject = mod->get_object();
1603  master()->set_inhibit_compile_errors(0);
1604  if (!realObject)
1605  {
1606  FATAL("Failed to compile update instance, re-installing");
1607  }
1608  }
1609  }
1610 
1611  if ( objectp(realObject) && functionp(realObject->version) &&
1612  functionp(realObject->upgrade) )
1613  {
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.",
1617  E_ERROR);
1618 
1619  version = realObject->get_version();
1620 
1621  mixed erg = master()->upgrade(object_program(realObject));
1622  if (!intp(erg) || erg<0)
1623  {
1624  if (stringp(erg))
1625  THROW(erg, backtrace());
1626  else
1627  {
1628  FATAL("New version of "+oname+" doesn't implement old "+
1629  "versions interface");
1630  master()->upgrade(object_program(mod->get_object()),1);
1631  }
1632  }
1633  module = mod;
1634  }
1635  else if ( !objectp(module) )
1636  {
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);
1642  if (!module)
1643  {
1644  FATAL("Failed to compile new instance - throwing");
1645  THROW("Failed to load module\n"+e->get()+"\n"+
1646  e->get_warnings(), backtrace());
1647  }
1648  }
1649 
1650  MESSAGE("Installing module %s ...", oname);
1651  if ( !stringp(source) )
1652  source = "";
1653 
1654  if ( module->get_object_class() & CLASS_PACKAGE ) {
1655 
1656  if ( module->install(source, version) == 0 )
1657  error(sprintf("Failed to install module %s !", oname));
1658  else
1659  MESSAGE("Installation of module %s succeeded.", oname);
1660  }
1661  _Server->register_module(module);
1662 
1663  _Server->run_global_event(EVENT_REGISTER_MODULE, PHASE_NOTIFY,
1664  this_object(), ({ module }) );
1665  LOG_DB("event is run");
1666  if ( objectp(module) )
1667  {
1668  set_variable("#"+oname, module->get_object_id());
1669  _Server->register_module(module);
1670  return module->get_object_id();
1671  }
1672  return 0;
1673 }
1674 
1675 /**
1676  * Check if a database handle is connected to a properly setup database.
1677  *
1678  * @param Sql.Sql handle - the handle to check
1679  * @return 1 - old format
1680  * @return 2 - new format
1681  * @see setup_sTeam_tables
1682  */
1683 int validate_db_handle(object handle)
1684 {
1685  multiset tables = (<>);
1686  array aTables = handle->list_tables();
1687 
1688  foreach(aTables, string table)
1689  tables[table] = true;
1690  if (tables["objects"] && tables["doc_data"])
1691  return 1;
1692  if (tables["ob_class"] && tables["ob_data"] && tables["doc_data"])
1693  return 2;
1694 }
1695 
1696 protected:
1697  void add_database_update(object handle, string name)
1698 {
1699  handle->query("insert into database_updates values(\""+name+"\")");
1700 }
1701 
1702 public:
1703 
1704 protected:
1705  int is_database_update(object handle, string name)
1706 {
1707  object result = handle->big_query("select * from database_updates where "
1708  + "(database_update='" + name + "')");
1709  if ( !objectp(result) || result->num_rows() == 0 )
1710  return 0;
1711  return 1;
1712 }
1713 
1714 public:
1715 
1716 protected:
1717  int check_database_updates(object handle)
1718 {
1719  if ( search(handle->list_tables(), "database_updates") == -1 ) {
1720  handle->query("create table database_updates (database_update char(128))");
1721  }
1722 
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')" );
1730  array rows = ({ });
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;
1735  rows += ({ row });
1736  }
1737  }
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')" );
1753  }
1754  }
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')" );
1758  rows = ({ });
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;
1763  rows += ({ row });
1764  }
1765  }
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')" );
1781  }
1782  }
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." );
1791  }
1792  else {
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() );
1799  }
1800  }
1801 }
1802 
1803 public:
1804 
1805 int check_updates()
1806 {
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);
1811  }
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);
1819  }
1820 
1821  return 0;
1822 }
1823 
1824 protected:
1825  void check_tables(object handle)
1826 {
1827  handle->check_tables();
1828 }
1829 
1830 public:
1831 
1832 protected:
1833  void check_journaling(Sql.Sql handle)
1834 {
1835  mixed row, res, err;
1836 
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);
1844  int oid;
1845  string ident, attr, val, rest;
1846  if ( sscanf(ll, "%d: %s", oid, ll) != 2 )
1847  continue;
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);
1850  err = catch {
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");
1856  }
1857  else {
1858  handle->query(sprintf("insert into ob_data values (%d,%s,%s,%s)",
1859  oid, ident, attr, val));
1860  }
1861  ll = rest;
1862  };
1863  if ( err ) {
1864  FATAL("Failed to restore data: %O", err);
1865  }
1866  if ( stringp(rest) || strlen(rest) > 1 )
1867  ll = rest[1..];
1868  else
1869  ll = "";
1870  }
1871  }
1872  }
1873 }
1874 
1875 public:
1876 
1877 protected:
1878  void add_new_tables(Sql.Sql handle) {
1879  MESSAGE("adding new format tables\n");
1880  MESSAGE("adding ob_class ");
1881  catch {
1882  handle->query("drop table if exists ob_class");
1883  handle->query("drop table if exists ob_data");
1884  };
1885  handle->query("create table ob_class ("+
1886  " ob_id int primary key, "+
1887  " ob_class char(128) "+
1888  ")");
1889 
1890  MESSAGE("adding ob_data ");
1891  handle->query("create table ob_data ("+
1892  " ob_id int, "+
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))"+
1900  ")");
1901 
1902  handle->query("create table ob_journaling ("+
1903  " ob_id int, "+
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)"+
1909  ")");
1910 }
1911 
1912 public:
1913 
1914 /**
1915  * set up the base sTeam tables to create an empty database.
1916  *
1917  * @param none
1918  * @return (1|0)
1919  */
1920 int setup_sTeam_tables(object handle)
1921 {
1922  /* make sure no old tables exist and delete them properly */
1923  MESSAGE("Checking for old tables.\n");
1924 
1925  array res = handle->list_tables();
1926  if (sizeof(res))
1927  {
1928  foreach(res, string table)
1929  {
1930  MESSAGE(sprintf("dropping (%s)\n",table));
1931  handle->big_query("drop table "+table);
1932  }
1933  }
1934  else
1935  MESSAGE("no old tables found");
1936 
1937  MESSAGE("CREATING NEW BASE TABLES:");
1938  handle->create_tables();
1939 
1940  res = handle->list_tables();
1941  if (!sizeof(res)) {
1942  FATAL("\nFATAL: failed to create base tables");
1943  }
1944  else
1945  {
1946  MESSAGE("\nPOST CHECK retrieves: ");
1947  foreach(res, string table)
1948  MESSAGE(table+" ");
1949  }
1950  return 1;
1951 }
1952 
1953 /**
1954  * create and return a new instance of db_file
1955  *
1956  * @param int iContentID - 0|ID of a given Content
1957  * @return the db_file-handle
1958  * @see db_file
1959  * @see file/IO
1960  */
1961 object new_db_file_handle(int iContentID, string mode)
1962 {
1963  return new("/kernel/db_file.pike", iContentID, mode);
1964 }
1965 
1966 /**
1967  * connect_db_file, connect a /kernel/db_file instance with the database
1968  * calculate new content id if none given.
1969  *
1970  * @param id
1971  * @return function db()
1972  */
1973 final mixed connect_db_file(int id)
1974 {
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)});
1978 }
1979 
1980 /**
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
1986  */
1987 private:
1988 private bool valid_db_mapping(object m)
1989 {
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"))
1998  return true;
1999  return false;
2000 }
2001 
2002 public:
2003 
2004 /**
2005  * connect_mapping, connect a /kernel/db_mapping instance with the database
2006  * @param none
2007  * @return a pair ({ function db, string tablename })
2008  */
2009 final mixed connect_db_mapping()
2010 {
2011  if (!(valid_db_mapping(CALLER)))
2012  {
2013  FATAL("illegal access %s from %O\n",CALLINGFUNCTION, CALLER);
2014  THROW("illegal access to database ", E_ACCESS);
2015  }
2016  string sDbTable;
2017  // hack to allow the modules table to be a member of _Database
2018 
2019  sDbTable = CALLER->get_table_name();
2020 
2021 
2022  if (!sDbTable)
2023  THROW(sprintf("Invalid tablename [%s] in module '%s'\n",sDbTable,
2024  master()->describe_program(CALLERPROGRAM)), E_ERROR);
2025  return ({ db, sDbTable });
2026 }
2027 
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; }
2033 
2034 
2035 
2036 
2037 /**
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)
2044  */
2045 final array get_objects_by_class(string|program|int mClass)
2046 {
2047  Sql.sql_result res;
2048  int i, sz;
2049  object security;
2050  array aObjects;
2051  string sClass;
2052 
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);
2056  }
2057 
2058  if ( intp(mClass) ) {
2059  mixed factory = _Server->get_factory(mClass);
2060  if ( !objectp(factory) )
2061  return UNDEFINED;
2062  if ( !stringp(CLASS_PATH) || !stringp(factory->get_class_name()) )
2063  return UNDEFINED;
2064  mClass = CLASS_PATH + factory->get_class_name();
2065  }
2066 
2067  if (programp(mClass))
2068  sClass = master()->describe_program(mClass);
2069  else
2070  sClass = mClass;
2071 
2072  res = db()->big_query("select ob_id from ob_class where ob_class='"+
2073  mClass+"'");
2074 
2075  aObjects = allocate((sz=res->num_rows()));
2076 
2077  for (i=0;i<sz;i++)
2078  {
2079  aObjects[i]=find_object((int)res->fetch_row()[0]);
2080  }
2081  return aObjects;
2082 }
2083 
2084 
2085 /**
2086  * get_all_objects()
2087  * mainly for maintenance reasons
2088  * @return array all objects found in the database
2089  * throws on access violation. (ROLE_READ_ALL required)
2090  */
2091 final array get_all_objects()
2092 {
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();
2096 }
2097 
2098 private:
2099 private array low_get_all_objects()
2100 {
2101  Sql.sql_result res;
2102  int i, sz;
2103  array aObjects;
2104 
2105  res = db()->big_query("select ob_id from ob_class where ob_class !='-'");
2106  aObjects = allocate((sz=res->num_rows()));
2107 
2108  for (i=0;i<sz;i++)
2109  {
2110  aObjects[i]=find_object((int)res->fetch_row()[0]);
2111  }
2112  return aObjects;
2113 }
2114 
2115 public:
2116 
2117 /**
2118  * visit_all_objects
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
2122  * @return nothing
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.
2128  */
2129 final void visit_all_objects(function visitor, mixed ... args)
2130 {
2131  FATAL("visit_all_objects not yet converted to new database format");
2132  return;
2133  Sql.sql_result res = db()->big_query("select ob_id,ob_class from ob_class");
2134  int i;
2135  int oid;
2136  string oclass;
2137  object p;
2138  FATAL("Number of objects found:"+res->num_rows());
2139  for (i=0;i<res->num_rows();i++)
2140  {
2141  mixed erg = res->fetch_row();
2142  oid = (int) erg[0]; // wrong casting with
2143  oclass = erg[1]; // [oid, oclass] = res->fetch_row()
2144 
2145  if (oclass[0]=='/') // some heuristics to avoid nonsene classes
2146  {
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
2150  visitor(p, @args);
2151  }
2152  }
2153 }
2154 
2155 /**
2156  * Check for a list of objects, if they really exist in the database
2157  *
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
2161  */
2162 array get_existing(array ids)
2163 {
2164  Sql.sql_result res;
2165  int i, sz;
2166  string query = "select ob_id from ob_class where ob_id in (";
2167  array result;
2168 
2169  if (!ids || !sizeof(ids))
2170  return ({ });
2171  for (i=0,sz=sizeof(ids)-1;i<sz;i++)
2172  query +=ids[i]+",";
2173  query+=ids[i]+")";
2174  res = db()->big_query(query);
2175 
2176  result = allocate((sz=res->num_rows()));
2177  for (i=0;i<sz;i++)
2178  result[i]=(int) res->fetch_row()[0];
2179 
2180  return result;
2181 }
2182 
2183 /**
2184  * Get a list of the not-existing objects.
2185  *
2186  * @param objects - the list of objects to be checked
2187  * @return a list of objects that are not existing
2188  * @see
2189  */
2190 array get_not_existing(array ids)
2191 {
2192  return ids - get_existing(ids);
2193 }
2194 
2195 object get_environment() { return 0; }
2196 object get_acquire() { return 0; }
2197 
2198 mapping get_xml_data()
2199 {
2200  return ([ "configs":({_Server->get_configs, XML_NORMAL}), ]);
2201 }
2202 
2203 /**
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
2207  * anymore.
2208  * @param none
2209  * @returns a debug string containing the number of deleted doc_id's
2210  */
2211 string clear_lost_content()
2212 {
2213  Sql.Sql h = db();
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];
2219 
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())
2226  {
2227  oid = (int)a[0];
2228  if (p=find_object(oid))
2229  {
2230  FATAL("accessing object"+oid);
2231  object try;
2232  catch{try=p->get_object();};
2233  if (objectp(try) &&
2234  Program.inherits(object_program(try),
2235  (program)"/base/content"))
2236  {
2237  FATAL("content "+p->get_content_id()+" is in use");
2238  doc_ids -= ({ p->get_content_id() });
2239  }
2240  }
2241  }
2242 
2243  FATAL("number of doc_ids to be deleted is:"+sizeof(doc_ids));
2244 
2245  foreach (doc_ids, int did)
2246  {
2247  h->big_query("delete from doc_data where doc_id = "+did);
2248  FATAL("deleting doc_id"+did);
2249  }
2250  FATAL("calling optimize");
2251  h->big_query("optimize table doc_data");
2252  return "deleted "+sizeof(doc_ids)+"lost contents";
2253 }
2254 
2255 object lookup (string identifier)
2256 {
2257  return get_module("objects")->lookup(identifier);
2258 }
2259 
2260 object lookup_user (string identifier)
2261 {
2262  return get_module("users")->get_user(identifier);
2263 }
2264 
2265 object lookup_group (string identifier)
2266 {
2267  return get_module("groups")->get_group(identifier);
2268 }
2269 
2270 int supported_classes () { return CLASS_ALL; }
2271 
2272 /**
2273  * Searches for users in the persistence layer.
2274  *
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
2284  */
2285 array search_users ( mapping terms, bool any, string|void wildcard ) {
2286  string eq = " = ";
2287  if ( stringp(wildcard) && sizeof(wildcard) > 0 ) eq = " like ";
2288 
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 )
2294  continue;
2295  switch ( attr ) {
2296  case "login" :
2297  case "firstname" :
2298  case "lastname" :
2299  case "email" :
2300  // additional strings might be mapped to column names...
2301  break;
2302  default : continue; // don't make invalid queries
2303  }
2304  if ( stringp(wildcard) && sizeof(wildcard) > 0 )
2305  value = replace( value, wildcard, "%" );
2306  queries += ({ attr + eq + "'" + db()->quote(value) + "'" });
2307  }
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 );
2315  return ({ });
2316  }
2317 
2318  Sql.sql_result res = db()->big_query( "select distinct ob_id from " +
2319  "i_userlookup where " + where );
2320  mixed row;
2321 
2322  if ( !objectp(res) )
2323  return 0;
2324 
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() });
2330  }
2331  destruct(res);
2332  return result;
2333 }
2334 
2335 
2336 /**
2337  * Searches for groups in the persistence layer.
2338  *
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
2342  * wildcard param.
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
2348  */
2349 array search_groups ( mapping terms, bool any, string|void wildcard ) {
2350  string eq = " = ";
2351  if ( stringp(wildcard) && sizeof(wildcard) > 0 ) eq = " like ";
2352 
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 )
2358  continue;
2359  switch ( attr ) {
2360  case "name" : attr = "k"; break;
2361  // additional strings might be mapped to column names...
2362  default : continue; // don't make invalid queries
2363  }
2364  if ( stringp(wildcard) && sizeof(wildcard) > 0 )
2365  value = replace( value, wildcard, "%" );
2366  queries += ({ attr + eq + "'" + db()->quote(value) + "'" });
2367  }
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 );
2375  return ({ });
2376  }
2377 
2378  Sql.sql_result res = db()->big_query( "select distinct v from i_groups " +
2379  "where " + where );
2380  mixed row;
2381 
2382  if ( !objectp(res) )
2383  return 0;
2384 
2385  array result = ({ });
2386  while ( row = res->fetch_row() ) {
2387  int ob_id;
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() });
2392  }
2393  }
2394  destruct(res);
2395  return result;
2396 }
2397 
2398 
2399 };