X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=extlib%2FDB%2FDataObject.php;h=a69fbae86bdf35ce156bf13e77b4a17d1bcc0a8e;hb=9abe6fa66632f1a0ddbfa028e51c6676a4357d5c;hp=0c6a13dc28f7cec5c702d2ad09fb41704bc6b018;hpb=003c63e587128c6d095386c563c2615c2ac245c2;p=quix0rs-gnu-social.git diff --git a/extlib/DB/DataObject.php b/extlib/DB/DataObject.php index 0c6a13dc28..a69fbae86b 100644 --- a/extlib/DB/DataObject.php +++ b/extlib/DB/DataObject.php @@ -2,11 +2,11 @@ /** * Object Based Database Query Builder and data store * - * PHP versions 4 and 5 + * For PHP versions 4,5 and 6 * - * LICENSE: This source file is subject to version 3.0 of the PHP license + * LICENSE: This source file is subject to version 3.01 of the PHP license * that is available through the world-wide-web at the following URI: - * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * http://www.php.net/license/3_01.txt. If you did not receive a copy of * the PHP License and are unable to obtain it through the web, please * send a note to license@php.net so we can mail you a copy immediately. * @@ -14,8 +14,8 @@ * @package DB_DataObject * @author Alan Knowles * @copyright 1997-2006 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id: DataObject.php,v 1.439 2008/01/30 02:14:06 alan_k Exp $ + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: DataObject.php 291349 2009-11-27 09:15:18Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ @@ -235,7 +235,7 @@ class DB_DataObject extends DB_DataObject_Overload * @access private * @var string */ - var $_DB_DataObject_version = "1.8.8"; + var $_DB_DataObject_version = "1.9.0"; /** * The Database table (used by table extends) @@ -309,7 +309,8 @@ class DB_DataObject extends DB_DataObject_Overload /** * An autoloading, caching static get method using key, value (based on get) - * + * (depreciated - use ::get / and your own caching method) + * * Usage: * $object = DB_DataObject::staticGet("DbTable_mytable",12); * or @@ -942,9 +943,13 @@ class DB_DataObject extends DB_DataObject_Overload } $this->$key = $keyvalue; } - - - + + // if we haven't set disable_null_strings to "full" + $ignore_null = !isset($options['disable_null_strings']) + || !is_string($options['disable_null_strings']) + || strtolower($options['disable_null_strings']) !== 'full' ; + + foreach($items as $k => $v) { // if we are using autoincrement - skip the column... @@ -953,7 +958,10 @@ class DB_DataObject extends DB_DataObject_Overload } - if (!isset($this->$k)) { + + + // Ignore variables which aren't set to a value + if ( !isset($this->$k) && $ignore_null) { continue; } // dont insert data into mysql timestamps @@ -980,8 +988,7 @@ class DB_DataObject extends DB_DataObject_Overload } - - if (!isset($options['disable_null_strings']) && is_string($this->$k) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $rightq .= " NULL "; continue; } @@ -1027,7 +1034,13 @@ class DB_DataObject extends DB_DataObject_Overload if ($leftq || $useNative) { $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table); - $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) "); + + if (($dbtype == 'pgsql') && empty($leftq)) { + $r = $this->_query("INSERT INTO {$table} DEFAULT VALUES"); + } else { + $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) "); + } + @@ -1188,8 +1201,14 @@ class DB_DataObject extends DB_DataObject_Overload $options = $_DB_DATAOBJECT['CONFIG']; + $ignore_null = !isset($options['disable_null_strings']) + || !is_string($options['disable_null_strings']) + || strtolower($options['disable_null_strings']) !== 'full' ; + + foreach($items as $k => $v) { - if (!isset($this->$k)) { + + if (!isset($this->$k) && $ignore_null) { continue; } // ignore stuff thats @@ -1228,7 +1247,7 @@ class DB_DataObject extends DB_DataObject_Overload } // special values ... at least null is handled... - if (!isset($options['disable_null_strings']) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $settings .= "$kSql = NULL "; continue; } @@ -1339,7 +1358,7 @@ class DB_DataObject extends DB_DataObject_Overload * build the condition only using the object parameters. * * @access public - * @return mixed True on success, false on failure, 0 on no data affected + * @return mixed Int (No. of rows affected) on success, false on failure, 0 on no data affected */ function delete($useWhere = false) { @@ -1369,7 +1388,13 @@ class DB_DataObject extends DB_DataObject_Overload if (($this->_query !== false) && $this->_query['condition']) { $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table); - $sql = "DELETE FROM {$table} {$this->_query['condition']}{$extra_cond}"; + $sql = "DELETE "; + // using a joined delete. - with useWhere.. + $sql .= (!empty($this->_join) && $useWhere) ? + "{$table} FROM {$table} {$this->_join} " : + "FROM {$table} "; + + $sql .= $this->_query['condition']. $extra_cond; // add limit.. @@ -1521,15 +1546,15 @@ class DB_DataObject extends DB_DataObject_Overload } $keys = $this->keys(); - if (!$keys[0] && !is_string($countWhat)) { + if (empty($keys[0]) && (!is_string($countWhat) || (strtoupper($countWhat) == 'DISTINCT'))) { $this->raiseError( - "You cannot do run count without keys - use \$do->keys('id');", + "You cannot do run count without keys - use \$do->count('id'), or use \$do->count('distinct id')';", DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE); return false; } $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table); - $key_col = ($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]); + $key_col = empty($keys[0]) ? '' : (($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0])); $as = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM'); // support distinct on default keys. @@ -1784,10 +1809,15 @@ class DB_DataObject extends DB_DataObject_Overload } - + $_DB_DATAOBJECT['INI'][$this->_database] = array(); foreach ($schemas as $ini) { if (file_exists($ini) && is_file($ini)) { - $_DB_DATAOBJECT['INI'][$this->_database] = parse_ini_file($ini, true); + + $_DB_DATAOBJECT['INI'][$this->_database] = array_merge( + $_DB_DATAOBJECT['INI'][$this->_database], + parse_ini_file($ini, true) + ); + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { if (!is_readable ($ini)) { $this->debug("ini file is not readable: $ini","databaseStructure",1); @@ -2044,7 +2074,7 @@ class DB_DataObject extends DB_DataObject_Overload // technically postgres native here... // we need to get the new improved tabledata sorted out first. - if ( in_array($dbtype , array('psql', 'mysql', 'mysqli', 'mssql', 'ifx')) && + if ( in_array($dbtype , array('pgsql', 'mysql', 'mysqli', 'mssql', 'ifx')) && ($table[$usekey] & DB_DATAOBJECT_INT) && isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N') ) { @@ -2125,10 +2155,13 @@ class DB_DataObject extends DB_DataObject_Overload $this->_loadConfig(); } // Set database driver for reference - $db_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver']; - // is it already connected ? - + $db_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? + 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver']; + + // is it already connected ? if ($this->_database_dsn_md5 && !empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { + + // connection is an error... if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { return $this->raiseError( $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message, @@ -2137,7 +2170,7 @@ class DB_DataObject extends DB_DataObject_Overload } - if (!$this->_database) { + if (empty($this->_database)) { $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); @@ -2166,6 +2199,7 @@ class DB_DataObject extends DB_DataObject_Overload // try and work out what to use for the dsn ! $options= &$_DB_DATAOBJECT['CONFIG']; + // if the databse dsn dis defined in the object.. $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null; if (!$dsn) { @@ -2173,14 +2207,14 @@ class DB_DataObject extends DB_DataObject_Overload $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("Checking for database database_{$this->_database} in options","CONNECT"); + $this->debug("Checking for database specific ini ('{$this->_database}') : database_{$this->_database} in options","CONNECT"); } if ($this->_database && !empty($options["database_{$this->_database}"])) { - $dsn = $options["database_{$this->_database}"]; } else if (!empty($options['database'])) { $dsn = $options['database']; + } } @@ -2205,6 +2239,9 @@ class DB_DataObject extends DB_DataObject_Overload if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("USING CACHED CONNECTION", "CONNECT",3); } + + + if (!$this->_database) { $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); @@ -2221,7 +2258,7 @@ class DB_DataObject extends DB_DataObject_Overload return true; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("NEW CONNECTION", "CONNECT",3); + $this->debug("NEW CONNECTION TP DATABASE :" .$this->_database , "CONNECT",3); /* actualy make a connection */ $this->debug(print_r($dsn,true) ." {$this->_database_dsn_md5}", "CONNECT",3); } @@ -2265,8 +2302,8 @@ class DB_DataObject extends DB_DataObject_Overload ); } - - if (!$this->_database) { + + if (empty($this->_database)) { $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); $this->_database = ($db_driver != 'DB' && $hasGetDatabase) @@ -2357,38 +2394,38 @@ class DB_DataObject extends DB_DataObject_Overload $t= explode(' ',microtime()); $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1]; - - do { - if ($_DB_driver == 'DB') { - $result = $DB->query($string); - } else { - switch (strtolower(substr(trim($string),0,6))) { + for ($tries = 0;$tries < 3;$tries++) { - case 'insert': - case 'update': - case 'delete': - $result = $DB->exec($string); - break; - - default: - $result = $DB->query($string); - break; + if ($_DB_driver == 'DB') { + + $result = $DB->query($string); + } else { + switch (strtolower(substr(trim($string),0,6))) { + + case 'insert': + case 'update': + case 'delete': + $result = $DB->exec($string); + break; + + default: + $result = $DB->query($string); + break; + } + } + + // see if we got a failure.. - try again a few times.. + if (!is_a($result,'PEAR_Error')) { + break; } + if ($result->getCode() != -14) { // *DB_ERROR_NODBSELECTED + break; // not a connection error.. + } + sleep(1); // wait before retyring.. + $DB->connect($DB->dsn); } - - // try to reconnect, at most 3 times - $again = false; - if (is_a($result, 'PEAR_Error') - AND $result->getCode() == DB_ERROR_NODBSELECTED - AND $cpt++<3) { - $DB->disconnect(); - sleep(1); - $DB->connect($DB->dsn); - $again = true; - } - - } while ($again); + if (is_a($result,'PEAR_Error')) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { @@ -2459,7 +2496,8 @@ class DB_DataObject extends DB_DataObject_Overload $x = new DB_DataObject; $this->_query= $x->_query; } - + + foreach($keys as $k => $v) { // index keys is an indexed array /* these filter checks are a bit suspicious.. @@ -2500,7 +2538,7 @@ class DB_DataObject extends DB_DataObject_Overload continue; } - if (!isset($options['disable_null_strings']) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $this->whereAdd(" $kSql IS NULL"); continue; } @@ -2556,11 +2594,13 @@ class DB_DataObject extends DB_DataObject_Overload * use @ to silence it (if you are sure it is acceptable) * eg. $do = @DB_DataObject::factory('person') * - * table name will eventually be databasename/table + * table name can bedatabasename/table * - and allow modular dataobjects to be written.. * (this also helps proxy creation) * - * + * Experimental Support for Multi-Database factory eg. mydatabase.mytable + * + * * @param string $table tablename (use blank to create a new instance of the same class.) * @access private * @return DataObject|PEAR_Error @@ -2570,9 +2610,27 @@ class DB_DataObject extends DB_DataObject_Overload function factory($table = '') { global $_DB_DATAOBJECT; + + + // multi-database support.. - experimental. + $database = ''; + + if (strpos( $table,'/') !== false ) { + list($database,$table) = explode('.',$table, 2); + + } + if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } + // no configuration available for database + if (!empty($database) && empty($_DB_DATAOBJECT['CONFIG']['database_'.$database])) { + return DB_DataObject::raiseError( + "unable to find database_{$database} in Configuration, It is required for factory with database" + , 0, PEAR_ERROR_DIE ); + } + + if ($table === '') { if (is_a($this,'DB_DataObject') && strlen($this->__table)) { @@ -2584,17 +2642,38 @@ class DB_DataObject extends DB_DataObject_Overload } } + // does this need multi db support?? + $cp = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? + explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']['class_prefix']) : ''; - $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? - $_DB_DATAOBJECT['CONFIG']['class_prefix'] : ''; - $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)); + // multiprefix support. + $tbl = preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)); + if (is_array($cp)) { + $class = array(); + foreach($cp as $cpr) { + $ce = substr(phpversion(),0,1) > 4 ? class_exists($cpr . $tbl,false) : class_exists($cpr . $tbl); + if ($ce) { + $class = $cpr . $tbl; + break; + } + $class[] = $cpr . $tbl; + } + } else { + $class = $tbl; + $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class); + } - $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class); - $class = $ce ? $class : DB_DataObject::_autoloadClass($class); + + $rclass = $ce ? $class : DB_DataObject::_autoloadClass($class, $table); // proxy = full|light - if (!$class && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { + if (!$rclass && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { + + DB_DataObject::debug("FAILED TO Autoload $database.$table - using proxy.","FACTORY",1); + + $proxyMethod = 'getProxy'.$_DB_DATAOBJECT['CONFIG']['proxy']; + // if you have loaded (some other way) - dont try and load it again.. class_exists('DB_DataObject_Generator') ? '' : require_once 'DB/DataObject/Generator.php'; @@ -2609,22 +2688,29 @@ class DB_DataObject extends DB_DataObject_Overload return $x->$proxyMethod( $d->_database, $table); } - if (!$class) { + if (!$rclass) { return DB_DataObject::raiseError( - "factory could not find class $class from $table", + "factory could not find class " . + (is_array($class) ? implode(PATH_SEPARATOR, $class) : $class ). + "from $table", DB_DATAOBJECT_ERROR_INVALIDCONFIG); } - - return new $class; + $ret = new $rclass; + if (!empty($database)) { + DB_DataObject::debug("Setting database to $database","FACTORY",1); + $ret->database($database); + } + return $ret; } /** * autoload Class * - * @param string $class Class + * @param string|array $class Class + * @param string $table Table trying to load. * @access private * @return string classname on Success */ - function _autoloadClass($class) + function _autoloadClass($class, $table=false) { global $_DB_DATAOBJECT; @@ -2634,32 +2720,62 @@ class DB_DataObject extends DB_DataObject_Overload $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? '' : $_DB_DATAOBJECT['CONFIG']['class_prefix']; - $table = substr($class,strlen($class_prefix)); + $table = $table ? $table : substr($class,strlen($class_prefix)); // only include the file if it exists - and barf badly if it has parse errors :) if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) || empty($_DB_DATAOBJECT['CONFIG']['class_location'])) { return false; } + // support for: + // class_location = mydir/ => maps to mydir/Tablename.php + // class_location = mydir/myfile_%s.php => maps to mydir/myfile_Tablename + // with directory sepr + // class_location = mydir/:mydir2/: => tries all of thes locations. + $cl = $_DB_DATAOBJECT['CONFIG']['class_location']; - if (strpos($_DB_DATAOBJECT['CONFIG']['class_location'],'%s') !== false) { - $file = sprintf($_DB_DATAOBJECT['CONFIG']['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($table))); - } else { - $file = $_DB_DATAOBJECT['CONFIG']['class_location'].'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php"; + switch (true) { + case (strpos($cl ,'%s') !== false): + $file = sprintf($cl , preg_replace('/[^A-Z0-9]/i','_',ucfirst($table))); + break; + + case (strpos($cl , PATH_SEPARATOR) !== false): + $file = array(); + foreach(explode(PATH_SEPARATOR, $cl ) as $p) { + $file[] = $p .'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php"; + } + break; + default: + $file = $cl .'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php"; + break; } - if (!file_exists($file)) { + $cls = is_array($class) ? $class : array($class); + + if (is_array($file) || !file_exists($file)) { $found = false; - foreach(explode(PATH_SEPARATOR, ini_get('include_path')) as $p) { - if (file_exists("$p/$file")) { - $file = "$p/$file"; - $found = true; + + $file = is_array($file) ? $file : array($file); + $search = implode(PATH_SEPARATOR, $file); + foreach($file as $f) { + foreach(explode(PATH_SEPARATOR, '' . PATH_SEPARATOR . ini_get('include_path')) as $p) { + $ff = empty($p) ? $f : "$p/$f"; + + if (file_exists($ff)) { + $file = $ff; + $found = true; + break; + } + } + if ($found) { break; } } if (!$found) { DB_DataObject::raiseError( - "autoload:Could not find class {$class} using class_location value", + "autoload:Could not find class " . implode(',', $cls) . + " using class_location value :" . $search . + " using include_path value :" . ini_get('include_path'), DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } @@ -2667,12 +2783,18 @@ class DB_DataObject extends DB_DataObject_Overload include_once $file; - - $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class); - + + $ce = false; + foreach($cls as $c) { + $ce = substr(phpversion(),0,1) > 4 ? class_exists($c,false) : class_exists($c); + if ($ce) { + $class = $c; + break; + } + } if (!$ce) { DB_DataObject::raiseError( - "autoload:Could not autoload {$class}", + "autoload:Could not autoload " . implode(',', $cls) , DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } @@ -2738,7 +2860,7 @@ class DB_DataObject extends DB_DataObject_Overload } - + $_DB_DATAOBJECT['LINKS'][$this->_database] = array(); foreach ($schemas as $ini) { $links = @@ -2746,9 +2868,13 @@ class DB_DataObject extends DB_DataObject_Overload $_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] : str_replace('.ini','.links.ini',$ini); - if (empty($_DB_DATAOBJECT['LINKS'][$this->_database]) && file_exists($links) && is_file($links)) { + if (file_exists($links) && is_file($links)) { /* not sure why $links = ... here - TODO check if that works */ - $_DB_DATAOBJECT['LINKS'][$this->_database] = parse_ini_file($links, true); + $_DB_DATAOBJECT['LINKS'][$this->_database] = array_merge( + $_DB_DATAOBJECT['LINKS'][$this->_database], + parse_ini_file($links, true) + ); + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("Loaded links.ini file: $links","links",1); } @@ -2929,14 +3055,12 @@ class DB_DataObject extends DB_DataObject_Overload } if ($link) { if ($obj->get($link, $this->$row)) { - $obj->free(); return $obj; } return false; } if ($obj->get($this->$row)) { - $obj->free(); return $obj; } return false; @@ -3079,7 +3203,7 @@ class DB_DataObject extends DB_DataObject_Overload return; } - + //echo '
'; print_r(func_get_args());
         $useWhereAsOn = false;
         // support for 2nd argument as an array of options
         if (is_array($joinType)) {
@@ -3119,8 +3243,39 @@ class DB_DataObject extends DB_DataObject_Overload
         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
        
 
+        /// CHANGED 26 JUN 2009 - we prefer links from our local table over the remote one.
         
-        
+        /* otherwise see if there are any links from this table to the obj. */
+        //print_r($this->links());
+        if (($ofield === false) && ($links = $this->links())) {
+            foreach ($links as $k => $v) {
+                /* link contains {this column} = {linked table}:{linked column} */
+                $ar = explode(':', $v);
+                // Feature Request #4266 - Allow joins with multiple keys
+                if (strpos($k, ',') !== false) {
+                    $k = explode(',', $k);
+                }
+                if (strpos($ar[1], ',') !== false) {
+                    $ar[1] = explode(',', $ar[1]);
+                }
+
+                if ($ar[0] == $obj->__table) {
+                    if ($joinCol !== false) {
+                        if ($k == $joinCol) {
+                            $tfield = $k;
+                            $ofield = $ar[1];
+                            break;
+                        } else {
+                            continue;
+                        }
+                    } else {
+                        $tfield = $k;
+                        $ofield = $ar[1];
+                        break;
+                    }
+                }
+            }
+        }
          /* look up the links for obj table */
         //print_r($obj->links());
         if (!$ofield && ($olinks = $obj->links())) {
@@ -3164,37 +3319,6 @@ class DB_DataObject extends DB_DataObject_Overload
             }
         }
 
-        /* otherwise see if there are any links from this table to the obj. */
-        //print_r($this->links());
-        if (($ofield === false) && ($links = $this->links())) {
-            foreach ($links as $k => $v) {
-                /* link contains {this column} = {linked table}:{linked column} */
-                $ar = explode(':', $v);
-                // Feature Request #4266 - Allow joins with multiple keys
-                if (strpos($k, ',') !== false) {
-                    $k = explode(',', $k);
-                }
-                if (strpos($ar[1], ',') !== false) {
-                    $ar[1] = explode(',', $ar[1]);
-                }
-
-                if ($ar[0] == $obj->__table) {
-                    if ($joinCol !== false) {
-                        if ($k == $joinCol) {
-                            $tfield = $k;
-                            $ofield = $ar[1];
-                            break;
-                        } else {
-                            continue;
-                        }
-                    } else {
-                        $tfield = $k;
-                        $ofield = $ar[1];
-                        break;
-                    }
-                }
-            }
-        }
         // finally if these two table have column names that match do a join by default on them
 
         if (($ofield === false) && $joinCol) {
@@ -3267,14 +3391,23 @@ class DB_DataObject extends DB_DataObject_Overload
                     DB_DATAOBJECT_ERROR_INVALIDCONFIG);
                 return false;
             }
