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