~gz/ubuntu/wily/steam/new_rel_udev_rules

« back to all changes in this revision

Viewing changes to server/database.pike

  • Committer: Package Import Robot
  • Author(s): Felix Geyer
  • Date: 2013-10-29 19:51:18 UTC
  • mfrom: (1.1.4) (0.1.4 trusty-proposed)
  • Revision ID: package-import@ubuntu.com-20131029195118-b9bxciz5hwx5z459
Tags: 1:1.0.0.39-2ubuntu1
Add an epoch to the version number as there was an unrelated steam package
in the archive with a higher version number.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* Copyright (C) 2000-2006  Thomas Bopp, Thorsten Hampel, Ludger Merkens
2
 
 *
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.
7
 
 *
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.
12
 
 *
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
16
 
 * 
17
 
 * $Id: database.pike,v 1.22 2006/10/09 19:17:47 astra Exp $
18
 
 */
19
 
 
20
 
constant cvs_version="$Id: database.pike,v 1.22 2006/10/09 19:17:47 astra Exp $";
21
 
 
22
 
inherit "/base/serialize.pike";
23
 
inherit Thread.Mutex : muBusy;
24
 
inherit Thread.Mutex : muLowSave;
25
 
 
26
 
#include <macros.h>
27
 
#include <assert.h>
28
 
#include <attributes.h>
29
 
#include <database.h>
30
 
#include <config.h>
31
 
#include <classes.h>
32
 
#include <access.h>
33
 
#include <roles.h>
34
 
#include <events.h>
35
 
#include <exception.h>
36
 
#include <types.h>
37
 
#include <configure.h>
38
 
 
39
 
#define MODULE_SECURITY _Server->get_module("security")
40
 
#define MODULE_USERS    _Server->get_module("users")
41
 
 
42
 
#define PROXY "/kernel/proxy.pike"
43
 
 
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();
48
 
 
49
 
private static int                     iCacheID;
50
 
private static object                 oSQLCache;
51
 
private static object                oSaveQueue;
52
 
 
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;
59
 
 
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;
67
 
 
68
 
#define DBM_UNDEF   0
69
 
#define DBM_ID      1
70
 
#define DBM_NAME    2
71
 
 
72
 
#define CHECKISO(s, mime, obj) if (search(s, mime)>0) { obj->restore_attr_data(mime, DOC_ENCODING); werror(" %s",mime); }
73
 
 
74
 
private static mapping(string:object) oModuleCache;
75
 
private static Calendar.Calendar cal = Calendar.ISO->set_language("german");
76
 
 
77
 
private static int CONVERSION = 0;
78
 
 
79
 
private static mapping(string:int) mSaveRequests = ([ ]);
80
 
 
81
 
string generate_request_name(int oid, mixed ident, mixed index)
82
 
{
83
 
    return (string)oid+"|"+(stringp(ident)? ident : "0")+
84
 
        "|" + (stringp(index)? index : "0");
85
 
}
86
 
 
87
 
class SqlReadRecord {
88
 
  int  iMaxRecNbr; // record number
89
 
  int         iID; // doc_it
90
 
  int iNextRecNbr; // record number current
91
 
  object   dbfile;
92
 
  function restore; // function if record needs to be restored
93
 
  Thread.Fifo   contFifo;
94
 
  Thread.Mutex fullMutex;
95
 
 
96
 
  int stopRead = 0;
97
 
  int myId = time();
98
 
 
99
 
  int check_timeout() {
100
 
    if ( objectp(dbfile) ) {
101
 
      int t = time() - dbfile->get_last_access();
102
 
      if ( !objectp(contFifo) )
103
 
        return 0;
104
 
      if ( t > 600 )
105
 
        return 0;
106
 
      return 1;
107
 
    }
108
 
    return 0;
109
 
  }
110
 
 
111
 
}
112
 
 
113
 
class SqlHandle {
114
 
    Sql.Sql oHandle;
115
 
    private string db_connect;
116
 
 
117
 
    void keep() {
118
 
        Sql.sql_result res =
119
 
            oHandle->big_query("select ob_class from ob_class "+
120
 
                               "where ob_id = 13");
121
 
        res->fetch_row();
122
 
    }
123
 
    
124
 
    void create(string connect) {
125
 
        db_connect = connect;
126
 
        oHandle = Sql.Sql(db_connect);
127
 
    }
128
 
    
129
 
    int|object big_query(object|string q, mixed ... extraargs) {
130
 
        Sql.sql_result res;
131
 
        mixed err = catch { res=oHandle->big_query(q, @extraargs); };
132
 
        if (err)
133
 
        {
134
 
          FATAL(cal->Second()->format_nice()+
135
 
                " Database Error ("+(string)oHandle->error()+")\n"+
136
 
                master()->describe_backtrace(err));
137
 
          throw(err);
138
 
        }
139
 
        return res;
140
 
    }
141
 
    
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);};
145
 
        if (err)
146
 
        {
147
 
            FATAL(cal->Second()->format_nice()+
148
 
                   " Database Error("+(string)oHandle->error()+")");
149
 
            destruct(oHandle);
150
 
            oHandle = Sql.Sql(db_connect);
151
 
            res = oHandle->query(q, @extraargs);
152
 
            return res;
153
 
        }
154
 
        return res;
155
 
    }