+            
+            $ignore_null = !isset($options['disable_null_strings'])
+                    || !is_string($options['disable_null_strings'])
+                    || strtolower($options['disable_null_strings']) !== 'full' ;
+            
 
             foreach($items as $k => $v) {
-                if (!isset($obj->$k)) {
+                if (!isset($obj->$k) && $ignore_null) {
                     continue;
                 }
                 
                 $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
                 
+                if (DB_DataObject::_is_null($obj,$k)) {
+                	$obj->whereAdd("{$joinAs}.{$kSql} IS NULL");
+                	continue;
+                }
                 
                 if ($v & DB_DATAOBJECT_STR) {
                     $obj->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) (
@@ -3296,14 +3429,9 @@ class DB_DataObject extends DB_DataObject_Overload
                     if (PEAR::isError($value)) {
                         $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
                         return false;
-                    }
-                    if (!isset($options['disable_null_strings']) && strtolower($value) === 'null') {
-                        $obj->whereAdd("{$joinAs}.{$kSql} IS NULL");
-                        continue;
-                    } else {
-                        $obj->whereAdd("{$joinAs}.{$kSql} = $value");
-                        continue;
-                    }
+                    } 
+                    $obj->whereAdd("{$joinAs}.{$kSql} = $value");
+                    continue;
                 }
                 
                 
@@ -3383,22 +3511,25 @@ class DB_DataObject extends DB_DataObject_Overload
             case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
                 
                 // Feature Request #4266 - Allow joins with multiple keys
-                $this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
+                $jadd = "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
+                //$this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
                 if (is_array($ofield)) {
                 	$key_count = count($ofield);
                     for($i = 0; $i < $key_count; $i++) {
                     	if ($i == 0) {
-                    		$this->_join .= " ON ({$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]}) ";
+                    		$jadd .= " ON ({$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]}) ";
                     	}
                     	else {
-                    		$this->_join .= " AND {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} ";
+                    		$jadd .= " AND {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} ";
                     	}
                     }
-                    $this->_join .= ' ' . $appendJoin . ' ';
+                    $jadd .= ' ' . $appendJoin . ' ';
                 } else {
-	                $this->_join .= " ON ({$joinAs}.{$ofield}={$table}.{$tfield}) {$appendJoin} ";
+	                $jadd .= " ON ({$joinAs}.{$ofield}={$table}.{$tfield}) {$appendJoin} ";
                 }
-
+                // jadd avaliable for debugging join build.
+                //echo $jadd ."\n";
+                $this->_join .= $jadd;
                 break;
                 
             case '': // this is just a standard multitable select..
@@ -3459,11 +3590,11 @@ class DB_DataObject extends DB_DataObject_Overload
                 continue;
             }
             
