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