Merge commit 'catalyst/MOODLE_19_STABLE' into mdl19-linuxchix
[moodle-linuxchix.git] / lib / adodb / adodb-active-record.inc.php
blob0654828c00d7e163c02637886149688ed85db90e
1 <?php
2 /*
4 @version V4.98 13 Feb 2008 (c) 2000-2008 John Lim (jlim#natsoft.com.my). All rights reserved.
5 Latest version is available at http://adodb.sourceforge.net
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.
11 Active Record implementation. Superset of Zend Framework's.
13 Version 0.09
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
19 global $_ADODB_ACTIVE_DBS;
20 global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
21 global $ACTIVE_RECORD_SAFETY; // set to false to disable safety checks
22 global $ADODB_ACTIVE_DEFVALS; // use default values of table definition when creating new active record.
24 // array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
25 $_ADODB_ACTIVE_DBS = array();
26 $ACTIVE_RECORD_SAFETY = true;
27 $ADODB_ACTIVE_DEFVALS = false;
29 class ADODB_Active_DB {
30 var $db; // ADOConnection
31 var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
34 class ADODB_Active_Table {
35 var $name; // table name
36 var $flds; // assoc array of adofieldobjs, indexed by fieldname
37 var $keys; // assoc array of primary keys, indexed by fieldname
38 var $_created; // only used when stored as a cached file
41 // returns index into $_ADODB_ACTIVE_DBS
42 function ADODB_SetDatabaseAdapter(&$db)
44 global $_ADODB_ACTIVE_DBS;
46 foreach($_ADODB_ACTIVE_DBS as $k => $d) {
47 if (PHP_VERSION >= 5) {
48 if ($d->db === $db) return $k;
49 } else {
50 if ($d->db->_connectionID === $db->_connectionID && $db->database == $d->db->database)
51 return $k;
55 $obj = new ADODB_Active_DB();
56 $obj->db =& $db;
57 $obj->tables = array();
59 $_ADODB_ACTIVE_DBS[] = $obj;
61 return sizeof($_ADODB_ACTIVE_DBS)-1;
65 class ADODB_Active_Record {
66 var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
67 var $_table; // tablename, if set in class definition then use it as table name
68 var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
69 var $_where; // where clause set in Load()
70 var $_saved = false; // indicates whether data is already inserted.
71 var $_lasterr = false; // last error message
72 var $_original = false; // the original values loaded or inserted, refreshed on update
74 // should be static
75 function UseDefaultValues($bool=null)
77 global $ADODB_ACTIVE_DEFVALS;
78 if (isset($bool)) $ADODB_ACTIVE_DEFVALS = $bool;
79 return $ADODB_ACTIVE_DEFVALS;
82 // should be static
83 function SetDatabaseAdapter(&$db)
85 return ADODB_SetDatabaseAdapter($db);
88 // php4 constructor
89 function ADODB_Active_Record($table = false, $pkeyarr=false, $db=false)
91 ADODB_Active_Record::__construct($table,$pkeyarr,$db);
94 // php5 constructor
95 function __construct($table = false, $pkeyarr=false, $db=false)
97 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
99 if ($db == false && is_object($pkeyarr)) {
100 $db = $pkeyarr;
101 $pkeyarr = false;
104 if (!$table) {
105 if (!empty($this->_table)) $table = $this->_table;
106 else $table = $this->_pluralize(get_class($this));
108 if ($db) {
109 $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db);
110 } else
111 $this->_dbat = sizeof($_ADODB_ACTIVE_DBS)-1;
114 if ($this->_dbat < 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor');
116 $this->_table = $table;
117 $this->_tableat = $table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
118 $this->UpdateActiveTable($pkeyarr);
121 function __wakeup()
123 $class = get_class($this);
124 new $class;
127 function _pluralize($table)
129 $ut = strtoupper($table);
130 $len = strlen($table);
131 $lastc = $ut[$len-1];
132 $lastc2 = substr($ut,$len-2);
133 switch ($lastc) {
134 case 'S':
135 return $table.'es';
136 case 'Y':
137 return substr($table,0,$len-1).'ies';
138 case 'X':
139 return $table.'es';
140 case 'H':
141 if ($lastc2 == 'CH' || $lastc2 == 'SH')
142 return $table.'es';
143 default:
144 return $table.'s';
148 //////////////////////////////////
150 // update metadata
151 function UpdateActiveTable($pkeys=false,$forceUpdate=false)
153 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
154 global $ADODB_ACTIVE_DEFVALS;
156 $activedb =& $_ADODB_ACTIVE_DBS[$this->_dbat];
158 $table = $this->_table;
159 $tables = $activedb->tables;
160 $tableat = $this->_tableat;
161 if (!$forceUpdate && !empty($tables[$tableat])) {
162 $tobj =& $tables[$tableat];
163 foreach($tobj->flds as $name => $fld) {
164 if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value))
165 $this->$name = $fld->default_value;
166 else
167 $this->$name = null;
169 return;
172 $db =& $activedb->db;
173 $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache';
174 if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
175 $fp = fopen($fname,'r');
176 @flock($fp, LOCK_SH);
177 $acttab = unserialize(fread($fp,100000));
178 fclose($fp);
179 if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) {
180 // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
181 // ideally, you should cache at least 32 secs
182 $activedb->tables[$table] = $acttab;
184 //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
185 return;
186 } else if ($db->debug) {
187 ADOConnection::outp("Refreshing cached active record file: $fname");
190 $activetab = new ADODB_Active_Table();
191 $activetab->name = $table;
194 $cols = $db->MetaColumns($table);
195 if (!$cols) {
196 $this->Error("Invalid table name: $table",'UpdateActiveTable');
197 return false;
199 $fld = reset($cols);
200 if (!$pkeys) {
201 if (isset($fld->primary_key)) {
202 $pkeys = array();
203 foreach($cols as $name => $fld) {
204 if (!empty($fld->primary_key)) $pkeys[] = $name;
206 } else
207 $pkeys = $this->GetPrimaryKeys($db, $table);
209 if (empty($pkeys)) {
210 $this->Error("No primary key found for table $table",'UpdateActiveTable');
211 return false;
214 $attr = array();
215 $keys = array();
217 switch($ADODB_ASSOC_CASE) {
218 case 0:
219 foreach($cols as $name => $fldobj) {
220 $name = strtolower($name);
221 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
222 $this->$name = $fldobj->default_value;
223 else
224 $this->$name = null;
225 $attr[$name] = $fldobj;
227 foreach($pkeys as $k => $name) {
228 $keys[strtolower($name)] = strtolower($name);
230 break;
232 case 1:
233 foreach($cols as $name => $fldobj) {
234 $name = strtoupper($name);
236 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
237 $this->$name = $fldobj->default_value;
238 else
239 $this->$name = null;
240 $attr[$name] = $fldobj;
243 foreach($pkeys as $k => $name) {
244 $keys[strtoupper($name)] = strtoupper($name);
246 break;
247 default:
248 foreach($cols as $name => $fldobj) {
249 $name = ($fldobj->name);
251 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
252 $this->$name = $fldobj->default_value;
253 else
254 $this->$name = null;
255 $attr[$name] = $fldobj;
257 foreach($pkeys as $k => $name) {
258 $keys[$name] = $cols[$name]->name;
260 break;
263 $activetab->keys = $keys;
264 $activetab->flds = $attr;
266 if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
267 $activetab->_created = time();
268 $s = serialize($activetab);
269 if (!function_exists('adodb_write_file')) include(ADODB_DIR.'/adodb-csvlib.inc.php');
270 adodb_write_file($fname,$s);
272 $activedb->tables[$table] = $activetab;
275 function GetPrimaryKeys(&$db, $table)
277 return $db->MetaPrimaryKeys($table);
280 // error handler for both PHP4+5.
281 function Error($err,$fn)
283 global $_ADODB_ACTIVE_DBS;
285 $fn = get_class($this).'::'.$fn;
286 $this->_lasterr = $fn.': '.$err;
288 if ($this->_dbat < 0) $db = false;
289 else {
290 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
291 $db =& $activedb->db;
294 if (function_exists('adodb_throw')) {
295 if (!$db) adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
296 else adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db);
297 } else
298 if (!$db || $db->debug) ADOConnection::outp($this->_lasterr);
302 // return last error message
303 function ErrorMsg()
305 if (!function_exists('adodb_throw')) {
306 if ($this->_dbat < 0) $db = false;
307 else $db = $this->DB();
309 // last error could be database error too
310 if ($db && $db->ErrorMsg()) return $db->ErrorMsg();
312 return $this->_lasterr;
315 function ErrorNo()
317 if ($this->_dbat < 0) return -9999; // no database connection...
318 $db = $this->DB();
320 return (int) $db->ErrorNo();
324 // retrieve ADOConnection from _ADODB_Active_DBs
325 function &DB()
327 global $_ADODB_ACTIVE_DBS;
329 if ($this->_dbat < 0) {
330 $false = false;
331 $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
332 return $false;
334 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
335 $db =& $activedb->db;
336 return $db;
339 // retrieve ADODB_Active_Table
340 function &TableInfo()
342 global $_ADODB_ACTIVE_DBS;
344 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
345 $table =& $activedb->tables[$this->_tableat];
346 return $table;
349 // set a numeric array (using natural table field ordering) as object properties
350 function Set(&$row)
352 global $ACTIVE_RECORD_SAFETY;
354 $db =& $this->DB();
356 if (!$row) {
357 $this->_saved = false;
358 return false;
361 $this->_saved = true;
363 $table =& $this->TableInfo();
364 if ($ACTIVE_RECORD_SAFETY && sizeof($table->flds) != sizeof($row)) {
365 $bad_size = TRUE;
366 if (sizeof($row) == 2 * sizeof($table->flds)) {
367 // Only keep string keys
368 $keys = array_filter(array_keys($row), 'is_string');
369 if (sizeof($keys) == sizeof($table->flds))
370 $bad_size = FALSE;
372 if ($bad_size) {
373 $this->Error("Table structure of $this->_table has changed","Load");
374 return false;
377 else
378 $keys = array_keys($row);
380 reset($keys);
381 $this->_original = array();
382 foreach($table->flds as $name=>$fld) {
383 $value = $row[current($keys)];
384 $this->$name = $value;
385 $this->_original[] = $value;
386 next($keys);
388 # </AP>
389 return true;
392 // get last inserted id for INSERT
393 function LastInsertID(&$db,$fieldname)
395 if ($db->hasInsertID)
396 $val = $db->Insert_ID($this->_table,$fieldname);
397 else
398 $val = false;
400 if (is_null($val) || $val === false) {
401 // this might not work reliably in multi-user environment
402 return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
404 return $val;
407 // quote data in where clause
408 function doquote(&$db, $val,$t)
410 switch($t) {
411 case 'D':
412 case 'T':
413 if (empty($val)) return 'null';
415 case 'C':
416 case 'X':
417 if (is_null($val)) return 'null';
419 if (strncmp($val,"'",1) != 0 && substr($val,strlen($val)-1,1) != "'") {
420 return $db->qstr($val);
421 break;
423 default:
424 return $val;
425 break;
429 // generate where clause for an UPDATE/SELECT
430 function GenWhere(&$db, &$table)
432 $keys = $table->keys;
433 $parr = array();
435 foreach($keys as $k) {
436 $f = $table->flds[$k];
437 if ($f) {
438 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
441 return implode(' and ', $parr);
445 //------------------------------------------------------------ Public functions below
447 function Load($where,$bindarr=false)
449 $db =& $this->DB(); if (!$db) return false;
450 $this->_where = $where;
452 $save = $db->SetFetchMode(ADODB_FETCH_NUM);
453 $row = $db->GetRow("select * from ".$this->_table.' WHERE '.$where,$bindarr);
454 $db->SetFetchMode($save);
456 return $this->Set($row);
459 // false on error
460 function Save()
462 if ($this->_saved) $ok = $this->Update();
463 else $ok = $this->Insert();
465 return $ok;
468 // false on error
469 function Insert()
471 $db =& $this->DB(); if (!$db) return false;
472 $cnt = 0;
473 $table =& $this->TableInfo();
475 $valarr = array();
476 $names = array();
477 $valstr = array();
479 foreach($table->flds as $name=>$fld) {
480 $val = $this->$name;
481 if(!is_null($val) || !array_key_exists($name, $table->keys)) {
482 $valarr[] = $val;
483 $names[] = $name;
484 $valstr[] = $db->Param($cnt);
485 $cnt += 1;
489 if (empty($names)){
490 foreach($table->flds as $name=>$fld) {
491 $valarr[] = null;
492 $names[] = $name;
493 $valstr[] = $db->Param($cnt);
494 $cnt += 1;
497 $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
498 $ok = $db->Execute($sql,$valarr);
500 if ($ok) {
501 $this->_saved = true;
502 $autoinc = false;
503 foreach($table->keys as $k) {
504 if (is_null($this->$k)) {
505 $autoinc = true;
506 break;
509 if ($autoinc && sizeof($table->keys) == 1) {
510 $k = reset($table->keys);
511 $this->$k = $this->LastInsertID($db,$k);
515 $this->_original = $valarr;
516 return !empty($ok);
519 function Delete()
521 $db =& $this->DB(); if (!$db) return false;
522 $table =& $this->TableInfo();
524 $where = $this->GenWhere($db,$table);
525 $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
526 $ok = $db->Execute($sql);
528 return $ok ? true : false;
531 // returns an array of active record objects
532 function &Find($whereOrderBy,$bindarr=false,$pkeysArr=false)
534 $db =& $this->DB(); if (!$db || empty($this->_table)) return false;
535 $arr =& $db->GetActiveRecordsClass(get_class($this),$this->_table, $whereOrderBy,$bindarr,$pkeysArr);
536 return $arr;
539 // returns 0 on error, 1 on update, 2 on insert
540 function Replace()
542 global $ADODB_ASSOC_CASE;
544 $db =& $this->DB(); if (!$db) return false;
545 $table =& $this->TableInfo();
547 $pkey = $table->keys;
549 foreach($table->flds as $name=>$fld) {
550 $val = $this->$name;
552 if (is_null($val)) {
553 if (isset($fld->not_null) && $fld->not_null) {
554 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
555 else {
556 $this->Error("Cannot update null into $name","Replace");
557 return false;
561 if (is_null($val) && !empty($fld->auto_increment)) {
562 continue;
564 $t = $db->MetaType($fld->type);
565 $arr[$name] = $this->doquote($db,$val,$t);
566 $valarr[] = $val;
569 if (!is_array($pkey)) $pkey = array($pkey);
572 if ($ADODB_ASSOC_CASE == 0)
573 foreach($pkey as $k => $v)
574 $pkey[$k] = strtolower($v);
575 elseif ($ADODB_ASSOC_CASE == 1)
576 foreach($pkey as $k => $v)
577 $pkey[$k] = strtoupper($v);
579 $ok = $db->Replace($this->_table,$arr,$pkey);
580 if ($ok) {
581 $this->_saved = true; // 1= update 2=insert
582 if ($ok == 2) {
583 $autoinc = false;
584 foreach($table->keys as $k) {
585 if (is_null($this->$k)) {
586 $autoinc = true;
587 break;
590 if ($autoinc && sizeof($table->keys) == 1) {
591 $k = reset($table->keys);
592 $this->$k = $this->LastInsertID($db,$k);
596 $this->_original =& $valarr;
598 return $ok;
601 // returns 0 on error, 1 on update, -1 if no change in data (no update)
602 function Update()
604 $db =& $this->DB(); if (!$db) return false;
605 $table =& $this->TableInfo();
607 $where = $this->GenWhere($db, $table);
609 if (!$where) {
610 $this->error("Where missing for table $table", "Update");
611 return false;
613 $valarr = array();
614 $neworig = array();
615 $pairs = array();
616 $i = -1;
617 $cnt = 0;
618 foreach($table->flds as $name=>$fld) {
619 $i += 1;
620 $val = $this->$name;
621 $neworig[] = $val;
623 if (isset($table->keys[$name])) {
624 continue;
627 if (is_null($val)) {
628 if (isset($fld->not_null) && $fld->not_null) {
629 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
630 else {
631 $this->Error("Cannot set field $name to NULL","Update");
632 return false;
637 if (isset($this->_original[$i]) && $val == $this->_original[$i]) {
638 continue;
640 $valarr[] = $val;
641 $pairs[] = $name.'='.$db->Param($cnt);
642 $cnt += 1;
646 if (!$cnt) return -1;
647 $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where;
648 $ok = $db->Execute($sql,$valarr);
649 if ($ok) {
650 $this->_original =& $neworig;
651 return 1;
653 return 0;
656 function GetAttributeNames()
658 $table =& $this->TableInfo();
659 if (!$table) return false;
660 return array_keys($table->flds);