-            if (empty($from[$k]) && $skipEmpty) {
+            if (empty($from[sprintf($format,$k)]) && $skipEmpty) {
                 continue;
             }
             
-            if (!isset($from[sprintf($format,$k)])) {
+            if (!isset($from[sprintf($format,$k)]) && !DB_DataObject::_is_null($from, sprintf($format,$k))) {
                 continue;
             }
            
@@ -3592,7 +3723,7 @@ class DB_DataObject extends DB_DataObject_Overload
             
             // if not null - and it's not set.......
             
-            if (!isset($this->$key) && ($val & DB_DATAOBJECT_NOTNULL)) {
+            if ($val & DB_DATAOBJECT_NOTNULL && DB_DataObject::_is_null($this, $key)) {
                 // dont check empty sequence key values..
                 if (($key == $seq[0]) && ($seq[1] == true)) {
                     continue;
@@ -3602,7 +3733,7 @@ class DB_DataObject extends DB_DataObject_Overload
             }
             
             
-            if (!isset($options['disable_null_strings']) && is_string($this->$key) && (strtolower($this->$key) == 'null')) {
+             if (DB_DataObject::_is_null($this, $key)) {
                 if ($val & DB_DATAOBJECT_NOTNULL) {
                     $this->debug("'null' field used for '$key', but it is defined as NOT NULL", 'VALIDATION', 4);
                     $ret[$key] = false;
@@ -3817,13 +3948,14 @@ class DB_DataObject extends DB_DataObject_Overload
         //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
         switch (true) {
             // set to null and column is can be null...
-            case (!isset($options['disable_null_strings']) && (strtolower($value) == 'null') && (!($cols[$col] & DB_DATAOBJECT_NOTNULL))):
+            case ((!($cols[$col] & DB_DATAOBJECT_NOTNULL)) && DB_DataObject::_is_null($value, false)):
             case (is_object($value) && is_a($value,'DB_DataObject_Cast')): 
                 $this->$col = $value;
                 return true;
                 
             // fail on setting null on a not null field..
-            case (!isset($options['disable_null_strings']) && (strtolower($value) == 'null') && ($cols[$col] & DB_DATAOBJECT_NOTNULL)):
+            case (($cols[$col] & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($value,false)):
+
                 return false;
         
             case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
@@ -4138,9 +4270,65 @@ class DB_DataObject extends DB_DataObject_Overload
         if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array();
         }
+
+        if (is_array($this->_link_loaded)) {
+            foreach ($this->_link_loaded as $do) {
+                $do->free();
+            }
+        }
+
         
     }
+    /**
+    * Evaluate whether or not a value is set to null, taking the 'disable_null_strings' option into account.
+    * If the value is a string set to "null" and the "disable_null_strings" option is not set to 
+    * true, then the value is considered to be null.
+    * If the value is actually a PHP NULL value, and "disable_null_strings" has been set to 
+    * the value "full", then it will also be considered null. - this can not differenticate between not set
+    * 
+    * 
+    * @param  object|array $obj_or_ar 
+    * @param  string|false $prop prperty
     
+    * @access private
+    * @return bool  object
+    */
+    function _is_null($obj_or_ar , $prop) 
+    {
+    	global $_DB_DATAOBJECT;
+    	
+        
+        $isset = $prop === false ? isset($obj_or_ar) : 
+            (is_array($obj_or_ar) ? isset($obj_or_ar[$prop]) : isset($obj_or_ar->$prop));
+        
+        $value = $isset ? 
+            ($prop === false ? $obj_or_ar : 
+                (is_array($obj_or_ar) ? $obj_or_ar[$prop] : $obj_or_ar->$prop))
+            : null;
+        
+        
+        
+    	$options = $_DB_DATAOBJECT['CONFIG'];
+    	
+        $null_strings = !isset($options['disable_null_strings'])
+                    || $options['disable_null_strings'] === false;
+                    
+        $crazy_null = isset($options['disable_null_strings'])
+                && is_string($options['disable_null_strings'])
+                && strtolower($options['disable_null_strings'] === 'full');
+        
+        if ( $null_strings && $isset  && is_string($value)  && (strtolower($value) === 'null') ) {
+            return true;
+        }
+        
+        if ( $crazy_null && !$isset )  {
+        	return true;
+        }
+        
+        return false;
+        
+    	
+    }
     
     /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/
     
@@ -4163,3 +4351,4 @@ if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
     }
 }
 
+