Update to upstream version 4.94
[Packages/TYPO3.CMS.git] / typo3 / sysext / adodb / adodb / adodb-active-record.inc.php
1 <?php
2 /*
3
4 @version V4.94 23 Jan 2007 (c) 2000-2007 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.07
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 function ErrorNo()
289 {
290 if ($this->_dbat < 0) return -9999; // no database connection...
291 $db = $this->DB();
292
293 return (int) $db->ErrorNo();
294 }
295
296
297 // retrieve ADOConnection from _ADODB_Active_DBs
298 function &DB()
299 {
300 global $_ADODB_ACTIVE_DBS;
301
302 if ($this->_dbat < 0) {
303 $false = false;
304 $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
305 return $false;
306 }
307 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
308 $db =& $activedb->db;
309 return $db;
310 }
311
312 // retrieve ADODB_Active_Table
313 function &TableInfo()
314 {
315 global $_ADODB_ACTIVE_DBS;
316
317 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
318 $table =& $activedb->tables[$this->_tableat];
319 return $table;
320 }
321
322 // set a numeric array (using natural table field ordering) as object properties
323 function Set(&$row)
324 {
325 $db =& $this->DB();
326
327 if (!$row) {
328 $this->_saved = false;
329 return false;
330 }
331
332 $this->_saved = true;
333
334 $table =& $this->TableInfo();
335 if (sizeof($table->flds) != sizeof($row)) {
336 $this->Error("Table structure of $this->_table has changed","Load");
337 return false;
338 }
339
340 $cnt = 0;
341 foreach($table->flds as $name=>$fld) {
342 $this->$name = $row[$cnt];
343 $cnt += 1;
344 }
345 $this->_original = $row;
346 return true;
347 }
348
349 // get last inserted id for INSERT
350 function LastInsertID(&$db,$fieldname)
351 {
352 if ($db->hasInsertID)
353 $val = $db->Insert_ID($this->_table,$fieldname);
354 else
355 $val = false;
356
357 if (is_null($val) || $val === false) {
358 // this might not work reliably in multi-user environment
359 return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
360 }
361 return $val;
362 }
363
364 // quote data in where clause
365 function doquote(&$db, $val,$t)
366 {
367 switch($t) {
368 case 'D':
369 case 'T':
370 if (empty($val)) return 'null';
371
372 case 'C':
373 case 'X':
374 if (is_null($val)) return 'null';
375
376 if (strncmp($val,"'",1) != 0 && substr($val,strlen($val)-1,1) != "'") {
377 return $db->qstr($val);
378 break;
379 }
380 default:
381 return $val;
382 break;
383 }
384 }
385
386 // generate where clause for an UPDATE/SELECT
387 function GenWhere(&$db, &$table)
388 {
389 $keys = $table->keys;
390 $parr = array();
391
392 foreach($keys as $k) {
393 $f = $table->flds[$k];
394 if ($f) {
395 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
396 }
397 }
398 return implode(' and ', $parr);
399 }
400
401
402 //------------------------------------------------------------ Public functions below
403
404 function Load($where,$bindarr=false)
405 {
406 $db =& $this->DB(); if (!$db) return false;
407 $this->_where = $where;
408
409 $save = $db->SetFetchMode(ADODB_FETCH_NUM);
410 $row = $db->GetRow("select * from ".$this->_table.' WHERE '.$where,$bindarr);
411 $db->SetFetchMode($save);
412
413 return $this->Set($row);
414 }
415
416 // false on error
417 function Save()
418 {
419 if ($this->_saved) $ok = $this->Update();
420 else $ok = $this->Insert();
421
422 return $ok;
423 }
424
425 // false on error
426 function Insert()
427 {
428 $db =& $this->DB(); if (!$db) return false;
429 $cnt = 0;
430 $table =& $this->TableInfo();
431
432 $valarr = array();
433 $names = array();
434 $valstr = array();
435
436 foreach($table->flds as $name=>$fld) {
437 $val = $this->$name;
438 if(!is_null($val) || !array_key_exists($name, $table->keys)) {
439 $valarr[] = $val;
440 $names[] = $name;
441 $valstr[] = $db->Param($cnt);
442 $cnt += 1;
443 }
444 }
445
446 if (empty($names)){
447 foreach($table->flds as $name=>$fld) {
448 $valarr[] = null;
449 $names[] = $name;
450 $valstr[] = $db->Param($cnt);
451 $cnt += 1;
452 }
453 }
454 $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
455 $ok = $db->Execute($sql,$valarr);
456
457 if ($ok) {
458 $this->_saved = true;
459 $autoinc = false;
460 foreach($table->keys as $k) {
461 if (is_null($this->$k)) {
462 $autoinc = true;
463 break;
464 }
465 }
466 if ($autoinc && sizeof($table->keys) == 1) {
467 $k = reset($table->keys);
468 $this->$k = $this->LastInsertID($db,$k);
469 }
470 }
471
472 $this->_original = $valarr;
473 return !empty($ok);
474 }
475
476 function Delete()
477 {
478 $db =& $this->DB(); if (!$db) return false;
479 $table =& $this->TableInfo();
480
481 $where = $this->GenWhere($db,$table);
482 $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
483 $ok = $db->Execute($sql);
484
485 return $ok ? true : false;
486 }
487
488 // returns an array of active record objects
489 function &Find($whereOrderBy,$bindarr=false,$pkeysArr=false)
490 {
491 $db =& $this->DB(); if (!$db || empty($this->_table)) return false;
492 $arr =& $db->GetActiveRecordsClass(get_class($this),$this->_table, $whereOrderBy,$bindarr,$pkeysArr);
493 return $arr;
494 }
495
496 // returns 0 on error, 1 on update, 2 on insert
497 function Replace()
498 {
499 global $ADODB_ASSOC_CASE;
500
501 $db =& $this->DB(); if (!$db) return false;
502 $table =& $this->TableInfo();
503
504 $pkey = $table->keys;
505
506 foreach($table->flds as $name=>$fld) {
507 $val = $this->$name;
508 /*
509 if (is_null($val)) {
510 if (isset($fld->not_null) && $fld->not_null) {
511 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
512 else {
513 $this->Error("Cannot update null into $name","Replace");
514 return false;
515 }
516 }
517 }*/
518 if (is_null($val) && !empty($fld->auto_increment)) {
519 continue;
520 }
521 $t = $db->MetaType($fld->type);
522 $arr[$name] = $this->doquote($db,$val,$t);
523 $valarr[] = $val;
524 }
525
526 if (!is_array($pkey)) $pkey = array($pkey);
527
528
529 if ($ADODB_ASSOC_CASE == 0)
530 foreach($pkey as $k => $v)
531 $pkey[$k] = strtolower($v);
532 elseif ($ADODB_ASSOC_CASE == 0)
533 foreach($pkey as $k => $v)
534 $pkey[$k] = strtoupper($v);
535
536 $ok = $db->Replace($this->_table,$arr,$pkey);
537 if ($ok) {
538 $this->_saved = true; // 1= update 2=insert
539 if ($ok == 2) {
540 $autoinc = false;
541 foreach($table->keys as $k) {
542 if (is_null($this->$k)) {
543 $autoinc = true;
544 break;
545 }
546 }
547 if ($autoinc && sizeof($table->keys) == 1) {
548 $k = reset($table->keys);
549 $this->$k = $this->LastInsertID($db,$k);
550 }
551 }
552
553 $this->_original =& $valarr;
554 }
555 return $ok;
556 }
557
558 // returns 0 on error, 1 on update, -1 if no change in data (no update)
559 function Update()
560 {
561 $db =& $this->DB(); if (!$db) return false;
562 $table =& $this->TableInfo();
563
564 $where = $this->GenWhere($db, $table);
565
566 if (!$where) {
567 $this->error("Where missing for table $table", "Update");
568 return false;
569 }
570 $valarr = array();
571 $neworig = array();
572 $pairs = array();
573 $i = -1;
574 $cnt = 0;
575 foreach($table->flds as $name=>$fld) {
576 $i += 1;
577 $val = $this->$name;
578 $neworig[] = $val;
579
580 if (isset($table->keys[$name])) {
581 continue;
582 }
583
584 if (is_null($val)) {
585 if (isset($fld->not_null) && $fld->not_null) {
586 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
587 else {
588 $this->Error("Cannot set field $name to NULL","Update");
589 return false;
590 }
591 }
592 }
593
594 if (isset($this->_original[$i]) && $val == $this->_original[$i]) {
595 continue;
596 }
597 $valarr[] = $val;
598 $pairs[] = $name.'='.$db->Param($cnt);
599 $cnt += 1;
600 }
601
602
603 if (!$cnt) return -1;
604 $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where;
605 $ok = $db->Execute($sql,$valarr);
606 if ($ok) {
607 $this->_original =& $neworig;
608 return 1;
609 }
610 return 0;
611 }
612
613 function GetAttributeNames()
614 {
615 $table =& $this->TableInfo();
616 if (!$table) return false;
617 return array_keys($table->flds);
618 }
619
620 };
621
622 ?>