fb2395154f3e751a40083c4867eb2b11f8a8b1ea
[Packages/TYPO3.CMS.git] / typo3 / sysext / adodb / adodb / adodb-active-record.inc.php
1 <?php
2 /*
3
4 @version V4.93 10 Oct 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved.
5 Latest version is available at http://adodb.sourceforge.net
6
7 Released under both BSD license and Lesser GPL library license.
8 Whenever there is any discrepancy between the two licenses,
9 the BSD license will take precedence.
10
11 Active Record implementation. Superset of Zend Framework's.
12
13 Version 0.04
14
15 See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord
16 for info on Ruby on Rails Active Record implementation
17 */
18
19 global $_ADODB_ACTIVE_DBS;
20 global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
21
22 // array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
23 $_ADODB_ACTIVE_DBS = array();
24
25
26 class ADODB_Active_DB {
27 var $db; // ADOConnection
28 var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
29 }
30
31 class ADODB_Active_Table {
32 var $name; // table name
33 var $flds; // assoc array of adofieldobjs, indexed by fieldname
34 var $keys; // assoc array of primary keys, indexed by fieldname
35 var $_created; // only used when stored as a cached file
36 }
37
38 // returns index into $_ADODB_ACTIVE_DBS
39 function ADODB_SetDatabaseAdapter(&$db)
40 {
41 global $_ADODB_ACTIVE_DBS;
42
43 foreach($_ADODB_ACTIVE_DBS as $k => $d) {
44 if (PHP_VERSION >= 5) {
45 if ($d->db == $db) return $k;
46 } else {
47 if ($d->db->_connectionID == $db->_connectionID && $db->database == $d->db->database)
48 return $k;
49 }
50 }
51
52 $obj = new ADODB_Active_DB();
53 $obj->db =& $db;
54 $obj->tables = array();
55
56 $_ADODB_ACTIVE_DBS[] = $obj;
57
58 return sizeof($_ADODB_ACTIVE_DBS)-1;
59 }
60
61
62 class ADODB_Active_Record {
63 var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
64 var $_table; // tablename, if set in class definition then use it as table name
65 var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
66 var $_where; // where clause set in Load()
67 var $_saved = false; // indicates whether data is already inserted.
68 var $_lasterr = false; // last error message
69 var $_original = false; // the original values loaded or inserted, refreshed on update
70
71 // should be static
72 function SetDatabaseAdapter(&$db)
73 {
74 return ADODB_SetDatabaseAdapter($db);
75 }
76
77 // php4 constructor
78 function ADODB_Active_Record($table = false, $pkeyarr=false, $db=false)
79 {
80 ADODB_Active_Record::__construct($table,$pkeyarr,$db);
81 }
82
83 // php5 constructor
84 function __construct($table = false, $pkeyarr=false, $db=false)
85 {
86 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
87
88 if ($db == false && is_object($pkeyarr)) {
89 $db = $pkeyarr;
90 $pkeyarr = false;
91 }
92
93 if (!$table) {
94 if (!empty($this->_table)) $table = $this->_table;
95 else $table = $this->_pluralize(get_class($this));
96 }
97 if ($db) {
98 $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db);
99 } else
100 $this->_dbat = sizeof($_ADODB_ACTIVE_DBS)-1;
101
102
103 if ($this->_dbat < 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor');
104
105 $this->_table = $table;
106 $this->_tableat = $table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
107 $this->UpdateActiveTable($pkeyarr);
108 }
109
110 function __wakeup()
111 {
112 $class = get_class($this);
113 new $class;
114 }
115
116 function _pluralize($table)
117 {
118 $ut = strtoupper($table);
119 $len = strlen($table);
120 $lastc = $ut[$len-1];
121 $lastc2 = substr($ut,$len-2);
122 switch ($lastc) {
123 case 'S':
124 return $table.'es';
125 case 'Y':
126 return substr($table,0,$len-1).'ies';
127 case 'X':
128 return $table.'es';
129 case 'H':
130 if ($lastc2 == 'CH' || $lastc2 == 'SH')
131 return $table.'es';
132 default:
133 return $table.'s';
134 }
135 }
136
137 //////////////////////////////////
138
139 // update metadata
140 function UpdateActiveTable($pkeys=false,$forceUpdate=false)
141 {
142 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
143
144 $activedb =& $_ADODB_ACTIVE_DBS[$this->_dbat];
145
146 $table = $this->_table;
147 $tables = $activedb->tables;
148 $tableat = $this->_tableat;
149 if (!$forceUpdate && !empty($tables[$tableat])) {
150 $tobj =& $tables[$tableat];
151 foreach($tobj->flds as $name => $fld)
152 $this->$name = null;
153 return;
154 }
155
156 $db =& $activedb->db;
157 $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache';
158 if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
159 $fp = fopen($fname,'r');
160 @flock($fp, LOCK_SH);
161 $acttab = unserialize(fread($fp,100000));
162 fclose($fp);
163 if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) {
164 // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
165 // ideally, you should cache at least 32 secs
166 $activedb->tables[$table] = $acttab;
167
168 //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
169 return;
170 } else if ($db->debug) {
171 ADOConnection::outp("Refreshing cached active record file: $fname");
172 }
173 }
174 $activetab = new ADODB_Active_Table();
175 $activetab->name = $table;
176
177
178 $cols = $db->MetaColumns($table);
179 if (!$cols) {
180 $this->Error("Invalid table name: $table",'UpdateActiveTable');
181 return false;
182 }
183 $fld = reset($cols);
184 if (!$pkeys) {
185 if (isset($fld->primary_key)) {
186 $pkeys = array();
187 foreach($cols as $name => $fld) {
188 if (!empty($fld->primary_key)) $pkeys[] = $name;
189 }
190 } else
191 $pkeys = $this->GetPrimaryKeys($db, $table);
192 }
193 if (empty($pkeys)) {
194 $this->Error("No primary key found for table $table",'UpdateActiveTable');
195 return false;
196 }
197
198 $attr = array();
199 $keys = array();
200
201 switch($ADODB_ASSOC_CASE) {
202 case 0:
203 foreach($cols as $name => $fldobj) {
204 $name = strtolower($name);
205 $this->$name = null;
206 $attr[$name] = $fldobj;
207 }
208 foreach($pkeys as $k => $name) {
209 $keys[strtolower($name)] = strtolower($name);
210 }
211 break;
212
213 case 1:
214 foreach($cols as $name => $fldobj) {
215 $name = strtoupper($name);
216 $this->$name = null;
217 $attr[$name] = $fldobj;
218 }
219
220 foreach($pkeys as $k => $name) {
221 $keys[strtoupper($name)] = strtoupper($name);
222 }
223 break;
224 default:
225 foreach($cols as $name => $fldobj) {
226 $name = ($fldobj->$name);
227 $this->$name = null;
228 $attr[$name] = $fldobj;
229 }
230 foreach($pkeys as $k => $name) {
231 $keys[$name] = $cols[$name]->name;
232 }
233 break;
234 }
235
236 $activetab->keys = $keys;
237 $activetab->flds = $attr;
238
239 if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
240 $activetab->_created = time();
241 $s = serialize($activetab);
242 if (!function_exists('adodb_write_file')) include(ADODB_DIR.'/adodb-csvlib.inc.php');
243 adodb_write_file($fname,$s);
244 }
245 $activedb->tables[$table] = $activetab;
246 }
247
248 function GetPrimaryKeys(&$db, $table)
249 {
250 return $db->MetaPrimaryKeys($table);
251 }
252
253 // error handler for both PHP4+5.
254 function Error($err,$fn)
255 {
256 global $_ADODB_ACTIVE_DBS;
257
258 $fn = get_class($this).'::'.$fn;
259 $this->_lasterr = $fn.': '.$err;
260
261 if ($this->_dbat < 0) $db = false;
262 else {
263 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
264 $db =& $activedb->db;
265 }
266
267 if (function_exists('adodb_throw')) {
268 if (!$db) adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
269 else adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db);
270 } else
271 if (!$db || $db->debug) ADOConnection::outp($this->_lasterr);
272
273 }
274
275 // return last error message
276 function ErrorMsg()
277 {
278 if (!function_exists('adodb_throw')) {
279 if ($this->_dbat < 0) $db = false;
280 else $db = $this->DB();
281
282 // last error could be database error too
283 if ($db && $db->ErrorMsg()) return $db->ErrorMsg();
284 }
285 return $this->_lasterr;
286 }
287
288 // retrieve ADOConnection from _ADODB_Active_DBs
289 function &DB()
290 {
291 global $_ADODB_ACTIVE_DBS;
292
293 if ($this->_dbat < 0) {
294 $false = false;
295 $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
296 return $false;
297 }
298 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
299 $db =& $activedb->db;
300 return $db;
301 }
302
303 // retrieve ADODB_Active_Table
304 function &TableInfo()
305 {
306 global $_ADODB_ACTIVE_DBS;
307
308 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
309 $table =& $activedb->tables[$this->_tableat];
310 return $table;
311 }
312
313 // set a numeric array (using natural table field ordering) as object properties
314 function Set(&$row)
315 {
316 $db =& $this->DB();
317
318 if (!$row) {
319 $this->_saved = false;
320 return false;
321 }
322
323 $this->_saved = true;
324
325 $table =& $this->TableInfo();
326 if (sizeof($table->flds) != sizeof($row)) {
327 $this->Error("Table structure of $this->_table has changed","Load");
328 return false;
329 }
330
331 $cnt = 0;
332 foreach($table->flds as $name=>$fld) {
333 $this->$name = $row[$cnt];
334 $cnt += 1;
335 }
336 $this->_original = $row;
337 return true;
338 }
339
340 // get last inserted id for INSERT
341 function LastInsertID(&$db,$fieldname)
342 {
343 if ($db->hasInsertID)
344 $val = $db->Insert_ID($this->_table,$fieldname);
345 else
346 $val = false;
347
348 if (is_null($val) || $val === false) {
349 // this might not work reliably in multi-user environment
350 return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
351 }
352 return $val;
353 }
354
355 // quote data in where clause
356 function doquote(&$db, $val,$t)
357 {
358 switch($t) {
359 case 'D':
360 case 'T':
361 if (empty($val)) return 'null';
362
363 case 'C':
364 case 'X':
365 if (is_null($val)) return 'null';
366
367 if (strncmp($val,"'",1) != 0 && substr($val,strlen($val)-1,1) != "'") {
368 return $db->qstr($val);
369 break;
370 }
371 default:
372 return $val;
373 break;
374 }
375 }
376
377 // generate where clause for an UPDATE/SELECT
378 function GenWhere(&$db, &$table)
379 {
380 $keys = $table->keys;
381 $parr = array();
382
383 foreach($keys as $k) {
384 $f = $table->flds[$k];
385 if ($f) {
386 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
387 }
388 }
389 return implode(' and ', $parr);
390 }
391
392
393 //------------------------------------------------------------ Public functions below
394
395 function Load($where,$bindarr=false)
396 {
397 $db =& $this->DB(); if (!$db) return false;
398 $this->_where = $where;
399
400 $save = $db->SetFetchMode(ADODB_FETCH_NUM);
401 $row = $db->GetRow("select * from ".$this->_table.' WHERE '.$where,$bindarr);
402 $db->SetFetchMode($save);
403
404 return $this->Set($row);
405 }
406
407 // false on error
408 function Save()
409 {
410 if ($this->_saved) $ok = $this->Update();
411 else $ok = $this->Insert();
412
413 return $ok;
414 }
415
416 // false on error
417 function Insert()
418 {
419 $db =& $this->DB(); if (!$db) return false;
420 $cnt = 0;
421 $table =& $this->TableInfo();
422
423 $valarr = array();
424 $names = array();
425 $valstr = array();
426
427 foreach($table->flds as $name=>$fld) {
428 $val = $this->$name;
429 if(!is_null($val) || !array_key_exists($name, $table->keys)) {
430 $valarr[] = $val;
431 $names[] = $name;
432 $valstr[] = $db->Param($cnt);
433 $cnt += 1;
434 }
435 }
436
437 if (empty($names)){
438 foreach($table->flds as $name=>$fld) {
439 $valarr[] = null;
440 $names[] = $name;
441 $valstr[] = $db->Param($cnt);
442 $cnt += 1;
443 }
444 }
445 $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
446 $ok = $db->Execute($sql,$valarr);
447
448 if ($ok) {
449 $this->_saved = true;
450 $autoinc = false;
451 foreach($table->keys as $k) {
452 if (is_null($this->$k)) {
453 $autoinc = true;
454 break;
455 }
456 }
457 if ($autoinc && sizeof($table->keys) == 1) {
458 $k = reset($table->keys);
459 $this->$k = $this->LastInsertID($db,$k);
460 }
461 }
462
463 $this->_original = $valarr;
464 return !empty($ok);
465 }
466
467 function Delete()
468 {
469 $db =& $this->DB(); if (!$db) return false;
470 $table =& $this->TableInfo();
471
472 $where = $this->GenWhere($db,$table);
473 $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
474 $ok = $db->Execute($sql);
475
476 return $ok ? true : false;
477 }
478
479 // returns an array of active record objects
480 function &Find($whereOrderBy,$bindarr=false,$pkeysArr=false)
481 {
482 $db =& $this->DB(); if (!$db || empty($this->_table)) return false;
483 $arr =& $db->GetActiveRecordsClass(get_class($this),$this->_table, $whereOrderBy,$bindarr,$pkeysArr);
484 return $arr;
485 }
486
487 // returns 0 on error, 1 on update, 2 on insert
488 function Replace()
489 {
490 global $ADODB_ASSOC_CASE;
491
492 $db =& $this->DB(); if (!$db) return false;
493 $table =& $this->TableInfo();
494
495 $pkey = $table->keys;
496
497 foreach($table->flds as $name=>$fld) {
498 $val = $this->$name;
499 /*
500 if (is_null($val)) {
501 if (isset($fld->not_null) && $fld->not_null) {
502 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
503 else {
504 $this->Error("Cannot update null into $name","Replace");
505 return false;
506 }
507 }
508 }*/
509 if (is_null($val) && !empty($fld->auto_increment)) {
510 continue;
511 }
512 $t = $db->MetaType($fld->type);
513 $arr[$name] = $this->doquote($db,$val,$t);
514 $valarr[] = $val;
515 }
516
517 if (!is_array($pkey)) $pkey = array($pkey);
518
519
520 if ($ADODB_ASSOC_CASE == 0)
521 foreach($pkey as $k => $v)
522 $pkey[$k] = strtolower($v);
523 elseif ($ADODB_ASSOC_CASE == 0)
524 foreach($pkey as $k => $v)
525 $pkey[$k] = strtoupper($v);
526
527 $ok = $db->Replace($this->_table,$arr,$pkey);
528 if ($ok) {
529 $this->_saved = true; // 1= update 2=insert
530 if ($ok == 2) {
531 $autoinc = false;
532 foreach($table->keys as $k) {
533 if (is_null($this->$k)) {
534 $autoinc = true;
535 break;
536 }
537 }
538 if ($autoinc && sizeof($table->keys) == 1) {
539 $k = reset($table->keys);
540 $this->$k = $this->LastInsertID($db,$k);
541 }
542 }
543
544 $this->_original =& $valarr;
545 }
546 return $ok;
547 }
548
549 // returns 0 on error, 1 on update, -1 if no change in data (no update)
550 function Update()
551 {
552 $db =& $this->DB(); if (!$db) return false;
553 $table =& $this->TableInfo();
554
555 $where = $this->GenWhere($db, $table);
556
557 if (!$where) {
558 $this->error("Where missing for table $table", "Update");
559 return false;
560 }
561 $valarr = array();
562 $neworig = array();
563 $pairs = array();
564 $i = -1;
565 $cnt = 0;
566 foreach($table->flds as $name=>$fld) {
567 $i += 1;
568 $val = $this->$name;
569 $neworig[] = $val;
570
571 if (isset($table->keys[$name])) {
572 continue;
573 }
574
575 if (is_null($val)) {
576 if (isset($fld->not_null) && $fld->not_null) {
577 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
578 else {
579 $this->Error("Cannot set field $name to NULL","Update");
580 return false;
581 }
582 }
583 }
584
585 if (isset($this->_original[$i]) && $val == $this->_original[$i]) {
586 continue;
587 }
588 $valarr[] = $val;
589 $pairs[] = $name.'='.$db->Param($cnt);
590 $cnt += 1;
591 }
592
593
594 if (!$cnt) return -1;
595 $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where;
596 $ok = $db->Execute($sql,$valarr);
597 if ($ok) {
598 $this->_original =& $neworig;
599 return 1;
600 }
601 return 0;
602 }
603
604 function GetAttributeNames()
605 {
606 $table =& $this->TableInfo();
607 if (!$table) return false;
608 return array_keys($table->flds);
609 }
610
611 };
612
613 ?>