156
 
    
157
 
    function `->(string fname) {
158
 
        switch(fname) {
159
 
          case "query": return query;
160
 
          case "big_query" : return big_query;
161
 
          case "keep": return keep;
162
 
          default : 
163
 
            if ( !objectp(oHandle) )
164
 
              return 0;
165
 
            return oHandle[fname];
166
 
        }
167
 
    }
168
 
    string describe() { return "SqlHandle()"; }
169
 
}
170
 
 
171
 
 
172
 
/**
173
 
 * return a thread-local (valid) db-handle
174
 
 *
175
 
 * @param  none
176
 
 * @return the database handle
177
 
 */
178
 
private static Sql.Sql db()
179
 
{
180
 
    if (this_thread() == tSaveDemon) // give saveDemon its own handle
181
 
        return oDemonDbHandle;
182
 
 
183
 
    // everybody else gets the same shared handle
184
 
    if (!objectp(oDbHandle))
185
 
    {
186
 
        oDbHandle = SqlHandle(STEAM_DB_CONNECT);
187
 
        if (!validate_db_handle(oDbHandle))
188
 
            setup_sTeam_tables(oDbHandle);
189
 
    }
190
 
 
191
 
 
192
 
    //    FATAL(cal->Second()->format_nice()+": database handle requested.");
193
 
    return oDbHandle;
194
 
}
195
 
    
196
 
/**
197
 
 * mimick object id for serialization etc. 
198
 
 * @return  ID_DATABASE from database.h
199
 
 * @see    object.get_object_id
200
 
 * @author Ludger Merkens 
201
 
 */
202
 
final int get_object_id()
203
 
{
204
 
    return ID_DATABASE;
205
 
}
206
 
 
207
 
private static void db_execute(string db_query)
208
 
{
209
 
    db()->big_query(db_query);
210
 
}
211
 
 
212
 
int get_save_size()
213
 
{
214
 
    return oSaveQueue->size();
215
 
}
216
 
 
217
 
/**
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.
221
 
 *
222
 
 * @param  nothing
223
 
 * @return void
224
 
 * @see    save_object
225
 
 * @author Ludger Merkens 
226
 
 */
227
 
void database_save_demon()
228
 
{
229
 
    MESSAGE("DATABASE SAVE DEMON ENABLED");
230
 
    mixed job;
231
 
    object lGuard;
232
 
    object lBusy;
233
 
 
234
 
    while(1)
235
 
    {
236
 
        job = oSaveQueue->read();
237
 
 
238
 
        if (!lBusy) {
239
 
            lBusy = muBusy::lock(); 
240
 
        }
241
 
        mixed cerr = catch {
242
 
            if (arrayp(job))
243
 
            {
244
 
                object proxy;
245
 
                string ident;
246
 
                string index;
247
 
                [proxy, ident, index] = job;
248
 
                low_save_object(proxy, ident, index);
249
 
            }
250
 
            else
251
 
                if (stringp(job)) 
252
 
                    db_execute(job);
253
 
        };
254
 
        if (oSaveQueue->size() == 0) {
255
 
            destruct(lBusy);
256
 
        }
257
 
 
258
 
        if ( cerr ) {
259
 
          FATAL("/**************** database_save_demon *************/\n"+
260
 
                PRINT_BT(cerr));
261
 
        }
262
 
    }
263
 
}
264
 
 
265
 
/**
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.
269
 
 *
270
 
 * @param nothing
271
 
 * @return the key object 
272
 
 * @see Thread.Mutex->lock
273
 
 * @author Ludger Merkens
274
 
 */
275
 
object wait_for_db_lock()
276
 
{
277
 
    return muBusy::lock();
278
 
}
279
 
 
280
 
/**
281
 
 * constructor for database.pike
282
 
 * - starts thread to keep objects persistent
283
 
 * - enables commands in database
284
 
 * @param   none
285
 
 * @return  void
286
 
 * @author Ludger Merkens 
287
 
 */
288
 
void create()
289
 
{
290
 
    // first check for lost data, etc.
291
 
  
292
 
    mProxyLookup = ([ ]);
293
 
    mCurrMaxID = ([ ]);
294
 
    oSaveQueue = Thread.Queue();
295
 
    oTlDbHandle = thread_local();
296
 
    mDbMaps = ([]);
297
 
}
298
 
 
299
 
void init()
300
 
{
301
 
    _Persistence->register( "database", this_object() );
302
 
}
303
 
 
304
 
object enable_modules()
305
 
{
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) });
313
 
    
314
 
    oModules = ((program)"/modules/modules.pike")();
315
 
    oModuleCache = ([ "modules": oModules ]);
316
 
 
317
 
    oDemonDbHandle = SqlHandle(STEAM_DB_CONNECT);
318
 
    int x=validate_db_handle(oDemonDbHandle);
319
 
    if (x==0)
320
 
        setup_sTeam_tables(oDemonDbHandle);
321
 
    else if (x==1)
322
 
        add_new_tables(oDemonDbHandle);
323
 
 
324
 
    check_journaling(oDemonDbHandle);
325
 
    return oModules;
326
 
}
327
 
 
328
 
//#define DBREAD(l, args...) werror("%O"+l+"\n", Thread.this_thread(), args)
329
 
#define DBREAD(l, args ...)
330
 
 
331
 
static void add_record(object record)
332
 
{
333
 
  readQueue->write(record);
334
 
}
335
 
 
336
 
void db_reader()
337
 
{
338
 
  SqlReadRecord   record;
339
 
  Sql.sql_result odbData;
340
 
  array       fetch_line;
341
 
 
342
 
  while ( 1 ) {
343
 
    DBREAD("Waiting for queue...");
344
 
    
345
 
    mixed err = catch {
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 ) {
358
 
            break;
359
 
          }
360
 
          record->contFifo->write(fetch_line[0]);
361
 
          record->iNextRecNbr= (int)fetch_line[1] +1;
362
 
        }
363
 
        DBREAD("next=%d, last=%d", record->iNextRecNbr, record->iMaxRecNbr);
364
 
        if ( record->iNextRecNbr > 0 &&
365
 
             record->iNextRecNbr <= record->iMaxRecNbr) 
366
 
        {
367
 
          DBREAD("Continue reading...\n");
368
 
          object mlock = record->fullMutex->lock();
369
 
          if ( record->contFifo->size() > 100 ) 
370
 
            record->restore = add_record;
371
 
          else
372
 
            readQueue->write(record); // further reading
373
 
          destruct(mlock);
374
 
        }
375
 
        else {
376
 
          DBREAD("Read finished...\n");
377
 
          record->contFifo->write(0);
378
 
        }
379
 
 
380
 
      }
381
 
      else 
382
 
        destruct(record);
383
 
    };
384
 
    if ( err ) {
385
 
      FATAL("Error while reading from database: %O", err);
386
 
      catch {
387
 
        DBREAD("finished read on %d", record->iID);
388
 
        record->contFifo->write(0);
389
 
      };
390
 
    }
391
 
  }
392
 
 
393
 
}
394
 
 
395
 
object read_from_database(int id, int nextID, int maxID, object dbfile) 
396
 
{
397
 
  SqlReadRecord record = SqlReadRecord();
398
 
  record->iID = id;
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);
406
 
  return record;
407
 
}
408
 
 
409
 
 
410
 
int check_save_demon()
411
 
{
412
 
  if ( CALLER != _Server )
413
 
    error( "Unauthorized call to check_save_demon() !" );
414
 
 
415
 
  int status = tSaveDemon->status();
416
 
  //werror(ctime(time())+" Checking Database SAVE DEMON\n");
417
 
  if ( status != 0 ) {
418
 
    FATAL("----- DATABASE SAVE DEMON restarted ! ---");
419
 
    tSaveDemon = thread_create(database_save_demon);
420
 
  }
421
 
  oDbHandle->keep();
422
 
  oDemonDbHandle->keep();
423
 
 
424
 
  if ( objectp(globalQueue) ) {
425
 
    int sz = globalQueue->size();
426
 
    while ( sz > 0 ) {
427
 
      sz--;
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;
435
 
        destruct(record);
436
 
        destruct(dbfile); // make sure everything is gone and freed
437
 
      }
438
 
      else
439
 
        globalQueue->write(record); // keep
440
 
    }
441
 
  }
442
 
    
443
 
  return status;
444
 
}
445
 
 
446
 
void register_transient(array(object) obs)
447
 
{
448
 
    ASSERTINFO(CALLER==MODULE_SECURITY || CALLER== this_object(), 
449
 
               "Invalid CALLER at register_transient()");
450
 
    object obj;
451
 
    foreach (obs, obj) {
452
 
        if (objectp(obj))
453
 
            mProxyLookup[obj->get_object_id()] = obj;
454
 
    }
455
 
}
456
 
 
457
 
 
458
 
/**
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
464
 
 * @see get_variable
465
 
 */
466
 
void set_variable(string name, int value)
467
 
{
468
 
  if(sizeof(db()->query("SELECT var FROM variables WHERE var='"+name+"'"))) 
469
 
  {
470
 
    db()->big_query("UPDATE variables SET value='"+value+
471
 
                    "' WHERE var='"+name+"'" );
472
 
  }
473
 
  else
474
 
  {
475
 
    db()->big_query("INSERT into variables values('"+name+"','"+value+"')");
476
 
  }
477
 
}
478
 
 
479
 
/**
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
484
 
 * @see set_variable
485
 
 */
486
 
int get_variable(string name)
487
 
{
488
 
    object res;
489
 
    res = db()->big_query("select value from variables where "+
490
 
                          "var ='"+name+"'");
491
 
    if (objectp(res) && res->num_rows())
492
 
        return (int) res->fetch_row()[0];
493
 
    
494
 
    return 0;
495
 
}
496
 
    
497
 
/**
498
 
 * reads the currently used max ID from the database and given table
499
 
 * and increments. for performance reasons this ID is cached.
500
 
 * 
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 
506
 
 */
507
 
private static
508
 
int create_new_database_id(string table)
509
 
{
510
 
    if (!mCurrMaxID[table])
511
 
    {
512
 
        string          query;
513
 
        int            result;
514
 
        Sql.sql_result    res;
515
 
 
516
 
        result = get_variable(table);
517
 
        if (!result)
518
 
        {
519
 
            switch(table)
520
 
            {
521
 
              case "doc_data" :
522
 
                  query = sprintf("select max(doc_id) from %s",table);
523
 
                  res = db()->big_query(query);
524
 
                  result = (int) res->fetch_row()[0];
525
 
                  break;
526
 
              case "ob_class":
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);
530
 
            }
531
 
        }
532
 
        mCurrMaxID[table] = result;
533
 
    }
534
 
    mCurrMaxID[table] += 1;
535
 
    //    MESSAGE("Created new database ID"+(int) mCurrMaxID[table]);
536
 
    set_variable(table, mCurrMaxID[table]);
537
 
    return mCurrMaxID[table];
538
 
}
539
 
 
540
 
/**
541
 
 * called in case, a newly created database id is obsolete,
542
 
 * usually called to handle an error occuring in further handling
543
 
 *
544
 
 * @param  int       db - Database to connect to
545
 
 * @param  string table - table choosen
546
 
 * @return void
547
 
 * @see    create_new_databas_id()
548
 
 * @author Ludger Merkens 
549
 
 */
550
 
void free_last_db_id(string table)
551
 
{
552
 
    mCurrMaxID[table]--;
553
 
}
554
 
 
555
 
/**
556
 
 * creates a new persistent sTeam object.
557
 
 *
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 
563
 
 */
564
 
mixed new_object(object obj, string prog_name)
565
 
{
566
 
    int         new_db_id;
567
 
    string sData, sAccess;
568
 
    object p;
569
 
    // check for valid object has to be added
570
 
    // create database ID
571
 
 
572
 
    if ( CALLER != _Persistence )
573
 
      error("Only Persistence Module is allowed to get in here !");
574
 
 
575
 
    int id = obj->get_object_id();
576
 
    if ( id )
577
 
    {
578
 
        ASSERTINFO((p=mProxyLookup[id])->get_object_id() == id,
579
 
                   "Attempt to reregister object in database!");
580
 
        return ({ id, p });
581
 
    }
582
 
 
583
 
    object lock = createMutex->lock(); // make sure creation is save
584
 
    mixed err = catch {
585
 
 
586
 
      if (CONVERSION)
587
 
        new_db_id = create_new_database_id("objects");
588
 
      else
589
 
        new_db_id = create_new_database_id("ob_class");
590
 
      
591
 
      p = new(PROXY, new_db_id, obj );
592
 
      if (!objectp(p->get_object())) // error occured during creation
593
 
      {
594
 
          free_last_db_id("ob_class");
595
 
          destruct(p);
596
 
      }
597
 
      
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)
603
 
                     );
604
 
        mProxyLookup[new_db_id] = p;
605
 
        save_object(p, 0);
606
 
      }
607
 
    };
608
 
    if ( err ) {
609
 
      FATAL("database.new_object: failed to create object\n %O", err);
610
 
    }
611
 
    destruct(lock); 
612
 
    return ({ new_db_id, p});
613
 
}
614
 
 
615
 
/**
616
 
 * permanently destroys an object from the database.
617
 
 * @param  object represented by (proxy) to delete
618
 
 * @return (0|1)
619
 
 * @see    new_object
620
 
 * @author Ludger Merkens 
621
 
 */
622
 
bool delete_object(object p)
623
 
{
624
 
  if ( CALLER!=_Persistence &&
625
 
       (!MODULE_SECURITY->valid_object(CALLER) || CALLER->this() != p->this()))
626
 
  {
627
 
    werror("caller of delete_object() is %O\n", CALLER);
628
 
    THROW("Illegal call to database.delete_object", E_ACCESS);
629
 
  }
630
 
  return do_delete(p);
631
 
}
632
 
 
633
 
private bool do_delete(object p)
634
 
{
635
 
    object proxy;
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);
643
 
 
644
 
    return 1;
645
 
}
646
 
 
647
 
private int|object
648
 
get_old_instance(int iOID, string sClass, object proxy)
649
 
{
650
 
    object o;
651
 
    
652
 
    if (sClass == "-") {
653
 
        return 2;
654
 
    }
655
 
    mixed catched = catch {
656
 
        o = new(sClass, proxy);
657
 
    };
658
 
 
659
 
    if (!objectp(o)) // somehow failed to load file
660
 
    {
661
 
        if ( catched ) {
662
 
            FATAL("/**** while loading:"+ sClass + "****/\n" +
663
 
                  PRINT_BT(catched));
664
 
        }
665
 
        return 1; // class exists but failes to compile
666
 
    }
667
 
    return o;
668
 
}
669
 
 
670
 
/**
671
 
 * load and restore values of an object with given Object ID
672
 
 * @param   int OID
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
677
 
 * @return  the object
678
 
 * @see
679
 
 * @author Ludger Merkens 
680
 
 */
681
 
int|object load_object(object proxy, int|object iOID)
682
 
{
683
 
    string      sClass;
684
 
    string      sIdent;
685
 
    string     sAttrib;
686
 
    string       sData;
687
 
    int              i;
688
 
    array(string) inds;
689
 
    object o;
690
 
    
691
 
    if ( CALLER != _Persistence )
692
 
      error("Unable to load objects directly !");
693
 
        
694
 
    mixed catched;
695
 
    
696
 
    if (CONVERSION)
697
 
    {
698
 
        Sql.sql_result res = db()->big_query(
699
 
            sprintf("select ob_class, ob_data from objects where ob_id = %d",
700
 
                    iOID) );
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 "+
706
 
                             "Object("+iOID+")"+
707
 
                             (arrayp(line) ? sizeof(line) : "- not found" ),
708
 
                             backtrace()})));
709
 
            return 0;
710
 
        }
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); }
715
 
            else return 0;
716
 
        }
717
 
        else
718
 
            o = iOID;
719
 
        
720
 
        if ( objectp(o) && functionp(o->get_object_id) )
721
 
          compatibility_load_object(iOID, o, sData);
722
 
 
723
 
        return o;
724
 
    }
725
 
 
726
 
    Sql.sql_result res = db()->big_query(
727
 
        sprintf("select ob_class from ob_class where ob_id = %d", iOID)
728
 
        );
729
 
    mixed line = res->fetch_row();
730
 
    if (!arrayp(line))
731
 
    {
732
 
        FATAL("Failed to load object (id %d) - deleted?\n", iOID);
733
 
        return 0;
734
 
    }
735
 
    ASSERTINFO(arrayp(line), "Failed to load object - database corrupt");
736
 
 
737
 
    if (objectp(iOID))
738
 
        o = iOID;
739
 
    else
740
 
    {
741
 
        catched = catch {
742
 
          sClass = line[0];
743
 
          int pos;
744
 
          if ( (pos = search(sClass, "DB:#")) >= 0 ) {
745
 
            sClass = "/"+sClass[pos..];
746
 
          }
747
 
          o = new(sClass, proxy);
748
 
        };
749
 
 
750
 
        if (!objectp(o)) // somehow failed to load file
751
 
        {
752
 
            if ( catched ) {
753
 
                _Server->add_error(time(), catched);
754
 
                string pikeClass = sClass;
755
 
                sscanf(pikeClass, "%s.pike", pikeClass);
756
 
 
757
 
                if (!master()->master_file_stat(pikeClass+".pike"))
758
 
                {
759
 
                    FATAL(
760
 
                       "You may have stale objects in the database of class:"+
761
 
                       sClass);
762
 
                    return 2;
763
 
                }
764
 
                FATAL("Error loading object %d (%s)\n%s", iOID, sClass, 
765
 
                      master()->describe_backtrace(catched));
766
 
            }
767
 
            return 1; // class exists but failes to compile
768
 
        }
769
 
        
770
 
        proxy->set_steam_obj(o); // o is the real thing - no proxy!
771
 
    }
772
 
    mapping mData;
773
 
 
774
 
    res = db()->big_query(
775
 
        sprintf("select ob_ident, ob_attr, ob_data from ob_data where "+
776
 
                "ob_id = %d", iOID));
777
 
 
778
 
    if ( !objectp(o) ) 
779
 
        FATAL("Warning: Object not set %O", iOID);
780
 
 
781
 
    mapping mStorage = get_storage_handlers(o);
782
 
    if ( !mappingp(mStorage) || equal(mStorage, ([ ])) ) {
783
 
        proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
784
 
        return 3;
785
 
    }
786
 
    mapping mIndexedStorage = ([]);
787
 
 
788
 
    foreach(indices(mStorage), string _ident) // precreate indexed idents
789
 
        if (mStorage[_ident][2])
790
 
            mIndexedStorage[_ident]=([]);
791
 
    
792
 
    int mem = 0;
793
 
    while (line = res->fetch_row())
794
 
    {
795
 
        [sIdent, sAttrib, sData] = line;
796
 
        mem += strlen(sData);
797
 
        catched = catch {
798
 
            mData = unserialize(sData); // second arg is "this_object()"
799
 
        };
800
 
        if ( catched ) {
801
 
            FATAL("While loading ("+iOID+","+sClass+"):\n"+ catched[0] +"\n"+
802
 
                  master()->print_backtrace(catched));
803
 
            proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
804
 
            return 0;
805
 
        }
806
 
        if ( sAttrib != "")
807
 
        {
808
 
          if ( !mIndexedStorage[sIdent] ) {
809
 
            FATAL("Missing Storage %s in %d, %s", sIdent, iOID, sClass);
810
 
            proxy->set_status(PSTAT_FAIL_UNSERIALIZE);
811
 
            return 0;
812
 
          }
813
 
          mIndexedStorage[sIdent][sAttrib]=mData;
814
 
        }
815
 
        else if ( !mStorage[sIdent] ) {
816
 
          FATAL("No storage handler %s defined in %d (%s).\ndata=%O",
817
 
                sIdent, iOID, sClass, mData);
818
 
        }
819
 
        else
820
 
        {
821
 
          catched = catch {
822
 
            if ( proxy->is_loaded() ) {
823
 
              error(sprintf("Fatal error: already loaded %O\n", proxy));
824
 
            }
825
 
            mStorage[sIdent][1](mData); // actual function call
826
 
          };
827
 
          if ( catched ) {
828
 
            FATAL("Error while loading (%d, %s)\n"+
829
 
                  "Error while calling storage handler %s: %O\n"+
830
 
                  "Full Storage:%O\nData: %O", 
831
 
                  iOID, sClass,
832
 
                  sIdent, catched, 
833
 
                  mStorage, mData);
834
 
          }
835
 
        }
836
 
    } 
837
 
 
838
 
    foreach(indices(mIndexedStorage), string _ident)
839
 
        mStorage[_ident][1](mIndexedStorage[_ident]); // prepared call
840
 
 
841
 
    mixed err = catch { 
842
 
      o->this()->set_status(PSTAT_SAVE_OK);
843
 
      o->loaded(); 
844
 
    };
845
 
    return o;
846
 
}
847
 
 
848
 
static mapping get_storage_handlers(object o)
849
 
{
850
 
    if ( !objectp(o) )
851
 
        FATAL("Getting storage handlers for %O", o);
852
 
    
853
 
    mapping storage = _Persistence->get_storage_handlers(o);
854
 
    return storage;
855
 
}
856
 
 
857
 
mixed call_storage_handler(function f, mixed ... params)
858
 
{
859
 
  if ( CALLER != _Persistence )
860
 
    error("Database:: call_storage_handler(): Unauthorized call !");
861
 
  mixed res = f(@params);
862
 
  return res;
863
 
}
864
 
 
865
 
mapping convert_attribute_mapping(mapping old_fashion)
866
 
{
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"
900
 
    ]);
901
 
 
902
 
    foreach(indices(old_fashion), mixed Attr)
903
 
    {
904
 
        if (string newAttr=m_conversion[Attr])
905
 
        {
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);
912
 
            }
913
 
            else {
914
 
                old_fashion[newAttr]=old_fashion[Attr];
915
 
                m_delete(old_fashion, Attr);
916
 
            }
917
 
        } else
918
 
        {
919
 
            if (intp(Attr))
920
 
            {
921
 
                FATAL("FAILED conversion - incomplete conversion "+
922
 
                      "table [%O]", Attr);
923
 
                THROW("CONVERSION FAILED - TERMINATING", E_ERROR);
924
 
            }
925
 
            else if (!stringp(Attr))
926
 
            {
927
 
                FATAL("Warning - strange Attribute name found "+
928
 
                      "[%O]\n", Attr);
929
 
                m_delete(old_fashion, Attr);
930
 
            }
931
 
        }
932
 
    }
933
 
    return old_fashion;
934
 
}
935
 
    
936
 
/**
937
 
 * load and restore values of an object with given Object ID
938
 
 * @param   int OID
939
 
 * @return  0, object deleted
940
 
 * @return  1, object failed to compile
941
 
 * @return  2, objects class deleted thus instance deleted
942
 
 * @return  the object
943
 
 * @see
944
 
 * @author Ludger Merkens 
945
 
 */
946
 
private static int|object
947
 
compatibility_load_object(int iOID, object o, string sData)
948
 
{
949
 
    int              i;
950
 
    array(string) inds;
951
 
    mixed catched;
952
 
    mapping mData;
953
 
    
954
 
    catched = catch {
955
 
        mData = unserialize(sData); // second arg is "this_object()"
956
 
    };
957
 
    if ( catched ) {
958
 
        FATAL("Deserialisation failed (%d,%O)\n%s",
959
 
              iOID, o, master()->describe_backtrace(catched));
960
 
        return 0;
961
 
    }
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")
968
 
            {
969
 
 
970
 
                if (mData[line])
971
 
                {
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]);
982
 
 
983
 
                    if (mData["restore_content_data"]) // it's a document
984
 
                    {
985
 
                        string doctype = o->query_attribute(DOC_MIME_TYPE);
986
 
                        if (doctype && strlen(doctype) &&
987
 
                            search(doctype, "text")>-1)
988
 
                        {
989
 
                            //werror(" MIME = %s", doctype);
990
 
                            string sContent = o->get_content();
991
 
                            if (sContent && strlen(sContent))
992
 
                            {
993
 
                                CHECKISO(sContent, "iso-8859-1", o);
994
 
                                CHECKISO(sContent, "utf-8", o);
995
 
                            }
996
 
                        }
997
 
                    }
998
 
                }
999
 
            }
1000
 
            else if (line=="restore_icons" && functionp(o->restore_icons) )
1001
 
            {
1002
 
                foreach(indices(mData[line]["icons"]), mixed icondx)
1003
 
                    o->restore_icons(mData[line]["icons"][icondx],
1004
 
                                     (stringp(icondx) ? "\""+icondx :
1005
 
                                      "#"+(string)icondx));
1006
 
            }
1007
 
            else if (line=="restore_content_data")
1008
 
            {
1009
 
                if (mData[line])
1010
 
                    o->restore_content_data(mData[line]);
1011
 
            }
1012
 
            else if (line=="restore_annotations")
1013
 
            {
1014
 
                o->restore_annotations(mData[line]);
1015
 
                if (mData[line]["Annotates"])
1016
 
                {
1017
 
                    string sContent = o->get_content();
1018
 
                    if (!stringp(sContent))
1019
 
                      FATAL("\n!!!Missing Content in Annotation %d\n",
1020
 
                            o->get_object_id());
1021
 
                    else {
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());
1029
 
                    }
1030
 
                }
1031
 
            }
1032
 
            else
1033
 
            {
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));
1039
 
                }
1040
 
            }
1041
 
        }
1042
 
    } 
1043
 
    //werror("Loaded "+ o->get_object_id() + " - " + o->get_identifier()+"\n");
1044
 
    mixed err = catch { o->loaded(); };
1045
 
    return o;
1046
 
}
1047
 
 
1048
 
/**
1049
 
 * find an object from the global object cache or retreive it from the
1050
 
 * database.
1051
 
 *
1052
 
 * @param  int - iOID ( object ID from object to find ) 
1053
 
 * @return object (proxy associated with object)
1054
 
 * @see    load_object
1055
 
 * @author Ludger Merkens 
1056
 
 */
1057
 
final object find_object(int|string iOID)
1058
 
{
1059
 
    object p;
1060
 
 
1061
 
    if ( stringp(iOID) ) 
1062
 
        return _Server->get_module("filepath:tree")->path_to_object(iOID);
1063
 
 
1064
 
    if ( !intp(iOID) )
1065
 
        THROW("Wrong argument to find_object() - expected integer!",E_ERROR);
1066
 
 
1067
 
    if ( iOID == 0 ) return 0;
1068
 
    if ( iOID == 1 ) return this_object();
1069
 
    
1070
 
    if ( objectp(p = mProxyLookup[iOID]) )
1071
 
        return p;
1072
 
 
1073
 
    Sql.sql_result res;
1074
 
    if (CONVERSION)
1075
 
        res = db()->big_query(sprintf("select ob_class, ob_data from "+
1076
 
                                  "objects where ob_id = %d", iOID));
1077
 
    else
1078
 
        res = db()->big_query(sprintf("select ob_class from ob_class "+
1079
 
                                  "where ob_id = %d", iOID));
1080
 
    
1081
 
    if (!objectp(res) || res->num_rows()==0)
1082
 
    {
1083
 
        return 0;
1084
 
    }
1085
 
 
1086
 
    // cache the query for a following load_object.
1087
 
    if (CONVERSION)
1088
 
    {
1089
 
        iCacheID = iOID;
1090
 
        if (objectp(oSQLCache))
1091
 
            destruct(oSQLCache);
1092
 
        oSQLCache = res;
1093
 
    }
1094
 
    
1095
 
    // create an empty proxy to avoid recursive loading of objects
1096
 
    p = new(PROXY, iOID);
1097
 
    mProxyLookup[iOID] = p;
1098
 
    return p;
1099
 
}
1100
 
 
1101
 
/**
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.
1104
 
 *  
1105
 
 * @author Thomas Bopp (astra@upb.de) 
1106
 
 * @see save_object
1107
 
 */
1108
 
void require_save(object proxy, void|string ident, void|string index)
1109
 
{
1110
 
    if (CONVERSION) {
1111
 
      return;
1112
 
    }
1113
 
    if (proxy && proxy->status()>=PSTAT_SAVE_OK)
1114
 
    {
1115
 
        string request =
1116
 
            generate_request_name( proxy->get_object_id(), ident, index);
1117
 
        mSaveRequests[request]++;
1118
 
        save_object(proxy, ident, index);
1119
 
    }
1120
 
}
1121
 
 
1122
 
 
1123
 
/**
1124
 
 * callback-function called to indicate that an object has been modified
1125
 
 * in memory and needs to be saved to the database.
1126
 
 *
1127
 
 * @param  object p - (proxy) object to be saved
1128
 
 * @return void
1129
 
 * @see    load_object
1130
 
 * @see    find_object
1131
 
 * @author Ludger Merkens 
1132
 
 */
1133
 
static void
1134
 
save_object(object proxy, void|string ident, void|string index)
1135
 
{
1136
 
    if ( !objectp(proxy) )
1137
 
        return;
1138
 
 
1139
 
    if (proxy->status() == PSTAT_SAVE_OK)
1140
 
        proxy->set_status(PSTAT_SAVE_PENDING);
1141
 
 
1142
 
    oSaveQueue->write(({proxy, ident, index}));
1143
 
}
1144
 
 
1145
 
/**
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>
1151
 
 */
1152
 
private static string
1153
 
quote_data(string data, object o, string ident, function quoter, int|void utf8)
1154
 
{
1155
 
    if (utf8)
1156
 
        data = serialize(data, "utf-8");
1157
 
    else
1158
 
        data = serialize(data);
1159
 
    
1160
 
    if (strlen(data)> 16777215)
1161
 
        FATAL("!!! FATAL - data truncated inserting %d bytes for %s block %s",
1162
 
              strlen(data),
1163
 
              (objectp(o) ? "broken" : o->get_identifier()),
1164
 
              ident);
1165
 
    return quoter(data);
1166
 
}
1167
 
 
1168
 
/**
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
1177
 
 */
1178
 
private static string
1179
 
prepare_save_statement(object o, mapping storage,
1180
 
                       string|void ident, string|void index)
1181
 
{
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);
1188
 
    string data;
1189
 
    string sClass = master()->describe_program(object_program(o->get_object()));
1190
 
    function db_quote_data = db()->quote;
1191
 
    foreach(idents, string _ident)
1192
 
    {
1193
 
        if (!arrayp(storage[_ident]))
1194
 
        {
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);
1198
 
        }
1199
 
        else if (storage[_ident][2]) // an indexed data-storage
1200
 
        {
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))
1204
 
            {
1205
 
                mapping mData = storage[_ident][0](); // retrieve all
1206
 
                foreach(indices(mData), mixed _index)
1207
 
                {
1208
 
                    if (!stringp(_index))
1209
 
                        continue;
1210
 
                    data = quote_data(mData[_index], o, _ident,
1211
 
                                      db_quote_data, CONVERSION);
1212
 
                    statements +=
1213
 
                        ({ sprintf("(\"%d\",\"%s\",\"%s\",\"%s\")",
1214
 
                                   oid, _ident, db_quote_data(_index),
1215
 
                                   data )
1216
 
                        });
1217
 
                }
1218
 
            }
1219
 
            else
1220
 
            {
1221
 
                if (_ident != "user" && index!="UserPassword")
1222
 
                    data = quote_data(storage[_ident][0](index), o, _ident,
1223
 
                                      db_quote_data, CONVERSION);
1224
 
                else
1225
 
                    data = quote_data(storage[_ident][0](index), o, _ident,
1226
 
                                      db_quote_data); // never convert user pw
1227
 
                                                      // to utf8
1228
 
                statements += ({
1229
 
                    sprintf("(\"%d\",\"%s\",\"%s\",\"%s\")",
1230
 
                            oid, _ident, db_quote_data(index), data)
1231
 
                });
1232
 
            }
1233
 
            
1234
 
        }
1235
 
        else // the usual unindexed data-storage
1236
 
        {
1237
 
            data = quote_data(storage[_ident][0](), o, "all", db_quote_data,
1238
 
                              CONVERSION);
1239
 
            statements += ({
1240
 
                sprintf("(\"%d\",\"%s\",\"%s\",\"%s\")",
1241
 
                        oid, _ident, "", data)
1242
 
            });
1243
 
        }
1244
 
    }
1245
 
    return statements*",";
1246
 
}
1247
 
 
1248
 
 
1249
 
/**
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
1254
 
 */
1255
 
private static string
1256
 
prepare_clear_statement(object o, mapping storage,
1257
 
                        string|void ident, string|void index, string|void tb)
1258
 
{
1259
 
    if ( !stringp(tb) )
1260
 
      tb = "ob_data";
1261
 
 
1262
 
    if (ident=="0" || index=="0")
1263
 
      FATAL("strange call to prepare_clear_statement \n%s\n",
1264
 
            describe_backtrace(backtrace()));
1265
 
    
1266
 
    if (!storage[ident]) ident =0; // better save then sorry - wrong ident
1267
 
                                   // invoces a full save.
1268
 
    if (ident && index)
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);
1272
 
    else if (ident)
1273
 
        return sprintf("delete from %s where ob_id='%d' and "+
1274
 
                       "ob_ident='%s'", tb, o->get_object_id(), ident);
1275
 
    else
1276
 
        return sprintf("delete from %s where ob_id='%d'",
1277
 
                       tb, o->get_object_id());
1278
 
        
1279
 
}
1280
 
 
1281
 
/**
1282
 
 * low level database function to store a given (proxy) object into the
1283
 
 * database immediately.
1284
 
 *
1285
 
 * @param  object proxy - the object to be saved
1286
 
 * @return void
1287
 
 * @see    save_object
1288
 
 * @author Ludger Merkens 
1289
 
 */
1290
 
private static void
1291
 
low_save_object(object p, string|void ident, string|void index)
1292
 
{
1293
 
    string sStatement;
1294
 
#if 1
1295
 
    LOG_DB("low_save_object:"+p->get_object_id()+"("+PSTAT(p->status())+") "+
1296
 
                (p->status()>0 ? p->get_identifier() : ""));
1297
 
#endif
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
1302
 
 
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");
1308
 
 
1309
 
    if (p->status() < PSTAT_SAVE_OK)
1310
 
    {
1311
 
        FATAL("DBSAVEDEMON ->broken instance not saved(%d, %s, status=%s)",
1312
 
              p->get_object_id(),
1313
 
              master()->describe_object(p->get_object()),
1314
 
              PSTAT(p->status()));
1315
 
        return;
1316
 
    }
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");
1320
 
        return;
1321
 
    }
1322
 
 
1323
 
    Thread.MutexKey low=muLowSave::lock(1);    
1324
 
 
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);
1329
 
 
1330
 
    if (p->status()<PSTAT_SAVE_OK)
1331
 
        THROW("Invalid proxy status for object:"+
1332
 
              p->get_object_id()+"("+p->status()+")", E_MEMORY);
1333
 
 
1334
 
    mapping storage = get_storage_handlers(p);
1335
 
    ASSERTINFO(mappingp(storage),
1336
 
               "Corrupted data_storage in "+master()->stupid_describe(p));
1337
 
 
1338
 
    
1339
 
    p->set_status(PSTAT_SAVE_OK);
1340
 
    destruct(low);                    // status set, so unlock
1341
 
    
1342
 
    if (master()->describe_program(object_program(p->get_object()))=="-")
1343
 
        return; // temporary objects like executer
1344
 
    
1345
 
    sStatement =
1346
 
        prepare_save_statement(p, storage, ident, index );
1347
 
 
1348
 
    ASSERTINFO(strlen(sStatement)!=0,
1349
 
               sprintf("trying to insert empty data into object %d class %s",
1350
 
                    p->get_object_id(),
1351
 
                    master()->describe_program(object_program(p->get_object()))));
1352
 
    
1353
 
    mixed err;
1354
 
    string s;
1355
 
 
1356
 
 
1357
 
    err = catch {
1358
 
      // First journaling 
1359
 
      catch(db()->big_query("insert into ob_journaling values " + sStatement));
1360
 
      
1361
 
      // remove from ob_dat
1362
 
      mixed delete_err = catch {
1363
 
        s = prepare_clear_statement(p, storage, ident, index, "ob_data");
1364
 
        db()->big_query(s);
1365
 
      };
1366
 
      if ( delete_err ) 
1367
 
        FATAL("!!! FATAL in save-demon, deletion statement %s failed\n%O: %O\n",
1368
 
              s, delete_err[0], delete_err[1]);
1369
 
 
1370
 
      // add new value
1371
 
      db()->big_query("insert into ob_data values " + sStatement);
1372
 
 
1373
 
      s = prepare_clear_statement(p, storage, ident, index, "ob_journaling");
1374
 
      catch(db()->big_query(s));
1375
 
    };
1376
 
    if ( err )
1377
 
    {
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) ) {
1382
 
        catch {
1383
 
          lostData->write(sprintf("%d: %s\n\n", p->get_object_id(), sStatement));
1384
 
        };
1385
 
      }
1386
 
    }
1387
 
}
1388
 
 
1389
 
/**
1390
 
 * Change the class of an object in the database. Drop the object to
1391
 
 * get an object with the modified class.
1392
 
 *  
1393
 
 * @param object doc - change class for this document
1394
 
 * @param string classfile - the new class
1395
 
 */
1396
 
int change_object_class(object doc, string classfile)
1397
 
{
1398
 
    if ( CALLER != _Persistence )
1399
 
        steam_error("Illegal call to database.change_class() !");
1400
 
    
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();
1404
 
 
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+"')");
1408
 
    return 1; 
1409
 
}
1410
 
 
1411
 
/**
1412
 
 * register an module with its name
1413
 
 * e.g. register_module("users", new("/modules/users"));
1414
 
 *
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 
1421
 
 */
1422
 
int register_module(string oname, object module, void|string source)
1423
 
{
1424
 
    object realObject;
1425
 
    string version = "";
1426
 
 
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);
1431
 
 
1432
 
    object mod;
1433
 
    int imod = get_variable("#" + oname);
1434
 
    
1435
 
    if ( imod > 0 )
1436
 
    {
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);
1444
 
            if (!realObject)
1445
 
            {
1446
 
                FATAL("Failed to compile new instance of module '" + oname
1447
 
                      + "'- throwing");
1448
 
                THROW("Failed to load module\n"+e->get()+"\n"+
1449
 
                      e->get_warnings(), backtrace());
1450
 
            }
1451
 
            //FATAL(sprintf("module found is %O", realObject));
1452
 
        }
1453
 
    }
1454
 
    if ( objectp(realObject) ) {
1455
 
        werror("Found previously registered version of module '" + oname
1456
 
               + "' !\n");
1457
 
        if ( objectp(module) && module->get_object() != realObject )
1458
 
            THROW("Trying to register previously registered module '"
1459
 
                  + oname +"'.", E_ERROR);
1460
 
        
1461
 
        version = realObject->get_version();
1462
 
        
1463
 
        mixed erg = master()->upgrade(object_program(realObject));
1464
 
        //FATAL(sprintf("upgrade resulted in %O", erg));
1465
 
        if (!intp(erg) ||  erg<0)
1466
 
        {
1467
 
            if (stringp(erg))
1468
 
                THROW(erg, backtrace());
1469
 
            else
1470
 
            {
1471
 
                FATAL("New version of "+oname+" doesn't implement old "+
1472
 
                    "versions interface");
1473
 
                master()->upgrade(object_program(mod->get_object()),1);
1474
 
            }
1475
 
        }
1476
 
        //FATAL("Upgrading done !");
1477
 
        module = mod;
1478
 
    }
1479
 
    else if ( !objectp(module) ) 
1480
 
    {
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);
1486
 
        if (!module)
1487
 
        {
1488
 
            FATAL("Failed to compile new instance of module '" + oname
1489
 
                  + "' - throwing");
1490
 
            THROW("Failed to load module\n"+e->get()+"\n"+
1491
 
                  e->get_warnings(), backtrace());
1492
 
        }
1493
 
    }
1494
 
    
1495
 
    MESSAGE(sprintf("Installing module %s", oname));
1496
 
    if ( !stringp(source) )
1497
 
        source = "";
1498
 
    
1499
 
    if ( module->get_object_class() & CLASS_PACKAGE ) {
1500
 
        
1501
 
        if ( module->install(source, version) == 0 )
1502
 
            error("Failed to install module !");
1503
 
    }    
1504
 
    _Server->register_module(module);
1505
 
 
1506
 
    _Server->run_global_event(EVENT_REGISTER_MODULE, PHASE_NOTIFY, 
1507
 
                              this_object(), ({ module }) );
1508
 
    LOG_DB("event is run");
1509
 
    if ( objectp(module) ) 
1510
 
    {
1511
 
        set_variable("#"+oname, module->get_object_id());
1512
 
        _Server->register_module(module);
1513
 
        return module->get_object_id();
1514
 
    }
1515
 
    return 0;
1516
 
}
1517
 
 
1518
 
/**
1519
 
 * Check if a database handle is connected to a properly setup database.
1520
 
 *
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 
1526
 
 */
1527
 
int validate_db_handle(SqlHandle handle)
1528
 
{
1529
 
    multiset tables = (<>);
1530
 
    array(string) aTables = handle->list_tables();
1531
 
 
1532
 
    foreach(aTables, string table)
1533
 
        tables[table] = true;
1534
 
    if (tables["objects"] && tables["doc_data"])
1535
 
        return 1;
1536
 
    if (tables["ob_class"] && tables["ob_data"] && tables["doc_data"])
1537
 
        return 2;
1538
 
}
1539
 
 
1540
 
bool check_convert()
1541
 
{
1542
 
    multiset tables = (<>);
1543
 
    Sql.Sql handle = SqlHandle(STEAM_DB_CONNECT);
1544
 
    array(string) aTables = handle->list_tables();
1545
 
 
1546
 
    foreach(aTables, string table)
1547
 
        tables[table] = true;
1548
 
 
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)" );
1562
 
            }
1563
 
            break;
1564
 
          }
1565
 
          row = result->fetch_row();
1566
 
        }
1567
 
      }
1568
 
    }
1569
 
 
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 );
1593
 
            }
1594
 
            break;
1595
 
          }
1596
 
        }
1597
 
      }
1598
 
    }
1599
 
 
1600
 
    return tables["objects"];
1601
 
}
1602
 
 
1603
 
 
1604
 
void set_converting(int i)
1605
 
{
1606
 
    CONVERSION =i;
1607
 
}
1608
 
 
1609
 
int get_converting()
1610
 
{
1611
 
    return CONVERSION;
1612
 
}
1613
 
 
1614
 
void convert_tables()
1615
 
{
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");
1625
 
    exit(1);
1626
 
}
1627
 
 
1628
 
/**
1629
 
 * Convert an old database of sTeam Version below 1.5 to new format
1630
 
 *
1631
 
 *
1632
 
 */
1633
 
private static void
1634
 
convert_old_tables(SqlHandle handle)
1635
 
{
1636
 
    string sOid, sOclass, sOdata;
1637
 
    mixed erg;
1638
 
    mapping mData;
1639
 
    object o,p;
1640
 
    mapping storage;
1641
 
    string sStatement;
1642
 
    
1643
 
 // uncatched ... we want to die in that case.
1644
 
 
1645
 
    convert_index_tables(handle, "i_groups");
1646
 
    convert_index_tables(handle, "i_users");
1647
 
 
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");
1651
 
 
1652
 
    while (erg = instream->fetch_row())
1653
 
    {
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;
1659
 
        
1660
 
        werror("CONV %s(%s):", sOid, sOclass);
1661
 
        p = find_object((int) sOid);
1662
 
        werror("id-");
1663
 
        mixed mIdent = p->get_identifier();
1664
 
        werror("(%O)", mIdent);
1665
 
        if (p->status()==PSTAT_SAVE_OK)
1666
 
        {
1667
 
            o=p->get_object();
1668
 
            convert_keywords(writehandle, o);
1669
 
            sOclass = master()->describe_program(object_program(o));
1670
 
            writehandle->query(
1671
 
                sprintf("insert into ob_class values(%s,'%s')",
1672
 
                        sOid, sOclass));
1673
 
            // compatibility_load_object((int)sOid, o, sOdata);
1674
 
            werror("-prep-");
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);
1680
 
 
1681
 
            writehandle->query("replace into ob_data values "+sStatement);
1682
 
            switch ( sOclass ) {
1683
 
              case "/classes/User" : write(" post conversion ");
1684
 
                  mixed err;
1685
 
                  err = catch {
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)))
1689
 
                      {
1690
 
                          oForwardModule->add_forward(p, "/"+p->get_identifier());
1691
 
                          oForwardModule->add_forward(p, p->query_attribute(USER_EMAIL));
1692
 
                      }
1693
 
                  };
1694
 
                  if (err)
1695
 
                      write("failed : %s\n",master()->describe_backtrace(err));
1696
 
                  else
1697
 
                      werror("ok\n");
1698
 
                  break;
1699
 
              default : werror("ok\n");
1700
 
            }
1701
 
        }
1702
 
        else
1703
 
            werror("Failed to load %s (%O)\n", sOid, p);
1704
 
    }
1705
 
}
1706
 
 
1707
 
private static void convert_keywords(Sql.Sql handle, object o)
1708
 
{
1709
 
    if ( !functionp(o->get_object_id) )
1710
 
        return;
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())
1715
 
        if (key[0] != "0")
1716
 
            o->restore_keywords(1, string_to_utf8(key[0]));
1717
 
}
1718
 
 
1719
 
private static void convert_index_tables(Sql.Sql handle, string index)
1720
 
{
1721
 
    mixed res = handle->query("select k,v from "+ index);
1722
 
    handle->query("delete from "+ index);
1723
 
    foreach(res, mapping line)
1724
 
    {
1725
 
        handle->query("insert into "+index+" values(%s,%s)",
1726
 
                      string_to_utf8(line["k"]), line["v"]);
1727
 
    }
1728
 
}
1729
 
 
1730
 
static void check_journaling(Sql.Sql handle) 
1731
 
{
1732
 
  mixed row, res, err;
1733
 
 
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);
1741
 
      int oid;
1742
 
      string ident, attr, val, rest;
1743
 
      if ( sscanf(ll, "%d: %s", oid, ll) != 2 )
1744
 
        continue;
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);
1747
 
        err = catch {
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");
1753
 
          }
1754
 
          else {
1755
 
            handle->query(sprintf("insert into ob_data values (%d,%s,%s,%s)",
1756
 
                                  oid, ident, attr, val));
1757
 
          }
1758
 
        };
1759
 
        if ( err ) {
1760
 
          FATAL("Failed to restore data: %O", err);
1761
 
        }
1762
 
        if ( stringp(rest) || strlen(rest) > 1 )
1763
 
          ll = rest[1..];
1764
 
        else
1765
 
          ll = "";
1766
 
      }
1767
 
    }
1768
 
  }
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)"+
1776
 
                ")");
1777
 
  res = 
1778
 
    handle->big_query("select ob_id, ob_ident, ob_attr, ob_data from ob_journaling");
1779
 
 
1780
 
  if ( !objectp(res) )
1781
 
    return;
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]);
1788
 
      err = catch {
1789
 
        handle->query(c);
1790
 
        handle->query(q);
1791
 
      };
1792
 
      if ( err != 0 ) {
1793
 
        error("Failed to restore journaling table: " + err[0] +
1794
 
            sprintf("\n%O\n%O", err[1], row));
1795
 
      }
1796
 
    }
1797
 
  }
1798
 
  handle->query("delete from ob_journaling where 1");
1799
 
}
1800
 
 
1801
 
static void add_new_tables(Sql.Sql handle) {
1802
 
    MESSAGE("adding new format tables\n");
1803
 
    MESSAGE("adding ob_class ");
1804
 
    catch {
1805
 
        handle->query("drop table if exists ob_class");
1806
 
        handle->query("drop table if exists ob_data");
1807
 
    };
1808
 
    handle->query("create table if not exists ob_class ("+
1809
 
                  " ob_id int primary key, "+
1810
 
                  " ob_class char(128) "+
1811
 
                  ")");
1812
 
 
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))"+
1822
 
                  ")");
1823
 
 
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)"+
1831
 
                  ")");
1832
 
}
1833
 
 
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 ("+
1837
 
                      " rec_data text, "+
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
1844
 
    
1845
 
    
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) "+
1850
 
                      ")");
1851
 
 
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)"+
1859
 
                      ")");
1860
 
    MESSAGE("creating table \"variables\" ");
1861
 
    handle->big_query("create table if not exists variables ("+
1862
 
                      " var char(100) primary key, "+
1863
 
                      " value int"+
1864
 
                      ")");
1865
 
}
1866
 
 
1867
 
/**
1868
 
 * set up the base sTeam tables to create an empty database.
1869
 
 *
1870
 
 * @param  none
1871
 
 * @return (1|0)
1872
 
 * @author Ludger Merkens 
1873
 
 */
1874
 
int setup_sTeam_tables(SqlHandle handle)
1875
 
{
1876
 
    /* make sure no old tables exist and delete them properly */
1877
 
    MESSAGE("Checking for old tables.\n");
1878
 
 
1879
 
    if (CONVERSION)
1880
 
    {
1881
 
        THROW("preliminary sTeam Table setup during CONVERSION!", E_ERROR);
1882
 
    }
1883
 
    //    Sql.Sql_result res = handle->big_query("show tables");
1884
 
    array(string) res = handle->list_tables();
1885
 
    if (sizeof(res))
1886
 
    {
1887
 
        foreach(res, string table)
1888
 
        {
1889
 
            MESSAGE(sprintf("dropping (%s)\n",table));
1890
 
            handle->big_query("drop table "+table);
1891
 
        }
1892
 
    }
1893
 
    else
1894
 
        MESSAGE("no old tables found");
1895
 
 
1896
 
    MESSAGE("CREATING NEW BASE TABLES:");
1897
 
    create_tables(handle);
1898
 
    
1899
 
    res = handle->list_tables();
1900
 
    if (!sizeof(res)) {
1901
 
        FATAL("\nFATAL: failed to create base tables");
1902
 
    }
1903
 
    else
1904
 
    {
1905
 
        MESSAGE("\nPOST CHECK retrieves: ");
1906
 
        foreach(res, string table)
1907
 
            MESSAGE(table+" ");
1908
 
    }
1909
 
    return 1;
1910
 
}
1911
 
 
1912
 
/**
1913
 
 * create and return a new instance of db_file
1914
 
 *
1915
 
 * @param  int iContentID - 0|ID of a given Content
1916
 
 * @return the db_file-handle
1917
 
 * @see    db_file
1918
 
 * @see    file/IO
1919
 
 * @author Ludger Merkens 
1920
 
 */
1921
 
object new_db_file_handle(int iContentID, string mode)
1922
 
{
1923
 
    return new("/kernel/db_file.pike", iContentID, mode);
1924
 
}
1925
 
 
1926
 
/**
1927
 
 * Check if a given gile handle is valid, eg inherits db_file.pike
1928
 
 *  
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>) 
1932
 
 */
1933
 
private static bool valid_db_file(object m)
1934
 
{
1935
 
    if (Program.inherits(object_program(m), (program)"/kernel/db_file.pike"))
1936
 
        return true;
1937
 
    return false;
1938
 
}
1939
 
 
1940
 
/**
1941
 
 * connect_db_file, connect a /kernel/db_file instance with the database
1942
 
 * calculate new content id if none given.
1943
 
 *
1944
 
 * @param    id
1945
 
 * @return   function db()
1946
 
 */
1947
 
final mixed connect_db_file(int id)
1948
 
{
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)});
1953
 
}
1954
 
 
1955
 
/**
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 
1962
 
 */
1963
 
private static bool valid_db_mapping(object m)
1964
 
{
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"))
1973
 
        return true;
1974
 
    return false;
1975
 
}
1976
 
 
1977
 
/**
1978
 
 * connect_mapping, connect a /kernel/db_mapping instance with the database
1979
 
 * @param    none
1980
 
 * @return   a pair ({ function db, string tablename })
1981
 
 */
1982
 
final mixed connect_db_mapping()
1983
 
{
1984
 
    if (!(valid_db_mapping(CALLER)))
1985
 
    {
1986
 
        FATAL("illegal access %s from %O\n",CALLINGFUNCTION, CALLER);
1987
 
        THROW("illegal access to database ", E_ACCESS);
1988
 
    }
1989
 
    string sDbTable;
1990
 
    // hack to allow the modules table to be a member of _Database
1991
 
 
1992
 
    sDbTable = CALLER->get_table_name();
1993
 
 
1994
 
 
1995
 
    if (!sDbTable)
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)
2000
 
    {
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)));
2005
 
        return ({ 0,0 });
2006
 
        }*/
2007
 
    return ({ db, sDbTable });
2008
 
}
2009
 
 
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(); }
2015
 
 
2016
 
 
2017
 
 
2018
 
 
2019
 
/**
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
2027
 
 */
2028
 
final array(object) get_objects_by_class(string|program|int mClass)
2029
 
{
2030
 
    Sql.sql_result res;
2031
 
    int i, sz;
2032
 
    object security;
2033
 
    array(object) aObjects;
2034
 
    string sClass;
2035
 
    
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");
2040
 
 
2041
 
    if ( intp(mClass) ) {
2042
 
      mixed factory = _Server->get_factory(mClass);
2043
 
      if ( !objectp(factory) )
2044
 
        return UNDEFINED;
2045
 
      if ( !stringp(CLASS_PATH) || !stringp(factory->get_class_name()) )
2046
 
        return UNDEFINED;
2047
 
      mClass = CLASS_PATH + factory->get_class_name();
2048
 
    }
2049
 
 
2050
 
    if (programp(mClass))
2051
 
        sClass = master()->describe_program(mClass);
2052
 
    else
2053
 
        sClass = mClass;
2054
 
    
2055
 
    res = db()->big_query("select ob_id from ob_class where ob_class='"+
2056
 
                          mClass+"'");
2057
 
 
2058
 
    aObjects = allocate((sz=res->num_rows()));
2059
 
 
2060
 
    for (i=0;i<sz;i++)
2061
 
    {
2062
 
        aObjects[i]=find_object((int)res->fetch_row()[0]);
2063
 
    }
2064
 
    return aObjects;
2065
 
}
2066
 
 
2067
 
 
2068
 
/**
2069
 
 * get_all_objects()
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
2074
 
 */
2075
 
final array(object) get_all_objects()
2076
 
{
2077
 
    Sql.sql_result res;
2078
 
    int i, sz;
2079
 
    object security;
2080
 
    array(object) aObjects;
2081
 
 
2082
 
#if 0
2083
 
    if ( !_Server->is_a_factory(CALLER) )
2084
 
        THROW("Illegal attempt to call database.get_all_objects !", E_ACCESS);
2085
 
 
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");
2090
 
#endif
2091
 
    
2092
 
    res = db()->big_query("select ob_id from ob_class where ob_class !='-'");
2093
 
    aObjects = allocate((sz=res->num_rows()));
2094
 
 
2095
 
    for (i=0;i<sz;i++)
2096
 
    {
2097
 
        aObjects[i]=find_object((int)res->fetch_row()[0]);
2098
 
    }
2099
 
    return aObjects;
2100
 
}
2101
 
 
2102
 
/**
2103
 
 * visit_all_objects
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
2107
 
 * @return nothing
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.
2114
 
 */
2115
 
final void visit_all_objects(function visitor, mixed ... args)
2116
 
{
2117
 
    FATAL("visit_all_objects not yet converted to new database format");
2118
 
    return;
2119
 
    Sql.sql_result res = db()->big_query("select ob_id,ob_class from ob_class");
2120
 
    int i;
2121
 
    int oid;
2122
 
    string oclass;
2123
 
    object p;
2124
 
    FATAL("Number of objects found:"+res->num_rows());
2125
 
    for (i=0;i<res->num_rows();i++)
2126
 
    {
2127
 
        mixed erg =  res->fetch_row();
2128
 
        oid = (int) erg[0];  // wrong casting with 
2129
 
        oclass = erg[1];     // [oid, oclass] = res->fetch_row()
2130
 
        
2131
 
        if (oclass[0]=='/') // some heuristics to avoid nonsene classes
2132
 
        {
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
2136
 
                visitor(p, @args);
2137
 
        }
2138
 
    }
2139
 
}
2140
 
 
2141
 
/**
2142
 
 * Check for a list of objects, if they really exist in the database
2143
 
 *
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
2148
 
 */
2149
 
array(int) get_existing(array(int) ids)
2150
 
{
2151
 
    Sql.sql_result res;
2152
 
    int i, sz;
2153
 
    string query = "select ob_id from ob_class where ob_id in (";
2154
 
    array(int) result;
2155
 
    
2156
 
    if (!ids || !sizeof(ids))
2157
 
        return ({ });
2158
 
    for (i=0,sz=sizeof(ids)-1;i<sz;i++)
2159
 
        query +=ids[i]+",";
2160
 
    query+=ids[i]+")";
2161
 
    res = db()->big_query(query);
2162
 
 
2163
 
    result = allocate((sz=res->num_rows()));
2164
 
    for (i=0;i<sz;i++)
2165
 
        result[i]=(int) res->fetch_row()[0];
2166
 
 
2167
 
    return result;
2168
 
}
2169
 
 
2170
 
/**
2171
 
 * Get a list of the not-existing objects.
2172
 
 *  
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>) 
2176
 
 * @see 
2177
 
 */
2178
 
array(int) get_not_existing(array(int) ids)
2179
 
{
2180
 
    return ids - get_existing(ids);
2181
 
}
2182
 
 
2183
 
object get_environment() { return 0; }
2184
 
object get_acquire() { return 0; }
2185
 
 
2186
 
mapping get_xml_data()
2187
 
{
2188
 
    return ([ "configs":({_Server->get_configs, XML_NORMAL}), ]);
2189
 
}
2190
 
 
2191
 
/**
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
2195
 
 * anymore.
2196
 
 * @param none
2197
 
 * @returns a debug string containing the number of deleted doc_id's
2198
 
 */
2199
 
string clear_lost_content()
2200
 
{
2201
 
    Sql.Sql h = db();
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];
2207
 
 
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())
2214
 
    {
2215
 
        oid = (int)a[0];
2216
 
        if (p=find_object(oid))
2217
 
        {
2218
 
            FATAL("accessing object"+oid);
2219
 
            object try;
2220
 
            catch{try=p->get_object();};
2221
 
            if (objectp(try) &&
2222
 
                Program.inherits(object_program(try),
2223
 
                                 (program)"/base/content"))
2224
 
            {
2225
 
                FATAL("content "+p->get_content_id()+" is in use");
2226
 
                doc_ids  -= ({ p->get_content_id() });
2227
 
            }
2228
 
        }
2229
 
    }
2230
 
 
2231
 
    FATAL("number of doc_ids to be deleted is:"+sizeof(doc_ids));
2232
 
 
2233
 
    foreach (doc_ids, int did)
2234
 
    {
2235
 
        h->big_query("delete from doc_data where doc_id = "+did);
2236
 
        FATAL("deleting doc_id"+did);
2237
 
    }
2238
 
    FATAL("calling optimize");
2239
 
    h->big_query("optimize table doc_data");
2240
 
    return "deleted "+sizeof(doc_ids)+"lost contents";
2241
 
}
2242
 
 
2243
 
object lookup (string identifier)
2244
 
{
2245
 
  return get_module("objects")->lookup(identifier);
2246
 
}
2247
 
 
2248
 
object lookup_user (string identifier)
2249
 
{
2250
 
  return get_module("users")->get_user(identifier);
2251
 
}
2252
 
 
2253
 
object lookup_group (string identifier)
2254
 
{
2255
 
  return get_module("groups")->get_group(identifier);
2256
 
}
2257