From: Diogo Cordeiro Date: Sat, 27 Apr 2019 17:21:14 +0000 (+0100) Subject: Ugly patch to maintain old DB handle code working quietly X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=daa5f87f;p=quix0rs-gnu-social.git Ugly patch to maintain old DB handle code working quietly We have to replace this database engine with a modern one --- diff --git a/extlib/DB/DataObject.php b/extlib/DB/DataObject.php index e31f35db9f..aa569325a0 100644 --- a/extlib/DB/DataObject.php +++ b/extlib/DB/DataObject.php @@ -18,7 +18,7 @@ * @version CVS: $Id: DataObject.php 336751 2015-05-12 04:39:50Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ - + /* =========================================================================== * @@ -30,59 +30,6 @@ * =========================================================================== */ -/** - * The main "DB_DataObject" class is really a base class for your own tables classes - * - * // Set up the class by creating an ini file (refer to the manual for more details - * [DB_DataObject] - * database = mysql:/username:password@host/database - * schema_location = /home/myapplication/database - * class_location = /home/myapplication/DBTables/ - * clase_prefix = DBTables_ - * - * - * //Start and initialize...................... - dont forget the & - * $config = parse_ini_file('example.ini',true); - * $options = &PEAR::getStaticProperty('DB_DataObject','options'); - * $options = $config['DB_DataObject']; - * - * // example of a class (that does not use the 'auto generated tables data') - * class mytable extends DB_DataObject { - * // mandatory - set the table - * var $_database_dsn = "mysql://username:password@localhost/database"; - * var $__table = "mytable"; - * function table() { - * return array( - * 'id' => 1, // integer or number - * 'name' => 2, // string - * ); - * } - * function keys() { - * return array('id'); - * } - * } - * - * // use in the application - * - * - * Simple get one row - * - * $instance = new mytable; - * $instance->get("id",12); - * echo $instance->somedata; - * - * - * Get multiple rows - * - * $instance = new mytable; - * $instance->whereAdd("ID > 12"); - * $instance->whereAdd("ID < 14"); - * $instance->find(); - * while ($instance->fetch()) { - * echo $instance->somedata; - * } - - /** * Needed classes * - we use getStaticProperty from PEAR pretty extensively (cant remove it ATM) @@ -98,9 +45,6 @@ define('DB_DATAOBJECT_FETCHMODE_ORDERED', 1); define('DB_DATAOBJECT_FETCHMODE_ASSOC', 2); - - - /** * these are constants for the get_table array * user to determine what type of escaping is required around the object vars. @@ -160,7 +104,7 @@ define('DB_DATAOBJECT_WHEREADD_ONLY', true); * - config = aliased view of PEAR::getStaticPropery('DB_DataObject','options') * done for performance. * - array of loaded classes by autoload method - to stop it doing file access request over and over again! */ -$GLOBALS['_DB_DATAOBJECT']['RESULTS'] = array(); +$GLOBALS['_DB_DATAOBJECT']['RESULTS'] = array(); $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ'] = 1; $GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array(); $GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'] = array(); @@ -174,11 +118,10 @@ $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = false; $GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0; - // this will be horrifically slow!!!! // these two are BC/FC handlers for call in PHP4/5 - + if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) { class DB_DataObject_Overload { @@ -188,9 +131,10 @@ if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) { $this->_call($method, $args, $return); return $return; } + public function __sleep() { - return array_keys(get_object_vars($this)) ; + return array_keys(get_object_vars($this)); } } } else { @@ -200,18 +144,13 @@ if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) { } - - - - +/* +* +* @package DB_DataObject +* @author Alan Knowles +* @since PHP 4.0 +*/ - /* - * - * @package DB_DataObject - * @author Alan Knowles - * @since PHP 4.0 - */ - class DB_DataObject extends DB_DataObject_Overload { /** @@ -242,1562 +181,1074 @@ class DB_DataObject extends DB_DataObject_Overload /* Major Public Methods */ /* (designed to be optionally then called with parent::method()) */ /* ============================================================= */ - - /** - * Get a result using key, value. + * The Database connection dsn (as described in the PEAR DB) + * only used really if you are writing a very simple application/test.. + * try not to use this - it is better stored in configuration files.. * - * for example - * $object->get("ID",1234); - * Returns Number of rows located (usually 1) for success, - * and puts all the table columns into this classes variables + * @access private + * @var string + */ + public $_database_dsn = ''; + /** + * The Database connection id (md5 sum of databasedsn) * - * see the fetch example on how to extend this. + * @access private + * @var string + */ + public $_database_dsn_md5 = ''; + /** + * The Database name + * created in __connection * - * if no value is entered, it is assumed that $key is a value - * and get will then use the first key in keys() - * to obtain the key. + * @access private + * @var string + */ + public $_database = ''; + /** + * The QUERY rules + * This replaces alot of the private variables + * used to build a query, it is unset after find() is run. + * + * + * + * @access private + * @var array + */ + public $_query = array( + 'condition' => '', // the WHERE condition + 'group_by' => '', // the GROUP BY condition + 'order_by' => '', // the ORDER BY condition + 'having' => '', // the HAVING condition + 'useindex' => '', // the USE INDEX condition + 'limit_start' => '', // the LIMIT condition + 'limit_count' => '', // the LIMIT condition + 'data_select' => '*', // the columns to be SELECTed + 'unions' => array(), // the added unions, + 'derive_table' => '', // derived table name (BETA) + 'derive_select' => '', // derived table select (BETA) + ); + /** + * Database result id (references global $_DB_DataObject[results] + * + * @access private + * @var integer + */ + public $_DB_resultid; + /** + * ResultFields - on the last call to fetch(), resultfields is sent here, + * so we can clean up the memory. * - * @param string $k column - * @param string $v value * @access public - * @return int No. of rows + * @var array */ - public function get($k = null, $v = null) - { - global $_DB_DATAOBJECT; - if (empty($_DB_DATAOBJECT['CONFIG'])) { - DB_DataObject::_loadConfig(); - } - $keys = array(); - - if ($v === null) { - $v = $k; - $keys = $this->keys(); - if (!$keys) { - $this->raiseError("No Keys available for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); - return false; - } - $k = $keys[0]; - } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("$k $v " .print_r($keys, true), "GET"); - } - - if ($v === null) { - $this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS); - return false; - } - $this->$k = $v; - return $this->find(1); - } - + public $_resultFields = false; /** - * Get the value of the primary id + * Have the links been loaded? + * if they have it contains a array of those variables. * - * While I normally use 'id' as the PRIMARY KEY value, some database use - * {table}_id as the column name. + * @access private + * @var boolean | array + */ + public $_link_loaded = false; + /** + * The JOIN condition * - * To save a bit of typing, + * @access private + * @var string + */ + public $_join = ''; + /** + * Last Error that has occured + * - use $this->_lastError or + * $last_error = PEAR::getStaticProperty('DB_DataObject','lastError'); * - * $id = $do->pid(); + * @access public + * @var object PEAR_Error (or false) + */ + public $_lastError = false; + + /** + * sets and returns debug level + * eg. DB_DataObject::debugLevel(4); * - * @return the id + * @param int $v level + * @access public + * @return int|none */ - public function pid() + public static function debugLevel($v = null) { - $keys = $this->keys(); - if (!$keys) { - $this->raiseError( - "No Keys available for {$this->tableName()}", - DB_DATAOBJECT_ERROR_INVALIDCONFIG - ); - return false; + global $_DB_DATAOBJECT; + if (empty($_DB_DATAOBJECT['CONFIG'])) { + (new DB_DataObject)->_loadConfig(); } - $k = $keys[0]; - if (empty($this->$k)) { // we do not - $this->raiseError( - "pid() called on Object where primary key value not available", - DB_DATAOBJECT_ERROR_NODATA - ); - return false; + if ($v !== null) { + $r = isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0; + $_DB_DATAOBJECT['CONFIG']['debug'] = $v; + return $r; } - return $this->$k; + return isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0; } - - /** - * build the basic select query. + * Define the global $_DB_DATAOBJECT['CONFIG'] as an alias to PEAR::getStaticProperty('DB_DataObject','options'); * - * @access private + * After Profiling DB_DataObject, I discoved that the debug calls where taking + * considerable time (well 0.1 ms), so this should stop those calls happening. as + * all calls to debug are wrapped with direct variable queries rather than actually calling the funciton + * THIS STILL NEEDS FURTHER INVESTIGATION + * + * @access public + * @return void an error object */ - - public function _build_select() + public function _loadConfig() { global $_DB_DATAOBJECT; - $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); - if ($quoteIdentifiers) { - $this->_connect(); - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - } - $tn = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()) ; - if (!empty($this->_query['derive_table']) && !empty($this->_query['derive_select'])) { - - // this is a derived select.. - // not much support in the api yet.. - - $sql = 'SELECT ' . - $this->_query['derive_select'] - .' FROM ( SELECT'. - $this->_query['data_select'] . " \n" . - " FROM $tn " . $this->_query['useindex'] . " \n" . - $this->_join . " \n" . - $this->_query['condition'] . " \n" . - $this->_query['group_by'] . " \n" . - $this->_query['having'] . " \n" . - ') ' . $this->_query['derive_table']; - - return $sql; - } - - - - $sql = 'SELECT ' . - $this->_query['data_select'] . " \n" . - " FROM $tn " . $this->_query['useindex'] . " \n" . - $this->_join . " \n" . - $this->_query['condition'] . " \n" . - $this->_query['group_by'] . " \n" . - $this->_query['having'] . " \n"; - - return $sql; + + $_DB_DATAOBJECT['CONFIG'] = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); + return null; } - /** - * find results, either normal or crosstable - * - * for example - * - * $object = new mytable(); - * $object->ID = 1; - * $object->find(); - * - * - * will set $object->N to number of rows, and expects next command to fetch rows - * will return $object->N - * - * if an error occurs $object->N will be set to false and return value will also be false; - * if numRows is not supported it will - * - * - * @param boolean $n Fetch first result - * @access public - * @return mixed (number of rows returned, or true if numRows fetching is not supported) + * (deprecated - use ::get / and your own caching method) + * @param $class + * @param $k + * @param null $v + * @return bool */ - public function find($n = false) + public static function staticGet($class, $k, $v = null) { + $lclass = strtolower($class); global $_DB_DATAOBJECT; - if ($this->_query === false) { - $this->raiseError( - "You cannot do two queries on the same object (copy it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - return false; - } - if (empty($_DB_DATAOBJECT['CONFIG'])) { - DB_DataObject::_loadConfig(); + (new DB_DataObject)->_loadConfig(); } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug($n, "find", 1); + + $key = "$k:$v"; + if ($v === null) { + $key = $k; } - if (!strlen($this->tableName())) { - // xdebug can backtrace this! - trigger_error("NO \$__table SPECIFIED in class definition", E_USER_ERROR); + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + (new DB_DataObject)->debug("$class $key", "STATIC GET - TRY CACHE"); } - $this->N = 0; - $query_before = $this->_query; - $this->_build_condition($this->table()) ; - - - $this->_connect(); - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - - - $sql = $this->_build_select(); - - foreach ($this->_query['unions'] as $union_ar) { - $sql .= $union_ar[1] . $union_ar[0]->_build_select() . " \n"; + if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) { + return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; } - - $sql .= $this->_query['order_by'] . " \n"; - - - /* We are checking for method modifyLimitQuery as it is PEAR DB specific */ - if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) || - ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) { - /* PEAR DB specific */ - - if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { - $sql = $DB->modifyLimitQuery($sql, $this->_query['limit_start'], $this->_query['limit_count']); - } - } else { - /* theoretically MDB2! */ - if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { - $DB->setLimit($this->_query['limit_count'], $this->_query['limit_start']); - } + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + (new DB_DataObject)->debug("$class $key", "STATIC GET - NOT IN CACHE"); } - - - $err = $this->_query($sql); - if (is_a($err, 'PEAR_Error')) { - return false; - } - - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("CHECK autofetchd $n", "find", 1); - } - - // find(true) - - $ret = $this->N; - if (!$ret && !empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { - // clear up memory if nothing found!? - unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); + + $obj = DB_DataObject::factory(substr($class, strlen($_DB_DATAOBJECT['CONFIG']['class_prefix']))); + if ((new PEAR)->isError($obj)) { + $dor = new DB_DataObject(); + $dor->raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS); + $r = false; + return $r; } - - if ($n && $this->N > 0) { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("ABOUT TO AUTOFETCH", "find", 1); - } - $fs = $this->fetch(); - // if fetch returns false (eg. failed), then the backend doesnt support numRows (eg. ret=true) - // - hence find() also returns false.. - $ret = ($ret === true) ? $fs : $ret; + + if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) { + $_DB_DATAOBJECT['CACHE'][$lclass] = array(); } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("DONE", "find", 1); + if (!$obj->get($k, $v)) { + $dor = new DB_DataObject(); + $dor->raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA); + + $r = false; + return $r; } - $this->_query = $query_before; - return $ret; + $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj; + return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; } /** - * fetches next row into this objects var's - * - * returns 1 on success 0 on failure - * - * - * - * Example - * $object = new mytable(); - * $object->name = "fred"; - * $object->find(); - * $store = array(); - * while ($object->fetch()) { - * echo $this->ID; - * $store[] = $object; // builds an array of object lines. - * } + * Debugger. - use this in your extended classes to output debugging information. * - * to add features to a fetch - * function fetch () { - * $ret = parent::fetch(); - * $this->date_formated = date('dmY',$this->date); - * return $ret; - * } + * Uses DB_DataObject::DebugLevel(x) to turn it on * - * @access public - * @return boolean on success + * @param string $message - message to output + * @param int $logtype - bold at start + * @param int $level - output level + * @return void + * @access public */ - public function fetch() + public function debug($message, $logtype = 0, $level = 1) { global $_DB_DATAOBJECT; - if (empty($_DB_DATAOBJECT['CONFIG'])) { - DB_DataObject::_loadConfig(); - } - if (empty($this->N)) { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("No data returned from FIND (eg. N is 0)", "FETCH", 3); - } - return false; - } - - if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) || - !is_object($result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug('fetched on object after fetch completed (no results found)'); - } - return false; - } - - - $array = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC); - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug(serialize($array), "FETCH"); - } - - // fetched after last row.. - if ($array === null) { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $t= explode(' ', microtime()); - - $this->debug( - "Last Data Fetch'ed after " . - ($t[0]+$t[1]- $_DB_DATAOBJECT['QUERYENDTIME']) . - " seconds", - "FETCH", - 1 - ); - } - // reduce the memory usage a bit... (but leave the id in, so count() works ok on it) - unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); - - // we need to keep a copy of resultfields locally so toArray() still works - // however we dont want to keep it in the global cache.. - - if (!empty($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { - $this->_resultFields = $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]; - unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]); - } - // this is probably end of data!! - //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA); - return false; - } - // make sure resultFields is always empty.. - $this->_resultFields = false; - - if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { - // note: we dont declare this to keep the print_r size down. - $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array)); - } - $replace = array('.', ' '); - foreach ($array as $k=>$v) { - // use strpos as str_replace is slow. - $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ? - $k : str_replace($replace, '_', $k); - - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3); - } - $this->$kk = $array[$k]; + + if (empty($_DB_DATAOBJECT['CONFIG']['debug']) || + (is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) && $_DB_DATAOBJECT['CONFIG']['debug'] < $level)) { + return null; } - - // set link flag - $this->_link_loaded=false; - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("{$this->tableName()} DONE", "fetchrow", 2); + // this is a bit flaky due to php's wonderfull class passing around crap.. + // but it's about as good as it gets.. + $class = (isset($this) && is_a($this, 'DB_DataObject')) ? get_class($this) : 'DB_DataObject'; + + if (!is_string($message)) { + $message = print_r($message, true); } - if (($this->_query !== false) && empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) { - $this->_query = false; + if (!is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) && is_callable($_DB_DATAOBJECT['CONFIG']['debug'])) { + return call_user_func($_DB_DATAOBJECT['CONFIG']['debug'], $class, $message, $logtype, $level); } - return true; - } - - /** - * fetches all results as an array, - * - * return format is dependant on args. - * if selectAdd() has not been called on the object, then it will add the correct columns to the query. - * - * A) Array of values (eg. a list of 'id') - * - * $x = DB_DataObject::factory('mytable'); - * $x->whereAdd('something = 1') - * $ar = $x->fetchAll('id'); - * -- returns array(1,2,3,4,5) - * - * B) Array of values (not from table) - * - * $x = DB_DataObject::factory('mytable'); - * $x->whereAdd('something = 1'); - * $x->selectAdd(); - * $x->selectAdd('distinct(group_id) as group_id'); - * $ar = $x->fetchAll('group_id'); - * -- returns array(1,2,3,4,5) - * * - * C) A key=>value associative array - * - * $x = DB_DataObject::factory('mytable'); - * $x->whereAdd('something = 1') - * $ar = $x->fetchAll('id','name'); - * -- returns array(1=>'fred',2=>'blogs',3=> ....... - * - * D) array of objects - * $x = DB_DataObject::factory('mytable'); - * $x->whereAdd('something = 1'); - * $ar = $x->fetchAll(); - * - * E) array of arrays (for example) - * $x = DB_DataObject::factory('mytable'); - * $x->whereAdd('something = 1'); - * $ar = $x->fetchAll(false,false,'toArray'); - * - * - * @param string|false $k key - * @param string|false $v value - * @param string|false $method method to call on each result to get array value (eg. 'toArray') - * @access public - * @return array format dependant on arguments, may be empty - */ - public function fetchAll($k= false, $v = false, $method = false) - { - // should it even do this!!!?!? - if ($k !== false && - ( // only do this is we have not been explicit.. - empty($this->_query['data_select']) || - ($this->_query['data_select'] == '*') - ) - ) { - $this->selectAdd(); - $this->selectAdd($k); - if ($v !== false) { - $this->selectAdd($v); - } + if (!ini_get('html_errors')) { + echo "$class : $logtype : $message\n"; + flush(); + return null; } - - $this->find(); - $ret = array(); - while ($this->fetch()) { - if ($v !== false) { - $ret[$this->$k] = $this->$v; - continue; - } - $ret[] = $k === false ? - ($method == false ? clone($this) : $this->$method()) - : $this->$k; + if (!is_string($message)) { + $message = print_r($message, true); } - return $ret; + $colorize = ($logtype == 'ERROR') ? '' : ''; + echo "{$colorize}$class: $logtype: " . nl2br(htmlspecialchars($message)) . "
\n"; } - - + /** - * Adds a condition to the WHERE statement, defaults to AND + * classic factory method for loading a table class + * usage: $do = DB_DataObject::factory('person') + * WARNING - this may emit a include error if the file does not exist.. + * use @ to silence it (if you are sure it is acceptable) + * eg. $do = @DB_DataObject::factory('person') * - * $object->whereAdd(); //reset or cleaer ewhwer - * $object->whereAdd("ID > 20"); - * $object->whereAdd("age > 20","OR"); + * table name can bedatabasename/table + * - and allow modular dataobjects to be written.. + * (this also helps proxy creation) * - * @param string $cond condition - * @param string $logic optional logic "OR" (defaults to "AND") - * @access public - * @return string|PEAR::Error - previous condition or Error when invalid args found + * 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|PEAR_Error|true */ - public function whereAdd($cond = false, $logic = 'AND') + + + public static function factory($table = '') { - // for PHP5.2.3 - there is a bug with setting array properties of an object. - $_query = $this->_query; - - if (!isset($this->_query) || ($_query === false)) { - return $this->raiseError( - "You cannot do two queries on the same object (clone it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - } - - if ($cond === false) { - $r = $this->_query['condition']; - $_query['condition'] = ''; - $this->_query = $_query; - return preg_replace('/^\s+WHERE\s+/', '', $r); + global $_DB_DATAOBJECT; + + + // multi-database support.. - experimental. + $database = ''; + + if (strpos($table, '/') !== false) { + list($database, $table) = explode('.', $table, 2); } - // check input...= 0 or ' ' == error! - if (!trim($cond)) { - return $this->raiseError("WhereAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + + if (empty($_DB_DATAOBJECT['CONFIG'])) { + (new DB_DataObject)->_loadConfig(); } - $r = $_query['condition']; - if ($_query['condition']) { - $_query['condition'] .= " {$logic} ( {$cond} )"; - $this->_query = $_query; - return $r; + // no configuration available for database + if (!empty($database) && empty($_DB_DATAOBJECT['CONFIG']['database_' . $database])) { + $do = new DB_DataObject(); + $do->raiseError( + "unable to find database_{$database} in Configuration, It is required for factory with database", + 0, + PEAR_ERROR_DIE + ); } - $_query['condition'] = " WHERE ( {$cond} ) "; - $this->_query = $_query; - return $r; - } - /** - * Adds a 'IN' condition to the WHERE statement - * - * $object->whereAddIn('id', $array, 'int'); //minimal usage - * $object->whereAddIn('price', $array, 'float', 'OR'); // cast to float, and call whereAdd with 'OR' - * $object->whereAddIn('name', $array, 'string'); // quote strings - * - * @param string $key key column to match - * @param array $list list of values to match - * @param string $type string|int|integer|float|bool cast to type. - * @param string $logic optional logic to call whereAdd with eg. "OR" (defaults to "AND") - * @access public - * @return string|PEAR::Error - previous condition or Error when invalid args found - */ - public function whereAddIn($key, $list, $type, $logic = 'AND') - { - $not = ''; - if ($key[0] == '!') { - $not = 'NOT '; - $key = substr($key, 1); - } - // fix type for short entry. - $type = $type == 'int' ? 'integer' : $type; - if ($type == 'string') { - $this->_connect(); + /* + if ($table === '') { + if (is_a($this,'DB_DataObject') && strlen($this->tableName())) { + $table = $this->tableName(); + } else { + return DB_DataObject::raiseError( + "factory did not recieve a table name", + DB_DATAOBJECT_ERROR_INVALIDARGS); + } } - $ar = array(); - foreach ($list as $k) { - settype($k, $type); - $ar[] = $type == 'string' ? $this->_quote($k) : $k; - } - - if (!$ar) { - return $not ? $this->_query['condition'] : $this->whereAdd("1=0"); - } - return $this->whereAdd("$key $not IN (". implode(',', $ar). ')', $logic); - } + */ + // does this need multi db support?? + $cp = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? + explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']['class_prefix']) : ''; - - - /** - * Adds a order by condition - * - * $object->orderBy(); //clears order by - * $object->orderBy("ID"); - * $object->orderBy("ID,age"); - * - * @param string $order Order - * @access public - * @return none|PEAR::Error - invalid args only - */ - public function orderBy($order = false) - { - if ($this->_query === false) { - $this->raiseError( - "You cannot do two queries on the same object (copy it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - return false; + //print_r($cp); + + // 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); } - if ($order === false) { - $this->_query['order_by'] = ''; - return; + + + $rclass = $ce ? $class : (new DB_DataObject)->_autoloadClass($class, $table); + // proxy = full|light + if (!$rclass && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { + (new 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'; + require_once 'Generator.php'; + + $d = new DB_DataObject; + + $d->__table = $table; + + $ret = $d->_connect(); + if (is_object($ret) && is_a($ret, 'PEAR_Error')) { + return $ret; + } + + $x = new DB_DataObject_Generator; + return $x->$proxyMethod($d->_database, $table); } - // check input...= 0 or ' ' == error! - if (!trim($order)) { - return $this->raiseError("orderBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + + if (!$rclass || !class_exists($rclass)) { + $dor = new DB_DataObject(); + return $dor->raiseError( + "factory could not find class " . + (is_array($class) ? implode(PATH_SEPARATOR, $class) : $class) . + "from $table", + DB_DATAOBJECT_ERROR_INVALIDCONFIG + ); } - - if (!$this->_query['order_by']) { - $this->_query['order_by'] = " ORDER BY {$order} "; - return; + + $ret = new $rclass(); + + if (!empty($database)) { + (new DB_DataObject)->debug("Setting database to $database", "FACTORY", 1); + $ret->database($database); } - $this->_query['order_by'] .= " , {$order}"; + return $ret; } /** - * Adds a group by condition - * - * $object->groupBy(); //reset the grouping - * $object->groupBy("ID DESC"); - * $object->groupBy("ID,age"); + * Default error handling is to create a pear error, but never return it. + * if you need to handle errors you should look at setting the PEAR_Error callback + * this is due to the fact it would wreck havoc on the internal methods! * - * @param string $group Grouping + * @param int $message message + * @param int $type type + * @param int $behaviour behaviour (die or continue!); * @access public - * @return none|PEAR::Error - invalid args only + * @return error|int|object */ - public function groupBy($group = false) + public function raiseError($message, $type = null, $behaviour = null) { - if ($this->_query === false) { - $this->raiseError( - "You cannot do two queries on the same object (copy it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - return false; - } - if ($group === false) { - $this->_query['group_by'] = ''; - return; + global $_DB_DATAOBJECT; + + if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) { + $behaviour = null; } - // check input...= 0 or ' ' == error! - if (!trim($group)) { - return $this->raiseError("groupBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + + $error = &(new PEAR)->getStaticProperty('DB_DataObject', 'lastError'); + + + // no checks for production here?....... - we log errors before we throw them. + DB_DataObject::debug($message, 'ERROR', 1); + + + if ((new PEAR)->isError($message)) { + $error = $message; + } else { + //require_once 'DB/DataObject/Error.php'; + require_once 'Error.php'; + $dor = new PEAR(); + $error = $dor->raiseError( + $message, + $type, + $behaviour, + $opts = null, + $userinfo = null, + 'DB_DataObject_Error' + ); } - - - if (!$this->_query['group_by']) { - $this->_query['group_by'] = " GROUP BY {$group} "; - return; + // this will never work totally with PHP's object model. + // as this is passed on static calls (like staticGet in our case) + + $_DB_DATAOBJECT['LASTERROR'] = $error; + + if (isset($this) && is_object($this) && is_subclass_of($this, 'db_dataobject')) { + $this->_lastError = $error; } - $this->_query['group_by'] .= " , {$group}"; + + return $error; } /** - * Adds a having clause - * - * $object->having(); //reset the grouping - * $object->having("sum(value) > 0 "); + * autoload Class * - * @param string $having condition - * @access public - * @return none|PEAR::Error - invalid args only + * @param string|array $class Class + * @param bool $table Table trying to load. + * @return string classname on Success + * @access private */ - public function having($having = false) + public function _autoloadClass($class, $table = false) { - if ($this->_query === false) { - $this->raiseError( - "You cannot do two queries on the same object (copy it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); + global $_DB_DATAOBJECT; + + if (empty($_DB_DATAOBJECT['CONFIG'])) { + DB_DataObject::_loadConfig(); + } + $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? + '' : $_DB_DATAOBJECT['CONFIG']['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; } - if ($having === false) { - $this->_query['having'] = ''; - return; + // 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']; + + + 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; } - // check input...= 0 or ' ' == error! - if (!trim($having)) { - return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + + $cls = is_array($class) ? $class : array($class); + + if (is_array($file) || !file_exists($file)) { + $found = false; + + $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) { + $dor = new DB_DataObject(); + $dor->raiseError( + "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; + } } - - - if (!$this->_query['having']) { - $this->_query['having'] = " HAVING {$having} "; - return; + + include_once $file; + + + $ce = false; + foreach ($cls as $c) { + $ce = substr(phpversion(), 0, 1) > 4 ? class_exists($c, false) : class_exists($c); + if ($ce) { + $class = $c; + break; + } } - $this->_query['having'] .= " AND {$having}"; + if (!$ce) { + $dor = new DB_DataObject(); + $dor->raiseError( + "autoload:Could not autoload " . implode(',', $cls), + DB_DATAOBJECT_ERROR_INVALIDCONFIG + ); + return false; + } + return $class; } /** - * Adds a using Index + * connects to the database * - * $object->useIndex(); //reset the use Index - * $object->useIndex("some_index"); * - * Note do not put unfiltered user input into theis method. - * This is mysql specific at present? - might need altering to support other databases. + * TODO: tidy this up - This has grown to support a number of connection options like + * a) dynamic changing of ini file to change which database to connect to + * b) multi data via the table_{$table} = dsn ini option + * c) session based storage. * - * @param string|array $index index or indexes to use. - * @access public - * @return none|PEAR::Error - invalid args only + * @access private + * @return error|PEAR|true */ - public function useIndex($index = false) + public function _connect() { - if ($this->_query === false) { - $this->raiseError( - "You cannot do two queries on the same object (copy it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - return false; + global $_DB_DATAOBJECT; + if (empty($_DB_DATAOBJECT['CONFIG'])) { + $this->_loadConfig(); } - if ($index=== false) { - $this->_query['useindex'] = ''; - return; + // Set database driver for reference + $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 ((new PEAR)->isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { + return $this->raiseError( + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message, + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, + PEAR_ERROR_DIE + ); + } + + 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'); + + $this->_database = ($db_driver != 'DB' && $hasGetDatabase) + ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() + : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; + + + if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') + && is_file($this->_database)) { + $this->_database = basename($this->_database); + } + if ($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'ibase') { + $this->_database = substr(basename($this->_database), 0, -4); + } + } + // theoretically we have a md5, it's listed in connections and it's not an error. + // so everything is ok! + return true; } - // check input...= 0 or ' ' == error! - if ((is_string($index) && !trim($index)) || (is_array($index) && !count($index))) { - return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + + // it's not currently connected! + // 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) { + if (!$this->_database && !strlen($this->tableName())) { + $this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null; + } + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $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}"]; + } elseif (!empty($options['database'])) { + $dsn = $options['database']; + } } - $index = is_array($index) ? implode(', ', $index) : $index; - - if (!$this->_query['useindex']) { - $this->_query['useindex'] = " USE INDEX ({$index}) "; - return; + + // if still no database... + if (!$dsn) { + return $this->raiseError( + "No database name / dsn found anywhere", + DB_DATAOBJECT_ERROR_INVALIDCONFIG, + PEAR_ERROR_DIE + ); + } + + + if (is_string($dsn)) { + $this->_database_dsn_md5 = md5($dsn); + } else { + /// support array based dsn's + $this->_database_dsn_md5 = md5(serialize($dsn)); + } + + if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { + 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'); + $this->_database = ($db_driver != 'DB' && $hasGetDatabase) + ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() + : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; + + if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') + && is_file($this->_database)) { + $this->_database = basename($this->_database); + } + } + return true; + } + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $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); + } + + // Note this is verbose deliberatly! + + if ($db_driver == 'DB') { + + /* PEAR DB connect */ + + // this allows the setings of compatibility on DB + $db_options = (new PEAR)->getStaticProperty('DB', 'options'); + require_once 'DB.php'; + if ($db_options) { + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn, $db_options); + } else { + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn); + } + } else { + /* assumption is MDB2 */ + require_once 'MDB2.php'; + // this allows the setings of compatibility on MDB2 + $db_options = (new PEAR)->getStaticProperty('MDB2', 'options'); + $db_options = is_array($db_options) ? $db_options : array(); + $db_options['portability'] = isset($db_options['portability']) + ? $db_options['portability'] : MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE; + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn, $db_options); + } + + + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug(print_r($_DB_DATAOBJECT['CONNECTIONS'], true), "CONNECT", 5); } - $this->_query['useindex'] = substr($this->_query['useindex'], 0, -2) . ", {$index}) "; + if ((new PEAR)->isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { + $this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString(), "CONNECT FAILED", 5); + return $this->raiseError( + "Connect failed, turn on debugging to 5 see why", + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, + PEAR_ERROR_DIE + ); + } + + if (empty($this->_database)) { + $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); + + $this->_database = ($db_driver != 'DB' && $hasGetDatabase) + ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() + : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; + + + if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') + && is_file($this->_database)) { + $this->_database = basename($this->_database); + } + } + + // Oracle need to optimize for portibility - not sure exactly what this does though :) + + return true; } + /** - * Sets the Limit - * - * $boject->limit(); // clear limit - * $object->limit(12); - * $object->limit(12,10); + * Return or assign the name of the current table * - * Note this will emit an error on databases other than mysql/postgress - * as there is no 'clean way' to implement it. - you should consider refering to - * your database manual to decide how you want to implement it. * - * @param string $a limit start (or number), or blank to reset - * @param string $b number + * @param string optinal table name to set * @access public - * @return none|PEAR::Error - invalid args only + * @return string The name of the current table */ - public function limit($a = null, $b = null) + public function tableName() { - if ($this->_query === false) { - $this->raiseError( - "You cannot do two queries on the same object (copy it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - return false; + global $_DB_DATAOBJECT; + $args = func_get_args(); + if (count($args)) { + $this->__table = $args[0]; } - - if ($a === null) { - $this->_query['limit_start'] = ''; - $this->_query['limit_count'] = ''; - return; + if (empty($this->__table)) { + return ''; } - // check input...= 0 or ' ' == error! - if ((!is_int($a) && ((string)((int)$a) !== (string)$a)) - || (($b !== null) && (!is_int($b) && ((string)((int)$b) !== (string)$b)))) { - return $this->raiseError("limit: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { + return strtolower($this->__table); } - global $_DB_DATAOBJECT; - $this->_connect(); - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - - $this->_query['limit_start'] = ($b == null) ? 0 : (int)$a; - $this->_query['limit_count'] = ($b == null) ? (int)$a : (int)$b; + return $this->__table; } /** - * Adds a select columns + * Get a result using key, value. * - * $object->selectAdd(); // resets select to nothing! - * $object->selectAdd("*"); // default select - * $object->selectAdd("unixtime(DATE) as udate"); - * $object->selectAdd("DATE"); + * for example + * $object->get("ID",1234); + * Returns Number of rows located (usually 1) for success, + * and puts all the table columns into this classes variables * - * to prepend distict: - * $object->selectAdd('distinct ' . $object->selectAdd()); + * see the fetch example on how to extend this. * - * @param string $k - * @access public - * @return mixed null or old string if you reset it. + * if no value is entered, it is assumed that $key is a value + * and get will then use the first key in keys() + * to obtain the key. + * + * @param string $k column + * @param string $v value + * @access public + * @return int No. of rows */ - public function selectAdd($k = null) + public function get($k = null, $v = null) { - if ($this->_query === false) { - $this->raiseError( - "You cannot do two queries on the same object (copy it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - return false; + global $_DB_DATAOBJECT; + if (empty($_DB_DATAOBJECT['CONFIG'])) { + DB_DataObject::_loadConfig(); } - if ($k === null) { - $old = $this->_query['data_select']; - $this->_query['data_select'] = ''; - return $old; + $keys = array(); + + if ($v === null) { + $v = $k; + $keys = $this->keys(); + if (!$keys) { + $this->raiseError("No Keys available for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); + return false; + } + $k = $keys[0]; } - - // check input...= 0 or ' ' == error! - if (!trim($k)) { - return $this->raiseError("selectAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("$k $v " . print_r($keys, true), "GET"); } - - if ($this->_query['data_select']) { - $this->_query['data_select'] .= ', '; + + if ($v === null) { + $this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS); + return false; } - $this->_query['data_select'] .= " $k "; + $this->$k = $v; + return $this->find(1); } + /** - * Adds multiple Columns or objects to select with formating. + * get/set an array of table primary keys * - * $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......" - * // note with null it will also clear the '*' default select - * $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x" - * $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b" - * $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as - * objectTableName.colnameA as prefix_colnameA + * set usage: $do->keys('id','code'); * - * @param array|object|null the array or object to take column names from. - * @param string format in sprintf format (use %s for the colname) - * @param string table name eg. if you have joinAdd'd or send $from as an array. + * This is defined in the table definition if it gets it wrong, + * or you do not want to use ini tables, you can override this. + * @param string optional set the key + * @param * optional set more keys * @access public - * @return void + * @return array */ - public function selectAs($from = null, $format = '%s', $tableName=false) + public function keys() { - global $_DB_DATAOBJECT; - - if ($this->_query === false) { - $this->raiseError( - "You cannot do two queries on the same object (copy it before finding)", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - return false; - } - - if ($from === null) { - // blank the '*' - $this->selectAdd(); - $from = $this; - } - - - $table = $this->tableName(); - if (is_object($from)) { - $table = $from->tableName(); - $from = array_keys($from->table()); + // for temporary storage of database fields.. + // note this is not declared as we dont want to bloat the print_r output + $args = func_get_args(); + if (count($args)) { + $this->_database_keys = $args; } - - if ($tableName !== false) { - $table = $tableName; + if (isset($this->_database_keys)) { + return $this->_database_keys; } - $s = '%s'; - if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) { + + global $_DB_DATAOBJECT; + if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { $this->_connect(); - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - $s = $DB->quoteIdentifier($s); - $format = $DB->quoteIdentifier($format); } - foreach ($from as $k) { - $this->selectAdd(sprintf("{$s}.{$s} as {$format}", $table, $k, $k)); + if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName() . "__keys"])) { + return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName() . "__keys"]); } - $this->_query['data_select'] .= "\n"; + $this->databaseStructure(); + + if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName() . "__keys"])) { + return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName() . "__keys"]); + } + return array(); } + /** - * Insert the current objects variables into the database + * Autoload or manually load the table definitions * - * Returns the ID of the inserted element (if auto increment or sequences are used.) * - * for example + * usage : + * DB_DataObject::databaseStructure( 'databasename', + * parse_ini_file('mydb.ini',true), + * parse_ini_file('mydb.link.ini',true)); * - * Designed to be extended + * obviously you dont have to use ini files.. (just return array similar to ini files..) * - * $object = new mytable(); - * $object->name = "fred"; - * echo $object->insert(); + * It should append to the table structure array + * + * + * @param optional string name of database to assign / read + * @param optional array structure of database, and keys + * @param optional array table links * * @access public - * @return mixed false on failure, int when auto increment or sequence used, otherwise true on success + * @return true or PEAR:error on wrong paramenters.. or false if no file exists.. + * or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename) */ - public function insert() + public function databaseStructure() { global $_DB_DATAOBJECT; - - // we need to write to the connection (For nextid) - so us the real - // one not, a copyied on (as ret-by-ref fails with overload!) - - if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { - $this->_connect(); - } - - $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); - - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - - $items = $this->table(); - - if (!$items) { - $this->raiseError( - "insert:No table definition for {$this->tableName()}", - DB_DATAOBJECT_ERROR_INVALIDCONFIG - ); - return false; - } - $options = $_DB_DATAOBJECT['CONFIG']; + // Assignment code - $datasaved = 1; - $leftq = ''; - $rightq = ''; - - $seqKeys = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]) ? - $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] : - $this->sequenceKey(); - - $key = isset($seqKeys[0]) ? $seqKeys[0] : false; - $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false; - $seq = isset($seqKeys[2]) ? $seqKeys[2] : false; - - $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"]; - - - // nativeSequences or Sequences.. + if ($args = func_get_args()) { + if (count($args) == 1) { - // big check for using sequences - - if (($key !== false) && !$useNative) { - if (!$seq) { - $keyvalue = $DB->nextId($this->tableName()); + // this returns all the tables and their structure.. + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("Loading Generator as databaseStructure called with args", 1); + } + + $x = new DB_DataObject; + $x->_database = $args[0]; + $this->_connect(); + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + + $tables = $DB->getListOf('tables'); + class_exists('DB_DataObject_Generator') ? '' : + //require_once 'DB/DataObject/Generator.php'; + require_once 'Generator.php'; + + foreach ($tables as $table) { + $y = new DB_DataObject_Generator; + $y->fillTableSchema($x->_database, $table); + } + return $_DB_DATAOBJECT['INI'][$x->_database]; } else { - $f = $DB->getOption('seqname_format'); - $DB->setOption('seqname_format', '%s'); - $keyvalue = $DB->nextId($seq); - $DB->setOption('seqname_format', $f); - } - if (PEAR::isError($keyvalue)) { - $this->raiseError($keyvalue->toString(), DB_DATAOBJECT_ERROR_INVALIDCONFIG); - return false; - } - $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... - if ($key && ($k == $key) && $useNative) { - continue; - } - - - // Ignore INTEGERS which aren't set to a value - or empty string.. - if ((!isset($this->$k) || ($v == 1 && $this->$k === '')) - && $ignore_null - ) { - continue; - } - // dont insert data into mysql timestamps - // use query() if you really want to do this!!!! - if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) { - continue; - } - - if ($leftq) { - $leftq .= ', '; - $rightq .= ', '; - } - - $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ') : "$k "); - - if (is_object($this->$k) && is_a($this->$k, 'DB_DataObject_Cast')) { - $value = $this->$k->toString($v, $DB); - if (PEAR::isError($value)) { - $this->raiseError($value->toString(), DB_DATAOBJECT_ERROR_INVALIDARGS); - return false; + $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ? + $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1]; + + if (isset($args[1])) { + $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ? + $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2]; } - $rightq .= $value; - continue; - } - - - if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this, $k)) { - $rightq .= " NULL "; - continue; - } - // DATE is empty... on a col. that can be null.. - // note: this may be usefull for time as well.. - if (!$this->$k && - (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && - !($v & DB_DATAOBJECT_NOTNULL)) { - $rightq .= " NULL "; - continue; - } - - - if ($v & DB_DATAOBJECT_STR) { - $rightq .= $this->_quote((string) ( - ($v & DB_DATAOBJECT_BOOL) ? - // this is thanks to the braindead idea of postgres to - // use t/f for boolean. - (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) : - $this->$k - )) . " "; - continue; - } - if (is_numeric($this->$k)) { - $rightq .=" {$this->$k} "; - continue; - } - /* flag up string values - only at debug level... !!!??? */ - if (is_object($this->$k) || is_array($this->$k)) { - $this->debug('ODD DATA: ' .$k . ' ' . print_r($this->$k, true), 'ERROR'); + return true; } - - // at present we only cast to integers - // - V2 may store additional data about float/int - $rightq .= ' ' . intval($this->$k) . ' '; } - - // not sure why we let empty insert here.. - I guess to generate a blank row.. - - - if ($leftq || $useNative) { - $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); - - - if (($dbtype == 'pgsql') && empty($leftq)) { - $r = $this->_query("INSERT INTO {$table} DEFAULT VALUES"); - } else { - $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) "); - } - - - - - if (PEAR::isError($r)) { - $this->raiseError($r); - return false; - } - - if ($r < 1) { - return 0; - } - - - // now do we have an integer key! - - if ($key && $useNative) { - switch ($dbtype) { - case 'mysql': - case 'mysqli': - $method = "{$dbtype}_insert_id"; - $this->$key = $method( - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection - ); - break; - - case 'mssql': - // note this is not really thread safe - you should wrapp it with - // transactions = eg. - // $db->query('BEGIN'); - // $db->insert(); - // $db->query('COMMIT'); - $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; - $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne'; - $mssql_key = $DB->$method("SELECT @@IDENTITY"); - if (PEAR::isError($mssql_key)) { - $this->raiseError($mssql_key); - return false; - } - $this->$key = $mssql_key; - break; - - case 'pgsql': - if (!$seq) { - $seq = $DB->getSequenceName(strtolower($this->tableName())); - } - $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; - $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne'; - $pgsql_key = $DB->$method("SELECT currval('".$seq . "')"); - if (PEAR::isError($pgsql_key)) { - $this->raiseError($pgsql_key); - return false; - } - $this->$key = $pgsql_key; - break; - - case 'ifx': - $this->$key = array_shift( - ifx_fetch_row( - ifx_query( - "select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1", - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection, - IFX_SCROLL - ), - "FIRST" - ) - ); - break; - - } - } + if (!$this->_database) { + $this->_connect(); + } - if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) { - $this->_clear_cache(); - } - if ($key) { - return $this->$key; - } + + // if this table is already loaded this table.. + if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { return true; } - $this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA); - return false; - } - /** - * Updates current objects variables into the database - * uses the keys() to decide how to update - * Returns the true on success - * - * for example - * - * $object = DB_DataObject::factory('mytable'); - * $object->get("ID",234); - * $object->email="testing@test.com"; - * if(!$object->update()) - * echo "UPDATE FAILED"; - * - * to only update changed items : - * $dataobject->get(132); - * $original = $dataobject; // clone/copy it.. - * $dataobject->setFrom($_POST); - * if ($dataobject->validate()) { - * $dataobject->update($original); - * } // otherwise an error... - * - * performing global updates: - * $object = DB_DataObject::factory('mytable'); - * $object->status = "dead"; - * $object->whereAdd('age > 150'); - * $object->update(DB_DATAOBJECT_WHEREADD_ONLY); - * - * @param object dataobject (optional) | DB_DATAOBJECT_WHEREADD_ONLY - used to only update changed items. - * @access public - * @return int rows affected or false on failure - */ - public function update($dataObject = false) - { - global $_DB_DATAOBJECT; - // connect will load the config! - $this->_connect(); - - - $original_query = $this->_query; - - $items = $this->table(); - - // only apply update against sequence key if it is set????? - - $seq = $this->sequenceKey(); - if ($seq[0] !== false) { - $keys = array($seq[0]); - if (!isset($this->{$keys[0]}) && $dataObject !== true) { - $this->raiseError("update: trying to perform an update without - the key set, and argument to update is not - DB_DATAOBJECT_WHEREADD_ONLY - ". print_r(array('seq' => $seq , 'keys'=>$keys), true), DB_DATAOBJECT_ERROR_INVALIDARGS); - return false; - } - } else { - $keys = $this->keys(); + // initialize the ini data.. if empt.. + if (empty($_DB_DATAOBJECT['INI'][$this->_database])) { + $_DB_DATAOBJECT['INI'][$this->_database] = array(); } - - - if (!$items) { - $this->raiseError("update:No table definition for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); - return false; + + if (empty($_DB_DATAOBJECT['CONFIG'])) { + DB_DataObject::_loadConfig(); } - $datasaved = 1; - $settings = ''; - $this->_connect(); - - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - $dbtype = $DB->dsn["phptype"]; - $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); - $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) { - - // I think this is ignoring empty vlalues - if ((!isset($this->$k) || ($v == 1 && $this->$k === '')) - && $ignore_null - ) { - continue; - } - // ignore stuff thats - - // dont write things that havent changed.. - if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k === $this->$k)) { - continue; - } - - // - dont write keys to left.!!! - if (in_array($k, $keys)) { - continue; - } - - // dont insert data into mysql timestamps - // use query() if you really want to do this!!!! - if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) { - continue; - } - - - if ($settings) { - $settings .= ', '; - } - - $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k); - - if (is_object($this->$k) && is_a($this->$k, 'DB_DataObject_Cast')) { - $value = $this->$k->toString($v, $DB); - if (PEAR::isError($value)) { - $this->raiseError($value->getMessage(), DB_DATAOBJECT_ERROR_INVALIDARG); - return false; - } - $settings .= "$kSql = $value "; - continue; - } - - // special values ... at least null is handled... - if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this, $k)) { - $settings .= "$kSql = NULL "; - continue; - } - // DATE is empty... on a col. that can be null.. - // note: this may be usefull for time as well.. - if (!$this->$k && - (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && - !($v & DB_DATAOBJECT_NOTNULL)) { - $settings .= "$kSql = NULL "; - continue; - } - - if ($v & DB_DATAOBJECT_STR) { - $settings .= "$kSql = ". $this->_quote((string) ( - ($v & DB_DATAOBJECT_BOOL) ? - // this is thanks to the braindead idea of postgres to - // use t/f for boolean. - (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) : - $this->$k - )) . ' '; - continue; - } - if (is_numeric($this->$k)) { - $settings .= "$kSql = {$this->$k} "; - continue; + // we do not have the data for this table yet... + + // if we are configured to use the proxy.. + + if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) { + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("Loading Generator to fetch Schema", 1); } - // at present we only cast to integers - // - V2 may store additional data about float/int - $settings .= "$kSql = " . intval($this->$k) . ' '; + class_exists('DB_DataObject_Generator') ? '' : + //require_once 'DB/DataObject/Generator.php'; + require_once 'Generator.php'; + + + $x = new DB_DataObject_Generator; + $x->fillTableSchema($this->_database, $this->tableName()); + return true; } - - - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("got keys as ".serialize($keys), 3); + + + // if you supply this with arguments, then it will take those + // as the database and links array... + + $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ? + array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") : + array(); + + if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) { + $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ? + $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] : + explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]); } - if ($dataObject !== true) { - $this->_build_condition($items, $keys); - } else { - // prevent wiping out of data! - if (empty($this->_query['condition'])) { - $this->raiseError("update: global table update not available - do \$do->whereAdd('1=1'); if you really want to do that. - ", DB_DATAOBJECT_ERROR_INVALIDARGS); - return false; + + + $_DB_DATAOBJECT['INI'][$this->_database] = array(); + foreach ($schemas as $ini) { + if (file_exists($ini) && is_file($ini)) { + $_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); + } else { + $this->debug("Loaded ini file: $ini", "databaseStructure", 1); + } + } + } else { + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("Missing ini file: $ini", "databaseStructure", 1); + } } } - - - - // echo " $settings, $this->condition "; - if ($settings && isset($this->_query) && $this->_query['condition']) { - $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); - - $r = $this->_query("UPDATE {$table} SET {$settings} {$this->_query['condition']} "); - - // restore original query conditions. - $this->_query = $original_query; - - if (PEAR::isError($r)) { - $this->raiseError($r); - return false; - } - if ($r < 1) { - return 0; + // are table name lowecased.. + if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { + foreach ($_DB_DATAOBJECT['INI'][$this->_database] as $k => $v) { + // results in duplicate cols.. but not a big issue.. + $_DB_DATAOBJECT['INI'][$this->_database][strtolower($k)] = $v; } + } - $this->_clear_cache(); - return $r; + + // now have we loaded the structure.. + + if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { + return true; } - // restore original query conditions. - $this->_query = $original_query; - - // if you manually specified a dataobject, and there where no changes - then it's ok.. - if ($dataObject !== false) { + // - if not try building it.. + if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) { + class_exists('DB_DataObject_Generator') ? '' : + //require_once 'DB/DataObject/Generator.php'; + require_once 'Generator.php'; + + $x = new DB_DataObject_Generator; + $x->fillTableSchema($this->_database, $this->tableName()); + // should this fail!!!??? return true; } - - $this->raiseError( - "update: No Data specifed for query $settings , {$this->_query['condition']}", - DB_DATAOBJECT_ERROR_NODATA - ); + $this->debug("Cant find database schema: {$this->_database}/{$this->tableName()} \n" . + "in links file data: " . print_r($_DB_DATAOBJECT['INI'], true), "databaseStructure", 5); + // we have to die here!! - it causes chaos if we dont (including looping forever!) + $this->raiseError("Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE); return false; } /** - * Deletes items from table which match current objects variables - * - * Returns the true on success + * find results, either normal or crosstable * * for example * - * Designed to be extended - * * $object = new mytable(); - * $object->ID=123; - * echo $object->delete(); // builds a conditon + * $object->ID = 1; + * $object->find(); * - * $object = new mytable(); - * $object->whereAdd('age > 12'); - * $object->limit(1); - * $object->orderBy('age DESC'); - * $object->delete(true); // dont use object vars, use the conditions, limit and order. * - * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then - * we will build the condition only using the whereAdd's. Default is to - * build the condition only using the object parameters. + * will set $object->N to number of rows, and expects next command to fetch rows + * will return $object->N * - * @access public - * @return mixed Int (No. of rows affected) on success, false on failure, 0 on no data affected + * if an error occurs $object->N will be set to false and return value will also be false; + * if numRows is not supported it will + * + * + * @param boolean $n Fetch first result + * @access public + * @return mixed (number of rows returned, or true if numRows fetching is not supported) */ - public function delete($useWhere = false) + public function find($n = false) { global $_DB_DATAOBJECT; - // connect will load the config! - $this->_connect(); - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); - - $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : ''); - - if (!$useWhere) { - $keys = $this->keys(); - $this->_query = array(); // as it's probably unset! - $this->_query['condition'] = ''; // default behaviour not to use where condition - $this->_build_condition($this->table(), $keys); - // if primary keys are not set then use data from rest of object. - if (!$this->_query['condition']) { - $this->_build_condition($this->table(), array(), $keys); - } - $extra_cond = ''; - } - - - // don't delete without a condition - if (($this->_query !== false) && $this->_query['condition']) { - $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); - $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.. - - if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { - if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) || - ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) { - // pear DB - $sql = $DB->modifyLimitQuery($sql, $this->_query['limit_start'], $this->_query['limit_count']); - } else { - // MDB2 - $DB->setLimit($this->_query['limit_count'], $this->_query['limit_start']); - } - } - - - $r = $this->_query($sql); - - - if (PEAR::isError($r)) { - $this->raiseError($r); - return false; - } - if ($r < 1) { - return 0; - } - $this->_clear_cache(); - return $r; - } else { - $this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA); + if ($this->_query === false) { + $this->raiseError( + "You cannot do two queries on the same object (copy it before finding)", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); return false; } - } - /** - * fetches a specific row into this object variables - * - * Not recommended - better to use fetch() - * - * Returens true on success - * - * @param int $row row - * @access public - * @return boolean true on success - */ - public function fetchRow($row = null) - { - global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { - $this->_loadConfig(); - } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow", 3); - } - if (!$this->tableName()) { - $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG); - return false; - } - if ($row === null) { - $this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS); - return false; - } - if (!$this->N) { - $this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA); - return false; - } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow", 3); + DB_DataObject::_loadConfig(); } - - $result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]; - $array = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC, $row); - if (!is_array($array)) { - $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA); - return false; + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug($n, "find", 1); } - $replace = array('.', ' '); - foreach ($array as $k => $v) { - // use strpos as str_replace is slow. - $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ? - $k : str_replace($replace, '_', $k); - - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3); - } - $this->$kk = $array[$k]; + if (!strlen($this->tableName())) { + // xdebug can backtrace this! + trigger_error("NO \$__table SPECIFIED in class definition", E_USER_ERROR); } + $this->N = 0; + $query_before = $this->_query; + $this->_build_condition($this->table()); - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("{$this->tableName()} DONE", "fetchrow", 3); - } - return true; - } - /** - * Find the number of results from a simple query - * - * for example - * - * $object = new mytable(); - * $object->name = "fred"; - * echo $object->count(); - * echo $object->count(true); // dont use object vars. - * echo $object->count('distinct mycol'); count distinct mycol. - * echo $object->count('distinct mycol',true); // dont use object vars. - * echo $object->count('distinct'); // count distinct id (eg. the primary key) - * - * - * @param bool|string (optional) - * (true|false => see below not on whereAddonly) - * (string) - * "DISTINCT" => does a distinct count on the tables 'key' column - * otherwise => normally it counts primary keys - you can use - * this to do things like $do->count('distinct mycol'); - * - * @param bool $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then - * we will build the condition only using the whereAdd's. Default is to - * build the condition using the object parameters as well. - * - * @access public - * @return int - */ - public function count($countWhat = false, $whereAddOnly = false) - { - global $_DB_DATAOBJECT; - - if (is_bool($countWhat)) { - $whereAddOnly = $countWhat; - } - - $t = clone($this); - $items = $t->table(); - - $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); - - - if (!isset($t->_query)) { - $this->raiseError( - "You cannot do run count after you have run fetch()", - DB_DATAOBJECT_ERROR_INVALIDARGS - ); - return false; - } $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - - if (!$whereAddOnly && $items) { - $t->_build_condition($items); + + $sql = $this->_build_select(); + + foreach ($this->_query['unions'] as $union_ar) { + $sql .= $union_ar[1] . $union_ar[0]->_build_select() . " \n"; } - $keys = $this->keys(); - if (empty($keys[0]) && (!is_string($countWhat) || (strtoupper($countWhat) == 'DISTINCT'))) { - $this->raiseError( - "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; + $sql .= $this->_query['order_by'] . " \n"; + + + /* We are checking for method modifyLimitQuery as it is PEAR DB specific */ + if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) || + ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) { + /* PEAR DB specific */ + + if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { + $sql = $DB->modifyLimitQuery($sql, $this->_query['limit_start'], $this->_query['limit_count']); + } + } else { + /* theoretically MDB2! */ + if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { + $DB->setLimit($this->_query['limit_count'], $this->_query['limit_start']); + } } - $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); - $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. - $countWhat = (strtoupper($countWhat) == 'DISTINCT') ? - "DISTINCT {$table}.{$key_col}" : $countWhat; - - $countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}"; - - $r = $t->_query( - "SELECT count({$countWhat}) as $as - FROM $table {$t->_join} {$t->_query['condition']}" - ); - if (PEAR::isError($r)) { + + + $err = $this->_query($sql); + if (is_a($err, 'PEAR_Error')) { return false; } - - $result = $_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid]; - $l = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED); - // free the results - essential on oracle. - $t->free(); + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug('Count returned '. $l[0], 1); + $this->debug("CHECK autofetchd $n", "find", 1); } - return (int) $l[0]; - } - /** - * sends raw query to database - * - * Since _query has to be a private 'non overwriteable method', this is a relay - * - * @param string $string SQL Query - * @access public - * @return void or DB_Error - */ - public function query($string) - { - return $this->_query($string); - } + // find(true) + $ret = $this->N; + if (!$ret && !empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { + // clear up memory if nothing found!? + unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); + } - /** - * an escape wrapper around DB->escapeSimple() - * can be used when adding manual queries or clauses - * eg. - * $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'"); - * - * @param string $string value to be escaped - * @param bool $likeEscape escapes % and _ as well. - so like queries can be protected. - * @access public - * @return string - */ - public function escape($string, $likeEscape=false) - { - global $_DB_DATAOBJECT; - $this->_connect(); - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - // mdb2 uses escape... - $dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver']; - $ret = ($dd == 'DB') ? $DB->escapeSimple($string) : $DB->escape($string); - if ($likeEscape) { - $ret = str_replace(array('_','%'), array('\_','\%'), $ret); + if ($n && $this->N > 0) { + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("ABOUT TO AUTOFETCH", "find", 1); + } + $fs = $this->fetch(); + // if fetch returns false (eg. failed), then the backend doesnt support numRows (eg. ret=true) + // - hence find() also returns false.. + $ret = ($ret === true) ? $fs : $ret; } + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("DONE", "find", 1); + } + $this->_query = $query_before; return $ret; } @@ -1806,779 +1257,342 @@ class DB_DataObject extends DB_DataObject_Overload /* ==================================================== */ /** - * The Database connection dsn (as described in the PEAR DB) - * only used really if you are writing a very simple application/test.. - * try not to use this - it is better stored in configuration files.. + * Builds the WHERE based on the values of of this object * + * @param mixed $keys + * @param array $filter (used by update to only uses keys in this filter list). + * @param array $negative_filter (used by delete to prevent deleting using the keys mentioned..) * @access private - * @var string + * @return string */ - public $_database_dsn = ''; + public function _build_condition($keys, $filter = array(), $negative_filter = array()) + { + global $_DB_DATAOBJECT; + $this->_connect(); + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - /** - * The Database connection id (md5 sum of databasedsn) - * - * @access private - * @var string - */ - public $_database_dsn_md5 = ''; + $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + $options = $_DB_DATAOBJECT['CONFIG']; + + // if we dont have query vars.. - reset them. + if ($this->_query === false) { + $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.. + - need to check that update really wants to work this way */ + + if ($filter) { + if (!in_array($k, $filter)) { + continue; + } + } + if ($negative_filter) { + if (in_array($k, $negative_filter)) { + continue; + } + } + if (!isset($this->$k)) { + continue; + } + + $kSql = $quoteIdentifiers + ? ($DB->quoteIdentifier($this->tableName()) . '.' . $DB->quoteIdentifier($k)) + : "{$this->tableName()}.{$k}"; + + + if (is_object($this->$k) && is_a($this->$k, 'DB_DataObject_Cast')) { + $dbtype = $DB->dsn["phptype"]; + $value = $this->$k->toString($v, $DB); + if ((new PEAR)->isError($value)) { + $this->raiseError($value->getMessage(), DB_DATAOBJECT_ERROR_INVALIDARG); + return false; + } + if ((strtolower($value) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { + $this->whereAdd(" $kSql IS NULL"); + continue; + } + $this->whereAdd(" $kSql = $value"); + continue; + } + + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this, $k)) { + $this->whereAdd(" $kSql IS NULL"); + continue; + } + + + if ($v & DB_DATAOBJECT_STR) { + $this->whereAdd(" $kSql = " . $this->_quote((string)( + ($v & DB_DATAOBJECT_BOOL) ? + // this is thanks to the braindead idea of postgres to + // use t/f for boolean. + (($this->$k === 'f') ? 0 : (int)(bool)$this->$k) : + $this->$k + ))); + continue; + } + if (is_numeric($this->$k)) { + $this->whereAdd(" $kSql = {$this->$k}"); + continue; + } + /* this is probably an error condition! */ + $this->whereAdd(" $kSql = " . intval($this->$k)); + } + return ""; + } /** - * The Database name - * created in __connection + * Adds a condition to the WHERE statement, defaults to AND * - * @access private - * @var string + * $object->whereAdd(); //reset or cleaer ewhwer + * $object->whereAdd("ID > 20"); + * $object->whereAdd("age > 20","OR"); + * + * @param bool $cond condition + * @param string $logic optional logic "OR" (defaults to "AND") + * @return string|PEAR::Error - previous condition or Error when invalid args found + * @access public */ - public $_database = ''; + public function whereAdd($cond = false, $logic = 'AND') + { + // for PHP5.2.3 - there is a bug with setting array properties of an object. + $_query = $this->_query; + + if (!isset($this->_query) || ($_query === false)) { + return $this->raiseError( + "You cannot do two queries on the same object (clone it before finding)", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); + } + + if ($cond === false) { + $r = $this->_query['condition']; + $_query['condition'] = ''; + $this->_query = $_query; + return preg_replace('/^\s+WHERE\s+/', '', $r); + } + // check input...= 0 or ' ' == error! + if (!trim($cond)) { + return $this->raiseError("WhereAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + } + $r = $_query['condition']; + if ($_query['condition']) { + $_query['condition'] .= " {$logic} ( {$cond} )"; + $this->_query = $_query; + return $r; + } + $_query['condition'] = " WHERE ( {$cond} ) "; + $this->_query = $_query; + return $r; + } - - /** - * The QUERY rules - * This replaces alot of the private variables - * used to build a query, it is unset after find() is run. + * 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 * * - * - * @access private - * @var array + * @param object|array $obj_or_ar + * @param string|false $prop prperty + * @access private + * @return bool object */ - public $_query = array( - 'condition' => '', // the WHERE condition - 'group_by' => '', // the GROUP BY condition - 'order_by' => '', // the ORDER BY condition - 'having' => '', // the HAVING condition - 'useindex' => '', // the USE INDEX condition - 'limit_start' => '', // the LIMIT condition - 'limit_count' => '', // the LIMIT condition - 'data_select' => '*', // the columns to be SELECTed - 'unions' => array(), // the added unions, - 'derive_table' => '', // derived table name (BETA) - 'derive_select' => '', // derived table select (BETA) - ); - - - + public 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; + } /** - * Database result id (references global $_DB_DataObject[results] + * backend wrapper for quoting, as MDB2 and DB do it differently... * - * @access private - * @var integer + * @access private + * @param $str + * @return string quoted */ - public $_DB_resultid; - - /** - * ResultFields - on the last call to fetch(), resultfields is sent here, - * so we can clean up the memory. - * - * @access public - * @var array - */ - public $_resultFields = false; - - /* ============================================================== */ - /* Table definition layer (started of very private but 'came out'*/ - /* ============================================================== */ + public function _quote($str) + { + global $_DB_DATAOBJECT; + return (empty($_DB_DATAOBJECT['CONFIG']['db_driver']) || + ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) + ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quoteSmart($str) + : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quote($str); + } /** - * Autoload or manually load the table definitions - * - * - * usage : - * DB_DataObject::databaseStructure( 'databasename', - * parse_ini_file('mydb.ini',true), - * parse_ini_file('mydb.link.ini',true)); - * - * obviously you dont have to use ini files.. (just return array similar to ini files..) - * - * It should append to the table structure array - * - * - * @param optional string name of database to assign / read - * @param optional array structure of database, and keys - * @param optional array table links + * get/set an associative array of table columns * * @access public - * @return true or PEAR:error on wrong paramenters.. or false if no file exists.. - * or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename) + * @param array key=>type array + * @return array (associative) */ - public function databaseStructure() + public function table() { - global $_DB_DATAOBJECT; - - // Assignment code - - if ($args = func_get_args()) { - if (count($args) == 1) { - - // this returns all the tables and their structure.. - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("Loading Generator as databaseStructure called with args", 1); - } - - $x = new DB_DataObject; - $x->_database = $args[0]; - $this->_connect(); - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - - $tables = $DB->getListOf('tables'); - class_exists('DB_DataObject_Generator') ? '' : - require_once 'DB/DataObject/Generator.php'; - - foreach ($tables as $table) { - $y = new DB_DataObject_Generator; - $y->fillTableSchema($x->_database, $table); - } - return $_DB_DATAOBJECT['INI'][$x->_database]; - } else { - $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ? - $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1]; - - if (isset($args[1])) { - $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ? - $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2]; - } - return true; - } + + // for temporary storage of database fields.. + // note this is not declared as we dont want to bloat the print_r output + $args = func_get_args(); + if (count($args)) { + $this->_database_fields = $args[0]; } - - - - if (!$this->_database) { - $this->_connect(); + if (isset($this->_database_fields)) { + return $this->_database_fields; } - - - // if this table is already loaded this table.. - if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { - return true; + + + global $_DB_DATAOBJECT; + if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { + $this->_connect(); } - - // initialize the ini data.. if empt.. - if (empty($_DB_DATAOBJECT['INI'][$this->_database])) { - $_DB_DATAOBJECT['INI'][$this->_database] = array(); + + if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { + return $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()]; } - - if (empty($_DB_DATAOBJECT['CONFIG'])) { - DB_DataObject::_loadConfig(); + + $this->databaseStructure(); + + + $ret = array(); + if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { + $ret = $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()]; } - - // we do not have the data for this table yet... - - // if we are configured to use the proxy.. - - if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("Loading Generator to fetch Schema", 1); - } - class_exists('DB_DataObject_Generator') ? '' : - require_once 'DB/DataObject/Generator.php'; - - - $x = new DB_DataObject_Generator; - $x->fillTableSchema($this->_database, $this->tableName()); - return true; - } - - - - - // if you supply this with arguments, then it will take those - // as the database and links array... - - $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ? - array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") : - array() ; - - if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) { - $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ? - $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] : - explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]); - } - - - $_DB_DATAOBJECT['INI'][$this->_database] = array(); - foreach ($schemas as $ini) { - if (file_exists($ini) && is_file($ini)) { - $_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); - } else { - $this->debug("Loaded ini file: $ini", "databaseStructure", 1); - } - } - } else { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("Missing ini file: $ini", "databaseStructure", 1); - } - } - } - // are table name lowecased.. - if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { - foreach ($_DB_DATAOBJECT['INI'][$this->_database] as $k=>$v) { - // results in duplicate cols.. but not a big issue.. - $_DB_DATAOBJECT['INI'][$this->_database][strtolower($k)] = $v; - } - } - - - // now have we loaded the structure.. - - if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { - return true; - } - // - if not try building it.. - if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) { - class_exists('DB_DataObject_Generator') ? '' : - require_once 'DB/DataObject/Generator.php'; - - $x = new DB_DataObject_Generator; - $x->fillTableSchema($this->_database, $this->tableName()); - // should this fail!!!??? - return true; - } - $this->debug("Cant find database schema: {$this->_database}/{$this->tableName()} \n". - "in links file data: " . print_r($_DB_DATAOBJECT['INI'], true), "databaseStructure", 5); - // we have to die here!! - it causes chaos if we dont (including looping forever!) - $this->raiseError("Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE); - return false; - } - - - - /** - * Return or assign the name of the current table - * - * - * @param string optinal table name to set - * @access public - * @return string The name of the current table - */ - public function tableName() - { - global $_DB_DATAOBJECT; - $args = func_get_args(); - if (count($args)) { - $this->__table = $args[0]; - } - if (empty($this->__table)) { - return ''; - } - if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { - return strtolower($this->__table); - } - return $this->__table; - } - - /** - * Return or assign the name of the current database - * - * @param string optional database name to set - * @access public - * @return string The name of the current database - */ - public function database() - { - $args = func_get_args(); - if (count($args)) { - $this->_database = $args[0]; - } else { - $this->_connect(); - } - - return $this->_database; - } - - /** - * get/set an associative array of table columns - * - * @access public - * @param array key=>type array - * @return array (associative) - */ - public function table() - { - - // for temporary storage of database fields.. - // note this is not declared as we dont want to bloat the print_r output - $args = func_get_args(); - if (count($args)) { - $this->_database_fields = $args[0]; - } - if (isset($this->_database_fields)) { - return $this->_database_fields; - } - - - global $_DB_DATAOBJECT; - if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { - $this->_connect(); - } - - if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { - return $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()]; - } - - $this->databaseStructure(); - - - $ret = array(); - if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { - $ret = $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()]; - } - return $ret; } /** - * get/set an array of table primary keys - * - * set usage: $do->keys('id','code'); + * build the basic select query. * - * This is defined in the table definition if it gets it wrong, - * or you do not want to use ini tables, you can override this. - * @param string optional set the key - * @param * optional set more keys - * @access public - * @return array + * @access private */ - public function keys() + + public function _build_select() { - // for temporary storage of database fields.. - // note this is not declared as we dont want to bloat the print_r output - $args = func_get_args(); - if (count($args)) { - $this->_database_keys = $args; - } - if (isset($this->_database_keys)) { - return $this->_database_keys; - } - global $_DB_DATAOBJECT; - if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { + $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + if ($quoteIdentifiers) { $this->_connect(); + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; } - if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) { - return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]); - } - $this->databaseStructure(); - - if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) { - return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]); + $tn = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); + if (!empty($this->_query['derive_table']) && !empty($this->_query['derive_select'])) { + + // this is a derived select.. + // not much support in the api yet.. + + $sql = 'SELECT ' . + $this->_query['derive_select'] + . ' FROM ( SELECT' . + $this->_query['data_select'] . " \n" . + " FROM $tn " . $this->_query['useindex'] . " \n" . + $this->_join . " \n" . + $this->_query['condition'] . " \n" . + $this->_query['group_by'] . " \n" . + $this->_query['having'] . " \n" . + ') ' . $this->_query['derive_table']; + + return $sql; } - return array(); + + + $sql = 'SELECT ' . + $this->_query['data_select'] . " \n" . + " FROM $tn " . $this->_query['useindex'] . " \n" . + $this->_join . " \n" . + $this->_query['condition'] . " \n" . + $this->_query['group_by'] . " \n" . + $this->_query['having'] . " \n"; + + return $sql; } + + + /* ============================================================== */ + /* Table definition layer (started of very private but 'came out'*/ + /* ============================================================== */ + /** - * get/set an sequence key - * - * by default it returns the first key from keys() - * set usage: $do->sequenceKey('id',true); - * - * override this to return array(false,false) if table has no real sequence key. + * sends query to database - this is the private one that must work + * - internal functions use this rather than $this->query() * - * @param string optional the key sequence/autoinc. key - * @param boolean optional use native increment. default false - * @param false|string optional native sequence name - * @access public - * @return array (column,use_native,sequence_name) + * @param string $string + * @access private + * @return mixed none or PEAR_Error */ - public function sequenceKey() + public function _query($string) { global $_DB_DATAOBJECT; - - // call setting - if (!$this->_database) { - $this->_connect(); - } - - if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) { - $_DB_DATAOBJECT['SEQUENCE'][$this->_database] = array(); - } + $this->_connect(); - - $args = func_get_args(); - if (count($args)) { - $args[1] = isset($args[1]) ? $args[1] : false; - $args[2] = isset($args[2]) ? $args[2] : false; - $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = $args; - } - if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()])) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]; - } - // end call setting (eg. $do->sequenceKeys(a,b,c); ) - - - - - $keys = $this->keys(); - if (!$keys) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] - = array(false,false,false); + + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + + $options = $_DB_DATAOBJECT['CONFIG']; + + $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? + 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver']; + + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug($string, $log = "QUERY"); } - - $table = $this->table(); - - $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; - - $usekey = $keys[0]; - - - - $seqname = false; - - if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->tableName()])) { - $seqname = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->tableName()]; - if (strpos($seqname, ':') !== false) { - list($usekey, $seqname) = explode(':', $seqname); + if ( + strtoupper($string) == 'BEGIN' || + strtoupper($string) == 'START TRANSACTION' + ) { + if ($_DB_driver == 'DB') { + $DB->autoCommit(false); + $DB->simpleQuery('BEGIN'); + } else { + $DB->beginTransaction(); } + return true; } - - - // if the key is not an integer - then it's not a sequence or native - if (empty($table[$usekey]) || !($table[$usekey] & DB_DATAOBJECT_INT)) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,false); - } - - - if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) { - $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys']; - if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname); - } - if (is_string($ignore)) { - $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',', $ignore); - } - if (in_array($this->tableName(), $ignore)) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname); + + if (strtoupper($string) == 'COMMIT') { + $res = $DB->commit(); + if ($_DB_driver == 'DB') { + $DB->autoCommit(true); } + return $res; } - - - $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]; - - // if you are using an old ini file - go back to old behaviour... - if (is_numeric($realkeys[$usekey])) { - $realkeys[$usekey] = 'N'; - } - - // multiple unique primary keys without a native sequence... - if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname); - } - // use native sequence keys... - // technically postgres native here... - // we need to get the new improved tabledata sorted out first. - - // support named sequence keys.. - currently postgres only.. - - if (in_array($dbtype, array('pgsql')) && - ($table[$usekey] & DB_DATAOBJECT_INT) && - isset($realkeys[$usekey]) && strlen($realkeys[$usekey]) > 1) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,true, $realkeys[$usekey]); - } - - if (in_array($dbtype, array('pgsql', 'mysql', 'mysqli', 'mssql', 'ifx')) && - ($table[$usekey] & DB_DATAOBJECT_INT) && - isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N') - ) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,true,$seqname); - } - - - // if not a native autoinc, and we have not assumed all primary keys are sequence - if (($realkeys[$usekey] != 'N') && - !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) { - return array(false,false,false); - } - - - - // I assume it's going to try and be a nextval DB sequence.. (not native) - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,false,$seqname); - } - - - - /* =========================================================== */ - /* Major Private Methods - the core part! */ - /* =========================================================== */ - - - - /** - * clear the cache values for this class - normally done on insert/update etc. - * - * @access private - * @return void - */ - public function _clear_cache() - { - global $_DB_DATAOBJECT; - - $class = strtolower(get_class($this)); - - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("Clearing Cache for ".$class, 1); - } - - if (!empty($_DB_DATAOBJECT['CACHE'][$class])) { - unset($_DB_DATAOBJECT['CACHE'][$class]); - } - } - - - /** - * backend wrapper for quoting, as MDB2 and DB do it differently... - * - * @access private - * @return string quoted - */ - - public function _quote($str) - { - global $_DB_DATAOBJECT; - return (empty($_DB_DATAOBJECT['CONFIG']['db_driver']) || - ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) - ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quoteSmart($str) - : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quote($str); - } - - - /** - * connects to the database - * - * - * TODO: tidy this up - This has grown to support a number of connection options like - * a) dynamic changing of ini file to change which database to connect to - * b) multi data via the table_{$table} = dsn ini option - * c) session based storage. - * - * @access private - * @return true | PEAR::error - */ - public function _connect() - { - global $_DB_DATAOBJECT; - if (empty($_DB_DATAOBJECT['CONFIG'])) { - $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 ? - 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, - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, - PEAR_ERROR_DIE - ); - } - - 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'); - - $this->_database = ($db_driver != 'DB' && $hasGetDatabase) - ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() - : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; - - - - if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') - && is_file($this->_database)) { - $this->_database = basename($this->_database); - } - if ($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'ibase') { - $this->_database = substr(basename($this->_database), 0, -4); - } - } - // theoretically we have a md5, it's listed in connections and it's not an error. - // so everything is ok! - return true; - } - - // it's not currently connected! - // 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) { - if (!$this->_database && !strlen($this->tableName())) { - $this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null; - } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $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}"]; - } elseif (!empty($options['database'])) { - $dsn = $options['database']; - } - } - - // if still no database... - if (!$dsn) { - return $this->raiseError( - "No database name / dsn found anywhere", - DB_DATAOBJECT_ERROR_INVALIDCONFIG, - PEAR_ERROR_DIE - ); - } - - - if (is_string($dsn)) { - $this->_database_dsn_md5 = md5($dsn); - } else { - /// support array based dsn's - $this->_database_dsn_md5 = md5(serialize($dsn)); - } - - if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { - 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'); - $this->_database = ($db_driver != 'DB' && $hasGetDatabase) - ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() - : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; - - if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') - && is_file($this->_database)) { - $this->_database = basename($this->_database); - } - } - return true; - } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $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); - } - - // Note this is verbose deliberatly! - - if ($db_driver == 'DB') { - - /* PEAR DB connect */ - - // this allows the setings of compatibility on DB - $db_options = PEAR::getStaticProperty('DB', 'options'); - require_once 'DB.php'; - if ($db_options) { - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn, $db_options); - } else { - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn); - } - } else { - /* assumption is MDB2 */ - require_once 'MDB2.php'; - // this allows the setings of compatibility on MDB2 - $db_options = PEAR::getStaticProperty('MDB2', 'options'); - $db_options = is_array($db_options) ? $db_options : array(); - $db_options['portability'] = isset($db_options['portability']) - ? $db_options['portability'] : MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE; - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn, $db_options); - } - - - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug(print_r($_DB_DATAOBJECT['CONNECTIONS'], true), "CONNECT", 5); - } - if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { - $this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString(), "CONNECT FAILED", 5); - return $this->raiseError( - "Connect failed, turn on debugging to 5 see why", - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, - PEAR_ERROR_DIE - ); - } - - if (empty($this->_database)) { - $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); - - $this->_database = ($db_driver != 'DB' && $hasGetDatabase) - ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() - : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; - - - if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') - && is_file($this->_database)) { - $this->_database = basename($this->_database); - } - } - - // Oracle need to optimize for portibility - not sure exactly what this does though :) - - return true; - } - - /** - * sends query to database - this is the private one that must work - * - internal functions use this rather than $this->query() - * - * @param string $string - * @access private - * @return mixed none or PEAR_Error - */ - public function _query($string) - { - global $_DB_DATAOBJECT; - $this->_connect(); - - - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - $options = $_DB_DATAOBJECT['CONFIG']; - - $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? - 'DB': $_DB_DATAOBJECT['CONFIG']['db_driver']; - - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug($string, $log="QUERY"); - } - - if ( - strtoupper($string) == 'BEGIN' || - strtoupper($string) == 'START TRANSACTION' - ) { - if ($_DB_driver == 'DB') { - $DB->autoCommit(false); - $DB->simpleQuery('BEGIN'); - } else { - $DB->beginTransaction(); - } - return true; - } - - if (strtoupper($string) == 'COMMIT') { - $res = $DB->commit(); - if ($_DB_driver == 'DB') { - $DB->autoCommit(true); - } - return $res; - } - if (strtoupper($string) == 'ROLLBACK') { $DB->rollback(); if ($_DB_driver == 'DB') { @@ -2586,43 +1600,43 @@ class DB_DataObject extends DB_DataObject_Overload } return true; } - + if (!empty($options['debug_ignore_updates']) && (strtolower(substr(trim($string), 0, 6)) != 'select') && (strtolower(substr(trim($string), 0, 4)) != 'show') && (strtolower(substr(trim($string), 0, 8)) != 'describe')) { $this->debug('Disabling Update as you are in debug mode'); - return $this->raiseError("Disabling Update as you are in debug mode", null) ; + return $this->raiseError("Disabling Update as you are in debug mode", null); } //if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 1) { // this will only work when PEAR:DB supports it. //$this->debug($DB->getAll('explain ' .$string,DB_DATAOBJECT_FETCHMODE_ASSOC), $log="sql",2); //} - + // some sim - $t= explode(' ', microtime()); - $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1]; - - - for ($tries = 0;$tries < 3;$tries++) { + $t = explode(' ', microtime()); + $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0] + $t[1]; + + + for ($tries = 0; $tries < 3; $tries++) { 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_object($result) || !is_a($result, 'PEAR_Error')) { break; @@ -2633,7 +1647,7 @@ class DB_DataObject extends DB_DataObject_Overload sleep(1); // wait before retyring.. $DB->connect($DB->dsn); } - + if (is_object($result) && is_a($result, 'PEAR_Error')) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { @@ -2643,9 +1657,9 @@ class DB_DataObject extends DB_DataObject_Overload return $this->raiseError($result); } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $t= explode(' ', microtime()); - $_DB_DATAOBJECT['QUERYENDTIME'] = $t[0]+$t[1]; - $this->debug('QUERY DONE IN '.($t[0]+$t[1]-$time)." seconds", 'query', 1); + $t = explode(' ', microtime()); + $_DB_DATAOBJECT['QUERYENDTIME'] = $t[0] + $t[1]; + $this->debug('QUERY DONE IN ' . ($t[0] + $t[1] - $time) . " seconds", 'query', 1); } switch (strtolower(substr(trim($string), 0, 6))) { case 'insert': @@ -2659,8 +1673,8 @@ class DB_DataObject extends DB_DataObject_Overload } if (is_object($result)) { // lets hope that copying the result object is OK! - - $_DB_resultid = $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++; + + $_DB_resultid = $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++; $_DB_DATAOBJECT['RESULTS'][$_DB_resultid] = $result; $this->_DB_resultid = $_DB_resultid; } @@ -2674,368 +1688,1845 @@ class DB_DataObject extends DB_DataObject_Overload } else { $DB->expectError(MDB2_ERROR_UNSUPPORTED); } - + $this->N = $result->numRows(); //var_dump($this->N); - + if (is_object($this->N) && is_a($this->N, 'PEAR_Error')) { $this->N = true; } $DB->popExpect(); } + return null; } /** - * Builds the WHERE based on the values of of this object + * fetches next row into this objects var's * - * @param mixed $keys - * @param array $filter (used by update to only uses keys in this filter list). - * @param array $negative_filter (used by delete to prevent deleting using the keys mentioned..) - * @access private - * @return string - */ - public function _build_condition($keys, $filter = array(), $negative_filter=array()) - { - global $_DB_DATAOBJECT; - $this->_connect(); - $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - - $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); - $options = $_DB_DATAOBJECT['CONFIG']; - - // if we dont have query vars.. - reset them. - if ($this->_query === false) { - $x = new DB_DataObject; - $this->_query= $x->_query; + * returns 1 on success 0 on failure + * + * + * + * Example + * $object = new mytable(); + * $object->name = "fred"; + * $object->find(); + * $store = array(); + * while ($object->fetch()) { + * echo $this->ID; + * $store[] = $object; // builds an array of object lines. + * } + * + * to add features to a fetch + * function fetch () { + * $ret = parent::fetch(); + * $this->date_formated = date('dmY',$this->date); + * return $ret; + * } + * + * @access public + * @return boolean on success + */ + public function fetch() + { + global $_DB_DATAOBJECT; + if (empty($_DB_DATAOBJECT['CONFIG'])) { + DB_DataObject::_loadConfig(); } - - - foreach ($keys as $k => $v) { - // index keys is an indexed array - /* these filter checks are a bit suspicious.. - - need to check that update really wants to work this way */ - - if ($filter) { - if (!in_array($k, $filter)) { - continue; - } + if (empty($this->N)) { + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("No data returned from FIND (eg. N is 0)", "FETCH", 3); } - if ($negative_filter) { - if (in_array($k, $negative_filter)) { - continue; - } + return false; + } + + if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) || + !is_object($result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug('fetched on object after fetch completed (no results found)'); } - if (!isset($this->$k)) { - continue; + return false; + } + + + $array = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC); + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug(serialize($array), "FETCH"); + } + + // fetched after last row.. + if ($array === null) { + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $t = explode(' ', microtime()); + + $this->debug( + "Last Data Fetch'ed after " . + ($t[0] + $t[1] - $_DB_DATAOBJECT['QUERYENDTIME']) . + " seconds", + "FETCH", + 1 + ); } - - $kSql = $quoteIdentifiers - ? ($DB->quoteIdentifier($this->tableName()) . '.' . $DB->quoteIdentifier($k)) - : "{$this->tableName()}.{$k}"; - - - - if (is_object($this->$k) && is_a($this->$k, 'DB_DataObject_Cast')) { - $dbtype = $DB->dsn["phptype"]; - $value = $this->$k->toString($v, $DB); - if (PEAR::isError($value)) { - $this->raiseError($value->getMessage(), DB_DATAOBJECT_ERROR_INVALIDARG); - return false; - } - if ((strtolower($value) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { - $this->whereAdd(" $kSql IS NULL"); - continue; - } - $this->whereAdd(" $kSql = $value"); - continue; + // reduce the memory usage a bit... (but leave the id in, so count() works ok on it) + unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); + + // we need to keep a copy of resultfields locally so toArray() still works + // however we dont want to keep it in the global cache.. + + if (!empty($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { + $this->_resultFields = $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]; + unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]); } - - if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this, $k)) { - $this->whereAdd(" $kSql IS NULL"); - continue; + // this is probably end of data!! + //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA); + return false; + } + // make sure resultFields is always empty.. + $this->_resultFields = false; + + if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { + // note: we dont declare this to keep the print_r size down. + $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] = array_flip(array_keys($array)); + } + $replace = array('.', ' '); + foreach ($array as $k => $v) { + // use strpos as str_replace is slow. + $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ? + $k : str_replace($replace, '_', $k); + + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("$kk = " . $array[$k], "fetchrow LINE", 3); } - + $this->$kk = $array[$k]; + } - if ($v & DB_DATAOBJECT_STR) { - $this->whereAdd(" $kSql = " . $this->_quote((string) ( - ($v & DB_DATAOBJECT_BOOL) ? - // this is thanks to the braindead idea of postgres to - // use t/f for boolean. - (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) : - $this->$k - ))); - continue; + // set link flag + $this->_link_loaded = false; + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("{$this->tableName()} DONE", "fetchrow", 2); + } + if (($this->_query !== false) && empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) { + $this->_query = false; + } + return true; + } + + /** + * Get the value of the primary id + * + * While I normally use 'id' as the PRIMARY KEY value, some database use + * {table}_id as the column name. + * + * To save a bit of typing, + * + * $id = $do->pid(); + * + * @return bool|the + */ + public function pid() + { + $keys = $this->keys(); + if (!$keys) { + $this->raiseError( + "No Keys available for {$this->tableName()}", + DB_DATAOBJECT_ERROR_INVALIDCONFIG + ); + return false; + } + $k = $keys[0]; + if (empty($this->$k)) { // we do not + $this->raiseError( + "pid() called on Object where primary key value not available", + DB_DATAOBJECT_ERROR_NODATA + ); + return false; + } + return $this->$k; + } + + /** + * fetches all results as an array, + * + * return format is dependant on args. + * if selectAdd() has not been called on the object, then it will add the correct columns to the query. + * + * A) Array of values (eg. a list of 'id') + * + * $x = DB_DataObject::factory('mytable'); + * $x->whereAdd('something = 1') + * $ar = $x->fetchAll('id'); + * -- returns array(1,2,3,4,5) + * + * B) Array of values (not from table) + * + * $x = DB_DataObject::factory('mytable'); + * $x->whereAdd('something = 1'); + * $x->selectAdd(); + * $x->selectAdd('distinct(group_id) as group_id'); + * $ar = $x->fetchAll('group_id'); + * -- returns array(1,2,3,4,5) + * * + * C) A key=>value associative array + * + * $x = DB_DataObject::factory('mytable'); + * $x->whereAdd('something = 1') + * $ar = $x->fetchAll('id','name'); + * -- returns array(1=>'fred',2=>'blogs',3=> ....... + * + * D) array of objects + * $x = DB_DataObject::factory('mytable'); + * $x->whereAdd('something = 1'); + * $ar = $x->fetchAll(); + * + * E) array of arrays (for example) + * $x = DB_DataObject::factory('mytable'); + * $x->whereAdd('something = 1'); + * $ar = $x->fetchAll(false,false,'toArray'); + * + * + * @param string|false $k key + * @param string|false $v value + * @param string|false $method method to call on each result to get array value (eg. 'toArray') + * @access public + * @return array format dependant on arguments, may be empty + */ + public function fetchAll($k = false, $v = false, $method = false) + { + // should it even do this!!!?!? + if ($k !== false && + ( // only do this is we have not been explicit.. + empty($this->_query['data_select']) || + ($this->_query['data_select'] == '*') + ) + ) { + $this->selectAdd(); + $this->selectAdd($k); + if ($v !== false) { + $this->selectAdd($v); } - if (is_numeric($this->$k)) { - $this->whereAdd(" $kSql = {$this->$k}"); + } + + $this->find(); + $ret = array(); + while ($this->fetch()) { + if ($v !== false) { + $ret[$this->$k] = $this->$v; continue; } - /* this is probably an error condition! */ - $this->whereAdd(" $kSql = ".intval($this->$k)); + $ret[] = $k === false ? + ($method == false ? clone($this) : $this->$method()) + : $this->$k; } + return $ret; } - - /** - * classic factory method for loading a table class - * usage: $do = DB_DataObject::factory('person') - * WARNING - this may emit a include error if the file does not exist.. - * use @ to silence it (if you are sure it is acceptable) - * eg. $do = @DB_DataObject::factory('person') - * - * 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 - */ - - - - public static function factory($table = '') + * Adds a select columns + * + * $object->selectAdd(); // resets select to nothing! + * $object->selectAdd("*"); // default select + * $object->selectAdd("unixtime(DATE) as udate"); + * $object->selectAdd("DATE"); + * + * to prepend distict: + * $object->selectAdd('distinct ' . $object->selectAdd()); + * + * @param string $k + * @access public + * @return mixed null or old string if you reset it. + */ + public function selectAdd($k = null) { - global $_DB_DATAOBJECT; - - - // multi-database support.. - experimental. - $database = ''; - - if (strpos($table, '/') !== false) { - list($database, $table) = explode('.', $table, 2); + if ($this->_query === false) { + $this->raiseError( + "You cannot do two queries on the same object (copy it before finding)", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); + return false; } - - if (empty($_DB_DATAOBJECT['CONFIG'])) { - DB_DataObject::_loadConfig(); + if ($k === null) { + $old = $this->_query['data_select']; + $this->_query['data_select'] = ''; + return $old; } - // no configuration available for database - if (!empty($database) && empty($_DB_DATAOBJECT['CONFIG']['database_'.$database])) { - $do = new DB_DataObject(); - $do->raiseError( - "unable to find database_{$database} in Configuration, It is required for factory with database", - 0, - PEAR_ERROR_DIE - ); + + // check input...= 0 or ' ' == error! + if (!trim($k)) { + return $this->raiseError("selectAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); } - - - /* - if ($table === '') { - if (is_a($this,'DB_DataObject') && strlen($this->tableName())) { - $table = $this->tableName(); - } else { - return DB_DataObject::raiseError( - "factory did not recieve a table name", - DB_DATAOBJECT_ERROR_INVALIDARGS); + + if ($this->_query['data_select']) { + $this->_query['data_select'] .= ', '; + } + $this->_query['data_select'] .= " $k "; + return null; + } + + /** + * Adds a 'IN' condition to the WHERE statement + * + * $object->whereAddIn('id', $array, 'int'); //minimal usage + * $object->whereAddIn('price', $array, 'float', 'OR'); // cast to float, and call whereAdd with 'OR' + * $object->whereAddIn('name', $array, 'string'); // quote strings + * + * @param string $key key column to match + * @param array $list list of values to match + * @param string $type string|int|integer|float|bool cast to type. + * @param string $logic optional logic to call whereAdd with eg. "OR" (defaults to "AND") + * @access public + * @return string|PEAR::Error - previous condition or Error when invalid args found + */ + public function whereAddIn($key, $list, $type, $logic = 'AND') + { + $not = ''; + if ($key[0] == '!') { + $not = 'NOT '; + $key = substr($key, 1); + } + // fix type for short entry. + $type = $type == 'int' ? 'integer' : $type; + + if ($type == 'string') { + $this->_connect(); + } + + $ar = array(); + foreach ($list as $k) { + settype($k, $type); + $ar[] = $type == 'string' ? $this->_quote($k) : $k; + } + + if (!$ar) { + return $not ? $this->_query['condition'] : $this->whereAdd("1=0"); + } + return $this->whereAdd("$key $not IN (" . implode(',', $ar) . ')', $logic); + } + + + + /* =========================================================== */ + /* Major Private Methods - the core part! */ + /* =========================================================== */ + + /** + * Adds a order by condition + * + * $object->orderBy(); //clears order by + * $object->orderBy("ID"); + * $object->orderBy("ID,age"); + * + * @param bool $order Order + * @return bool|error|none|PEAR + * @access public + */ + public function orderBy($order = false) + { + if ($this->_query === false) { + $this->raiseError( + "You cannot do two queries on the same object (copy it before finding)", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); + return false; + } + if ($order === false) { + $this->_query['order_by'] = ''; + return null; + } + // check input...= 0 or ' ' == error! + if (!trim($order)) { + return $this->raiseError("orderBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + } + + if (!$this->_query['order_by']) { + $this->_query['order_by'] = " ORDER BY {$order} "; + return null; + } + $this->_query['order_by'] .= " , {$order}"; + return null; + } + + /** + * Adds a group by condition + * + * $object->groupBy(); //reset the grouping + * $object->groupBy("ID DESC"); + * $object->groupBy("ID,age"); + * + * @param bool $group Grouping + * @return bool|none|PEAR + * @access public + */ + public function groupBy($group = false) + { + if ($this->_query === false) { + $this->raiseError( + "You cannot do two queries on the same object (copy it before finding)", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); + return false; + } + if ($group === false) { + $this->_query['group_by'] = ''; + return null; + } + // check input...= 0 or ' ' == error! + if (!trim($group)) { + return $this->raiseError("groupBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + } + + + if (!$this->_query['group_by']) { + $this->_query['group_by'] = " GROUP BY {$group} "; + return null; + } + $this->_query['group_by'] .= " , {$group}"; + return null; + } + + /** + * Adds a having clause + * + * $object->having(); //reset the grouping + * $object->having("sum(value) > 0 "); + * + * @param bool $having condition + * @return bool|none|PEAR + * @access public + */ + public function having($having = false) + { + if ($this->_query === false) { + $this->raiseError( + "You cannot do two queries on the same object (copy it before finding)", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); + return false; + } + if ($having === false) { + $this->_query['having'] = ''; + return null; + } + // check input...= 0 or ' ' == error! + if (!trim($having)) { + return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + } + + + if (!$this->_query['having']) { + $this->_query['having'] = " HAVING {$having} "; + return null; + } + $this->_query['having'] .= " AND {$having}"; + return null; + } + + /** + * Adds a using Index + * + * $object->useIndex(); //reset the use Index + * $object->useIndex("some_index"); + * + * Note do not put unfiltered user input into theis method. + * This is mysql specific at present? - might need altering to support other databases. + * + * @param bool $index index or indexes to use. + * @return bool|none|PEAR + * @access public + */ + public function useIndex($index = false) + { + if ($this->_query === false) { + $this->raiseError( + "You cannot do two queries on the same object (copy it before finding)", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); + return false; + } + if ($index === false) { + $this->_query['useindex'] = ''; + return null; + } + // check input...= 0 or ' ' == error! + if ((is_string($index) && !trim($index)) || (is_array($index) && !count($index))) { + return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + } + $index = is_array($index) ? implode(', ', $index) : $index; + + if (!$this->_query['useindex']) { + $this->_query['useindex'] = " USE INDEX ({$index}) "; + return null; + } + $this->_query['useindex'] = substr($this->_query['useindex'], 0, -2) . ", {$index}) "; + return null; + } + + /** + * Sets the Limit + * + * $boject->limit(); // clear limit + * $object->limit(12); + * $object->limit(12,10); + * + * Note this will emit an error on databases other than mysql/postgress + * as there is no 'clean way' to implement it. - you should consider refering to + * your database manual to decide how you want to implement it. + * + * @param string $a limit start (or number), or blank to reset + * @param string $b number + * @return bool|none|PEAR + * @access public + */ + public function limit($a = null, $b = null) + { + if ($this->_query === false) { + $this->raiseError( + "You cannot do two queries on the same object (copy it before finding)", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); + return false; + } + + if ($a === null) { + $this->_query['limit_start'] = ''; + $this->_query['limit_count'] = ''; + return null; + } + // check input...= 0 or ' ' == error! + if ((!is_int($a) && ((string)((int)$a) !== (string)$a)) + || (($b !== null) && (!is_int($b) && ((string)((int)$b) !== (string)$b)))) { + return $this->raiseError("limit: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); + } + global $_DB_DATAOBJECT; + $this->_connect(); + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + + $this->_query['limit_start'] = ($b == null) ? 0 : (int)$a; + $this->_query['limit_count'] = ($b == null) ? (int)$a : (int)$b; + return null; + } + + /** + * Insert the current objects variables into the database + * + * Returns the ID of the inserted element (if auto increment or sequences are used.) + * + * for example + * + * Designed to be extended + * + * $object = new mytable(); + * $object->name = "fred"; + * echo $object->insert(); + * + * @access public + * @return mixed false on failure, int when auto increment or sequence used, otherwise true on success + */ + public function insert() + { + global $_DB_DATAOBJECT; + + // we need to write to the connection (For nextid) - so us the real + // one not, a copyied on (as ret-by-ref fails with overload!) + + if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { + $this->_connect(); + } + + $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + + $items = $this->table(); + + if (!$items) { + $this->raiseError( + "insert:No table definition for {$this->tableName()}", + DB_DATAOBJECT_ERROR_INVALIDCONFIG + ); + return false; + } + $options = $_DB_DATAOBJECT['CONFIG']; + + + $datasaved = 1; + $leftq = ''; + $rightq = ''; + + $seqKeys = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]) ? + $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] : + $this->sequenceKey(); + + $key = isset($seqKeys[0]) ? $seqKeys[0] : false; + $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false; + $seq = isset($seqKeys[2]) ? $seqKeys[2] : false; + + $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"]; + + + // nativeSequences or Sequences.. + + // big check for using sequences + + if (($key !== false) && !$useNative) { + if (!$seq) { + $keyvalue = $DB->nextId($this->tableName()); + } else { + $f = $DB->getOption('seqname_format'); + $DB->setOption('seqname_format', '%s'); + $keyvalue = $DB->nextId($seq); + $DB->setOption('seqname_format', $f); + } + if ((new PEAR)->isError($keyvalue)) { + $this->raiseError($keyvalue->toString(), DB_DATAOBJECT_ERROR_INVALIDCONFIG); + return false; + } + $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... + if ($key && ($k == $key) && $useNative) { + continue; + } + + + // Ignore INTEGERS which aren't set to a value - or empty string.. + if ((!isset($this->$k) || ($v == 1 && $this->$k === '')) + && $ignore_null + ) { + continue; + } + // dont insert data into mysql timestamps + // use query() if you really want to do this!!!! + if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) { + continue; + } + + if ($leftq) { + $leftq .= ', '; + $rightq .= ', '; + } + + $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ') : "$k "); + + if (is_object($this->$k) && is_a($this->$k, 'DB_DataObject_Cast')) { + $value = $this->$k->toString($v, $DB); + if ((new PEAR)->isError($value)) { + $this->raiseError($value->toString(), DB_DATAOBJECT_ERROR_INVALIDARGS); + return false; + } + $rightq .= $value; + continue; + } + + + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this, $k)) { + $rightq .= " NULL "; + continue; + } + // DATE is empty... on a col. that can be null.. + // note: this may be usefull for time as well.. + if (!$this->$k && + (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && + !($v & DB_DATAOBJECT_NOTNULL)) { + $rightq .= " NULL "; + continue; + } + + + if ($v & DB_DATAOBJECT_STR) { + $rightq .= $this->_quote((string)( + ($v & DB_DATAOBJECT_BOOL) ? + // this is thanks to the braindead idea of postgres to + // use t/f for boolean. + (($this->$k === 'f') ? 0 : (int)(bool)$this->$k) : + $this->$k + )) . " "; + continue; + } + if (is_numeric($this->$k)) { + $rightq .= " {$this->$k} "; + continue; + } + /* flag up string values - only at debug level... !!!??? */ + if (is_object($this->$k) || is_array($this->$k)) { + $this->debug('ODD DATA: ' . $k . ' ' . print_r($this->$k, true), 'ERROR'); + } + + // at present we only cast to integers + // - V2 may store additional data about float/int + $rightq .= ' ' . intval($this->$k) . ' '; + } + + // not sure why we let empty insert here.. - I guess to generate a blank row.. + + + if ($leftq || $useNative) { + $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); + + + if (($dbtype == 'pgsql') && empty($leftq)) { + $r = $this->_query("INSERT INTO {$table} DEFAULT VALUES"); + } else { + $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) "); + } + + + if ((new PEAR)->isError($r)) { + $this->raiseError($r); + return false; + } + + if ($r < 1) { + return 0; + } + + + // now do we have an integer key! + + if ($key && $useNative) { + switch ($dbtype) { + case 'mysql': + case 'mysqli': + $method = "{$dbtype}_insert_id"; + $this->$key = $method( + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection + ); + break; + + case 'mssql': + // note this is not really thread safe - you should wrapp it with + // transactions = eg. + // $db->query('BEGIN'); + // $db->insert(); + // $db->query('COMMIT'); + $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; + $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne'; + $mssql_key = $DB->$method("SELECT @@IDENTITY"); + if ((new PEAR)->isError($mssql_key)) { + $this->raiseError($mssql_key); + return false; + } + $this->$key = $mssql_key; + break; + + case 'pgsql': + if (!$seq) { + $seq = $DB->getSequenceName(strtolower($this->tableName())); + } + $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; + $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne'; + $pgsql_key = $DB->$method("SELECT currval('" . $seq . "')"); + + + if ((new PEAR)->isError($pgsql_key)) { + $this->raiseError($pgsql_key); + return false; + } + $this->$key = $pgsql_key; + break; + + case 'ifx': + $this->$key = array_shift( + ifx_fetch_row( + ifx_query( + "select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1", + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection, + IFX_SCROLL + ), + "FIRST" + ) + ); + break; + + } + } + + if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) { + $this->_clear_cache(); + } + if ($key) { + return $this->$key; + } + return true; + } + $this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA); + return false; + } + + /** + * get/set an sequence key + * + * by default it returns the first key from keys() + * set usage: $do->sequenceKey('id',true); + * + * override this to return array(false,false) if table has no real sequence key. + * + * @param string optional the key sequence/autoinc. key + * @param boolean optional use native increment. default false + * @param false|string optional native sequence name + * @access public + * @return array (column,use_native,sequence_name) + */ + public function sequenceKey() + { + global $_DB_DATAOBJECT; + + // call setting + if (!$this->_database) { + $this->_connect(); + } + + if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) { + $_DB_DATAOBJECT['SEQUENCE'][$this->_database] = array(); + } + + + $args = func_get_args(); + if (count($args)) { + $args[1] = isset($args[1]) ? $args[1] : false; + $args[2] = isset($args[2]) ? $args[2] : false; + $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = $args; + } + if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()])) { + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]; + } + // end call setting (eg. $do->sequenceKeys(a,b,c); ) + + + $keys = $this->keys(); + if (!$keys) { + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] + = array(false, false, false); + } + + + $table = $this->table(); + + $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; + + $usekey = $keys[0]; + + + $seqname = false; + + if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_' . $this->tableName()])) { + $seqname = $_DB_DATAOBJECT['CONFIG']['sequence_' . $this->tableName()]; + if (strpos($seqname, ':') !== false) { + list($usekey, $seqname) = explode(':', $seqname); + } + } + + + // if the key is not an integer - then it's not a sequence or native + if (empty($table[$usekey]) || !($table[$usekey] & DB_DATAOBJECT_INT)) { + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false, false, false); + } + + + if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) { + $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys']; + if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) { + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false, false, $seqname); + } + if (is_string($ignore)) { + $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',', $ignore); + } + if (in_array($this->tableName(), $ignore)) { + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false, false, $seqname); + } + } + + + $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName() . "__keys"]; + + // if you are using an old ini file - go back to old behaviour... + if (is_numeric($realkeys[$usekey])) { + $realkeys[$usekey] = 'N'; + } + + // multiple unique primary keys without a native sequence... + if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) { + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false, false, $seqname); + } + // use native sequence keys... + // technically postgres native here... + // we need to get the new improved tabledata sorted out first. + + // support named sequence keys.. - currently postgres only.. + + if (in_array($dbtype, array('pgsql')) && + ($table[$usekey] & DB_DATAOBJECT_INT) && + isset($realkeys[$usekey]) && strlen($realkeys[$usekey]) > 1) { + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey, true, $realkeys[$usekey]); + } + + if (in_array($dbtype, array('pgsql', 'mysql', 'mysqli', 'mssql', 'ifx')) && + ($table[$usekey] & DB_DATAOBJECT_INT) && + isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N') + ) { + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey, true, $seqname); + } + + + // if not a native autoinc, and we have not assumed all primary keys are sequence + if (($realkeys[$usekey] != 'N') && + !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) { + return array(false, false, false); + } + + + // I assume it's going to try and be a nextval DB sequence.. (not native) + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey, false, $seqname); + } + + /** + * clear the cache values for this class - normally done on insert/update etc. + * + * @access private + * @return void + */ + public function _clear_cache() + { + global $_DB_DATAOBJECT; + + $class = strtolower(get_class($this)); + + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("Clearing Cache for " . $class, 1); + } + + if (!empty($_DB_DATAOBJECT['CACHE'][$class])) { + unset($_DB_DATAOBJECT['CACHE'][$class]); + } + } + + /** + * Updates current objects variables into the database + * uses the keys() to decide how to update + * Returns the true on success + * + * for example + * + * $object = DB_DataObject::factory('mytable'); + * $object->get("ID",234); + * $object->email="testing@test.com"; + * if(!$object->update()) + * echo "UPDATE FAILED"; + * + * to only update changed items : + * $dataobject->get(132); + * $original = $dataobject; // clone/copy it.. + * $dataobject->setFrom($_POST); + * if ($dataobject->validate()) { + * $dataobject->update($original); + * } // otherwise an error... + * + * performing global updates: + * $object = DB_DataObject::factory('mytable'); + * $object->status = "dead"; + * $object->whereAdd('age > 150'); + * $object->update(DB_DATAOBJECT_WHEREADD_ONLY); + * + * @param bool $dataObject + * @return int rows affected or false on failure + * @access public + */ + public function update($dataObject = false) + { + global $_DB_DATAOBJECT; + // connect will load the config! + $this->_connect(); + + + $original_query = $this->_query; + + $items = $this->table(); + + // only apply update against sequence key if it is set????? + + $seq = $this->sequenceKey(); + if ($seq[0] !== false) { + $keys = array($seq[0]); + if (!isset($this->{$keys[0]}) && $dataObject !== true) { + $this->raiseError("update: trying to perform an update without + the key set, and argument to update is not + DB_DATAOBJECT_WHEREADD_ONLY + " . print_r(array('seq' => $seq, 'keys' => $keys), true), DB_DATAOBJECT_ERROR_INVALIDARGS); + return false; + } + } else { + $keys = $this->keys(); + } + + + if (!$items) { + $this->raiseError("update:No table definition for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); + return false; + } + $datasaved = 1; + $settings = ''; + $this->_connect(); + + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $dbtype = $DB->dsn["phptype"]; + $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + $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) { + + // I think this is ignoring empty vlalues + if ((!isset($this->$k) || ($v == 1 && $this->$k === '')) + && $ignore_null + ) { + continue; + } + // ignore stuff thats + + // dont write things that havent changed.. + if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k === $this->$k)) { + continue; + } + + // - dont write keys to left.!!! + if (in_array($k, $keys)) { + continue; + } + + // dont insert data into mysql timestamps + // use query() if you really want to do this!!!! + if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) { + continue; + } + + + if ($settings) { + $settings .= ', '; + } + + $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k); + + if (is_object($this->$k) && is_a($this->$k, 'DB_DataObject_Cast')) { + $value = $this->$k->toString($v, $DB); + if ((new PEAR)->isError($value)) { + $this->raiseError($value->getMessage(), DB_DATAOBJECT_ERROR_INVALIDARG); + return false; + } + $settings .= "$kSql = $value "; + continue; + } + + // special values ... at least null is handled... + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this, $k)) { + $settings .= "$kSql = NULL "; + continue; + } + // DATE is empty... on a col. that can be null.. + // note: this may be usefull for time as well.. + if (!$this->$k && + (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && + !($v & DB_DATAOBJECT_NOTNULL)) { + $settings .= "$kSql = NULL "; + continue; + } + + + if ($v & DB_DATAOBJECT_STR) { + $settings .= "$kSql = " . $this->_quote((string)( + ($v & DB_DATAOBJECT_BOOL) ? + // this is thanks to the braindead idea of postgres to + // use t/f for boolean. + (($this->$k === 'f') ? 0 : (int)(bool)$this->$k) : + $this->$k + )) . ' '; + continue; + } + if (is_numeric($this->$k)) { + $settings .= "$kSql = {$this->$k} "; + continue; + } + // at present we only cast to integers + // - V2 may store additional data about float/int + $settings .= "$kSql = " . intval($this->$k) . ' '; + } + + + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("got keys as " . serialize($keys), 3); + } + if ($dataObject !== true) { + $this->_build_condition($items, $keys); + } else { + // prevent wiping out of data! + if (empty($this->_query['condition'])) { + $this->raiseError("update: global table update not available + do \$do->whereAdd('1=1'); if you really want to do that. + ", DB_DATAOBJECT_ERROR_INVALIDARGS); + return false; + } + } + + + // echo " $settings, $this->condition "; + if ($settings && isset($this->_query) && $this->_query['condition']) { + $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); + + $r = $this->_query("UPDATE {$table} SET {$settings} {$this->_query['condition']} "); + + // restore original query conditions. + $this->_query = $original_query; + + if ((new PEAR)->isError($r)) { + $this->raiseError($r); + return false; + } + if ($r < 1) { + return 0; + } + + $this->_clear_cache(); + return $r; + } + // restore original query conditions. + $this->_query = $original_query; + + // if you manually specified a dataobject, and there where no changes - then it's ok.. + if ($dataObject !== false) { + return true; + } + + $this->raiseError( + "update: No Data specifed for query $settings , {$this->_query['condition']}", + DB_DATAOBJECT_ERROR_NODATA + ); + return false; + } + + /** + * Deletes items from table which match current objects variables + * + * Returns the true on success + * + * for example + * + * Designed to be extended + * + * $object = new mytable(); + * $object->ID=123; + * echo $object->delete(); // builds a conditon + * + * $object = new mytable(); + * $object->whereAdd('age > 12'); + * $object->limit(1); + * $object->orderBy('age DESC'); + * $object->delete(true); // dont use object vars, use the conditions, limit and order. + * + * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then + * we will build the condition only using the whereAdd's. Default is to + * build the condition only using the object parameters. + * + * @access public + * @return mixed Int (No. of rows affected) on success, false on failure, 0 on no data affected + */ + public function delete($useWhere = false) + { + global $_DB_DATAOBJECT; + // connect will load the config! + $this->_connect(); + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + + $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : ''); + + if (!$useWhere) { + $keys = $this->keys(); + $this->_query = array(); // as it's probably unset! + $this->_query['condition'] = ''; // default behaviour not to use where condition + $this->_build_condition($this->table(), $keys); + // if primary keys are not set then use data from rest of object. + if (!$this->_query['condition']) { + $this->_build_condition($this->table(), array(), $keys); + } + $extra_cond = ''; + } + + + // don't delete without a condition + if (($this->_query !== false) && $this->_query['condition']) { + $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); + $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.. + + if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { + if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) || + ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) { + // pear DB + $sql = $DB->modifyLimitQuery($sql, $this->_query['limit_start'], $this->_query['limit_count']); + } else { + // MDB2 + $DB->setLimit($this->_query['limit_count'], $this->_query['limit_start']); + } + } + + + $r = $this->_query($sql); + + + if ((new PEAR)->isError($r)) { + $this->raiseError($r); + return false; + } + if ($r < 1) { + return 0; + } + $this->_clear_cache(); + return $r; + } else { + $this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA); + return false; + } + } + + /** + * fetches a specific row into this object variables + * + * Not recommended - better to use fetch() + * + * Returens true on success + * + * @param int $row row + * @access public + * @return boolean true on success + */ + public function fetchRow($row = null) + { + global $_DB_DATAOBJECT; + if (empty($_DB_DATAOBJECT['CONFIG'])) { + $this->_loadConfig(); + } + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow", 3); + } + if (!$this->tableName()) { + $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG); + return false; + } + if ($row === null) { + $this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS); + return false; + } + if (!$this->N) { + $this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA); + return false; + } + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow", 3); + } + + + $result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]; + $array = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC, $row); + if (!is_array($array)) { + $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA); + return false; + } + $replace = array('.', ' '); + foreach ($array as $k => $v) { + // use strpos as str_replace is slow. + $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ? + $k : str_replace($replace, '_', $k); + + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("$kk = " . $array[$k], "fetchrow LINE", 3); + } + $this->$kk = $array[$k]; + } + + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug("{$this->tableName()} DONE", "fetchrow", 3); + } + return true; + } + + /** + * Find the number of results from a simple query + * + * for example + * + * $object = new mytable(); + * $object->name = "fred"; + * echo $object->count(); + * echo $object->count(true); // dont use object vars. + * echo $object->count('distinct mycol'); count distinct mycol. + * echo $object->count('distinct mycol',true); // dont use object vars. + * echo $object->count('distinct'); // count distinct id (eg. the primary key) + * + * + * @param bool|string (optional) + * (true|false => see below not on whereAddonly) + * (string) + * "DISTINCT" => does a distinct count on the tables 'key' column + * otherwise => normally it counts primary keys - you can use + * this to do things like $do->count('distinct mycol'); + * + * @param bool $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then + * we will build the condition only using the whereAdd's. Default is to + * build the condition using the object parameters as well. + * + * @access public + * @return int + */ + public function count($countWhat = false, $whereAddOnly = false) + { + global $_DB_DATAOBJECT; + + if (is_bool($countWhat)) { + $whereAddOnly = $countWhat; + } + + $t = clone($this); + $items = $t->table(); + + $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + + + if (!isset($t->_query)) { + $this->raiseError( + "You cannot do run count after you have run fetch()", + DB_DATAOBJECT_ERROR_INVALIDARGS + ); + return false; + } + $this->_connect(); + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + + + if (!$whereAddOnly && $items) { + $t->_build_condition($items); + } + $keys = $this->keys(); + + if (empty($keys[0]) && (!is_string($countWhat) || (strtoupper($countWhat) == 'DISTINCT'))) { + $this->raiseError( + "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->tableName()) : $this->tableName()); + $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. + $countWhat = (strtoupper($countWhat) == 'DISTINCT') ? + "DISTINCT {$table}.{$key_col}" : $countWhat; + + $countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}"; + + $r = $t->_query( + "SELECT count({$countWhat}) as $as + FROM $table {$t->_join} {$t->_query['condition']}" + ); + if ((new PEAR)->isError($r)) { + return false; + } + + $result = $_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid]; + $l = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED); + // free the results - essential on oracle. + $t->free(); + if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { + $this->debug('Count returned ' . $l[0], 1); + } + return (int)$l[0]; + } + + /** + * Free global arrays associated with this object. + * + * + * @access public + * @return none + */ + public function free() + { + global $_DB_DATAOBJECT; + + if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { + unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]); + } + if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { + unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); + } + // clear the staticGet cache as well. + $this->_clear_cache(); + // this is a huge bug in DB! + 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) { + if ( + !empty($this->{$do}) && + is_object($this->{$do}) && + method_exists($this->{$do}, 'free') + ) { + $this->{$do}->free(); + } + } + } + return null; + } + + /** + * sends raw query to database + * + * Since _query has to be a private 'non overwriteable method', this is a relay + * + * @param string $string SQL Query + * @access public + * @return void or DB_Error + */ + public function query($string) + { + return $this->_query($string); + } + + /** + * an escape wrapper around DB->escapeSimple() + * can be used when adding manual queries or clauses + * eg. + * $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'"); + * + * @param string $string value to be escaped + * @param bool $likeEscape escapes % and _ as well. - so like queries can be protected. + * @access public + * @return string + */ + public function escape($string, $likeEscape = false) + { + global $_DB_DATAOBJECT; + $this->_connect(); + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + // mdb2 uses escape... + $dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver']; + $ret = ($dd == 'DB') ? $DB->escapeSimple($string) : $DB->escape($string); + if ($likeEscape) { + $ret = str_replace(array('_', '%'), array('\_', '\%'), $ret); + } + return $ret; + } + + /** + * Return or assign the name of the current database + * + * @param string optional database name to set + * @access public + * @return string The name of the current database + */ + public function database() + { + $args = func_get_args(); + if (count($args)) { + $this->_database = $args[0]; + } else { + $this->_connect(); + } + + return $this->_database; + } + + /** + * generic getter/setter for links + * + * This is the new 'recommended' way to get get/set linked objects. + * must be used with links.ini + * + * usage: + * get: + * $obj = $do->link('company_id'); + * $obj = $do->link(array('local_col', 'linktable:linked_col')); + * + * set: + * $do->link('company_id',0); + * $do->link('company_id',$obj); + * $do->link('company_id', array($obj)); + * + * example function + * + * function company() { + * $this->link(array('company_id','company:id'), func_get_args()); + * } + * + * + * + * @param $field + * @param array $set_args + * @return mixed true or false on setting, object on getting + * @author Alan Knowles + * @access public + */ + public function link($field, $set_args = array()) + { + //require_once 'DB/DataObject/Links.php'; + require_once 'Links.php'; + $l = new DB_DataObject_Links($this); + return $l->link($field, $set_args); + } + + /** + * load related objects + * + * Generally not recommended to use this. + * The generator should support creating getter_setter methods which are better suited. + * + * Relies on .links.ini + * + * Sets properties on the calling dataobject you can change what + * object vars the links are stored in by changeing the format parameter + * + * + * @param string format (default _%s) where %s is the table name. + * @return boolean , true on success + * @author Tim White + * @access public + */ + public function getLinks($format = '_%s') + { + //require_once 'DB/DataObject/Links.php'; + require_once 'Links.php'; + $l = new DB_DataObject_Links($this); + return $l->applyLinks($format); + } + + /** + * deprecited : @use link() + * @param $row + * @param null $table + * @param bool $link + * @return mixed + */ + public function getLink($row, $table = null, $link = false) + { + //require_once 'DB/DataObject/Links.php'; + require_once 'Links.php'; + $l = new DB_DataObject_Links($this); + return $l->getLink($row, $table === null ? false : $table, $link); + } + + /** + * getLinkArray + * Fetch an array of related objects. This should be used in conjunction with a .links.ini file configuration (see the introduction on linking for details on this). + * You may also use this with all parameters to specify, the column and related table. + * This is highly dependant on naming columns 'correctly' :) + * using colname = xxxxx_yyyyyy + * xxxxxx = related table; (yyyyy = user defined..) + * looks up table xxxxx, for value id=$this->xxxxx + * stores it in $this->_xxxxx_yyyyy + * + * @access public + * @param $row + * @param string $table - name of table to look up value in + * @return array - array of results (empty array on failure) + * + * Example - Getting the related objects + * + * $person = new DataObjects_Person; + * $person->get(12); + * $children = $person->getLinkArray('children'); + * + * echo 'There are ', count($children), ' descendant(s):
'; + * foreach ($children as $child) { + * echo $child->name, '
'; + * } + */ + public function getLinkArray($row, $table = null) + { + //require_once 'DB/DataObject/Links.php'; + require_once 'Links.php'; + $l = new DB_DataObject_Links($this); + return $l->getLinkArray($row, $table === null ? false : $table); + } + + /** + * unionAdd - adds another dataobject to this, building a unioned query. + * + * usage: + * $doTable1 = DB_DataObject::factory("table1"); + * $doTable2 = DB_DataObject::factory("table2"); + * + * $doTable1->selectAdd(); + * $doTable1->selectAdd("col1,col2"); + * $doTable1->whereAdd("col1 > 100"); + * $doTable1->orderBy("col1"); + * + * $doTable2->selectAdd(); + * $doTable2->selectAdd("col1, col2"); + * $doTable2->whereAdd("col2 = 'v'"); + * + * $doTable1->unionAdd($doTable2); + * $doTable1->find(); + * + * Note: this model may be a better way to implement joinAdd?, eg. do the building in find? + * + * + * @param $obj object|false the union object or false to reset + * @param string $is_all string 'ALL' to do all. + * @return false|mixed|object + */ + + public function unionAdd($obj, $is_all = '') + { + if ($obj === false) { + $ret = $this->_query['unions']; + $this->_query['unions'] = array(); + return $ret; + } + $this->_query['unions'][] = array($obj, 'UNION ' . $is_all . ' '); + return $obj; + } + + /** + * autoJoin - using the links.ini file, it builds a query with all the joins + * usage: + * $x = DB_DataObject::factory('mytable'); + * $x->autoJoin(); + * $x->get(123); + * will result in all of the joined data being added to the fetched object.. + * + * $x = DB_DataObject::factory('mytable'); + * $x->autoJoin(); + * $ar = $x->fetchAll(); + * will result in an array containing all the data from the table, and any joined tables.. + * + * $x = DB_DataObject::factory('mytable'); + * $jdata = $x->autoJoin(); + * $x->selectAdd(); //reset.. + * foreach($_REQUEST['requested_cols'] as $c) { + * if (!isset($jdata[$c])) continue; // ignore columns not available.. + * $x->selectAdd( $jdata[$c] . ' as ' . $c); + * } + * $ar = $x->fetchAll(); + * will result in only the columns requested being fetched... + * + * + * + * @param array Configuration + * exclude Array of columns to exclude from results (eg. modified_by_id) + * links The equivilant links.ini data for this table eg. + * array( 'person_id' => 'person:id', .... ) + * include Array of columns to include + * distinct Array of distinct columns. + * + * @return array info about joins + * cols => map of resulting {joined_tablename}.{joined_table_column_name} + * join_names => map of resulting {join_name_as}.{joined_table_column_name} + * count => the column to count on. + * @access public + */ + public function autoJoin($cfg = array()) + { + global $_DB_DATAOBJECT; + //var_Dump($cfg);exit; + $pre_links = $this->links(); + if (!empty($cfg['links'])) { + $this->links(array_merge($pre_links, $cfg['links'])); + } + $map = $this->links(); + + $this->databaseStructure(); + $dbstructure = $_DB_DATAOBJECT['INI'][$this->_database]; + //print_r($map); + $tabdef = $this->table(); + + // we need this as normally it's only cleared by an empty selectAs call. + + + $keys = array_keys($tabdef); + if (!empty($cfg['exclude'])) { + $keys = array_intersect($keys, array_diff($keys, $cfg['exclude'])); + } + if (!empty($cfg['include'])) { + $keys = array_intersect($keys, $cfg['include']); + } + + $selectAs = array(); + + if (!empty($keys)) { + $selectAs = array(array($keys, '%s', false)); + } + + $ret = array( + 'cols' => array(), + 'join_names' => array(), + 'count' => false, + ); + + + $has_distinct = false; + if (!empty($cfg['distinct']) && $keys) { + + // reset the columsn? + $cols = array(); + + //echo '
' ;print_r($xx);exit;
+            foreach ($keys as $c) {
+                //var_dump($c);
+
+                if ($cfg['distinct'] == $c) {
+                    $has_distinct = 'DISTINCT( ' . $this->tableName() . '.' . $c . ') as ' . $c;
+                    $ret['count'] = 'DISTINCT  ' . $this->tableName() . '.' . $c . '';
+                    continue;
+                }
+                // cols is in our filtered keys...
+                $cols = $c;
             }
+            // apply our filtered version, which excludes the distinct column.
+
+            $selectAs = empty($cols) ? array() : array(array(array($cols), '%s', false));
+        }
+
+        foreach ($keys as $k) {
+            $ret['cols'][$k] = $this->tableName() . '.' . $k;
         }
 
-        */
-        // does this need multi db support??
-        $cp = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
-            explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']['class_prefix']) : '';
-        
-        //print_r($cp);
-        
-        // 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;
+
+        foreach ($map as $ocl => $info) {
+            list($tab, $col) = explode(':', $info);
+            // what about multiple joins on the same table!!!
+
+            // if links point to a table that does not exist - ignore.
+            if (!isset($dbstructure[$tab])) {
+                continue;
+            }
+
+            $xx = DB_DataObject::factory($tab);
+            if (!is_object($xx) || !is_a($xx, 'DB_DataObject')) {
+                continue;
+            }
+            // skip columns that are excluded.
+
+            // we ignore include here... - as
+
+            // this is borked ... for multiple jions..
+            $this->joinAdd($xx, 'LEFT', 'join_' . $ocl . '_' . $col, $ocl);
+
+            if (!empty($cfg['exclude']) && in_array($ocl, $cfg['exclude'])) {
+                continue;
+            }
+
+            $tabdef = $xx->table();
+            $table = $xx->tableName();
+
+            $keys = array_keys($tabdef);
+
+
+            if (!empty($cfg['exclude'])) {
+                $keys = array_intersect($keys, array_diff($keys, $cfg['exclude']));
+
+                foreach ($keys as $k) {
+                    if (in_array($ocl . '_' . $k, $cfg['exclude'])) {
+                        $keys = array_diff($keys, $k); // removes the k..
+                    }
                 }
-                $class[] = $cpr . $tbl;
-            }
-        } else {
-            $class = $tbl;
-            $ce = substr(phpversion(), 0, 1) > 4 ? class_exists($class, false) : class_exists($class);
-        }
-        
-        
-        $rclass = $ce ? $class  : DB_DataObject::_autoloadClass($class, $table);
-        // proxy = full|light
-        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';
-            
-            $d = new DB_DataObject;
-           
-            $d->__table = $table;
-            
-            $ret = $d->_connect();
-            if (is_object($ret) && is_a($ret, 'PEAR_Error')) {
-                return $ret;
             }
-            
-            $x = new DB_DataObject_Generator;
-            return $x->$proxyMethod($d->_database, $table);
-        }
-        
-        if (!$rclass || !class_exists($rclass)) {
-            $dor = new DB_DataObject();
-            return $dor->raiseError(
-                "factory could not find class " .
-                (is_array($class) ? implode(PATH_SEPARATOR, $class)  : $class).
-                "from $table",
-                DB_DATAOBJECT_ERROR_INVALIDCONFIG
-            );
-        }
- 
-        $ret = new $rclass();
- 
-        if (!empty($database)) {
-            DB_DataObject::debug("Setting database to $database", "FACTORY", 1);
-            $ret->database($database);
-        }
-        return $ret;
-    }
-    /**
-     * autoload Class
-     *
-     * @param  string|array  $class  Class
-     * @param  string  $table  Table trying to load.
-     * @access private
-     * @return string classname on Success
-     */
-    public function _autoloadClass($class, $table=false)
-    {
-        global $_DB_DATAOBJECT;
-        
-        if (empty($_DB_DATAOBJECT['CONFIG'])) {
-            DB_DataObject::_loadConfig();
-        }
-        $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
-                '' : $_DB_DATAOBJECT['CONFIG']['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'];
-        
-        
-        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";
+            if (!empty($cfg['include'])) {
+                // include will basically be BASECOLNAME_joinedcolname
+                $nkeys = array();
+                foreach ($keys as $k) {
+                    if (in_array(sprintf($ocl . '_%s', $k), $cfg['include'])) {
+                        $nkeys[] = $k;
+                    }
                 }
-                break;
-            default:
-                $file = $cl .'/'.preg_replace('/[^A-Z0-9]/i', '_', ucfirst($table)).".php";
-                break;
-        }
-        
-        $cls = is_array($class) ? $class : array($class);
-        
-        if (is_array($file) || !file_exists($file)) {
-            $found = false;
-            
-            $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";
+                $keys = $nkeys;
+            }
 
-                    if (file_exists($ff)) {
-                        $file = $ff;
-                        $found = true;
-                        break;
+            if (empty($keys)) {
+                continue;
+            }
+            // got distinct, and not yet found it..
+            if (!$has_distinct && !empty($cfg['distinct'])) {
+                $cols = array();
+                foreach ($keys as $c) {
+                    $tn = sprintf($ocl . '_%s', $c);
+
+                    if ($tn == $cfg['distinct']) {
+                        $has_distinct = 'DISTINCT( ' . 'join_' . $ocl . '_' . $col . '.' . $c . ')  as ' . $tn;
+                        $ret['count'] = 'DISTINCT  join_' . $ocl . '_' . $col . '.' . $c;
+                        // var_dump($this->countWhat );
+                        continue;
                     }
+                    $cols[] = $c;
                 }
-                if ($found) {
-                    break;
+
+                if (!empty($cols)) {
+                    $selectAs[] = array($cols, $ocl . '_%s', 'join_' . $ocl . '_' . $col);
                 }
+            } else {
+                $selectAs[] = array($keys, $ocl . '_%s', 'join_' . $ocl . '_' . $col);
             }
-            if (!$found) {
-                $dor = new DB_DataObject();
-                $dor->raiseError(
-                    "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;
+
+            foreach ($keys as $k) {
+                $ret['cols'][sprintf('%s_%s', $ocl, $k)] = $tab . '.' . $k;
+                $ret['join_names'][sprintf('%s_%s', $ocl, $k)] = sprintf('join_%s_%s.%s', $ocl, $col, $k);
             }
         }
-        
-        include_once $file;
-        
-       
-        $ce = false;
-        foreach ($cls as $c) {
-            $ce = substr(phpversion(), 0, 1) > 4 ? class_exists($c, false) : class_exists($c);
-            if ($ce) {
-                $class = $c;
-                break;
-            }
+
+        // fill in the select details..
+        $this->selectAdd();
+
+        if ($has_distinct) {
+            $this->selectAdd($has_distinct);
         }
-        if (!$ce) {
-            $dor = new DB_DataObject();
-            $dor->raiseError(
-                "autoload:Could not autoload " . implode(',', $cls),
-                DB_DATAOBJECT_ERROR_INVALIDCONFIG
-            );
-            return false;
+
+        foreach ($selectAs as $ar) {
+            $this->selectAs($ar[0], $ar[1], $ar[2]);
         }
-        return $class;
+        // restore links..
+        $this->links($pre_links);
+
+        return $ret;
     }
-    
-    
-    
+
     /**
-     * Have the links been loaded?
-     * if they have it contains a array of those variables.
+     * Get the links associate array  as defined by the links.ini file.
      *
-     * @access  private
-     * @var     boolean | array
+     *
+     * Experimental... -
+     * Should look a bit like
+     *       [local_col_name] => "related_tablename:related_col_name"
+     *
+     * @return   array|null
+     *           array       = if there are links defined for this table.
+     *           empty array - if there is a links.ini file, but no links on this table
+     *           false       - if no links.ini exists for this database (hence try auto_links).
+     * @access   public
+     * @see      DB_DataObject::getLinks(), DB_DataObject::getLink()
      */
-    public $_link_loaded = false;
-    
-    /**
-    * Get the links associate array  as defined by the links.ini file.
-    *
-    *
-    * Experimental... -
-    * Should look a bit like
-    *       [local_col_name] => "related_tablename:related_col_name"
-    *
-    * @param    array $new_links optional - force update of the links for this table
-    *               You probably want to restore it to it's original state after,
-    *               as modifying here does it for the whole PHP request.
-    *
-    * @return   array|null
-    *           array       = if there are links defined for this table.
-    *           empty array - if there is a links.ini file, but no links on this table
-    *           false       - if no links.ini exists for this database (hence try auto_links).
-    * @access   public
-    * @see      DB_DataObject::getLinks(), DB_DataObject::getLink()
-    */
-    
+
     public function links()
     {
         global $_DB_DATAOBJECT;
@@ -3044,10 +3535,10 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         // have to connect.. -> otherwise things break later.
         $this->_connect();
-        
+
         // alias for shorter code..
-        $lcfg  = &$_DB_DATAOBJECT['LINKS'];
-        $cfg   =  $_DB_DATAOBJECT['CONFIG'];
+        $lcfg = &$_DB_DATAOBJECT['LINKS'];
+        $cfg = $_DB_DATAOBJECT['CONFIG'];
 
         if ($args = func_get_args()) {
             // an associative array was specified, that updates the current
@@ -3067,11 +3558,11 @@ class DB_DataObject extends DB_DataObject_Overload
             // either no file, or empty..
             return $lcfg[$this->_database] === false ? null : array();
         }
-        
+
         // links are same place as schema by default.
         $schemas = isset($cfg['schema_location']) ?
             array("{$cfg['schema_location']}/{$this->_database}.ini") :
-            array() ;
+            array();
 
         // if ini_* is set look there instead.
         // and support multiple locations.
@@ -3080,15 +3571,15 @@ class DB_DataObject extends DB_DataObject_Overload
                 $cfg["ini_{$this->_database}"] :
                 explode(PATH_SEPARATOR, $cfg["ini_{$this->_database}"]);
         }
-                        
+
         // default to not available.
         $lcfg[$this->_database] = false;
 
         foreach ($schemas as $ini) {
             $links = isset($cfg["links_{$this->_database}"]) ?
-                    $cfg["links_{$this->_database}"] :
-                    str_replace('.ini', '.links.ini', $ini);
-            
+                $cfg["links_{$this->_database}"] :
+                str_replace('.ini', '.links.ini', $ini);
+
             // file really exists..
             if (!file_exists($links) || !is_file($links)) {
                 if (!empty($cfg['debug'])) {
@@ -3106,196 +3597,41 @@ class DB_DataObject extends DB_DataObject_Overload
                 parse_ini_file($links, true)
             );
 
-                        
+
             if (!empty($cfg['debug'])) {
                 $this->debug("Loaded links.ini file: $links", "links", 1);
             }
         }
-        
+
         if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) {
-            foreach ($lcfg[$this->_database] as $k=>$v) {
+            foreach ($lcfg[$this->_database] as $k => $v) {
                 $nk = strtolower($k);
                 // results in duplicate cols.. but not a big issue..
                 $lcfg[$this->_database][$nk] = isset($lcfg[$this->_database][$nk])
-                    ? $lcfg[$this->_database][$nk]  : array();
-                
-                foreach ($v as $kk =>$vv) {
-                    //var_Dump($vv);exit;
-                    $vv =explode(':', $vv);
-                    $vv[0] = strtolower($vv[0]);
-                    $lcfg[$this->_database][$nk][$kk] = implode(':', $vv);
-                }
-            }
-        }
-        //echo '
';print_r($lcfg);exit;
-        
-        // if there is no link data at all on the file!
-        // we return null.
-        if ($lcfg[$this->_database] === false) {
-            return null;
-        }
-        
-        if (isset($lcfg[$this->_database][$this->tableName()])) {
-            return $lcfg[$this->_database][$this->tableName()];
-        }
-        
-        return array();
-    }
-    
-    
-    /**
-     * generic getter/setter for links
-     *
-     * This is the new 'recommended' way to get get/set linked objects.
-     * must be used with links.ini
-     *
-     * usage:
-     *  get:
-     *  $obj = $do->link('company_id');
-     *  $obj = $do->link(array('local_col', 'linktable:linked_col'));
-     *
-     *  set:
-     *  $do->link('company_id',0);
-     *  $do->link('company_id',$obj);
-     *  $do->link('company_id', array($obj));
-     *
-     *  example function
-     *
-     *  function company() {
-     *     $this->link(array('company_id','company:id'), func_get_args());
-     *   }
-     *
-     *
-     *
-     * @param  mixed $link_spec              link specification (normally a string)
-     *                                       uses similar rules to  joinAdd() array argument.
-     * @param  mixed $set_value (optional)   int, DataObject, or array('set')
-     * @author Alan Knowles
-     * @access public
-     * @return mixed true or false on setting, object on getting
-     */
-    public function link($field, $set_args = array())
-    {
-        require_once 'DB/DataObject/Links.php';
-        $l = new DB_DataObject_Links($this);
-        return  $l->link($field, $set_args) ;
-    }
-    
-    /**
-     * load related objects
-     *
-     * Generally not recommended to use this.
-     * The generator should support creating getter_setter methods which are better suited.
-     *
-     * Relies on  .links.ini
-     *
-     * Sets properties on the calling dataobject  you can change what
-     * object vars the links are stored in by  changeing the format parameter
-     *
-     *
-     * @param  string format (default _%s) where %s is the table name.
-     * @author Tim White 
-     * @access public
-     * @return boolean , true on success
-     */
-    public function getLinks($format = '_%s')
-    {
-        require_once 'DB/DataObject/Links.php';
-        $l = new DB_DataObject_Links($this);
-        return $l->applyLinks($format);
-    }
+                    ? $lcfg[$this->_database][$nk] : array();
 
-    /**
-     * deprecited : @use link()
-     */
-    public function getLink($row, $table = null, $link = false)
-    {
-        require_once 'DB/DataObject/Links.php';
-        $l = new DB_DataObject_Links($this);
-        return $l->getLink($row, $table === null ? false: $table, $link);
-    }
+                foreach ($v as $kk => $vv) {
+                    //var_Dump($vv);exit;
+                    $vv = explode(':', $vv);
+                    $vv[0] = strtolower($vv[0]);
+                    $lcfg[$this->_database][$nk][$kk] = implode(':', $vv);
+                }
+            }
+        }
+        //echo '
';print_r($lcfg);exit;
 
-    /**
-     * getLinkArray
-     * Fetch an array of related objects. This should be used in conjunction with a .links.ini file configuration (see the introduction on linking for details on this).
-     * You may also use this with all parameters to specify, the column and related table.
-     * This is highly dependant on naming columns 'correctly' :)
-     * using colname = xxxxx_yyyyyy
-     * xxxxxx = related table; (yyyyy = user defined..)
-     * looks up table xxxxx, for value id=$this->xxxxx
-     * stores it in $this->_xxxxx_yyyyy
-     *
-     * @access public
-     * @param string $column - either column or column.xxxxx
-     * @param string $table - name of table to look up value in
-     * @return array - array of results (empty array on failure)
-     *
-     * Example - Getting the related objects
-     *
-     * $person = new DataObjects_Person;
-     * $person->get(12);
-     * $children = $person->getLinkArray('children');
-     *
-     * echo 'There are ', count($children), ' descendant(s):
'; - * foreach ($children as $child) { - * echo $child->name, '
'; - * } - * - */ - public function getLinkArray($row, $table = null) - { - require_once 'DB/DataObject/Links.php'; - $l = new DB_DataObject_Links($this); - return $l->getLinkArray($row, $table === null ? false: $table); - } + // if there is no link data at all on the file! + // we return null. + if ($lcfg[$this->_database] === false) { + return null; + } - /** - * unionAdd - adds another dataobject to this, building a unioned query. - * - * usage: - * $doTable1 = DB_DataObject::factory("table1"); - * $doTable2 = DB_DataObject::factory("table2"); - * - * $doTable1->selectAdd(); - * $doTable1->selectAdd("col1,col2"); - * $doTable1->whereAdd("col1 > 100"); - * $doTable1->orderBy("col1"); - * - * $doTable2->selectAdd(); - * $doTable2->selectAdd("col1, col2"); - * $doTable2->whereAdd("col2 = 'v'"); - * - * $doTable1->unionAdd($doTable2); - * $doTable1->find(); - * - * Note: this model may be a better way to implement joinAdd?, eg. do the building in find? - * - * - * @param $obj object|false the union object or false to reset - * @param optional $is_all string 'ALL' to do all. - * @returns $obj object|array the added object, or old list if reset. - */ - - public function unionAdd($obj, $is_all= '') - { - if ($obj === false) { - $ret = $this->_query['unions']; - $this->_query['unions'] = array(); - return $ret; + if (isset($lcfg[$this->_database][$this->tableName()])) { + return $lcfg[$this->_database][$this->tableName()]; } - $this->_query['unions'][] = array($obj, 'UNION ' . $is_all . ' ') ; - return $obj; - } - - - /** - * The JOIN condition - * - * @access private - * @var string - */ - public $_join = ''; + return array(); + } /** * joinAdd - adds another dataobject to this, building a joined query. @@ -3323,7 +3659,7 @@ class DB_DataObject extends DB_DataObject_Overload * } * * - * @param optional $obj object |array the joining object (no value resets the join) + * @param bool $obj object |array the joining object (no value resets the join) * If you use an array here it should be in the format: * array('local_column','remotetable:remote_column'); * if remotetable does not have a definition, you should @@ -3331,7 +3667,7 @@ class DB_DataObject extends DB_DataObject_Overload * array('local_column', $dataobject , 'remote_column'); * if array has 3 args, then second is assumed to be the linked dataobject. * - * @param optional $joinType string | array + * @param string $joinType string | array * 'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates * just select ... from a,b,c with no join and * links are added as where items. @@ -3343,11 +3679,10 @@ class DB_DataObject extends DB_DataObject_Overload * 'joinCol' => .... * 'useWhereAsOn' => false, * - * @param optional $joinAs string if you want to select the table as anther name + * @param bool $joinAs string if you want to select the table as anther name * useful when you want to select multiple columsn * from a secondary table. - - * @param optional $joinCol string The column on This objects table to match (needed + * @param bool $joinCol string The column on This objects table to match (needed * if this table links to the child object in * multiple places eg. * user->friend (is a link to another user) @@ -3358,46 +3693,46 @@ class DB_DataObject extends DB_DataObject_Overload * into ON arguments. * * - * @return none + * @return error|none * @access public * @author Stijn de Reede */ - public function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false) + public function joinAdd($obj = false, $joinType = 'INNER', $joinAs = false, $joinCol = false) { global $_DB_DATAOBJECT; if ($obj === false) { $this->_join = ''; - return; + return null; } - + //echo '
'; print_r(func_get_args());
         $useWhereAsOn = false;
         // support for 2nd argument as an array of options
         if (is_array($joinType)) {
             // new options can now go in here... (dont forget to document them)
             $useWhereAsOn = !empty($joinType['useWhereAsOn']);
-            $joinCol      = isset($joinType['joinCol'])  ? $joinType['joinCol']  : $joinCol;
-            $joinAs       = isset($joinType['joinAs'])   ? $joinType['joinAs']   : $joinAs;
-            $joinType     = isset($joinType['joinType']) ? $joinType['joinType'] : 'INNER';
+            $joinCol = isset($joinType['joinCol']) ? $joinType['joinCol'] : $joinCol;
+            $joinAs = isset($joinType['joinAs']) ? $joinType['joinAs'] : $joinAs;
+            $joinType = isset($joinType['joinType']) ? $joinType['joinType'] : 'INNER';
         }
         // support for array as first argument
         // this assumes that you dont have a links.ini for the specified table.
         // and it doesnt exist as am extended dataobject!! - experimental.
-        
+
         $ofield = false; // object field
         $tfield = false; // this field
         $toTable = false;
         if (is_array($obj)) {
             $tfield = $obj[0];
-            
+
             if (count($obj) == 3) {
                 $ofield = $obj[2];
                 $obj = $obj[1];
             } else {
                 list($toTable, $ofield) = explode(':', $obj[1]);
-            
+
                 $obj = is_string($toTable) ? DB_DataObject::factory($toTable) : $toTable;
-            
+
                 if (!$obj || !is_object($obj) || is_a($obj, 'PEAR_Error')) {
                     $obj = new DB_DataObject;
                     $obj->__table = $toTable;
@@ -3408,17 +3743,17 @@ class DB_DataObject extends DB_DataObject_Overload
             // things in the child table...???
             $items = array();
         }
-        
+
         if (!is_object($obj) || !is_a($obj, 'DB_DataObject')) {
             return $this->raiseError("joinAdd: called without an object", DB_DATAOBJECT_ERROR_NODATA, PEAR_ERROR_DIE);
         }
         /*  make sure $this->_database is set.  */
         $this->_connect();
         $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())) {
@@ -3428,12 +3763,11 @@ class DB_DataObject extends DB_DataObject_Overload
             // link contains this_column =  linked_table:linked_column
             foreach ($links as $k => $linkVar) {
                 if (!is_array($linkVar)) {
-                    $linkVar  = array($linkVar);
+                    $linkVar = array($linkVar);
                 }
                 foreach ($linkVar as $v) {
 
-                    
-                    
+
                     /* link contains {this column} = {linked table}:{linked column} */
                     $ar = explode(':', $v);
                     // Feature Request #4266 - Allow joins with multiple keys
@@ -3468,43 +3802,43 @@ class DB_DataObject extends DB_DataObject_Overload
             foreach ($olinks as $k => $linkVar) {
                 /* link contains {this column} = array ( {linked table}:{linked column} )*/
                 if (!is_array($linkVar)) {
-                    $linkVar  = array($linkVar);
+                    $linkVar = array($linkVar);
                 }
                 foreach ($linkVar as $v) {
-                    
+
                     /* link contains {this column} = {linked table}:{linked column} */
                     $ar = explode(':', $v);
-                    
+
                     // Feature Request #4266 - Allow joins with multiple keys
                     $links_key_array = strpos($k, ',');
                     if ($links_key_array !== false) {
                         $k = explode(',', $k);
                     }
-                    
+
                     $ar_array = strpos($ar[1], ',');
                     if ($ar_array !== false) {
                         $ar[1] = explode(',', $ar[1]);
                     }
-                 
+
                     if ($ar[0] != $this->tableName()) {
                         continue;
                     }
-                    
+
                     // you have explictly specified the column
                     // and the col is listed here..
                     // not sure if 1:1 table could cause probs here..
-                    
+
                     if ($joinCol !== false) {
                         $this->raiseError(
                             "joinAdd: You cannot target a join column in the " .
                             "'link from' table ({$obj->tableName()}). " .
-                            "Either remove the fourth argument to joinAdd() ".
+                            "Either remove the fourth argument to joinAdd() " .
                             "({$joinCol}), or alter your links.ini file.",
                             DB_DATAOBJECT_ERROR_NODATA
                         );
                         return false;
                     }
-                
+
                     $ofield = $k;
                     $tfield = $ar[1];
                     break;
@@ -3528,28 +3862,28 @@ class DB_DataObject extends DB_DataObject_Overload
             return false;
         }
         $joinType = strtoupper($joinType);
-        
+
         // we default to joining as the same name (this is remvoed later..)
-        
+
         if ($joinAs === false) {
             $joinAs = $obj->tableName();
         }
-        
+
         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
         $options = $_DB_DATAOBJECT['CONFIG'];
-        
+
         // not sure  how portable adding database prefixes is..
         $objTable = $quoteIdentifiers ?
-                $DB->quoteIdentifier($obj->tableName()) :
-                 $obj->tableName() ;
-                
-        $dbPrefix  = '';
-        if (strlen($obj->_database) && in_array($DB->dsn['phptype'], array('mysql','mysqli'))) {
+            $DB->quoteIdentifier($obj->tableName()) :
+            $obj->tableName();
+
+        $dbPrefix = '';
+        if (strlen($obj->_database) && in_array($DB->dsn['phptype'], array('mysql', 'mysqli'))) {
             $dbPrefix = ($quoteIdentifiers
-                         ? $DB->quoteIdentifier($obj->_database)
-                         : $obj->_database) . '.';
+                    ? $DB->quoteIdentifier($obj->_database)
+                    : $obj->_database) . '.';
         }
-        
+
         // if they are the same, then dont add a prefix...
         if ($obj->_database == $this->_database) {
             $dbPrefix = '';
@@ -3557,10 +3891,10 @@ class DB_DataObject extends DB_DataObject_Overload
         // as far as we know only mysql supports database prefixes..
         // prefixing the database name is now the default behaviour,
         // as it enables joining mutiple columns from multiple databases...
-         
+
         // prefix database (quoted if neccessary..)
         $objTable = $dbPrefix . $objTable;
-       
+
         $cond = '';
 
         // if obj only a dataobject - eg. no extended class has been defined..
@@ -3568,16 +3902,15 @@ class DB_DataObject extends DB_DataObject_Overload
         // until we get on the fly querying of tables..
         // note: we have already checked that it is_a(db_dataobject earlier)
         if (strtolower(get_class($obj)) != 'db_dataobject') {
-                 
+
             // now add where conditions for anything that is set in the object
-        
-        
-        
+
+
             $items = $obj->table();
             // will return an array if no items..
-            
+
             // only fail if we where expecting it to work (eg. not joined on a array)
-             
+
             if (!$items) {
                 $this->raiseError(
                     "joinAdd: No table definition for {$obj->tableName()}",
@@ -3585,31 +3918,31 @@ class DB_DataObject extends DB_DataObject_Overload
                 );
                 return false;
             }
-            
+
             $ignore_null = !isset($options['disable_null_strings'])
-                    || !is_string($options['disable_null_strings'])
-                    || strtolower($options['disable_null_strings']) !== 'full' ;
-            
+                || !is_string($options['disable_null_strings'])
+                || strtolower($options['disable_null_strings']) !== 'full';
+
 
             foreach ($items as $k => $v) {
                 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) (
+                    $obj->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string)(
                         ($v & DB_DATAOBJECT_BOOL) ?
-                                // this is thanks to the braindead idea of postgres to
-                                // use t/f for boolean.
-                                (($obj->$k === 'f') ? 0 : (int)(bool) $obj->$k) :
-                                $obj->$k
+                            // this is thanks to the braindead idea of postgres to
+                            // use t/f for boolean.
+                            (($obj->$k === 'f') ? 0 : (int)(bool)$obj->$k) :
+                            $obj->$k
                         )));
                     continue;
                 }
@@ -3617,18 +3950,18 @@ class DB_DataObject extends DB_DataObject_Overload
                     $obj->whereAdd("{$joinAs}.{$kSql} = {$obj->$k}");
                     continue;
                 }
-                            
+
                 if (is_object($obj->$k) && is_a($obj->$k, 'DB_DataObject_Cast')) {
                     $value = $obj->$k->toString($v, $DB);
-                    if (PEAR::isError($value)) {
+                    if ((new PEAR)->isError($value)) {
                         $this->raiseError($value->getMessage(), DB_DATAOBJECT_ERROR_INVALIDARG);
                         return false;
                     }
                     $obj->whereAdd("{$joinAs}.{$kSql} = $value");
                     continue;
                 }
-                
-                
+
+
                 /* this is probably an error condition! */
                 $obj->whereAdd("{$joinAs}.{$kSql} = 0");
             }
@@ -3650,10 +3983,8 @@ class DB_DataObject extends DB_DataObject_Overload
                 $this->whereAdd($cond);
             }
         }
-    
-        
-        
-        
+
+
         // nested (join of joined objects..)
         $appendJoin = '';
         if ($obj->_join) {
@@ -3666,45 +3997,44 @@ class DB_DataObject extends DB_DataObject_Overload
                 $appendJoin = $obj->_join;
             }
         }
-        
-  
+
+
         // fix for #2216
         // add the joinee object's conditions to the ON clause instead of the WHERE clause
         if ($useWhereAsOn && strlen($cond)) {
             $appendJoin = ' AND ' . $cond . ' ' . $appendJoin;
         }
-               
-        
-        
+
+
         $table = $this->tableName();
-        
+
         if ($quoteIdentifiers) {
-            $joinAs   = $DB->quoteIdentifier($joinAs);
-            $table    = $DB->quoteIdentifier($table);
-            $ofield   = (is_array($ofield)) ? array_map(array($DB, 'quoteIdentifier'), $ofield) : $DB->quoteIdentifier($ofield);
-            $tfield   = (is_array($tfield)) ? array_map(array($DB, 'quoteIdentifier'), $tfield) : $DB->quoteIdentifier($tfield);
+            $joinAs = $DB->quoteIdentifier($joinAs);
+            $table = $DB->quoteIdentifier($table);
+            $ofield = (is_array($ofield)) ? array_map(array($DB, 'quoteIdentifier'), $ofield) : $DB->quoteIdentifier($ofield);
+            $tfield = (is_array($tfield)) ? array_map(array($DB, 'quoteIdentifier'), $tfield) : $DB->quoteIdentifier($tfield);
         }
         // add database prefix if they are different databases
-       
-        
+
+
         $fullJoinAs = '';
-        $addJoinAs  = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->tableName()) : $obj->tableName()) != $joinAs;
+        $addJoinAs = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->tableName()) : $obj->tableName()) != $joinAs;
         if ($addJoinAs) {
             // join table a AS b - is only supported by a few databases and is probably not needed
             // , however since it makes the whole Statement alot clearer we are leaving it in
             // for those databases.
-            $fullJoinAs = in_array($DB->dsn["phptype"], array('mysql','mysqli','pgsql')) ? "AS {$joinAs}" :  $joinAs;
+            $fullJoinAs = in_array($DB->dsn["phptype"], array('mysql', 'mysqli', 'pgsql')) ? "AS {$joinAs}" : $joinAs;
         } else {
             // if
             $joinAs = $dbPrefix . $joinAs;
         }
-        
-        
+
+
         switch ($joinType) {
             case 'INNER':
             case 'LEFT':
             case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
-                
+
                 // Feature Request #4266 - Allow joins with multiple keys
                 $jadd = "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
                 //$this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
@@ -3725,221 +4055,73 @@ class DB_DataObject extends DB_DataObject_Overload
                 //echo $jadd ."\n";
                 $this->_join .= $jadd;
                 break;
-                
+
             case '': // this is just a standard multitable select..
                 $this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}";
                 $this->whereAdd("{$joinAs}.{$ofield}={$table}.{$tfield}");
         }
-         
-         
+
+
         return true;
     }
 
     /**
-     * autoJoin - using the links.ini file, it builds a query with all the joins
-     * usage:
-     * $x = DB_DataObject::factory('mytable');
-     * $x->autoJoin();
-     * $x->get(123);
-     *   will result in all of the joined data being added to the fetched object..
-     *
-     * $x = DB_DataObject::factory('mytable');
-     * $x->autoJoin();
-     * $ar = $x->fetchAll();
-     *   will result in an array containing all the data from the table, and any joined tables..
-     *
-     * $x = DB_DataObject::factory('mytable');
-     * $jdata = $x->autoJoin();
-     * $x->selectAdd(); //reset..
-     * foreach($_REQUEST['requested_cols'] as $c) {
-     *    if (!isset($jdata[$c])) continue; // ignore columns not available..
-     *    $x->selectAdd( $jdata[$c] . ' as ' . $c);
-     * }
-     * $ar = $x->fetchAll();
-     *   will result in only the columns requested being fetched...
-     *
-     *
+     * Adds multiple Columns or objects to select with formating.
      *
-     * @param     array     Configuration
-     *          exclude  Array of columns to exclude from results (eg. modified_by_id)
-     *          links    The equivilant links.ini data for this table eg.
-     *                    array( 'person_id' => 'person:id', .... )
-     *          include  Array of columns to include
-     *          distinct Array of distinct columns.
+     * $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......"
+     *                      // note with null it will also clear the '*' default select
+     * $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x"
+     * $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b"
+     * $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as
+     *                  objectTableName.colnameA as prefix_colnameA
      *
-     * @return   array      info about joins
-     *                      cols => map of resulting {joined_tablename}.{joined_table_column_name}
-     *                      join_names => map of resulting {join_name_as}.{joined_table_column_name}
-     *                      count => the column to count on.
-     * @access   public
+     * @param array|object|null the array or object to take column names from.
+     * @param string $format
+     * @param bool $tableName
+     * @return bool|void
+     * @access public
      */
-    public function autoJoin($cfg = array())
+    public function selectAs($from = null, $format = '%s', $tableName = false)
     {
         global $_DB_DATAOBJECT;
-        //var_Dump($cfg);exit;
-        $pre_links = $this->links();
-        if (!empty($cfg['links'])) {
-            $this->links(array_merge($pre_links, $cfg['links']));
-        }
-        $map = $this->links();
-        
-        $this->databaseStructure();
-        $dbstructure = $_DB_DATAOBJECT['INI'][$this->_database];
-        //print_r($map);
-        $tabdef = $this->table();
-         
-        // we need this as normally it's only cleared by an empty selectAs call.
-       
-        
-        $keys = array_keys($tabdef);
-        if (!empty($cfg['exclude'])) {
-            $keys = array_intersect($keys, array_diff($keys, $cfg['exclude']));
-        }
-        if (!empty($cfg['include'])) {
-            $keys =  array_intersect($keys, $cfg['include']);
-        }
-        
-        $selectAs = array();
-        
-        if (!empty($keys)) {
-            $selectAs = array(array( $keys , '%s', false));
-        }
-        
-        $ret = array(
-            'cols' => array(),
-            'join_names' => array(),
-            'count' => false,
-        );
-        
-        
-        
-        $has_distinct = false;
-        if (!empty($cfg['distinct']) && $keys) {
-            
-            // reset the columsn?
-            $cols = array();
-            
-            //echo '
' ;print_r($xx);exit;
-            foreach ($keys as $c) {
-                //var_dump($c);
-                
-                if ($cfg['distinct'] == $c) {
-                    $has_distinct = 'DISTINCT( ' . $this->tableName() .'.'. $c .') as ' . $c;
-                    $ret['count'] =  'DISTINCT  ' . $this->tableName() .'.'. $c .'';
-                    continue;
-                }
-                // cols is in our filtered keys...
-                $cols = $c;
-            }
-            // apply our filtered version, which excludes the distinct column.
-            
-            $selectAs = empty($cols) ?  array() : array(array(array(  $cols) , '%s', false)) ;
-        }
-                
-        foreach ($keys as $k) {
-            $ret['cols'][$k] = $this->tableName(). '.' . $k;
-        }
-        
-         
-        
-        foreach ($map as $ocl=>$info) {
-            list($tab, $col) = explode(':', $info);
-            // what about multiple joins on the same table!!!
-            
-            // if links point to a table that does not exist - ignore.
-            if (!isset($dbstructure[$tab])) {
-                continue;
-            }
-            
-            $xx = DB_DataObject::factory($tab);
-            if (!is_object($xx) || !is_a($xx, 'DB_DataObject')) {
-                continue;
-            }
-            // skip columns that are excluded.
-            
-            // we ignore include here... - as
-             
-            // this is borked ... for multiple jions..
-            $this->joinAdd($xx, 'LEFT', 'join_'.$ocl.'_'. $col, $ocl);
-            
-            if (!empty($cfg['exclude']) && in_array($ocl, $cfg['exclude'])) {
-                continue;
-            }
-            
-            $tabdef = $xx->table();
-            $table = $xx->tableName();
-            
-            $keys = array_keys($tabdef);
-            
-            
-            if (!empty($cfg['exclude'])) {
-                $keys = array_intersect($keys, array_diff($keys, $cfg['exclude']));
-                
-                foreach ($keys as $k) {
-                    if (in_array($ocl.'_'.$k, $cfg['exclude'])) {
-                        $keys = array_diff($keys, $k); // removes the k..
-                    }
-                }
-            }
-            
-            if (!empty($cfg['include'])) {
-                // include will basically be BASECOLNAME_joinedcolname
-                $nkeys = array();
-                foreach ($keys as $k) {
-                    if (in_array(sprintf($ocl.'_%s', $k), $cfg['include'])) {
-                        $nkeys[] = $k;
-                    }
-                }
-                $keys = $nkeys;
-            }
-            
-            if (empty($keys)) {
-                continue;
-            }
-            // got distinct, and not yet found it..
-            if (!$has_distinct && !empty($cfg['distinct'])) {
-                $cols = array();
-                foreach ($keys as $c) {
-                    $tn = sprintf($ocl.'_%s', $c);
-                      
-                    if ($tn == $cfg['distinct']) {
-                        $has_distinct = 'DISTINCT( ' . 'join_'.$ocl.'_'.$col.'.'.$c .')  as ' . $tn ;
-                        $ret['count'] =  'DISTINCT  join_'.$ocl.'_'.$col.'.'.$c;
-                        // var_dump($this->countWhat );
-                        continue;
-                    }
-                    $cols[] = $c;
-                }
-                
-                if (!empty($cols)) {
-                    $selectAs[] = array($cols, $ocl.'_%s', 'join_'.$ocl.'_'. $col);
-                }
-            } else {
-                $selectAs[] = array($keys, $ocl.'_%s', 'join_'.$ocl.'_'. $col);
-            }
-              
-            foreach ($keys as $k) {
-                $ret['cols'][sprintf('%s_%s', $ocl, $k)] = $tab.'.'.$k;
-                $ret['join_names'][sprintf('%s_%s', $ocl, $k)] = sprintf('join_%s_%s.%s', $ocl, $col, $k);
-            }
+
+        if ($this->_query === false) {
+            $this->raiseError(
+                "You cannot do two queries on the same object (copy it before finding)",
+                DB_DATAOBJECT_ERROR_INVALIDARGS
+            );
+            return false;
         }
-        
-        // fill in the select details..
-        $this->selectAdd();
-        
-        if ($has_distinct) {
-            $this->selectAdd($has_distinct);
+
+        if ($from === null) {
+            // blank the '*'
+            $this->selectAdd();
+            $from = $this;
         }
-       
-        foreach ($selectAs as $ar) {
-            $this->selectAs($ar[0], $ar[1], $ar[2]);
+
+
+        $table = $this->tableName();
+        if (is_object($from)) {
+            $table = $from->tableName();
+            $from = array_keys($from->table());
         }
-        // restore links..
-        $this->links($pre_links);
-        
-        return $ret;
+
+        if ($tableName !== false) {
+            $table = $tableName;
+        }
+        $s = '%s';
+        if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) {
+            $this->_connect();
+            $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+            $s = $DB->quoteIdentifier($s);
+            $format = $DB->quoteIdentifier($format);
+        }
+        foreach ($from as $k) {
+            $this->selectAdd(sprintf("{$s}.{$s} as {$format}", $table, $k, $k));
+        }
+        $this->_query['data_select'] .= "\n";
     }
-    
+
     /**
      * Factory method for calling DB_DataObject_Cast
      *
@@ -3955,9 +4137,9 @@ class DB_DataObject extends DB_DataObject_Overload
      *
      *
      * @param string $value (or type if used with 2 arguments)
-     * @param string $callvalue (optional) used with date/null etc..
+     * @return mixed
      */
-    
+
     public function sqlValue($value)
     {
         $method = 'sql';
@@ -3965,35 +4147,38 @@ class DB_DataObject extends DB_DataObject_Overload
             $method = $value;
             $value = func_get_arg(1);
         }
-        require_once 'DB/DataObject/Cast.php';
+        //require_once 'DB/DataObject/Cast.php';
+        require_once 'Cast.php';
         return call_user_func(array('DB_DataObject_Cast', $method), $value);
     }
-    
-    
+
+
+    /* ----------------------- Debugger ------------------ */
+
     /**
      * Copies items that are in the table definitions from an
      * array or object into the current object
      * will not override key values.
      *
      *
-     * @param    array | object  $from
-     * @param    string  $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
-     * @param    boolean  $skipEmpty (dont assign empty values if a column is empty (eg. '' / 0 etc...)
+     * @param array | object $from
+     * @param string $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
+     * @param boolean $skipEmpty (dont assign empty values if a column is empty (eg. '' / 0 etc...)
      * @access   public
-     * @return   true on success or array of key=>setValue error message
+     * @return array|true
      */
-    public function setFrom($from, $format = '%s', $skipEmpty=false)
+    public function setFrom($from, $format = '%s', $skipEmpty = false)
     {
         global $_DB_DATAOBJECT;
-        $keys  = $this->keys();
+        $keys = $this->keys();
         $items = $this->table();
-        
+
         if (!$items) {
             $this->raiseError(
                 "setFrom:Could not find table definition for {$this->tableName()}",
                 DB_DATAOBJECT_ERROR_INVALIDCONFIG
             );
-            return;
+            return null;
         }
         $overload_return = array();
         foreach (array_keys($items) as $k) {
@@ -4003,18 +4188,18 @@ class DB_DataObject extends DB_DataObject_Overload
             if (!$k) {
                 continue; // ignore empty keys!!! what
             }
-            
+
             $chk = is_object($from) &&
                 (
-                    version_compare(phpversion(), "5.1.0", ">=") ?
+                version_compare(phpversion(), "5.1.0", ">=") ?
                     property_exists($from, sprintf($format, $k)) :  // php5.1
                     array_key_exists(sprintf($format, $k), get_class_vars($from)) //older
                 );
             // if from has property ($format($k)
             if ($chk) {
                 $kk = (strtolower($k) == 'from') ? '_from' : $k;
-                if (method_exists($this, 'set'.$kk)) {
-                    $ret = $this->{'set'.$kk}($from->{sprintf($format, $k)});
+                if (method_exists($this, 'set' . $kk)) {
+                    $ret = $this->{'set' . $kk}($from->{sprintf($format, $k)});
                     if (is_string($ret)) {
                         $overload_return[$k] = $ret;
                     }
@@ -4023,22 +4208,22 @@ class DB_DataObject extends DB_DataObject_Overload
                 $this->$k = $from->{sprintf($format, $k)};
                 continue;
             }
-            
+
             if (is_object($from)) {
                 continue;
             }
-            
+
             if (empty($from[sprintf($format, $k)]) && $skipEmpty) {
                 continue;
             }
-            
+
             if (!isset($from[sprintf($format, $k)]) && !DB_DataObject::_is_null($from, sprintf($format, $k))) {
                 continue;
             }
-           
+
             $kk = (strtolower($k) == 'from') ? '_from' : $k;
-            if (method_exists($this, 'set'. $kk)) {
-                $ret =  $this->{'set'.$kk}($from[sprintf($format, $k)]);
+            if (method_exists($this, 'set' . $kk)) {
+                $ret = $this->{'set' . $kk}($from[sprintf($format, $k)]);
                 if (is_string($ret)) {
                     $overload_return[$k] = $ret;
                 }
@@ -4064,6 +4249,114 @@ class DB_DataObject extends DB_DataObject_Overload
         return true;
     }
 
+    /**
+     * standard set* implementation.
+     *
+     * takes data and uses it to set dates/strings etc.
+     * normally called from __call..
+     *
+     * Current supports
+     *   date      = using (standard time format, or unixtimestamp).... so you could create a method :
+     *               function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
+     *
+     *   time      = using strtotime
+     *   datetime  = using  same as date - accepts iso standard or unixtimestamp.
+     *   string    = typecast only..
+     *
+     * TODO: add formater:: eg. d/m/Y for date! ???
+     *
+     * @param string       column of database
+     * @param mixed        value to assign
+     *
+     * @return   true| false     (False on error)
+     * @access   public
+     * @see      DB_DataObject::_call
+     */
+
+
+    public function fromValue($col, $value)
+    {
+        global $_DB_DATAOBJECT;
+        $options = $_DB_DATAOBJECT['CONFIG'];
+        $cols = $this->table();
+        // dont know anything about this col..
+        if (!isset($cols[$col]) || is_a($value, 'DB_DataObject_Cast')) {
+            $this->$col = $value;
+            return true;
+        }
+        //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
+        switch (true) {
+            // set to null and column is can be null...
+            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 (($cols[$col] & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($value, false)):
+
+                return false;
+
+            case (($cols[$col] & DB_DATAOBJECT_DATE) && ($cols[$col] & DB_DATAOBJECT_TIME)):
+                // empty values get set to '' (which is inserted/updated as NULl
+                if (!$value) {
+                    $this->$col = '';
+                }
+
+                if (is_numeric($value)) {
+                    $this->$col = date('Y-m-d H:i:s', $value);
+                    return true;
+                }
+
+                // eak... - no way to validate date time otherwise...
+                $this->$col = (string)$value;
+                return true;
+
+            case ($cols[$col] & DB_DATAOBJECT_DATE):
+                // empty values get set to '' (which is inserted/updated as NULl
+
+                if (!$value) {
+                    $this->$col = '';
+                    return true;
+                }
+
+                if (is_numeric($value)) {
+                    $this->$col = date('Y-m-d', $value);
+                    return true;
+                }
+
+                // try date!!!!
+                require_once 'Date.php';
+                $x = new Date($value);
+                $this->$col = $x->format("%Y-%m-%d");
+                return true;
+
+            case ($cols[$col] & DB_DATAOBJECT_TIME):
+                // empty values get set to '' (which is inserted/updated as NULl
+                if (!$value) {
+                    $this->$col = '';
+                }
+
+                $guess = strtotime($value);
+                if ($guess != -1) {
+                    $this->$col = date('H:i:s', $guess);
+                    return $return = true;
+                }
+                // otherwise an error in type...
+                return false;
+
+            case ($cols[$col] & DB_DATAOBJECT_STR):
+
+                $this->$col = (string)$value;
+                return true;
+
+            // todo : floats numerics and ints...
+            default:
+                $this->$col = $value;
+                return true;
+        }
+    }
+
     /**
      * Returns an associative array from the current data
      * (kind of oblivates the idea behind DataObjects, but
@@ -4074,8 +4367,8 @@ class DB_DataObject extends DB_DataObject_Overload
      *
      * will also return links converted to arrays.
      *
-     * @param   string  sprintf format for array
-     * @param   bool||number    [true = elemnts that have a value set],
+     * @param string  sprintf format for array
+     * @param bool||number    [true = elemnts that have a value set],
      *                          [false = table + returned colums] ,
      *                          [0 = returned columsn only]
      *
@@ -4086,20 +4379,20 @@ class DB_DataObject extends DB_DataObject_Overload
     public function toArray($format = '%s', $hideEmpty = false)
     {
         global $_DB_DATAOBJECT;
-        
+
         // we use false to ignore sprintf.. (speed up..)
         $format = $format == '%s' ? false : $format;
-        
+
         $ret = array();
         $rf = ($this->_resultFields !== false) ? $this->_resultFields :
-                (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ?
-                 $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
-        
+            (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ?
+                $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
+
         $ar = ($rf !== false) ?
             (($hideEmpty === 0) ? $rf : array_merge($rf, $this->table())) :
             $this->table();
 
-        foreach ($ar as $k=>$v) {
+        foreach ($ar as $k => $v) {
             if (!isset($this->$k)) {
                 if (!$hideEmpty) {
                     $ret[$format === false ? $k : sprintf($format, $k)] = '';
@@ -4107,8 +4400,8 @@ class DB_DataObject extends DB_DataObject_Overload
                 continue;
             }
             // call the overloaded getXXXX() method. - except getLink and getLinks
-            if (method_exists($this, 'get'.$k) && !in_array(strtolower($k), array('links','link'))) {
-                $ret[$format === false ? $k : sprintf($format, $k)] = $this->{'get'.$k}();
+            if (method_exists($this, 'get' . $k) && !in_array(strtolower($k), array('links', 'link'))) {
+                $ret[$format === false ? $k : sprintf($format, $k)] = $this->{'get' . $k}();
                 continue;
             }
             // should this call toValue() ???
@@ -4120,7 +4413,7 @@ class DB_DataObject extends DB_DataObject_Overload
         foreach ($this->_link_loaded as $k) {
             $ret[$format === false ? $k : sprintf($format, $k)] = $this->$k->toArray();
         }
-        
+
         return $ret;
     }
 
@@ -4147,28 +4440,28 @@ class DB_DataObject extends DB_DataObject_Overload
      *
      *
      * @access  public
-     * @return  array of validation results (where key=>value, value=false|object if it failed) or true (if they all succeeded)
+     * @return array|bool
      */
     public function validate()
     {
         global $_DB_DATAOBJECT;
         require_once 'Validate.php';
         $table = $this->table();
-        $ret   = array();
-        $seq   = $this->sequenceKey();
+        $ret = array();
+        $seq = $this->sequenceKey();
         $options = $_DB_DATAOBJECT['CONFIG'];
         foreach ($table as $key => $val) {
-            
-            
+
+
             // call user defined validation always...
             $method = "Validate" . ucfirst($key);
             if (method_exists($this, $method)) {
                 $ret[$key] = $this->$method();
                 continue;
             }
-            
+
             // if not null - and it's not set.......
-            
+
             if ($val & DB_DATAOBJECT_NOTNULL && DB_DataObject::_is_null($this, $key)) {
                 // dont check empty sequence key values..
                 if (($key == $seq[0]) && ($seq[1] == true)) {
@@ -4177,8 +4470,8 @@ class DB_DataObject extends DB_DataObject_Overload
                 $ret[$key] = false;
                 continue;
             }
-            
-            
+
+
             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);
@@ -4189,16 +4482,16 @@ class DB_DataObject extends DB_DataObject_Overload
             }
 
             // ignore things that are not set. ?
-           
+
             if (!isset($this->$key)) {
                 continue;
             }
-            
+
             // if the string is empty.. assume it is ok..
-            if (!is_object($this->$key) && !is_array($this->$key) && !strlen((string) $this->$key)) {
+            if (!is_object($this->$key) && !is_array($this->$key) && !strlen((string)$this->$key)) {
                 continue;
             }
-            
+
             // dont try and validate cast objects - assume they are problably ok..
             if (is_object($this->$key) && is_a($this->$key, 'DB_DataObject_Cast')) {
                 continue;
@@ -4206,14 +4499,14 @@ class DB_DataObject extends DB_DataObject_Overload
             // at this point if you have set something to an object, and it's not expected
             // the Validate will probably break!!... - rightly so! (your design is broken,
             // so issuing a runtime error like PEAR_Error is probably not appropriate..
-            
+
             switch (true) {
                 // todo: date time.....
                 case  ($val & DB_DATAOBJECT_STR):
-                    $ret[$key] = Validate::string($this->$key, VALIDATE_PUNCTUATION . VALIDATE_NAME);
+                    $ret[$key] = (new Validate)->string($this->$key, VALIDATE_PUNCTUATION . VALIDATE_NAME);
                     break;
                 case  ($val & DB_DATAOBJECT_INT):
-                    $ret[$key] = Validate::number($this->$key, array('decimal'=>'.'));
+                    $ret[$key] = (new Validate)->number($this->$key, array('decimal' => '.'));
                     break;
             }
         }
@@ -4230,7 +4523,7 @@ class DB_DataObject extends DB_DataObject_Overload
      * Gets the DB object related to an object - so you can use funky peardb stuf with it :)
      *
      * @access public
-     * @return object The DB connection
+     * @return bool|object
      */
     public function getDatabaseConnection()
     {
@@ -4245,16 +4538,15 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         return $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
     }
- 
- 
+
     /**
      * Gets the DB result object related to the objects active query
      *  - so you can use funky pear stuff with it - like pager for example.. :)
      *
      * @access public
-     * @return object The DB result object
+     * @return bool|object
      */
-     
+
     public function getDatabaseResult()
     {
         global $_DB_DATAOBJECT;
@@ -4285,14 +4577,18 @@ class DB_DataObject extends DB_DataObject_Overload
      * has problems with 4.3.2RC2 here
      *
      * @access public
+     * @param $method
+     * @param $params
+     * @param $return
      * @return true?
+     * @throws ReflectionException
      * @see overload
      */
 
-    
+
     public function _call($method, $params, &$return)
     {
-        
+
         //$this->debug("ATTEMPTING OVERLOAD? $method");
         // ignore constructors : - mm
         if (strtolower($method) == strtolower(get_class($this))) {
@@ -4303,183 +4599,74 @@ class DB_DataObject extends DB_DataObject_Overload
         if (($type != 'set') && ($type != 'get')) {
             return false;
         }
-         
-        
-        
+
+
         // deal with naming conflick of setFrom = this is messy ATM!
-        
+
         if (strtolower($method) == 'set_from') {
             $return = $this->toValue('from', isset($params[0]) ? $params[0] : null);
-            return  true;
+            return true;
         }
-        
+
         $element = substr($method, 3);
-        
+
         // dont you just love php's case insensitivity!!!!
-        
-        $array =  array_keys(get_class_vars($class));
+
+        $array = array_keys(get_class_vars($class));
         /* php5 version which segfaults on 5.0.3 */
         if (class_exists('ReflectionClass')) {
-            $reflection = new ReflectionClass($class);
-            $array = array_keys($reflection->getdefaultProperties());
-        }
-        
-        if (!in_array($element, $array)) {
-            // munge case
-            foreach ($array as $k) {
-                $case[strtolower($k)] = $k;
-            }
-            if ((substr(phpversion(), 0, 1) == 5) && isset($case[strtolower($element)])) {
-                trigger_error("PHP5 set/get calls should match the case of the variable", E_USER_WARNING);
-                $element = strtolower($element);
-            }
-            
-            // does it really exist?
-            if (!isset($case[$element])) {
-                return false;
-            }
-            // use the mundged case
-            $element = $case[$element]; // real case !
-        }
-        
-        
-        if ($type == 'get') {
-            $return = $this->toValue($element, isset($params[0]) ? $params[0] : null);
-            return true;
-        }
-        
-        
-        $return = $this->fromValue($element, $params[0]);
-         
-        return true;
-    }
-        
-    
-    /**
-    * standard set* implementation.
-    *
-    * takes data and uses it to set dates/strings etc.
-    * normally called from __call..
-    *
-    * Current supports
-    *   date      = using (standard time format, or unixtimestamp).... so you could create a method :
-    *               function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
-    *
-    *   time      = using strtotime
-    *   datetime  = using  same as date - accepts iso standard or unixtimestamp.
-    *   string    = typecast only..
-    *
-    * TODO: add formater:: eg. d/m/Y for date! ???
-    *
-    * @param   string       column of database
-    * @param   mixed        value to assign
-    *
-    * @return   true| false     (False on error)
-    * @access   public
-    * @see      DB_DataObject::_call
-    */
-  
-    
-    public function fromValue($col, $value)
-    {
-        global $_DB_DATAOBJECT;
-        $options = $_DB_DATAOBJECT['CONFIG'];
-        $cols = $this->table();
-        // dont know anything about this col..
-        if (!isset($cols[$col]) || is_a($value, 'DB_DataObject_Cast')) {
-            $this->$col = $value;
-            return true;
-        }
-        //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
-        switch (true) {
-            // set to null and column is can be null...
-            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 (($cols[$col] & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($value, false)):
-
-                return false;
-        
-            case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
-                // empty values get set to '' (which is inserted/updated as NULl
-                if (!$value) {
-                    $this->$col = '';
-                }
-            
-                if (is_numeric($value)) {
-                    $this->$col = date('Y-m-d H:i:s', $value);
-                    return true;
-                }
-              
-                // eak... - no way to validate date time otherwise...
-                $this->$col = (string) $value;
-                return true;
-            
-            case ($cols[$col] & DB_DATAOBJECT_DATE):
-                // empty values get set to '' (which is inserted/updated as NULl
-                 
-                if (!$value) {
-                    $this->$col = '';
-                    return true;
-                }
-            
-                if (is_numeric($value)) {
-                    $this->$col = date('Y-m-d', $value);
-                    return true;
-                }
-                 
-                // try date!!!!
-                require_once 'Date.php';
-                $x = new Date($value);
-                $this->$col = $x->format("%Y-%m-%d");
-                return true;
-            
-            case ($cols[$col] & DB_DATAOBJECT_TIME):
-                // empty values get set to '' (which is inserted/updated as NULl
-                if (!$value) {
-                    $this->$col = '';
-                }
-            
-                $guess = strtotime($value);
-                if ($guess != -1) {
-                    $this->$col = date('H:i:s', $guess);
-                    return $return = true;
-                }
-                // otherwise an error in type...
+            $reflection = new ReflectionClass($class);
+            $array = array_keys($reflection->getdefaultProperties());
+        }
+
+        if (!in_array($element, $array)) {
+            // munge case
+            foreach ($array as $k) {
+                $case[strtolower($k)] = $k;
+            }
+            if ((substr(phpversion(), 0, 1) == 5) && isset($case[strtolower($element)])) {
+                trigger_error("PHP5 set/get calls should match the case of the variable", E_USER_WARNING);
+                $element = strtolower($element);
+            }
+
+            // does it really exist?
+            if (!isset($case[$element])) {
                 return false;
-            
-            case ($cols[$col] & DB_DATAOBJECT_STR):
-                
-                $this->$col = (string) $value;
-                return true;
-                
-            // todo : floats numerics and ints...
-            default:
-                $this->$col = $value;
-                return true;
+            }
+            // use the mundged case
+            $element = $case[$element]; // real case !
+        }
+
+
+        if ($type == 'get') {
+            $return = $this->toValue($element, isset($params[0]) ? $params[0] : null);
+            return true;
         }
+
+
+        $return = $this->fromValue($element, $params[0]);
+
+        return true;
     }
+
     /**
-    * standard get* implementation.
-    *
-    *  with formaters..
-    * supported formaters:
-    *   date/time : %d/%m/%Y (eg. php strftime) or pear::Date
-    *   numbers   : %02d (eg. sprintf)
-    *  NOTE you will get unexpected results with times like 0000-00-00 !!!
-    *
-    *
-    *
-    * @param   string       column of database
-    * @param   format       foramt
-    *
-    * @return   true     Description
-    * @access   public
-    * @see      DB_DataObject::_call(),strftime(),Date::format()
-    */
+     * standard get* implementation.
+     *
+     *  with formaters..
+     * supported formaters:
+     *   date/time : %d/%m/%Y (eg. php strftime) or pear::Date
+     *   numbers   : %02d (eg. sprintf)
+     *  NOTE you will get unexpected results with times like 0000-00-00 !!!
+     *
+     *
+     *
+     * @param string       column of database
+     * @param format       foramt
+     *
+     * @return string|true
+     * @access   public
+     * @see      DB_DataObject::_call(),strftime(),Date::format()
+     */
     public function toValue($col, $format = null)
     {
         if (is_null($format)) {
@@ -4487,7 +4674,7 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         $cols = $this->table();
         switch (true) {
-            case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
+            case (($cols[$col] & DB_DATAOBJECT_DATE) && ($cols[$col] & DB_DATAOBJECT_TIME)):
                 if (!$this->$col) {
                     return '';
                 }
@@ -4509,7 +4696,7 @@ class DB_DataObject extends DB_DataObject_Overload
                 require_once 'Date.php';
                 $x = new Date($this->$col);
                 return $x->format($format);
-                
+
             case ($cols[$col] & DB_DATAOBJECT_TIME):
                 if (!$this->$col) {
                     return '';
@@ -4520,317 +4707,37 @@ class DB_DataObject extends DB_DataObject_Overload
                 }
                 // otherwise an error in type...
                 return $this->$col;
-                
-            case ($cols[$col] &  DB_DATAOBJECT_MYSQLTIMESTAMP):
+
+            case ($cols[$col] & DB_DATAOBJECT_MYSQLTIMESTAMP):
                 if (!$this->$col) {
                     return '';
                 }
                 require_once 'Date.php';
-                
-                $x = new Date($this->$col);
-                
-                return $x->format($format);
-            
-             
-            case ($cols[$col] &  DB_DATAOBJECT_BOOL):
-                
-                if ($cols[$col] &  DB_DATAOBJECT_STR) {
-                    // it's a 't'/'f' !
-                    return ($this->$col === 't');
-                }
-                return (bool) $this->$col;
-            
-               
-            default:
-                return sprintf($format, $this->col);
-        }
-    }
-    
-    
-    /* ----------------------- Debugger ------------------ */
-
-    /**
-     * Debugger. - use this in your extended classes to output debugging information.
-     *
-     * Uses DB_DataObject::DebugLevel(x) to turn it on
-     *
-     * @param    string $message - message to output
-     * @param    string $logtype - bold at start
-     * @param    string $level   - output level
-     * @access   public
-     * @return   none
-     */
-    public function debug($message, $logtype = 0, $level = 1)
-    {
-        global $_DB_DATAOBJECT;
-
-        if (empty($_DB_DATAOBJECT['CONFIG']['debug'])  ||
-            (is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) &&  $_DB_DATAOBJECT['CONFIG']['debug'] < $level)) {
-            return;
-        }
-        // this is a bit flaky due to php's wonderfull class passing around crap..
-        // but it's about as good as it gets..
-        $class = (isset($this) && is_a($this, 'DB_DataObject')) ? get_class($this) : 'DB_DataObject';
-        
-        if (!is_string($message)) {
-            $message = print_r($message, true);
-        }
-        if (!is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) && is_callable($_DB_DATAOBJECT['CONFIG']['debug'])) {
-            return call_user_func($_DB_DATAOBJECT['CONFIG']['debug'], $class, $message, $logtype, $level);
-        }
-        
-        if (!ini_get('html_errors')) {
-            echo "$class   : $logtype       : $message\n";
-            flush();
-            return;
-        }
-        if (!is_string($message)) {
-            $message = print_r($message, true);
-        }
-        $colorize = ($logtype == 'ERROR') ? '' : '';
-        echo "{$colorize}$class: $logtype: ". nl2br(htmlspecialchars($message)) . "
\n"; - } - - /** - * sets and returns debug level - * eg. DB_DataObject::debugLevel(4); - * - * @param int $v level - * @access public - * @return none - */ - public static function debugLevel($v = null) - { - global $_DB_DATAOBJECT; - if (empty($_DB_DATAOBJECT['CONFIG'])) { - DB_DataObject::_loadConfig(); - } - if ($v !== null) { - $r = isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0; - $_DB_DATAOBJECT['CONFIG']['debug'] = $v; - return $r; - } - return isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0; - } - /** - * Last Error that has occured - * - use $this->_lastError or - * $last_error = PEAR::getStaticProperty('DB_DataObject','lastError'); - * - * @access public - * @var object PEAR_Error (or false) - */ - public $_lastError = false; + $x = new Date($this->$col); - /** - * Default error handling is to create a pear error, but never return it. - * if you need to handle errors you should look at setting the PEAR_Error callback - * this is due to the fact it would wreck havoc on the internal methods! - * - * @param int $message message - * @param int $type type - * @param int $behaviour behaviour (die or continue!); - * @access public - * @return error object - */ - public function raiseError($message, $type = null, $behaviour = null) - { - global $_DB_DATAOBJECT; - - if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) { - $behaviour = null; - } - - $error = &PEAR::getStaticProperty('DB_DataObject', 'lastError'); - - - // no checks for production here?....... - we log errors before we throw them. - DB_DataObject::debug($message, 'ERROR', 1); - - - if (PEAR::isError($message)) { - $error = $message; - } else { - require_once 'DB/DataObject/Error.php'; - $dor = new PEAR(); - $error = $dor->raiseError( - $message, - $type, - $behaviour, - $opts=null, - $userinfo=null, - 'DB_DataObject_Error' - ); - } - // this will never work totally with PHP's object model. - // as this is passed on static calls (like staticGet in our case) - - $_DB_DATAOBJECT['LASTERROR'] = $error; - - if (isset($this) && is_object($this) && is_subclass_of($this, 'db_dataobject')) { - $this->_lastError = $error; - } - - return $error; - } + return $x->format($format); - /** - * Define the global $_DB_DATAOBJECT['CONFIG'] as an alias to PEAR::getStaticProperty('DB_DataObject','options'); - * - * After Profiling DB_DataObject, I discoved that the debug calls where taking - * considerable time (well 0.1 ms), so this should stop those calls happening. as - * all calls to debug are wrapped with direct variable queries rather than actually calling the funciton - * THIS STILL NEEDS FURTHER INVESTIGATION - * - * @access public - * @return object an error object - */ - public function _loadConfig() - { - global $_DB_DATAOBJECT; - $_DB_DATAOBJECT['CONFIG'] = &PEAR::getStaticProperty('DB_DataObject', 'options'); - } - /** - * Free global arrays associated with this object. - * - * - * @access public - * @return none - */ - public function free() - { - global $_DB_DATAOBJECT; - - if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { - unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]); - } - if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { - unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); - } - // clear the staticGet cache as well. - $this->_clear_cache(); - // this is a huge bug in DB! - if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array(); - } + case ($cols[$col] & DB_DATAOBJECT_BOOL): - if (is_array($this->_link_loaded)) { - foreach ($this->_link_loaded as $do) { - if ( - !empty($this->{$do}) && - is_object($this->{$do}) && - method_exists($this->{$do}, 'free') - ) { - $this->{$do}->free(); + if ($cols[$col] & DB_DATAOBJECT_STR) { + // it's a 't'/'f' ! + return ($this->$col === 't'); } - } - } - } - /** - * 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 - */ - public 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; - } - - /** - * (deprecated - use ::get / and your own caching method) - */ - public static function staticGet($class, $k, $v = null) - { - $lclass = strtolower($class); - global $_DB_DATAOBJECT; - if (empty($_DB_DATAOBJECT['CONFIG'])) { - DB_DataObject::_loadConfig(); - } + return (bool)$this->$col; - - - $key = "$k:$v"; - if ($v === null) { - $key = $k; - } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - DB_DataObject::debug("$class $key", "STATIC GET - TRY CACHE"); - } - if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) { - return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; - } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - DB_DataObject::debug("$class $key", "STATIC GET - NOT IN CACHE"); - } - $obj = DB_DataObject::factory(substr($class, strlen($_DB_DATAOBJECT['CONFIG']['class_prefix']))); - if (PEAR::isError($obj)) { - $dor = new DB_DataObject(); - $dor->raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS); - $r = false; - return $r; - } - - if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) { - $_DB_DATAOBJECT['CACHE'][$lclass] = array(); - } - if (!$obj->get($k, $v)) { - $dor = new DB_DataObject(); - $dor->raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA); - - $r = false; - return $r; + default: + return sprintf($format, $this->col); } - $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj; - return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; } - + /** * autoload Class relating to a table * (deprecited - use ::factory) * - * @param string $table table + * @param string $table table * @access private * @return string classname on Success */ @@ -4843,23 +4750,25 @@ class DB_DataObject extends DB_DataObject_Overload $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? $_DB_DATAOBJECT['CONFIG']['class_prefix'] : ''; $class = $p . preg_replace('/[^A-Z0-9]/i', '_', ucfirst($table)); - + $ce = substr(phpversion(), 0, 1) > 4 ? class_exists($class, false) : class_exists($class); - $class = $ce ? $class : DB_DataObject::_autoloadClass($class); + $class = $ce ? $class : DB_DataObject::_autoloadClass($class); return $class; } - + /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/ - + public function _get_table() { return $this->table(); } + public function _get_keys() { return $this->keys(); } } + // technially 4.3.2RC1 was broken!! // looks like 4.3.3 may have problems too.... if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) { diff --git a/extlib/DB/DataObject/Cast.php b/extlib/DB/DataObject/Cast.php index 15ed077660..b9b22d5e6a 100644 --- a/extlib/DB/DataObject/Cast.php +++ b/extlib/DB/DataObject/Cast.php @@ -20,84 +20,91 @@ * @version CVS: $Id: Cast.php 287158 2009-08-12 13:58:31Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ - + /** -* -* Common usages: -* // blobs -* $data = DB_DataObject_Cast::blob($somefile); -* $data = DB_DataObject_Cast::string($somefile); -* $dataObject->someblobfield = $data -* -* // dates? -* $d1 = new DB_DataObject_Cast::date('12/12/2000'); -* $d2 = new DB_DataObject_Cast::date(2000,12,30); -* $d3 = new DB_DataObject_Cast::date($d1->year, $d1->month+30, $d1->day+30); -* -* // time, datetime.. ????????? -* -* // raw sql???? -* $data = DB_DataObject_Cast::sql('cast("123123",datetime)'); -* $data = DB_DataObject_Cast::sql('NULL'); -* -* // int's/string etc. are proably pretty pointless..!!!! -* -* -* inside DB_DataObject, -* if (is_a($v,'db_dataobject_class')) { -* $value .= $v->toString(DB_DATAOBJECT_INT,'mysql'); -* } -* -* -* -* - -*/ + * + * Common usages: + * // blobs + * $data = DB_DataObject_Cast::blob($somefile); + * $data = DB_DataObject_Cast::string($somefile); + * $dataObject->someblobfield = $data + * + * // dates? + * $d1 = new DB_DataObject_Cast::date('12/12/2000'); + * $d2 = new DB_DataObject_Cast::date(2000,12,30); + * $d3 = new DB_DataObject_Cast::date($d1->year, $d1->month+30, $d1->day+30); + * + * // time, datetime.. ????????? + * + * // raw sql???? + * $data = DB_DataObject_Cast::sql('cast("123123",datetime)'); + * $data = DB_DataObject_Cast::sql('NULL'); + * + * // int's/string etc. are proably pretty pointless..!!!! + * + * + * inside DB_DataObject, + * if (is_a($v,'db_dataobject_class')) { + * $value .= $v->toString(DB_DATAOBJECT_INT,'mysql'); + * } + * + * + * + * + + */ class DB_DataObject_Cast { - + /** - * Type of data Stored in the object.. - * - * @var string (date|blob|.....?) - * @access public - */ + * Type of data Stored in the object.. + * + * @var string (date|blob|.....?) + * @access public + */ public $type; - + /** - * Data For date representation - * - * @var int day/month/year - * @access public - */ + * Data For date representation + * + * @var int day/month/year + * @access public + */ public $day; public $month; public $year; - + /** - * Generic Data.. - * - * @var string - * @access public - */ + * Generic Data.. + * + * @var string + * @access public + */ public $value; - - + /** + * Data For time representation ** does not handle timezones!! + * + * @var int hour/minute/second + * @access public + */ + public $hour; + public $minute; + public $second; /** - * Blob consructor - * - * create a Cast object from some raw data.. (binary) - * - * - * @param string (with binary data!) - * - * @return object DB_DataObject_Cast - * @access public - */ - + * Blob consructor + * + * create a Cast object from some raw data.. (binary) + * + * + * @param string (with binary data!) + * + * @return object DB_DataObject_Cast + * @access public + */ + public function blob($value) { $r = new DB_DataObject_Cast; @@ -106,19 +113,18 @@ class DB_DataObject_Cast return $r; } - /** - * String consructor (actually use if for ints and everything else!!! - * - * create a Cast object from some string (not binary) - * - * - * @param string (with binary data!) - * - * @return object DB_DataObject_Cast - * @access public - */ - + * String consructor (actually use if for ints and everything else!!! + * + * create a Cast object from some string (not binary) + * + * + * @param string (with binary data!) + * + * @return object DB_DataObject_Cast + * @access public + */ + public function string($value) { $r = new DB_DataObject_Cast; @@ -126,18 +132,18 @@ class DB_DataObject_Cast $r->value = $value; return $r; } - + /** - * SQL constructor (for raw SQL insert) - * - * create a Cast object from some sql - * - * @param string (with binary data!) - * - * @return object DB_DataObject_Cast - * @access public - */ - + * SQL constructor (for raw SQL insert) + * + * create a Cast object from some sql + * + * @param string (with binary data!) + * + * @return object DB_DataObject_Cast + * @access public + */ + public function sql($value) { $r = new DB_DataObject_Cast; @@ -146,36 +152,97 @@ class DB_DataObject_Cast return $r; } + /** + * DateTime Constructor + * + * create a Cast object from a Date/Time + * Maybe should accept a Date object.! + * NO VALIDATION DONE, although some crappy re-calcing done! + * + * @param vargs... accepts + * noargs (now) + * yyyy-mm-dd HH:MM:SS (Iso) + * array(yyyy,mm,dd,HH,MM,SS) + * + * + * @return bool|object + * @access public + * @author therion 5 at hotmail + */ + + public function dateTime() + { + $args = func_get_args(); + switch (count($args)) { + case 0: // no args = now! + $datetime = date('Y-m-d G:i:s', mktime()); + + // no break + case 1: + // continue on from 0 args. + if (!isset($datetime)) { + $datetime = $args[0]; + } + + $parts = explode(' ', $datetime); + $bits = explode('-', $parts[0]); + $bits = array_merge($bits, explode(':', $parts[1])); + break; + + default: // 2 or more.. + $bits = $args; + + } + + if (count($bits) != 6) { + // PEAR ERROR? + return false; + } + + $r = DB_DataObject_Cast::date($bits[0], $bits[1], $bits[2]); + if (!$r) { + return $r; // pass thru error (False) - doesnt happen at present! + } + // change the type! + $r->type = 'datetime'; + + // should we mathematically sort this out.. + // (or just assume that no-one's dumb enough to enter 26:90:90 as a time! + $r->hour = $bits[3]; + $r->minute = $bits[4]; + $r->second = $bits[5]; + return $r; + } /** - * Date Constructor - * - * create a Cast object from some string (not binary) - * NO VALIDATION DONE, although some crappy re-calcing done! - * - * @param vargs... accepts - * dd/mm - * dd/mm/yyyy - * yyyy-mm - * yyyy-mm-dd - * array(yyyy,dd) - * array(yyyy,dd,mm) - * - * - * - * @return object DB_DataObject_Cast - * @access public - */ - + * Date Constructor + * + * create a Cast object from some string (not binary) + * NO VALIDATION DONE, although some crappy re-calcing done! + * + * @param vargs... accepts + * dd/mm + * dd/mm/yyyy + * yyyy-mm + * yyyy-mm-dd + * array(yyyy,dd) + * array(yyyy,dd,mm) + * + * + * + * @return object DB_DataObject_Cast + * @access public + */ + public function date() { $args = func_get_args(); switch (count($args)) { case 0: // no args = today! - $bits = explode('-', date('Y-m-d')); + $bits = explode('-', date('Y-m-d')); break; case 1: // one arg = a string - + if (strpos($args[0], '/') !== false) { $bits = array_reverse(explode('/', $args[0])); } else { @@ -188,11 +255,11 @@ class DB_DataObject_Cast if (count($bits) == 1) { // if YYYY set day = 1st.. $bits[] = 1; } - + if (count($bits) == 2) { // if YYYY-DD set day = 1st.. $bits[] = 1; } - + // if year < 1970 we cant use system tools to check it... // so we make a few best gueses.... // basically do date calculations for the year 2000!!! @@ -210,126 +277,49 @@ class DB_DataObject_Cast list($r->year, $r->month, $r->day) = $bits; return $r; } - - /** - * Data For time representation ** does not handle timezones!! - * - * @var int hour/minute/second - * @access public - */ - public $hour; - public $minute; - public $second; - - - /** - * DateTime Constructor - * - * create a Cast object from a Date/Time - * Maybe should accept a Date object.! - * NO VALIDATION DONE, although some crappy re-calcing done! - * - * @param vargs... accepts - * noargs (now) - * yyyy-mm-dd HH:MM:SS (Iso) - * array(yyyy,mm,dd,HH,MM,SS) - * - * - * @return object DB_DataObject_Cast - * @access public - * @author therion 5 at hotmail - */ - - public function dateTime() - { - $args = func_get_args(); - switch (count($args)) { - case 0: // no args = now! - $datetime = date('Y-m-d G:i:s', mktime()); - - // no break - case 1: - // continue on from 0 args. - if (!isset($datetime)) { - $datetime = $args[0]; - } - - $parts = explode(' ', $datetime); - $bits = explode('-', $parts[0]); - $bits = array_merge($bits, explode(':', $parts[1])); - break; - - default: // 2 or more.. - $bits = $args; - - } - - if (count($bits) != 6) { - // PEAR ERROR? - return false; - } - - $r = DB_DataObject_Cast::date($bits[0], $bits[1], $bits[2]); - if (!$r) { - return $r; // pass thru error (False) - doesnt happen at present! - } - // change the type! - $r->type = 'datetime'; - - // should we mathematically sort this out.. - // (or just assume that no-one's dumb enough to enter 26:90:90 as a time! - $r->hour = $bits[3]; - $r->minute = $bits[4]; - $r->second = $bits[5]; - return $r; - } - - - - /** - * time Constructor - * - * create a Cast object from a Date/Time - * Maybe should accept a Date object.! - * NO VALIDATION DONE, and no-recalcing done! - * - * @param vargs... accepts - * noargs (now) - * HH:MM:SS (Iso) - * array(HH,MM,SS) - * - * - * @return object DB_DataObject_Cast - * @access public - * @author therion 5 at hotmail - */ + * time Constructor + * + * create a Cast object from a Date/Time + * Maybe should accept a Date object.! + * NO VALIDATION DONE, and no-recalcing done! + * + * @param vargs... accepts + * noargs (now) + * HH:MM:SS (Iso) + * array(HH,MM,SS) + * + * + * @return bool|object + * @access public + * @author therion 5 at hotmail + */ public function time() { $args = func_get_args(); switch (count($args)) { case 0: // no args = now! $time = date('G:i:s', mktime()); - - // no break + + // no break case 1: // continue on from 0 args. if (!isset($time)) { $time = $args[0]; } - $bits = explode(':', $time); + $bits = explode(':', $time); break; - + default: // 2 or more.. $bits = $args; - + } - + if (count($bits) != 3) { return false; } - + // now take data from bits into object fields $r = new DB_DataObject_Cast; $r->type = 'time'; @@ -339,171 +329,169 @@ class DB_DataObject_Cast return $r; } - - + /** - * get the string to use in the SQL statement for this... - * - * - * @param int $to Type (DB_DATAOBJECT_* - * @param object $db DB Connection Object - * - * - * @return string - * @access public - */ - - public function toString($to=false, $db) + * get the string to use in the SQL statement for this... + * + * + * @param bool $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ + + public function toString($to = false, $db) { // if $this->type is not set, we are in serious trouble!!!! // values for to: - $method = 'toStringFrom'.$this->type; + $method = 'toStringFrom' . $this->type; return $this->$method($to, $db); } - + /** - * get the string to use in the SQL statement from a blob of binary data - * ** Suppots only blob->postgres::bytea - * - * @param int $to Type (DB_DATAOBJECT_* - * @param object $db DB Connection Object - * - * - * @return string - * @access public - */ + * get the string to use in the SQL statement from a blob of binary data + * ** Suppots only blob->postgres::bytea + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ public function toStringFromBlob($to, $db) { // first weed out invalid casts.. // in blobs can only be cast to blobs.! - + // perhaps we should support TEXT fields??? - + if (!($to & DB_DATAOBJECT_BLOB)) { - return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::blob to something other than a blob!'); + return (new PEAR)->raiseError('Invalid Cast from a DB_DataObject_Cast::blob to something other than a blob!'); } - + switch ($db->dsn["phptype"]) { case 'pgsql': - return "'".pg_escape_bytea($this->value)."'::bytea"; - + return "'" . pg_escape_bytea($this->value) . "'::bytea"; + case 'mysql': - return "'".mysql_real_escape_string($this->value, $db->connection)."'"; - + return "'" . mysql_real_escape_string($this->value, $db->connection) . "'"; + case 'mysqli': // this is funny - the parameter order is reversed ;) - return "'".mysqli_real_escape_string($db->connection, $this->value)."'"; - + return "'" . mysqli_real_escape_string($db->connection, $this->value) . "'"; + case 'sqlite': // this is funny - the parameter order is reversed ;) - return "'".sqlite_escape_string($this->value)."'"; - + return "'" . sqlite_escape_string($this->value) . "'"; + case 'mssql': - + if (is_numeric($this->value)) { return $this->value; } $unpacked = unpack('H*hex', $this->value); return '0x' . $unpacked['hex']; - - - + + default: - return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet"); + return (new PEAR)->raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet"); } } - + /** - * get the string to use in the SQL statement for a blob from a string! - * ** Suppots only string->postgres::bytea - * - * - * @param int $to Type (DB_DATAOBJECT_* - * @param object $db DB Connection Object - * - * - * @return string - * @access public - */ + * get the string to use in the SQL statement for a blob from a string! + * ** Suppots only string->postgres::bytea + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ public function toStringFromString($to, $db) { // first weed out invalid casts.. // in blobs can only be cast to blobs.! - + // perhaps we should support TEXT fields??? // - + // $to == a string field which is the default type (0) // so we do not test it here. - we assume that number fields // will accept a string?? - which is stretching it a bit ... // should probaly add that test as some point. - + switch ($db->dsn['phptype']) { case 'pgsql': - return "'".pg_escape_string($this->value)."'::bytea"; - + return "'" . pg_escape_string($this->value) . "'::bytea"; + case 'mysql': - return "'".mysql_real_escape_string($this->value, $db->connection)."'"; - - + return "'" . mysql_real_escape_string($this->value, $db->connection) . "'"; + + case 'mysqli': - return "'".mysqli_real_escape_string($db->connection, $this->value)."'"; + return "'" . mysqli_real_escape_string($db->connection, $this->value) . "'"; case 'mssql': // copied from the old DB mssql code...?? not sure how safe this is. return "'" . str_replace( - array("'", "\\\r\n", "\\\n"), - array("''", "\\\\\r\n\r\n", "\\\\\n\n"), - $this->value + array("'", "\\\r\n", "\\\n"), + array("''", "\\\\\r\n\r\n", "\\\\\n\n"), + $this->value ) . "'"; - + default: - return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet"); + return (new PEAR)->raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet"); } } - - + + /** - * get the string to use in the SQL statement for a date - * - * - * - * @param int $to Type (DB_DATAOBJECT_* - * @param object $db DB Connection Object - * - * - * @return string - * @access public - */ + * get the string to use in the SQL statement for a date + * + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ public function toStringFromDate($to, $db) { // first weed out invalid casts.. // in blobs can only be cast to blobs.! // perhaps we should support TEXT fields??? // - + if (($to !== false) && !($to & DB_DATAOBJECT_DATE)) { - return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a date!'. + return (new PEAR)->raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a date!' . ' (why not just use native features)'); } return "'{$this->year}-{$this->month}-{$this->day}'"; } - + /** - * get the string to use in the SQL statement for a datetime - * - * - * - * @param int $to Type (DB_DATAOBJECT_* - * @param object $db DB Connection Object - * - * - * @return string - * @access public - * @author therion 5 at hotmail - */ - + * get the string to use in the SQL statement for a datetime + * + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + * @author therion 5 at hotmail + */ + public function toStringFromDateTime($to, $db) { // first weed out invalid casts.. @@ -511,7 +499,7 @@ class DB_DataObject_Cast // perhaps we should support TEXT fields??? if (($to !== false) && !($to & (DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME))) { - return PEAR::raiseError('Invalid Cast from a ' . + return (new PEAR)->raiseError('Invalid Cast from a ' . ' DB_DataObject_Cast::dateTime to something other than a datetime!' . ' (try using native features)'); } @@ -519,18 +507,18 @@ class DB_DataObject_Cast } /** - * get the string to use in the SQL statement for a time - * - * - * - * @param int $to Type (DB_DATAOBJECT_* - * @param object $db DB Connection Object - * - * - * @return string - * @access public - * @author therion 5 at hotmail - */ + * get the string to use in the SQL statement for a time + * + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + * @author therion 5 at hotmail + */ public function toStringFromTime($to, $db) { @@ -538,23 +526,23 @@ class DB_DataObject_Cast // in blobs can only be cast to blobs.! // perhaps we should support TEXT fields??? if (($to !== false) && !($to & DB_DATAOBJECT_TIME)) { - return PEAR::raiseError('Invalid Cast from a' . - ' DB_DataObject_Cast::time to something other than a time!'. + return (new PEAR)->raiseError('Invalid Cast from a' . + ' DB_DataObject_Cast::time to something other than a time!' . ' (try using native features)'); } return "'{$this->hour}:{$this->minute}:{$this->second}'"; } - + /** - * get the string to use in the SQL statement for a raw sql statement. - * - * @param int $to Type (DB_DATAOBJECT_* - * @param object $db DB Connection Object - * - * - * @return string - * @access public - */ + * get the string to use in the SQL statement for a raw sql statement. + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ public function toStringFromSql($to, $db) { return $this->value; diff --git a/extlib/DB/DataObject/Error.php b/extlib/DB/DataObject/Error.php index 677526c579..a72c8ec113 100644 --- a/extlib/DB/DataObject/Error.php +++ b/extlib/DB/DataObject/Error.php @@ -21,32 +21,32 @@ * @version CVS: $Id: Error.php 287158 2009-08-12 13:58:31Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ - - + + class DB_DataObject_Error extends PEAR_Error { - + /** * DB_DataObject_Error constructor. * - * @param mixed $code DB error code, or string with error message. - * @param integer $mode what "error mode" to operate in - * @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER - * @param mixed $debuginfo additional debug info, such as the last query - * + * @param string $message + * @param mixed $code DB error code, or string with error message. + * @param integer $mode what "error mode" to operate in + * @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER * @access public * * @see PEAR_Error */ - public function DB_DataObject_Error( + public function __construct( $message = '', $code = DB_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE - ) { + ) + { $this->PEAR_Error('DB_DataObject Error: ' . $message, $code, $mode, $level); } - - + + // todo : - support code -> message handling, and translated error messages... } diff --git a/extlib/DB/DataObject/Generator.php b/extlib/DB/DataObject/Generator.php index 1ac0712a7e..b81a0b867a 100644 --- a/extlib/DB/DataObject/Generator.php +++ b/extlib/DB/DataObject/Generator.php @@ -18,19 +18,19 @@ * @version CVS: $Id: Generator.php 336719 2015-05-05 10:37:33Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ - - /* - * Security Notes: - * This class uses eval to create classes on the fly. - * The table name and database name are used to check the database before writing the - * class definitions, we now check for quotes and semi-colon's in both variables - * so I cant see how it would be possible to generate code even if - * for some crazy reason you took the classname and table name from User Input. - * - * If you consider that wrong, or can prove it.. let me know! - */ - - /** + +/* +* Security Notes: +* This class uses eval to create classes on the fly. +* The table name and database name are used to check the database before writing the +* class definitions, we now check for quotes and semi-colon's in both variables +* so I cant see how it would be possible to generate code even if +* for some crazy reason you took the classname and table name from User Input. +* +* If you consider that wrong, or can prove it.. let me know! +*/ + +/** * * Config _$ptions * [DB_DataObject] @@ -48,7 +48,7 @@ * We lazy load here, due to problems with the tests not setting up include path correctly. * FIXME! */ -class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php'; +class_exists('DB_DataObject') ? '' : /*require_once 'DB/DataObject.php'*/ require_once '../DataObject.php'; //require_once('Config.php'); /** @@ -93,6 +93,35 @@ class DB_DataObject_Generator extends DB_DataObject * @access private */ public $_fkeys; // active tablename + /** + * Output File was config object, now just string + * Used to generate the Tables + * + * @var string outputbuffer for table definitions + * @access private + */ + public $_newConfig; + /** + * class being extended (can be overridden by [DB_DataObject] extends=xxxx + * + * @var string + * @access private + */ + public $_extends = 'DB_DataObject'; + /** + * line to use for require('DB/DataObject.php'); + * + * @var string + * @access private + */ + public $_extendsFile = "DB/DataObject.php"; + /** + * class being generated + * + * @var string + * @access private + */ + public $_className; /** * The 'starter' = call this to start the process @@ -102,11 +131,11 @@ class DB_DataObject_Generator extends DB_DataObject */ public function start() { - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $databases = array(); - foreach ($options as $k=>$v) { + foreach ($options as $k => $v) { if (substr($k, 0, 9) == 'database_') { $databases[substr($k, 9)] = $v; } @@ -160,41 +189,32 @@ class DB_DataObject_Generator extends DB_DataObject } } $this->debug("DONE\n\n"); + return null; } - /** - * Output File was config object, now just string - * Used to generate the Tables - * - * @var string outputbuffer for table definitions - * @access private - */ - public $_newConfig; - /** * Build a list of tables; * and store it in $this->tables and $this->_definitions[tablename]; * * @access private - * @return none + * @return none|object */ public function _createTableList() { $this->_connect(); - - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); - + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); - $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + + $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $is_MDB2 = ($db_driver != 'DB') ? true : false; if (is_object($__DB) && is_a($__DB, 'PEAR_Error')) { - return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE); + return (new PEAR)->raiseError($__DB->toString(), null, PEAR_ERROR_DIE); } - + if (!$is_MDB2) { // try getting a list of schema tables first. (postgres) $__DB->expectError(DB_ERROR_UNSUPPORTED); @@ -204,11 +224,11 @@ class DB_DataObject_Generator extends DB_DataObject /** * set portability and some modules to fetch the informations */ - $db_options = PEAR::getStaticProperty('MDB2', 'options'); + $db_options = (new PEAR)->getStaticProperty('MDB2', 'options'); if (empty($db_options)) { $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); } - + $__DB->loadModule('Manager'); $__DB->loadModule('Reverse'); } @@ -230,21 +250,21 @@ class DB_DataObject_Generator extends DB_DataObject } if (is_object($this->tables) && is_a($this->tables, 'PEAR_Error')) { - return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE); + return (new PEAR)->raiseError($this->tables->toString(), null, PEAR_ERROR_DIE); } // build views as well if asked to. if (!empty($options['build_views'])) { if (!$is_MDB2) { $views = $__DB->getListOf(is_string($options['build_views']) ? - $options['build_views'] : 'views'); + $options['build_views'] : 'views'); } else { $views = $__DB->manager->listViews(); } if (is_object($views) && is_a($views, 'PEAR_Error')) { - return PEAR::raiseError( + return (new PEAR)->raiseError( 'Error getting Views (check the PEAR bug database for the fix to DB), ' . - $views->toString(), + $views->toString(), null, PEAR_ERROR_DIE ); @@ -258,20 +278,20 @@ class DB_DataObject_Generator extends DB_DataObject foreach ($this->tables as $table) { if (isset($options['generator_include_regex']) && - !preg_match($options['generator_include_regex'], $table)) { + !preg_match($options['generator_include_regex'], $table)) { $this->debug("SKIPPING (generator_include_regex) : $table"); continue; } - + if (isset($options['generator_exclude_regex']) && - preg_match($options['generator_exclude_regex'], $table)) { + preg_match($options['generator_exclude_regex'], $table)) { continue; } - + $strip = empty($options['generator_strip_schema']) ? false : $options['generator_strip_schema']; - $strip = is_numeric($strip) ? (bool) $strip : $strip; + $strip = is_numeric($strip) ? (bool)$strip : $strip; $strip = (is_string($strip) && strtolower($strip) == 'true') ? true : $strip; - + // postgres strip the schema bit from the if (!empty($strip)) { if (!is_string($strip) || preg_match($strip, $table)) { @@ -283,27 +303,26 @@ class DB_DataObject_Generator extends DB_DataObject } } $this->debug("EXTRACTING : $table"); - + $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $__DB->quoteIdentifier($table) : $table; - + if (!$is_MDB2) { - $defs = $__DB->tableInfo($quotedTable); + $defs = $__DB->tableInfo($quotedTable); } else { - $defs = $__DB->reverse->tableInfo($quotedTable); + $defs = $__DB->reverse->tableInfo($quotedTable); // rename the length value, so it matches db's return. } if (is_object($defs) && is_a($defs, 'PEAR_Error')) { // running in debug mode should pick this up as a big warning.. $this->debug("Error reading tableInfo: $table"); - $this->raiseError('Error reading tableInfo, '. $defs->toString()); + $this->raiseError('Error reading tableInfo, ' . $defs->toString()); continue; } // cast all definitions to objects - as we deal with that better. - foreach ($defs as $def) { if (!is_array($def)) { continue; @@ -312,7 +331,7 @@ class DB_DataObject_Generator extends DB_DataObject if (isset($def['length']) && !isset($def['len'])) { $def['len'] = $def['length']; } - $this->_definitions[$table][] = (object) $def; + $this->_definitions[$table][] = (object)$def; } // we find a matching table, just store it into a temporary array $tmp_table[] = $table; @@ -320,27 +339,28 @@ class DB_DataObject_Generator extends DB_DataObject // the temporary table array is now the right one (tables names matching // with regex expressions have been removed) $this->tables = $tmp_table; - + //print_r($this->_definitions); + return null; } - + /** * Auto generation of table data. * * it will output to db_oo_{database} the table definitions * * @access private - * @return none + * @return none|void */ public function generateDefinitions() { $this->debug("Generating Definitions file: "); if (!$this->tables) { $this->debug("-- NO TABLES -- \n"); - return; + return null; } - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); //$this->_newConfig = new Config('IniFile'); @@ -352,21 +372,21 @@ class DB_DataObject_Generator extends DB_DataObject // dont generate a schema if location is not set // it's created on the fly! if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"])) { - return; + return null; } if (!empty($options['generator_no_ini'])) { // built in ini files.. - return; + return null; } - $base = @$options['schema_location']; + $base = @$options['schema_location']; if (isset($options["ini_{$this->_database}"])) { $file = $options["ini_{$this->_database}"]; } else { $file = "{$base}/{$this->_database}.ini"; } - + if (!file_exists(dirname($file))) { require_once 'System.php'; - System::mkdir(array('-p','-m',0755,dirname($file))); + (new System)->mkdir(array('-p', '-m', 0755, dirname($file))); } $this->debug("Writing ini as {$file}\n"); //touch($file); @@ -374,8 +394,8 @@ class DB_DataObject_Generator extends DB_DataObject //print_r($this->_newConfig); $fh = fopen($tmpname, 'w'); if (!$fh) { - return PEAR::raiseError( - "Failed to create temporary file: $tmpname\n". + return (new PEAR)->raiseError( + "Failed to create temporary file: $tmpname\n" . "make sure session.save_path is set and is writable\n", null, PEAR_ERROR_DIE @@ -385,7 +405,7 @@ class DB_DataObject_Generator extends DB_DataObject fclose($fh); $perms = file_exists($file) ? fileperms($file) : 0755; // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. - + if (!@rename($tmpname, $file)) { unlink($file); rename($tmpname, $file); @@ -393,195 +413,25 @@ class DB_DataObject_Generator extends DB_DataObject chmod($file, $perms); //$ret = $this->_newConfig->writeInput($file,false); - //if (PEAR::isError($ret) ) { + //if ((new PEAR)->isError($ret) ) { // return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE); // } - } - /** - * create the data for Foreign Keys (for links.ini) - * Currenly only works with mysql / mysqli / posgtreas - * to use, you must set option: generate_links=true - * - * @author Pascal Sch�ni - */ - - public function _createForiegnKeys() - { - $options = PEAR::getStaticProperty('DB_DataObject', 'options'); - if (empty($options['generate_links'])) { - return false; - } - $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; - if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) { - echo "WARNING: cant handle non-mysql and pgsql introspection for defaults."; - return; // cant handle non-mysql introspection for defaults. - } - $this->debug("generateForeignKeys: Start"); - $DB = $this->getDatabaseConnection(); - - $fk = array(); - - - switch ($DB->phptype) { - - - case 'pgsql': - foreach ($this->tables as $this->table) { - $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table; - $res =& $DB->query("SELECT - pg_catalog.pg_get_constraintdef(r.oid, true) AS condef - FROM pg_catalog.pg_constraint r, - pg_catalog.pg_class c - WHERE c.oid=r.conrelid - AND r.contype = 'f' - AND c.relname = '" . $quotedTable . "'"); - if (PEAR::isError($res)) { - die($res->getMessage()); - } - - while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) { - $treffer = array(); - // this only picks up one of these.. see this for why: http://pear.php.net/bugs/bug.php?id=17049 - preg_match( - "/FOREIGN KEY \((\w*)\) REFERENCES (\w*)\((\w*)\)/i", - $row['condef'], - $treffer - ); - if (!count($treffer)) { - continue; - } - $fk[$this->table][$treffer[1]] = $treffer[2] . ":" . $treffer[3]; - } - } - break; - - - case 'mysql': - case 'mysqli': - default: - - foreach ($this->tables as $this->table) { - $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table; - - $res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable); - - if (PEAR::isError($res)) { - die($res->getMessage()); - } - - $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0); - $treffer = array(); - // Extract FOREIGN KEYS - preg_match_all( - "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i", - $text[1], - $treffer, - PREG_SET_ORDER - ); - - if (!count($treffer)) { - continue; - } - foreach ($treffer as $i=> $tref) { - $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3]; - } - } - - } - - - $this->_fkeys = $fk; - } - - - /** - * generate Foreign Keys (for links.ini) - * Currenly only works with mysql / mysqli - * to use, you must set option: generate_links=true - * - * @author Pascal Sch�ni - */ - public function generateForeignKeys() - { - $options = PEAR::getStaticProperty('DB_DataObject', 'options'); - if (empty($options['generate_links'])) { - return false; - } - $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; - if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) { - echo "WARNING: cant handle non-mysql and pgsql introspection for defaults."; - return; // cant handle non-mysql introspection for defaults. - } - $this->debug("generateForeignKeys: Start"); - - $fk = $this->_fkeys; - $links_ini = ""; - - foreach ($fk as $table => $details) { - $links_ini .= "[$table]\n"; - foreach ($details as $col => $ref) { - $links_ini .= "$col = $ref\n"; - } - $links_ini .= "\n"; - } - - // dont generate a schema if location is not set - // it's created on the fly! - $options = PEAR::getStaticProperty('DB_DataObject', 'options'); - - if (!empty($options['schema_location'])) { - $file = "{$options['schema_location']}/{$this->_database}.links.ini"; - } elseif (isset($options["ini_{$this->_database}"])) { - $file = preg_replace('/\.ini/', '.links.ini', $options["ini_{$this->_database}"]); - } else { - $this->debug("generateForeignKeys: SKIP - schema_location or ini_{database} was not set"); - return; - } - - - if (!file_exists(dirname($file))) { - mkdir(dirname($file), 0755, true); - } - - $this->debug("Writing ini as {$file}\n"); - - //touch($file); // not sure why this is needed? - $tmpname = tempnam(session_save_path(), 'DataObject_'); - - $fh = fopen($tmpname, 'w'); - if (!$fh) { - return PEAR::raiseError( - "Failed to create temporary file: $tmpname\n". - "make sure session.save_path is set and is writable\n", - null, - PEAR_ERROR_DIE - ); - } - fwrite($fh, $links_ini); - fclose($fh); - $perms = file_exists($file) ? fileperms($file) : 0755; - // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. - if (!@rename($tmpname, $file)) { - unlink($file); - rename($tmpname, $file); - } - chmod($file, $perms); + return null; } - /** * The table geneation part * * @access private - * @return tabledef and keys array. + * @return array|tabledef */ public function _generateDefinitionsTable() { global $_DB_DATAOBJECT; - $options = PEAR::getStaticProperty('DB_DataObject', 'options'); + $options = (new PEAR)->getStaticProperty('DB_DataObject', 'options'); $defs = $this->_definitions[$this->table]; $this->_newConfig .= "\n[{$this->table}]\n"; - $keys_out = "\n[{$this->table}__keys]\n"; + $keys_out = "\n[{$this->table}__keys]\n"; $keys_out_primary = ''; $keys_out_secondary = ''; if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { @@ -590,22 +440,21 @@ class DB_DataObject_Generator extends DB_DataObject } $DB = $this->getDatabaseConnection(); $dbtype = $DB->phptype; - + $ret = array( - 'table' => array(), - 'keys' => array(), - ); - + 'table' => array(), + 'keys' => array(), + ); + $ret_keys_primary = array(); $ret_keys_secondary = array(); - - - + + foreach ($defs as $t) { - $n=0; + $n = 0; $write_ini = true; - - + + switch (strtoupper($t->type)) { case 'INT': @@ -621,10 +470,10 @@ class DB_DataObject_Generator extends DB_DataObject case 'BIGINT': $type = DB_DATAOBJECT_INT; if ($t->len == 1) { - $type += DB_DATAOBJECT_BOOL; + $type += DB_DATAOBJECT_BOOL; } break; - + case 'REAL': case 'DOUBLE': case 'DOUBLE PRECISION': // double precision (firebird) @@ -637,142 +486,142 @@ class DB_DataObject_Generator extends DB_DataObject case 'NUMBER': // oci8 $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY... break; - + case 'YEAR': $type = DB_DATAOBJECT_INT; break; - + case 'BIT': case 'BOOL': case 'BOOLEAN': - + $type = DB_DATAOBJECT_BOOL; // postgres needs to quote '0' if ($dbtype == 'pgsql') { - $type += DB_DATAOBJECT_STR; + $type += DB_DATAOBJECT_STR; } break; - + case 'STRING': case 'CHAR': case 'VARCHAR': case 'VARCHAR2': case 'TINYTEXT': - + case 'ENUM': case 'SET': // not really but oh well - + case 'POINT': // mysql geometry stuff - not really string - but will do.. - + case 'TIMESTAMPTZ': // postgres case 'BPCHAR': // postgres case 'INTERVAL': // postgres (eg. '12 days') - + case 'CIDR': // postgres IP net spec case 'INET': // postgres IP case 'MACADDR': // postgress network Mac address. - + case 'INTEGER[]': // postgres type case 'BOOLEAN[]': // postgres type - + $type = DB_DATAOBJECT_STR; break; - + case 'TEXT': case 'MEDIUMTEXT': case 'LONGTEXT': case '_TEXT': //postgres (?? view ??) - + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT; break; - - + + case 'DATE': $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE; break; - + case 'TIME': $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME; break; - - + + case 'DATETIME': - + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; break; - + case 'TIMESTAMP': // do other databases use this??? - + $type = ($dbtype == 'mysql') ? DB_DATAOBJECT_MYSQLTIMESTAMP : DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; break; - - + + case 'BLOB': /// these should really be ignored!!!??? case 'TINYBLOB': case 'MEDIUMBLOB': case 'LONGBLOB': - + case 'CLOB': // oracle character lob support - + case 'BYTEA': // postgres blob support.. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB; break; - + default: - echo "*****************************************************************\n". - "** WARNING UNKNOWN TYPE **\n". - "** Found column '{$t->name}', of type '{$t->type}' **\n". - "** Please submit a bug, describe what type you expect this **\n". - "** column to be **\n". - "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n". - "** Try using MDB2 as the backend - eg set the config option **\n". - "** db_driver = MDB2 **\n". - "*****************************************************************\n"; + echo "*****************************************************************\n" . + "** WARNING UNKNOWN TYPE **\n" . + "** Found column '{$t->name}', of type '{$t->type}' **\n" . + "** Please submit a bug, describe what type you expect this **\n" . + "** column to be **\n" . + "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n" . + "** Try using MDB2 as the backend - eg set the config option **\n" . + "** db_driver = MDB2 **\n" . + "*****************************************************************\n"; $write_ini = false; break; } - + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) { - echo "*****************************************************************\n". - "** WARNING COLUMN NAME UNUSABLE **\n". - "** Found column '{$t->name}', of type '{$t->type}' **\n". - "** Since this column name can't be converted to a php variable **\n". - "** name, and the whole idea of mapping would result in a mess **\n". - "** This column has been ignored... **\n". - "*****************************************************************\n"; + echo "*****************************************************************\n" . + "** WARNING COLUMN NAME UNUSABLE **\n" . + "** Found column '{$t->name}', of type '{$t->type}' **\n" . + "** Since this column name can't be converted to a php variable **\n" . + "** name, and the whole idea of mapping would result in a mess **\n" . + "** This column has been ignored... **\n" . + "*****************************************************************\n"; continue; } - + if (!strlen(trim($t->name))) { continue; // is this a bug? } - + if (preg_match('/not[ _]null/i', $t->flags)) { $type += DB_DATAOBJECT_NOTNULL; } - - - if (in_array($t->name, array('null','yes','no','true','false'))) { - echo "*****************************************************************\n". - "** WARNING **\n". - "** Found column '{$t->name}', which is invalid in an .ini file **\n". - "** This line will not be writen to the file - you will have **\n". - "** define the keys()/method manually. **\n". - "*****************************************************************\n"; + + + if (in_array($t->name, array('null', 'yes', 'no', 'true', 'false'))) { + echo "*****************************************************************\n" . + "** WARNING **\n" . + "** Found column '{$t->name}', which is invalid in an .ini file **\n" . + "** This line will not be writen to the file - you will have **\n" . + "** define the keys()/method manually. **\n" . + "*****************************************************************\n"; $write_ini = false; } else { $this->_newConfig .= "{$t->name} = $type\n"; } - + $ret['table'][$t->name] = $type; // i've no idea if this will work well on other databases? // only use primary key or nextval(), cause the setFrom blocks you setting all key items... // if no keys exist fall back to using unique //echo "\n{$t->name} => {$t->flags}\n"; $secondary_key_match = isset($options['generator_secondary_key_match']) ? $options['generator_secondary_key_match'] : 'primary|unique'; - + $m = array(); if (preg_match('/(auto_increment|nextval\(([^)]*))/i', rawurldecode($t->flags), $m) || (isset($t->autoincrement) && ($t->autoincrement === true))) { @@ -786,126 +635,237 @@ class DB_DataObject_Generator extends DB_DataObject $keys_out_primary .= "{$t->name} = $sn\n"; } $ret_keys_primary[$t->name] = $sn; - } elseif ($secondary_key_match && preg_match('/('.$secondary_key_match.')/i', $t->flags)) { + } elseif ($secondary_key_match && preg_match('/(' . $secondary_key_match . ')/i', $t->flags)) { // keys.. = 1 $key_type = 'K'; if (!preg_match("/(primary)/i", $t->flags)) { $key_type = 'U'; } - + if ($write_ini) { $keys_out_secondary .= "{$t->name} = {$key_type}\n"; } $ret_keys_secondary[$t->name] = $key_type; } } - + $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary); $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary; - + if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { print_r(array("dump for {$this->table}", $ret)); } - + return $ret; } /** - * Convert a table name into a class name -> override this if you want a different mapping - * - * @access public - * @return string class name; - */ - - - public function getClassNameFromTableName($table) - { - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); - $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; - return $class_prefix.preg_replace('/[^A-Z0-9]/i', '_', ucfirst(trim($this->table))); - } - - - /** - * Convert a table name into a file name -> override this if you want a different mapping - * - * @access public - * @return string file name; - */ - - - public function getFileNameFromTableName($table) + * create the data for Foreign Keys (for links.ini) + * Currenly only works with mysql / mysqli / posgtreas + * to use, you must set option: generate_links=true + * + * @author Pascal Sch�ni + */ + + public function _createForiegnKeys() { - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); - $base = $options['class_location']; - if (strpos($base, '%s') !== false) { - $base = dirname($base); + $options = (new PEAR)->getStaticProperty('DB_DataObject', 'options'); + if (empty($options['generate_links'])) { + return false; } - if (!file_exists($base)) { - require_once 'System.php'; - System::mkdir(array('-p',$base)); + $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) { + echo "WARNING: cant handle non-mysql and pgsql introspection for defaults."; + return null; // cant handle non-mysql introspection for defaults. } - if (strpos($options['class_location'], '%s') !== false) { - $outfilename = sprintf( - $options['class_location'], - preg_replace('/[^A-Z0-9]/i', '_', ucfirst($this->table)) - ); - } else { - $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i', '_', ucfirst($this->table)).".php"; + $this->debug("generateForeignKeys: Start"); + $DB = $this->getDatabaseConnection(); + + $fk = array(); + + + switch ($DB->phptype) { + + + case 'pgsql': + foreach ($this->tables as $this->table) { + $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($this->table) : $this->table; + $res =& $DB->query("SELECT + pg_catalog.pg_get_constraintdef(r.oid, true) AS condef + FROM pg_catalog.pg_constraint r, + pg_catalog.pg_class c + WHERE c.oid=r.conrelid + AND r.contype = 'f' + AND c.relname = '" . $quotedTable . "'"); + if ((new PEAR)->isError($res)) { + die($res->getMessage()); + } + + while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) { + $treffer = array(); + // this only picks up one of these.. see this for why: http://pear.php.net/bugs/bug.php?id=17049 + preg_match( + "/FOREIGN KEY \((\w*)\) REFERENCES (\w*)\((\w*)\)/i", + $row['condef'], + $treffer + ); + if (!count($treffer)) { + continue; + } + $fk[$this->table][$treffer[1]] = $treffer[2] . ":" . $treffer[3]; + } + } + break; + + + case 'mysql': + case 'mysqli': + default: + + foreach ($this->tables as $this->table) { + $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($this->table) : $this->table; + + $res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable); + + if ((new PEAR)->isError($res)) { + die($res->getMessage()); + } + + $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0); + $treffer = array(); + // Extract FOREIGN KEYS + preg_match_all( + "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i", + $text[1], + $treffer, + PREG_SET_ORDER + ); + + if (!count($treffer)) { + continue; + } + foreach ($treffer as $i => $tref) { + $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3]; + } + } + } - return $outfilename; + + + $this->_fkeys = $fk; + return null; } - - + /** - * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX) - * - * @access public - * @return string method name; - */ - - - public function getMethodNameFromColumnName($col) + * generate Foreign Keys (for links.ini) + * Currenly only works with mysql / mysqli + * to use, you must set option: generate_links=true + * + * @author Pascal Sch�ni + */ + public function generateForeignKeys() { - return ucfirst($col); + $options = (new PEAR)->getStaticProperty('DB_DataObject', 'options'); + if (empty($options['generate_links'])) { + return false; + } + $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) { + echo "WARNING: cant handle non-mysql and pgsql introspection for defaults."; + return null; // cant handle non-mysql introspection for defaults. + } + $this->debug("generateForeignKeys: Start"); + + $fk = $this->_fkeys; + $links_ini = ""; + + foreach ($fk as $table => $details) { + $links_ini .= "[$table]\n"; + foreach ($details as $col => $ref) { + $links_ini .= "$col = $ref\n"; + } + $links_ini .= "\n"; + } + + // dont generate a schema if location is not set + // it's created on the fly! + $options = (new PEAR)->getStaticProperty('DB_DataObject', 'options'); + + if (!empty($options['schema_location'])) { + $file = "{$options['schema_location']}/{$this->_database}.links.ini"; + } elseif (isset($options["ini_{$this->_database}"])) { + $file = preg_replace('/\.ini/', '.links.ini', $options["ini_{$this->_database}"]); + } else { + $this->debug("generateForeignKeys: SKIP - schema_location or ini_{database} was not set"); + return null; + } + + + if (!file_exists(dirname($file))) { + mkdir(dirname($file), 0755, true); + } + + $this->debug("Writing ini as {$file}\n"); + + //touch($file); // not sure why this is needed? + $tmpname = tempnam(session_save_path(), 'DataObject_'); + + $fh = fopen($tmpname, 'w'); + if (!$fh) { + return (new PEAR)->raiseError( + "Failed to create temporary file: $tmpname\n" . + "make sure session.save_path is set and is writable\n", + null, + PEAR_ERROR_DIE + ); + } + fwrite($fh, $links_ini); + fclose($fh); + $perms = file_exists($file) ? fileperms($file) : 0755; + // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. + if (!@rename($tmpname, $file)) { + unlink($file); + rename($tmpname, $file); + } + chmod($file, $perms); + return null; } - - - - + + /* * building the class files * for each of the tables output a file! */ + public function generateClasses() { //echo "Generating Class files: \n"; - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); - + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); + $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends']; $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location']; - + foreach ($this->tables as $this->table) { - $this->table = trim($this->table); - $this->classname = $this->getClassNameFromTableName($this->table); + $this->table = trim($this->table); + $this->classname = $this->getClassNameFromTableName($this->table); $i = ''; - $outfilename = $this->getFileNameFromTableName($this->table); - + $outfilename = $this->getFileNameFromTableName($this->table); + $oldcontents = ''; if (file_exists($outfilename)) { // file_get_contents??? $oldcontents = implode('', file($outfilename)); } - + $out = $this->_generateClassTable($oldcontents); $this->debug("writing $this->classname\n"); $tmpname = tempnam(session_save_path(), 'DataObject_'); - + $fh = fopen($tmpname, "w"); if (!$fh) { - return PEAR::raiseError( - "Failed to create temporary file: $tmpname\n". + return (new PEAR)->raiseError( + "Failed to create temporary file: $tmpname\n" . "make sure session.save_path is set and is writable\n", null, PEAR_ERROR_DIE @@ -914,47 +874,72 @@ class DB_DataObject_Generator extends DB_DataObject fputs($fh, $out); fclose($fh); $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755; - + // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. if (!@rename($tmpname, $outfilename)) { unlink($outfilename); rename($tmpname, $outfilename); } - + chmod($outfilename, $perms); } //echo $out; + return null; } /** - * class being extended (can be overridden by [DB_DataObject] extends=xxxx + * Convert a table name into a class name -> override this if you want a different mapping * - * @var string - * @access private + * @access public + * @param $table + * @return string class name; */ - public $_extends = 'DB_DataObject'; - /** - * line to use for require('DB/DataObject.php'); - * - * @var string - * @access private - */ - public $_extendsFile = "DB/DataObject.php"; + + public function getClassNameFromTableName($table) + { + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); + $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; + return $class_prefix . preg_replace('/[^A-Z0-9]/i', '_', ucfirst(trim($this->table))); + } /** - * class being generated + * Convert a table name into a file name -> override this if you want a different mapping * - * @var string - * @access private + * @access public + * @param $table + * @return string file name; */ - public $_className; + + + public function getFileNameFromTableName($table) + { + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); + $base = $options['class_location']; + if (strpos($base, '%s') !== false) { + $base = dirname($base); + } + if (!file_exists($base)) { + require_once 'System.php'; + (new System)->mkdir(array('-p', $base)); + } + if (strpos($options['class_location'], '%s') !== false) { + $outfilename = sprintf( + $options['class_location'], + preg_replace('/[^A-Z0-9]/i', '_', ucfirst($this->table)) + ); + } else { + $outfilename = "{$base}/" . preg_replace('/[^A-Z0-9]/i', '_', ucfirst($this->table)) . ".php"; + } + return $outfilename; + } /** * The table class geneation part - single file. * * @access private - * @return none + * @param string $input + * @return none|string */ public function _generateClassTable($input = '') { @@ -965,10 +950,10 @@ class DB_DataObject_Generator extends DB_DataObject $head .= " */\n"; $head .= $this->derivedHookExtendsDocBlock(); - + // requires - if you set extends_location = (blank) then no require line will be set // this can be used if you have an autoloader - + if (!empty($this->_extendsFile)) { $head .= "require_once '{$this->_extendsFile}';\n\n"; } @@ -977,41 +962,41 @@ class DB_DataObject_Generator extends DB_DataObject $head .= $this->derivedHookClassDocBlock(); $head .= "class {$this->classname} extends {$this->_extends} \n{"; - $body = "\n ###START_AUTOCODE\n"; + $body = "\n ###START_AUTOCODE\n"; $body .= " /* the code below is auto generated do not remove the above tag */\n\n"; // table - $p = str_repeat(' ', max(2, (18 - strlen($this->table)))) ; - - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); - - + $p = str_repeat(' ', max(2, (18 - strlen($this->table)))); + + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); + + $var = (substr(phpversion(), 0, 1) > 4) ? 'public' : 'var'; $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var; - - + + $body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n"; - + // if we are using the option database_{databasename} = dsn // then we should add var $_database = here // as database names may not always match.. - + if (empty($GLOBALS['_DB_DATAOBJECT']['CONFIG'])) { DB_DataObject::_loadConfig(); } // Only include the $_database property if the omit_database_var is unset or false - + if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) { $p = str_repeat(' ', max(2, (16 - strlen($this->_database)))); $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n"; } - - + + if (!empty($options['generator_novars'])) { - $var = '//'.$var; + $var = '//' . $var; } - + $defs = $this->_definitions[$this->table]; // show nice information! @@ -1023,28 +1008,28 @@ class DB_DataObject_Generator extends DB_DataObject continue; } if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) { - echo "*****************************************************************\n". - "** WARNING COLUMN NAME UNUSABLE **\n". - "** Found column '{$t->name}', of type '{$t->type}' **\n". - "** Since this column name can't be converted to a php variable **\n". - "** name, and the whole idea of mapping would result in a mess **\n". - "** This column has been ignored... **\n". - "*****************************************************************\n"; + echo "*****************************************************************\n" . + "** WARNING COLUMN NAME UNUSABLE **\n" . + "** Found column '{$t->name}', of type '{$t->type}' **\n" . + "** Since this column name can't be converted to a php variable **\n" . + "** name, and the whole idea of mapping would result in a mess **\n" . + "** This column has been ignored... **\n" . + "*****************************************************************\n"; continue; } - + $pad = str_repeat(' ', max(2, (30 - strlen($t->name)))); - $length = empty($t->len) ? '' : '('.$t->len.')'; - $flags = strlen($t->flags) ? (' '. trim($t->flags)) : ''; - $body .=" {$var} \${$t->name}; {$pad}// {$t->type}{$length}{$flags}\n"; - + $length = empty($t->len) ? '' : '(' . $t->len . ')'; + $flags = strlen($t->flags) ? (' ' . trim($t->flags)) : ''; + $body .= " {$var} \${$t->name}; {$pad}// {$t->type}{$length}{$flags}\n"; + // can not do set as PEAR::DB table info doesnt support it. //if (substr($t->Type,0,3) == "set") // $sets[$t->Field] = "array".substr($t->Type,3); $body .= $this->derivedHookVar($t, strlen($p)); } - + $body .= $this->derivedHookPostVar($defs); // THIS IS TOTALLY BORKED old FC creation @@ -1058,16 +1043,16 @@ class DB_DataObject_Generator extends DB_DataObject $body .= " /* ZE2 compatibility trick*/\n"; $body .= " function __clone() { return \$this;}\n"; } - - + + // depricated - in here for BC... if (!empty($options['static_get'])) { - + // simple creation tools ! (static stuff!) $body .= "\n"; $body .= " /* Static get */\n"; $body .= " $static function staticGet(\$k,\$v=NULL) { " . - "return DB_DataObject::staticGet('{$this->classname}',\$k,\$v = null); }\n"; + "return DB_DataObject::staticGet('{$this->classname}',\$k,\$v = null); }\n"; } // generate getter and setter methods $body .= $this->_generateGetters($input); @@ -1083,7 +1068,7 @@ class DB_DataObject_Generator extends DB_DataObject // $kk = strtoupper($k); // $body .=" function getSets{$k}() { return {$v}; }\n"; //} - + if (!empty($options['generator_no_ini'])) { $def = $this->_generateDefinitionsTable(); // simplify this!? $body .= $this->_generateTableFunction($def['table']); @@ -1103,7 +1088,7 @@ class DB_DataObject_Generator extends DB_DataObject // stubs.. - + if (!empty($options['generator_add_validate_stubs'])) { foreach ($defs as $t) { if (!strlen(trim($t->name))) { @@ -1119,8 +1104,6 @@ class DB_DataObject_Generator extends DB_DataObject } - - $foot .= "}\n"; $full = $head . $body . $foot; @@ -1139,7 +1122,7 @@ class DB_DataObject_Generator extends DB_DataObject unless use set generator_class_rewrite to ANY or a name*/ $class_rewrite = 'DB_DataObject'; - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) { $class_rewrite = 'DB_DataObject'; } @@ -1148,97 +1131,49 @@ class DB_DataObject_Generator extends DB_DataObject } $input = preg_replace( - '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si', + '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' . $class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si', "\nclass {$this->classname} extends {$this->_extends} \n{\n", $input ); - $ret = preg_replace( + $ret = preg_replace( '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', $body, $input ); - + if (!strlen($ret)) { - return PEAR::raiseError( - "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n". - "pcre.backtrack_limit=1000000\n". + return (new PEAR)->raiseError( + "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n" . + "pcre.backtrack_limit=1000000\n" . "pcre.recursion_limit=1000000\n", null, PEAR_ERROR_DIE ); } - + return $ret; } /** - * hook to add extra methods to all classes - * - * called once for each class, use with $this->table and - * $this->_definitions[$this->table], to get data out of the current table, - * use it to add extra methods to the default classes. + * hook to add extra page-level (in terms of phpDocumentor) DocBlock * - * @access public - * @return string added to class eg. functions. + * called once for each class, use it add extra page-level docs + * @access public + * @return string added to class eg. functions. */ - public function derivedHookFunctions($input = "") + public function derivedHookPageLevelDocBlock() { - // This is so derived generator classes can generate functions - // It MUST NOT be changed here!!! - return ""; + return ''; } /** - * hook for var lines - * called each time a var line is generated, override to add extra var - * lines + * hook to add extra doc block (in terms of phpDocumentor) to extend string * - * @param object t containing type,len,flags etc. from tableInfo call - * @param int padding number of spaces - * @access public - * @return string added to class eg. functions. - */ - public function derivedHookVar(&$t, $padding) - { - // This is so derived generator classes can generate variabels - // It MUST NOT be changed here!!! - return ""; - } - /** - * hook for after var lines ( - * called at the end of the output of var line have generated, override to add extra var - * lines - * - * @param array cols containing array of objects with type,len,flags etc. from tableInfo call - * @access public - * @return string added to class eg. functions. - */ - public function derivedHookPostVar($t) - { - // This is so derived generator classes can generate variabels - // It MUST NOT be changed here!!! - return ""; - } - /** - * hook to add extra page-level (in terms of phpDocumentor) DocBlock - * - * called once for each class, use it add extra page-level docs - * @access public - * @return string added to class eg. functions. - */ - public function derivedHookPageLevelDocBlock() - { - return ''; - } - - /** - * hook to add extra doc block (in terms of phpDocumentor) to extend string - * - * called once for each class, use it add extra comments to extends - * string (require_once...) - * @access public - * @return string added to class eg. functions. + * called once for each class, use it add extra comments to extends + * string (require_once...) + * @access public + * @return string added to class eg. functions. */ public function derivedHookExtendsDocBlock() { @@ -1259,144 +1194,48 @@ class DB_DataObject_Generator extends DB_DataObject } /** - - /** - * getProxyFull - create a class definition on the fly and instantate it.. - * - * similar to generated files - but also evals the class definitoin code. - * - * - * @param string database name - * @param string table name of table to create proxy for. - * - * - * @return object Instance of class. or PEAR Error - * @access public - */ - public function getProxyFull($database, $table) + * hook for var lines + * called each time a var line is generated, override to add extra var + * lines + * + * @param object t containing type,len,flags etc. from tableInfo call + * @param int padding number of spaces + * @access public + * @return string added to class eg. functions. + */ + public function derivedHookVar(&$t, $padding) { - if ($err = $this->fillTableSchema($database, $table)) { - return $err; - } - - - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); - $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; - - $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends']; - $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location']; - - $classname = $this->classname = $this->getClassNameFromTableName($this->table); - - $out = $this->_generateClassTable(); - //echo $out; - eval('?>'.$out); - return new $classname; + // This is so derived generator classes can generate variabels + // It MUST NOT be changed here!!! + return ""; } - + /** - * fillTableSchema - set the database schema on the fly - * - * - * - * @param string database name - * @param string table name of table to create schema info for - * - * @return none | PEAR::error() - * @access public - */ - public function fillTableSchema($database, $table) + * hook for after var lines ( + * called at the end of the output of var line have generated, override to add extra var + * lines + * + * @param array cols containing array of objects with type,len,flags etc. from tableInfo call + * @access public + * @return string added to class eg. functions. + */ + public function derivedHookPostVar($t) { - global $_DB_DATAOBJECT; - // a little bit of sanity testing. - if ((false !== strpos($database, "'")) || (false !== strpos($database, ";"))) { - return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE); - } - - $this->_database = $database; - - $this->_connect(); - $table = trim($table); - - // a little bit of sanity testing. - if ((false !== strpos($table, "'")) || (false !== strpos($table, ";"))) { - return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE); - } - $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; - - - $options = PEAR::getStaticProperty('DB_DataObject', 'options'); - $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; - $is_MDB2 = ($db_driver != 'DB') ? true : false; - - if (!$is_MDB2) { - // try getting a list of schema tables first. (postgres) - $__DB->expectError(DB_ERROR_UNSUPPORTED); - $this->tables = $__DB->getListOf('schema.tables'); - $__DB->popExpect(); - } else { - /** - * set portability and some modules to fetch the informations - */ - $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); - $__DB->loadModule('Manager'); - $__DB->loadModule('Reverse'); - } - $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? - $__DB->quoteIdentifier($table) : $table; - - if (!$is_MDB2) { - $defs = $__DB->tableInfo($quotedTable); - } else { - $defs = $__DB->reverse->tableInfo($quotedTable); - if (PEAR::isError($defs)) { - return $defs; - } - foreach ($defs as $k => $v) { - if (!isset($defs[$k]['length'])) { - continue; - } - $defs[$k]['len'] = $defs[$k]['length']; - } - } - - if (PEAR::isError($defs)) { - return $defs; - } - - - - if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { - $this->debug("getting def for $database/$table", 'fillTable'); - $this->debug(print_r($defs, true), 'defs'); - } - // cast all definitions to objects - as we deal with that better. - - - foreach ($defs as $def) { - if (is_array($def)) { - $this->_definitions[$table][] = (object) $def; - } - } - - $this->table = trim($table); - $ret = $this->_generateDefinitionsTable(); - - $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table']; - $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys']; - return false; + // This is so derived generator classes can generate variabels + // It MUST NOT be changed here!!! + return ""; } - + /** - * Generate getter methods for class definition - * - * @param string $input Existing class contents - * @return string - * @access public - */ + * Generate getter methods for class definition + * + * @param string $input Existing class contents + * @return string + * @access public + */ public function _generateGetters($input) { - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); $getters = ''; // only generate if option is set to true @@ -1408,10 +1247,10 @@ class DB_DataObject_Generator extends DB_DataObject $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input); $getters .= "\n\n"; - $defs = $this->_definitions[$this->table]; + $defs = $this->_definitions[$this->table]; // loop through properties and create getter methods - foreach ($defs = $defs as $t) { + foreach ($defs as $t) { // build mehtod name $methodName = 'get' . $this->getMethodNameFromColumnName($t->name); @@ -1424,101 +1263,112 @@ class DB_DataObject_Generator extends DB_DataObject $getters .= " * Getter for \${$t->name}\n"; $getters .= " *\n"; $getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n" - : " * @return {$t->type}\n"; + : " * @return {$t->type}\n"; $getters .= " * @access public\n"; $getters .= " */\n"; $getters .= (substr(phpversion(), 0, 1) > 4) ? ' public ' - : ' '; + : ' '; $getters .= "function $methodName() {\n"; $getters .= " return \$this->{$t->name};\n"; $getters .= " }\n\n"; } - + return $getters; } + /** - * Generate link setter/getter methods for class definition - * - * @param string Existing class contents - * @return string - * @access public - */ - public function _generateLinkMethods($input) + * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX) + * + * @access public + * @param $col + * @return string method name; + */ + + + public function getMethodNameFromColumnName($col) { - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); + return ucfirst($col); + } + + /** + * Generate setter methods for class definition + * + * @param string Existing class contents + * @return string + * @access public + */ + public function _generateSetters($input) + { + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); $setters = ''; // only generate if option is set to true - - // generate_link_methods true:: - - - if (empty($options['generate_link_methods'])) { - //echo "skip lm? - not set"; - return ''; - } - - if (empty($this->_fkeys)) { - // echo "skip lm? - fkyes empty"; - return ''; - } - if (empty($this->_fkeys[$this->table])) { - //echo "skip lm? - no fkeys for {$this->table}"; + if (empty($options['generate_setters'])) { return ''; } - + // remove auto-generated code from input to be able to check if the method exists outside of the auto-code $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input); $setters .= "\n"; - $defs = $this->_fkeys[$this->table]; - - - // $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3]; + $defs = $this->_definitions[$this->table]; // loop through properties and create setter methods - foreach ($defs as $k => $info) { + foreach ($defs as $t) { // build mehtod name - $methodName = is_callable($options['generate_link_methods']) ? - $options['generate_link_methods']($k) : $k; + $methodName = 'set' . $this->getMethodNameFromColumnName($t->name); - if (!strlen(trim($k)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { + if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { continue; } $setters .= " /**\n"; - $setters .= " * Getter / Setter for \${$k}\n"; + $setters .= " * Setter for \${$t->name}\n"; $setters .= " *\n"; - $setters .= " * @param mixed (optional) value to assign\n"; + $setters .= " * @param mixed input value\n"; $setters .= " * @access public\n"; - $setters .= " */\n"; $setters .= (substr(phpversion(), 0, 1) > 4) ? ' public ' - : ' '; - $setters .= "function $methodName() {\n"; - $setters .= " return \$this->link('$k', func_get_args());\n"; + : ' '; + $setters .= "function $methodName(\$value) {\n"; + $setters .= " \$this->{$t->name} = \$value;\n"; $setters .= " }\n\n"; } - + + return $setters; } /** - * Generate setter methods for class definition + * Generate link setter/getter methods for class definition * - * @param string Existing class contents + * @param string Existing class contents * @return string * @access public */ - public function _generateSetters($input) + public function _generateLinkMethods($input) { - $options = &PEAR::getStaticProperty('DB_DataObject', 'options'); + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); $setters = ''; // only generate if option is set to true - if (empty($options['generate_setters'])) { + + // generate_link_methods true:: + + + if (empty($options['generate_link_methods'])) { + //echo "skip lm? - not set"; + return ''; + } + + if (empty($this->_fkeys)) { + // echo "skip lm? - fkyes empty"; + return ''; + } + if (empty($this->_fkeys[$this->table])) { + //echo "skip lm? - no fkeys for {$this->table}"; return ''; } @@ -1526,51 +1376,56 @@ class DB_DataObject_Generator extends DB_DataObject $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input); $setters .= "\n"; - $defs = $this->_definitions[$this->table]; + $defs = $this->_fkeys[$this->table]; + + + // $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3]; // loop through properties and create setter methods - foreach ($defs = $defs as $t) { + foreach ($defs as $k => $info) { // build mehtod name - $methodName = 'set' . $this->getMethodNameFromColumnName($t->name); + $methodName = is_callable($options['generate_link_methods']) ? + $options['generate_link_methods']($k) : $k; - if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { + if (!strlen(trim($k)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { continue; } $setters .= " /**\n"; - $setters .= " * Setter for \${$t->name}\n"; + $setters .= " * Getter / Setter for \${$k}\n"; $setters .= " *\n"; - $setters .= " * @param mixed input value\n"; + $setters .= " * @param mixed (optional) value to assign\n"; $setters .= " * @access public\n"; + $setters .= " */\n"; $setters .= (substr(phpversion(), 0, 1) > 4) ? ' public ' - : ' '; - $setters .= "function $methodName(\$value) {\n"; - $setters .= " \$this->{$t->name} = \$value;\n"; + : ' '; + $setters .= "function $methodName() {\n"; + $setters .= " return \$this->link('$k', func_get_args());\n"; $setters .= " }\n\n"; } - return $setters; } + /** - * Generate table Function - used when generator_no_ini is set. - * - * @param array table array. - * @return string - * @access public - */ + * Generate table Function - used when generator_no_ini is set. + * + * @param array table array. + * @return string + * @access public + */ public function _generateTableFunction($def) { $defines = explode(',', 'INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP'); - + $ret = "\n" . - " function table()\n" . - " {\n" . - " return array(\n"; - - foreach ($def as $k=>$v) { + " function table()\n" . + " {\n" . + " return array(\n"; + + foreach ($def as $k => $v) { $str = '0'; foreach ($defines as $dn) { if ($v & constant('DB_DATAOBJECT_' . $dn)) { @@ -1581,126 +1436,126 @@ class DB_DataObject_Generator extends DB_DataObject $str = substr($str, 3); // strip the 0 + } // hopefully addslashes is good enough here!!! - $ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n"; + $ret .= ' \'' . addslashes($k) . '\' => ' . $str . ",\n"; } return $ret . " );\n" . - " }\n"; + " }\n"; } + /** - * Generate keys Function - used generator_no_ini is set. - * - * @param array keys array. - * @return string - * @access public - */ + * Generate keys Function - used generator_no_ini is set. + * + * @param array keys array. + * @return string + * @access public + */ public function _generateKeysFunction($def) { $ret = "\n" . - " function keys()\n" . - " {\n" . - " return array("; - - foreach ($def as $k=>$type) { + " function keys()\n" . + " {\n" . + " return array("; + + foreach ($def as $k => $type) { // hopefully addslashes is good enough here!!! - $ret .= '\''.addslashes($k).'\', '; + $ret .= '\'' . addslashes($k) . '\', '; } $ret = preg_replace('#, $#', '', $ret); return $ret . ");\n" . - " }\n"; + " }\n"; } + /** - * Generate sequenceKey Function - used generator_no_ini is set. - * - * @param array table and key definition. - * @return string - * @access public - */ + * Generate sequenceKey Function - used generator_no_ini is set. + * + * @param array table and key definition. + * @return string + * @access public + */ public function _generateSequenceKeyFunction($def) { - + //print_r($def); // DB_DataObject::debugLevel(5); global $_DB_DATAOBJECT; // print_r($def); - - - $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; - $realkeys = $def['keys']; - $keys = array_keys($realkeys); - $usekey = isset($keys[0]) ? $keys[0] : false; - $table = $def['table']; - - + + + $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; + $realkeys = $def['keys']; + $keys = array_keys($realkeys); + $usekey = isset($keys[0]) ? $keys[0] : false; + $table = $def['table']; + + $seqname = false; - - - - - $ar = array(false,false,false); + + + $ar = array(false, false, false); if ($usekey !== false) { - if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) { - $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table]; + if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_' . $this->__table])) { + $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_' . $this->__table]; if (strpos($usekey, ':') !== false) { list($usekey, $seqname) = explode(':', $usekey); } } - - if (in_array($dbtype, array( 'mysql', 'mysqli', 'mssql', 'ifx')) && + + if (in_array($dbtype, array('mysql', 'mysqli', 'mssql', 'ifx')) && ($table[$usekey] & DB_DATAOBJECT_INT) && isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N') - ) { + ) { // use native sequence keys. - $ar = array($usekey,true,$seqname); + $ar = array($usekey, true, $seqname); } else { // use generated sequence keys.. if ($table[$usekey] & DB_DATAOBJECT_INT) { - $ar = array($usekey,false,$seqname); + $ar = array($usekey, false, $seqname); } } } - - - - + + $ret = "\n" . - " function sequenceKey() // keyname, use native, native name\n" . - " {\n" . - " return array("; + " function sequenceKey() // keyname, use native, native name\n" . + " {\n" . + " return array("; foreach ($ar as $v) { switch (gettype($v)) { case 'boolean': $ret .= ($v ? 'true' : 'false') . ', '; break; - + case 'string': $ret .= "'" . $v . "', "; break; - + default: // eak $ret .= "null, "; - + } } $ret = preg_replace('#, $#', '', $ret); return $ret . ");\n" . - " }\n"; + " }\n"; } + /** - * Generate defaults Function - used generator_add_defaults or generator_no_ini is set. - * Only supports mysql and mysqli ... welcome ideas for more.. - * - * - * @param array table and key definition. - * @return string - * @access public - */ + * Generate defaults Function - used generator_add_defaults or generator_no_ini is set. + * Only supports mysql and mysqli ... welcome ideas for more.. + * + * + * @param $table + * @param $defs + * @return string + * @access public + */ public function _generateDefaultsFunction($table, $defs) { - $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; - if (!in_array($__DB->phptype, array('mysql','mysqli'))) { - return; // cant handle non-mysql introspection for defaults. + $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + if (!in_array($__DB->phptype, array('mysql', 'mysqli'))) { + return null; // cant handle non-mysql introspection for defaults. } - $options = PEAR::getStaticProperty('DB_DataObject', 'options'); + $options = (new PEAR)->getStaticProperty('DB_DataObject', 'options'); $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $method = $db_driver == 'DB' ? 'getAll' : 'queryAll'; $res = $__DB->$method('DESCRIBE ' . $table, DB_FETCHMODE_ASSOC); @@ -1708,52 +1563,198 @@ class DB_DataObject_Generator extends DB_DataObject foreach ($res as $ar) { // this is initially very dumb... -> and it may mess up.. $type = $defs[$ar['Field']]; - + switch (true) { - + case (is_null($ar['Default'])): - $defaults[$ar['Field']] = 'null'; + $defaults[$ar['Field']] = 'null'; break; - + case ($type & DB_DATAOBJECT_DATE): case ($type & DB_DATAOBJECT_TIME): case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet.. break; - + case ($type & DB_DATAOBJECT_BOOL): - $defaults[$ar['Field']] = (int)(boolean) $ar['Default']; + $defaults[$ar['Field']] = (int)(boolean)$ar['Default']; break; - - + + case ($type & DB_DATAOBJECT_STR): - $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'"; + $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'"; break; - - + + default: // hopefully eveything else... - numbers etc. if (!strlen($ar['Default'])) { continue; } if (is_numeric($ar['Default'])) { - $defaults[$ar['Field']] = $ar['Default']; + $defaults[$ar['Field']] = $ar['Default']; } break; - + } //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']])); } if (empty($defaults)) { - return; + return null; } - + $ret = "\n" . - " function defaults() // column default values \n" . - " {\n" . - " return array(\n"; - foreach ($defaults as $k=>$v) { - $ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n"; + " function defaults() // column default values \n" . + " {\n" . + " return array(\n"; + foreach ($defaults as $k => $v) { + $ret .= ' \'' . addslashes($k) . '\' => ' . $v . ",\n"; } return $ret . " );\n" . - " }\n"; + " }\n"; + } + + /** + * hook to add extra methods to all classes + * + * called once for each class, use with $this->table and + * $this->_definitions[$this->table], to get data out of the current table, + * use it to add extra methods to the default classes. + * + * @access public + * @param string $input + * @return string added to class eg. functions. + */ + public function derivedHookFunctions($input = "") + { + // This is so derived generator classes can generate functions + // It MUST NOT be changed here!!! + return ""; + } + + /** + * + * /** + * getProxyFull - create a class definition on the fly and instantate it.. + * + * similar to generated files - but also evals the class definitoin code. + * + * + * @param string database name + * @param string table name of table to create proxy for. + * + * + * @return object Instance of class. or PEAR Error + * @access public + */ + public function getProxyFull($database, $table) + { + if ($err = $this->fillTableSchema($database, $table)) { + return $err; + } + + + $options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); + $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; + + $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends']; + $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location']; + + $classname = $this->classname = $this->getClassNameFromTableName($this->table); + + $out = $this->_generateClassTable(); + //echo $out; + eval('?>' . $out); + return new $classname; + } + + /** + * fillTableSchema - set the database schema on the fly + * + * + * + * @param string database name + * @param string table name of table to create schema info for + * + * @return none|object|PEAR + * @access public + */ + public function fillTableSchema($database, $table) + { + global $_DB_DATAOBJECT; + // a little bit of sanity testing. + if ((false !== strpos($database, "'")) || (false !== strpos($database, ";"))) { + return (new PEAR)->raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE); + } + + $this->_database = $database; + + $this->_connect(); + $table = trim($table); + + // a little bit of sanity testing. + if ((false !== strpos($table, "'")) || (false !== strpos($table, ";"))) { + return (new PEAR)->raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE); + } + $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + + + $options = (new PEAR)->getStaticProperty('DB_DataObject', 'options'); + $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; + $is_MDB2 = ($db_driver != 'DB') ? true : false; + + if (!$is_MDB2) { + // try getting a list of schema tables first. (postgres) + $__DB->expectError(DB_ERROR_UNSUPPORTED); + $this->tables = $__DB->getListOf('schema.tables'); + $__DB->popExpect(); + } else { + /** + * set portability and some modules to fetch the informations + */ + $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); + $__DB->loadModule('Manager'); + $__DB->loadModule('Reverse'); + } + $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? + $__DB->quoteIdentifier($table) : $table; + + if (!$is_MDB2) { + $defs = $__DB->tableInfo($quotedTable); + } else { + $defs = $__DB->reverse->tableInfo($quotedTable); + if ((new PEAR)->isError($defs)) { + return $defs; + } + foreach ($defs as $k => $v) { + if (!isset($defs[$k]['length'])) { + continue; + } + $defs[$k]['len'] = $defs[$k]['length']; + } + } + + if ((new PEAR)->isError($defs)) { + return $defs; + } + + + if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { + $this->debug("getting def for $database/$table", 'fillTable'); + $this->debug(print_r($defs, true), 'defs'); + } + // cast all definitions to objects - as we deal with that better. + + + foreach ($defs as $def) { + if (is_array($def)) { + $this->_definitions[$table][] = (object)$def; + } + } + + $this->table = trim($table); + $ret = $this->_generateDefinitionsTable(); + + $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table']; + $_DB_DATAOBJECT['INI'][$database][$table . '__keys'] = $ret['keys']; + return false; } } diff --git a/extlib/DB/DataObject/Links.php b/extlib/DB/DataObject/Links.php index 92a7000621..eb44d67b02 100644 --- a/extlib/DB/DataObject/Links.php +++ b/extlib/DB/DataObject/Links.php @@ -29,8 +29,8 @@ * Currenly only supports existing methods, and new 'link()' method * */ - - + + /** * Links class * @@ -39,11 +39,11 @@ class DB_DataObject_Links { /** - * @property {DB_DataObject} do DataObject to apply this to. - */ + * @property {DB_DataObject} do DataObject to apply this to. + */ public $do = false; - - + + /** * @property {Array|String} load What to load, 'all' or an array of properties. (default all) */ @@ -67,117 +67,102 @@ class DB_DataObject_Links * @property {Boolean} apply apply the result to this object, (default true) */ public $apply = true; - - + + //------------------------- RETURN ------------------------------------ /** * @property {Array} links key value associative array of links. */ public $links; - - + + /** * Constructor * -- good ole style.. - * @param {DB_DataObject} do DataObject to apply to. - * @param {Array} cfg Configuration (basically properties of this object) + * @param {DB_DataObject} do DataObject to apply to. + * @param array $cfg */ - - public function DB_DataObject_Links($do, $cfg= array()) + + public function __construct($do, $cfg = array()) { // check if do is set!!!? $this->do = $do; - - foreach ($cfg as $k=>$v) { + + foreach ($cfg as $k => $v) { $this->$k = $v; } } - + /** - * return name from related object + * a generic geter/setter provider.. * - * The relies on a .links.ini file, unless you specify the arguments. + * provides a generic getter setter for the referenced object + * eg. + * $link->link('company_id') returns getLink for the object + * if nothing is linked (it will return an empty dataObject) + * $link->link('company_id', array(1)) - just sets the * - * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName') + * also array as the field speck supports + * $link->link(array('company_id', 'company:id')) * * - * @param string $field|array either row or row.xxxxx or links spec. - * @param string|DB_DataObject $table (optional) name of table to look up value in - * @param string $link (optional) name of column in other table to match - * @author Tim White - * @access public - * @return mixed object on success false on failure or '0' when not linked + * @param string|array $field the field to fetch or link spec. + * @param array $args + * @return mixed true of false on set, the object on getter. + * @params array $args the arguments sent to the getter setter */ - public function getLink($field, $table= false, $link='') + public function link($field, $args = array()) { - static $cache = array(); - - // GUESS THE LINKED TABLE.. (if found - recursevly call self) - - if ($table == false) { - $info = $this->linkInfo($field); - - if ($info) { - return $this->getLink($field, $info[0], $link === false ? $info[1] : $link); - } - - // no links defined.. - use borked BC method... - // use the old _ method - this shouldnt happen if called via getLinks() - if (!($p = strpos($field, '_'))) { - return false; - } - $table = substr($field, 0, $p); - return $this->getLink($field, $table); - } - - $tn = is_string($table) ? $table : $table->tableName(); - - - - if (!isset($this->do->$field)) { - $this->do->raiseError("getLink: row not set $field", DB_DATAOBJECT_ERROR_NODATA); - return false; - } - - // check to see if we know anything about this table.. - - - if (empty($this->do->$field) || $this->do->$field < 0) { - return 0; // no record. - } - - if ($this->cached && isset($cache[$tn.':'. $link .':'. $this->do->$field])) { - return $cache[$tn.':'. $link .':'. $this->do->$field]; - } - - $obj = is_string($table) ? $this->do->factory($tn) : $table; - ; - - if (!is_a($obj, 'DB_DataObject')) { + $info = $this->linkInfo($field); + + if (!$info) { $this->do->raiseError( - "getLink:Could not find class for row $field, table $tn", + "getLink:Could not find link for row $field", DB_DATAOBJECT_ERROR_INVALIDCONFIG ); return false; } - // -1 or 0 -- no referenced record.. - - $ret = false; - if ($link) { - if ($obj->get($link, $this->do->$field)) { - $ret = $obj; + $field = $info[2]; + + + if (empty($args)) { // either an empty array or really empty.... + + if (!isset($this->do->$field)) { + return $info[0]; // empty dataobject. } - - - // this really only happens when no link config is set (old BC stuff) - } elseif ($obj->get($this->do->$field)) { - $ret= $obj; + + $ret = $this->getLink($field); + // nothing linked -- return new object.. + return ($ret === 0) ? $info[0] : $ret; } - if ($this->cached) { - $cache[$tn.':'. $link .':'. $this->do->$field] = $ret; + $assign = is_array($args) ? $args[0] : $args; + + // otherwise it's a set call.. + if (!is_a($assign, 'DB_DataObject')) { + if (is_numeric($assign) && is_integer($assign * 1)) { + if ($assign > 0) { + if (!$info) { + return false; + } + // check that record exists.. + if (!$info[0]->get($info[1], $assign)) { + return false; + } + } + + $this->do->$field = $assign; + return true; + } + + return false; } - return $ret; + + // otherwise we are assigning it ... + + $this->do->$field = $assign->{$info[1]}; + return true; } + /** * get link information for a field or field specification * @@ -187,11 +172,10 @@ class DB_DataObject_Links * array(3) : 'field', $dataobject, 'remote_col' (handy for joinAdd to do nested joins.) * * @param string|array $field or link spec to use. - * @return (false|array) array of dataobject and linked field or false. - * + * @return array|bool (false|array) array of dataobject and linked field or false. * */ - + public function linkInfo($field) { if (is_array($field)) { @@ -205,7 +189,7 @@ class DB_DataObject_Links ); } list($table, $link) = explode(':', $field[1]); - + return array( $this->do->factory($table), $link, @@ -213,103 +197,116 @@ class DB_DataObject_Links ); } // work out the link.. (classic way) - + $links = $this->do->links(); - + if (empty($links) || !is_array($links)) { return false; } - - + + if (!isset($links[$field])) { return false; } list($table, $link) = explode(':', $links[$field]); - - + + //??? needed??? if ($p = strpos($field, ".")) { $field = substr($field, 0, $p); } - + return array( $this->do->factory($table), $link, $field ); } - - - + /** - * a generic geter/setter provider.. - * - * provides a generic getter setter for the referenced object - * eg. - * $link->link('company_id') returns getLink for the object - * if nothing is linked (it will return an empty dataObject) - * $link->link('company_id', array(1)) - just sets the + * return name from related object * - * also array as the field speck supports - * $link->link(array('company_id', 'company:id')) + * The relies on a .links.ini file, unless you specify the arguments. * + * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName') * - * @param string|array $field the field to fetch or link spec. - * @params array $args the arguments sent to the getter setter - * @return mixed true of false on set, the object on getter. * + * @param string $field |array either row or row.xxxxx or links spec. + * @param bool $table (optional) name of table to look up value in + * @param string $link (optional) name of column in other table to match + * @return mixed object on success false on failure or '0' when not linked + * @author Tim White + * @access public */ - public function link($field, $args = array()) + public function getLink($field, $table = false, $link = '') { - $info = $this->linkInfo($field); - - if (!$info) { + static $cache = array(); + + // GUESS THE LINKED TABLE.. (if found - recursevly call self) + + if ($table == false) { + $info = $this->linkInfo($field); + + if ($info) { + return $this->getLink($field, $info[0], $link === false ? $info[1] : $link); + } + + // no links defined.. - use borked BC method... + // use the old _ method - this shouldnt happen if called via getLinks() + if (!($p = strpos($field, '_'))) { + return false; + } + $table = substr($field, 0, $p); + return $this->getLink($field, $table); + } + + $tn = is_string($table) ? $table : $table->tableName(); + + + if (!isset($this->do->$field)) { + $this->do->raiseError("getLink: row not set $field", DB_DATAOBJECT_ERROR_NODATA); + return false; + } + + // check to see if we know anything about this table.. + + + if (empty($this->do->$field) || $this->do->$field < 0) { + return 0; // no record. + } + + if ($this->cached && isset($cache[$tn . ':' . $link . ':' . $this->do->$field])) { + return $cache[$tn . ':' . $link . ':' . $this->do->$field]; + } + + $obj = is_string($table) ? $this->do->factory($tn) : $table;; + + if (!is_a($obj, 'DB_DataObject')) { $this->do->raiseError( - "getLink:Could not find link for row $field", + "getLink:Could not find class for row $field, table $tn", DB_DATAOBJECT_ERROR_INVALIDCONFIG ); return false; } - $field = $info[2]; - - - if (empty($args)) { // either an empty array or really empty.... - - if (!isset($this->do->$field)) { - return $info[0]; // empty dataobject. + // -1 or 0 -- no referenced record.. + + $ret = false; + if ($link) { + if ($obj->get($link, $this->do->$field)) { + $ret = $obj; } - - $ret = $this->getLink($field); - // nothing linked -- return new object.. - return ($ret === 0) ? $info[0] : $ret; + + + // this really only happens when no link config is set (old BC stuff) + } elseif ($obj->get($this->do->$field)) { + $ret = $obj; } - $assign = is_array($args) ? $args[0] : $args; - - // otherwise it's a set call.. - if (!is_a($assign, 'DB_DataObject')) { - if (is_numeric($assign) && is_integer($assign * 1)) { - if ($assign > 0) { - if (!$info) { - return false; - } - // check that record exists.. - if (!$info[0]->get($info[1], $assign)) { - return false; - } - } - - $this->do->$field = $assign ; - return true; - } - - return false; + if ($this->cached) { + $cache[$tn . ':' . $link . ':' . $this->do->$field] = $ret; } - - // otherwise we are assigning it ... - - $this->do->$field = $assign->{$info[1]}; - return true; + return $ret; } + /** * load related objects * @@ -322,26 +319,26 @@ class DB_DataObject_Links * object vars the links are stored in by changeing the format parameter * * - * @param string format (default _%s) where %s is the table name. + * @param string format (default _%s) where %s is the table name. + * @return boolean , true on success * @author Tim White * @access public - * @return boolean , true on success */ - + public function applyLinks($format = '_%s') { - + // get table will load the options. if ($this->do->_link_loaded) { return true; } - + $this->do->_link_loaded = false; - $cols = $this->do->table(); + $cols = $this->do->table(); $links = $this->do->links(); - + $loaded = array(); - + if ($links) { foreach ($links as $key => $match) { list($table, $link) = explode(':', $match); @@ -350,9 +347,9 @@ class DB_DataObject_Links if ($p = strpos($key, '.')) { $key = substr($key, 0, $p); } - + $this->do->$k = $this->getLink($key, $table, $link); - + if (is_object($this->do->$k)) { $loaded[] = $k; } @@ -367,14 +364,14 @@ class DB_DataObject_Links if (!is_null($links)) { return false; } - - + + foreach (array_keys($cols) as $key) { if (!($p = strpos($key, '_'))) { continue; } // does the table exist. - $k =sprintf($format, $key); + $k = sprintf($format, $key); $this->do->$k = $this->getLink($key); if (is_object($this->do->$k)) { $loaded[] = $k; @@ -383,7 +380,7 @@ class DB_DataObject_Links $this->do->_link_loaded = $loaded; return true; } - + /** * getLinkArray * Fetch an array of related objects. This should be used in conjunction with a @@ -392,11 +389,11 @@ class DB_DataObject_Links * You may also use this with all parameters to specify, the column and related table. * * @access public - * @param string $field- either column or column.xxxxx + * @param string $field - either column or column.xxxxx * @param string $table (optional) name of table to look up value in - * @param string $fkey (optional) fetchall key see DB_DataObject::fetchAll() - * @param string $fval (optional)fetchall val DB_DataObject::fetchAll() - * @param string $fval (optional) fetchall method DB_DataObject::fetchAll() + * @param bool $fkey (optional) fetchall key see DB_DataObject::fetchAll() + * @param bool $fval (optional) fetchall method DB_DataObject::fetchAll() + * @param bool $fmethod * @return array - array of results (empty array on failure) * * Example - Getting the related objects @@ -409,14 +406,13 @@ class DB_DataObject_Links * foreach ($children as $child) { * echo $child->name, '
'; * } - * */ public function getLinkArray($field, $table = null, $fkey = false, $fval = false, $fmethod = false) { $ret = array(); if (!$table) { $links = $this->do->links(); - + if (is_array($links)) { if (!isset($links[$field])) { // failed.. @@ -430,9 +426,9 @@ class DB_DataObject_Links } return $this->getLinkArray($field, substr($field, 0, $p)); } - - $c = $this->do->factory($table); - + + $c = $this->do->factory($table); + if (!is_object($c) || !is_a($c, 'DB_DataObject')) { $this->do->raiseError( "getLinkArray:Could not find class for row $field, table $table", diff --git a/extlib/DB/DataObject/createTables.php b/extlib/DB/DataObject/createTables.php index b6d3dedf8c..4645711665 100755 --- a/extlib/DB/DataObject/createTables.php +++ b/extlib/DB/DataObject/createTables.php @@ -25,32 +25,32 @@ define('DB_DATAOBJECT_NO_OVERLOAD', 1); //require_once 'DB/DataObject/Generator.php'; -require_once 'DB/DataObject/Generator.php'; +require_once 'Generator.php'; if (php_sapi_name() != 'cli') { - PEAR::raiseError("\nERROR: You must turn use the cli sapi to run this", null, PEAR_ERROR_DIE); + (new PEAR)->raiseError("\nERROR: You must turn use the cli sapi to run this", null, PEAR_ERROR_DIE); } if (!ini_get('register_argc_argv')) { - PEAR::raiseError("\nERROR: You must turn register_argc_argv On in you php.ini file for this to work\neg.\n\nregister_argc_argv = On\n\n", null, PEAR_ERROR_DIE); + (new PEAR)->raiseError("\nERROR: You must turn register_argc_argv On in you php.ini file for this to work\neg.\n\nregister_argc_argv = On\n\n", null, PEAR_ERROR_DIE); exit; } if (!@$_SERVER['argv'][1]) { - PEAR::raiseError("\nERROR: createTable.php usage:\n\n" .$_SERVER['argv'][0] . " example.ini\n\n", null, PEAR_ERROR_DIE); + (new PEAR)->raiseError("\nERROR: createTable.php usage:\n\n" . $_SERVER['argv'][0] . " example.ini\n\n", null, PEAR_ERROR_DIE); exit; } $config = parse_ini_file($_SERVER['argv'][1], true); -foreach ($config as $class=>$values) { - $options = &PEAR::getStaticProperty($class, 'options'); +foreach ($config as $class => $values) { + $options = &(new PEAR)->getStaticProperty($class, 'options'); $options = $values; } -$options = &PEAR::getStaticProperty('DB_DataObject', 'options'); +$options = &(new PEAR)->getStaticProperty('DB_DataObject', 'options'); if (empty($options)) { - PEAR::raiseError("\nERROR: could not read ini file\n\n", null, PEAR_ERROR_DIE); + (new PEAR)->raiseError("\nERROR: could not read ini file\n\n", null, PEAR_ERROR_DIE); exit; } set_time_limit(0); diff --git a/extlib/DB/common.php b/extlib/DB/common.php index 22fac96d99..7252ce2786 100644 --- a/extlib/DB/common.php +++ b/extlib/DB/common.php @@ -169,23 +169,23 @@ class DB_common extends PEAR } if (isset($this->autocommit)) { return array('autocommit', - 'dbsyntax', - 'dsn', - 'features', - 'fetchmode', - 'fetchmode_object_class', - 'options', - 'was_connected', - ); + 'dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); } else { return array('dbsyntax', - 'dsn', - 'features', - 'fetchmode', - 'fetchmode_object_class', - 'options', - 'was_connected', - ); + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); } } @@ -212,37 +212,37 @@ class DB_common extends PEAR // {{{ __toString() /** - * Automatic string conversion for PHP 5 + * DEPRECATED: String conversion method * * @return string a string describing the current PEAR DB object * - * @since Method available since Release 1.7.0 + * @deprecated Method deprecated in Release 1.7.0 */ - public function __toString() + public function toString() { - $info = strtolower(get_class($this)); - $info .= ': (phptype=' . $this->phptype . - ', dbsyntax=' . $this->dbsyntax . - ')'; - if ($this->connection) { - $info .= ' [connected]'; - } - return $info; + return $this->__toString(); } // }}} // {{{ toString() /** - * DEPRECATED: String conversion method + * Automatic string conversion for PHP 5 * * @return string a string describing the current PEAR DB object * - * @deprecated Method deprecated in Release 1.7.0 + * @since Method available since Release 1.7.0 */ - public function toString() + public function __toString() { - return $this->__toString(); + $info = strtolower(get_class($this)); + $info .= ': (phptype=' . $this->phptype . + ', dbsyntax=' . $this->dbsyntax . + ')'; + if ($this->connection) { + $info .= ' [connected]'; + } + return $info; } // }}} @@ -252,7 +252,7 @@ class DB_common extends PEAR * DEPRECATED: Quotes a string so it can be safely used within string * delimiters in a query * - * @param string $string the string to be quoted + * @param string $string the string to be quoted * * @return string the quoted string * @@ -271,77 +271,13 @@ class DB_common extends PEAR // }}} // {{{ quote() - /** - * DEPRECATED: Quotes a string so it can be safely used in a query - * - * @param string $string the string to quote - * - * @return string the quoted string or the string NULL - * if the value submitted is null. - * - * @see DB_common::quoteSmart(), DB_common::escapeSimple() - * @deprecated Deprecated in release 1.6.0 - */ - public function quote($string = null) - { - return $this->quoteSmart($string); - } - - // }}} - // {{{ quoteIdentifier() - - /** - * Quotes a string so it can be safely used as a table or column name - * - * Delimiting style depends on which database driver is being used. - * - * NOTE: just because you CAN use delimited identifiers doesn't mean - * you SHOULD use them. In general, they end up causing way more - * problems than they solve. - * - * Portability is broken by using the following characters inside - * delimited identifiers: - * + backtick (`) -- due to MySQL - * + double quote (") -- due to Oracle - * + brackets ([ or ]) -- due to Access - * - * Delimited identifiers are known to generally work correctly under - * the following drivers: - * + mssql - * + mysql - * + mysqli - * + oci8 - * + odbc(access) - * + odbc(db2) - * + pgsql - * + sqlite - * + sybase (must execute set quoted_identifier on sometime - * prior to use) - * - * InterBase doesn't seem to be able to use delimited identifiers - * via PHP 4. They work fine under PHP 5. - * - * @param string $str the identifier name to be quoted - * - * @return string the quoted identifier - * - * @since Method available since Release 1.6.0 - */ - public function quoteIdentifier($str) - { - return '"' . str_replace('"', '""', $str) . '"'; - } - - // }}} - // {{{ quoteSmart() - /** * Formats input so it can be safely used in a query * * The output depends on the PHP data type of input and the database * type being used. * - * @param mixed $in the data to be formatted + * @param mixed $in the data to be formatted * * @return mixed the formatted data. The format depends on the input's * PHP type: @@ -455,6 +391,46 @@ class DB_common extends PEAR } } + // }}} + // {{{ quoteIdentifier() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + public function quoteFloat($float) + { + return "'" . $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))) . "'"; + } + + // }}} + // {{{ quoteSmart() + + /** + * Escapes a string according to the current DBMS's standards + * + * In SQLite, this makes things safe for inserts/updates, but may + * cause problems when performing text comparisons against columns + * containing binary data. See the + * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + public function escapeSimple($str) + { + return str_replace("'", "''", $str); + } + // }}} // {{{ quoteBoolean() @@ -471,45 +447,69 @@ class DB_common extends PEAR { return $boolean ? '1' : '0'; } - + // }}} // {{{ quoteFloat() /** - * Formats a float value for use within a query in a locale-independent - * manner. + * DEPRECATED: Quotes a string so it can be safely used in a query * - * @param float the float value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. + * @param string $string the string to quote + * + * @return string the quoted string or the string NULL + * if the value submitted is null. + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Deprecated in release 1.6.0 */ - public function quoteFloat($float) + public function quote($string = null) { - return "'".$this->escapeSimple(str_replace(',', '.', strval(floatval($float))))."'"; + return $this->quoteSmart($string); } - + // }}} // {{{ escapeSimple() /** - * Escapes a string according to the current DBMS's standards + * Quotes a string so it can be safely used as a table or column name * - * In SQLite, this makes things safe for inserts/updates, but may - * cause problems when performing text comparisons against columns - * containing binary data. See the - * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * Delimiting style depends on which database driver is being used. * - * @param string $str the string to be escaped + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. * - * @return string the escaped string + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + odbc(access) + * + odbc(db2) + * + pgsql + * + sqlite + * + sybase (must execute set quoted_identifier on sometime + * prior to use) + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string $str the identifier name to be quoted + * + * @return string the quoted identifier * - * @see DB_common::quoteSmart() * @since Method available since Release 1.6.0 */ - public function escapeSimple($str) + public function quoteIdentifier($str) { - return str_replace("'", "''", $str); + return '"' . str_replace('"', '""', $str) . '"'; } // }}} @@ -518,7 +518,7 @@ class DB_common extends PEAR /** * Tells whether the present driver supports a given feature * - * @param string $feature the feature you're curious about + * @param string $feature the feature you're curious about * * @return bool whether this driver supports $feature */ @@ -533,9 +533,9 @@ class DB_common extends PEAR /** * Sets the fetch mode that should be used by default for query results * - * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC + * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC * or DB_FETCHMODE_OBJECT - * @param string $object_class the class name of the object to be returned + * @param string $object_class the class name of the object to be returned * by the fetch methods when the * DB_FETCHMODE_OBJECT mode is selected. * If no class is specified by default a cast @@ -543,6 +543,7 @@ class DB_common extends PEAR * done. There is also the posibility to use * and extend the 'DB_row' class. * + * @return object * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT */ public function setFetchMode($fetchmode, $object_class = 'stdClass') @@ -550,7 +551,7 @@ class DB_common extends PEAR switch ($fetchmode) { case DB_FETCHMODE_OBJECT: $this->fetchmode_object_class = $object_class; - // no break + // no break case DB_FETCHMODE_ORDERED: case DB_FETCHMODE_ASSOC: $this->fetchmode = $fetchmode; @@ -558,11 +559,95 @@ class DB_common extends PEAR default: return $this->raiseError('invalid fetchmode mode'); } + return null; } // }}} // {{{ setOption() + /** + * Communicates an error and invoke error callbacks, etc + * + * Basically a wrapper for PEAR::raiseError without the message string. + * + * @param mixed integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * @param int error mode, see PEAR_Error docs + * @param mixed if error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string extra debug information. Defaults to the last + * query and native error code. + * @param mixed native error code, integer or string depending the + * backend + * @param mixed dummy parameter for E_STRICT compatibility with + * PEAR::raiseError + * @param mixed dummy parameter for E_STRICT compatibility with + * PEAR::raiseError + * + * @return object the PEAR_Error object + * + * @see PEAR_Error + */ + public function &raiseError( + $code = DB_ERROR, + $mode = null, + $options = null, + $userinfo = null, + $nativecode = null, + $dummy1 = null, + $dummy2 = null + ) + { + // The error is yet a DB error object + if (is_object($code)) { + // because we the static PEAR::raiseError, our global + // handler should be used if it is set + if ($mode === null && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + $tmp = PEAR::raiseError( + $code, + null, + $mode, + $options, + null, + null, + true + ); + return $tmp; + } + + if ($userinfo === null) { + $userinfo = $this->last_query; + } + + if ($nativecode) { + $userinfo .= ' [nativecode=' . trim($nativecode) . ']'; + } else { + $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']'; + } + + $tmp = PEAR::raiseError( + null, + $code, + $mode, + $options, + $userinfo, + 'DB_Error', + true + ); + return $tmp; + } + + // }}} + // {{{ getOption() + /** * Sets run-time configuration options for PEAR DB * @@ -695,9 +780,9 @@ class DB_common extends PEAR * * * @param string $option option name - * @param mixed $value value for the option + * @param mixed $value value for the option * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object * * @see DB_common::$options */ @@ -715,173 +800,40 @@ class DB_common extends PEAR switch ($this->phptype) { case 'oci8': $this->options['portability'] = - DB_PORTABILITY_LOWERCASE | - DB_PORTABILITY_NUMROWS; - break; - case 'fbsql': - case 'mysql': - case 'mysqli': - case 'sqlite': - $this->options['portability'] = - DB_PORTABILITY_DELETE_COUNT; - break; - } - } else { - $this->options['portability'] = DB_PORTABILITY_NONE; - } - } - - return DB_OK; - } - return $this->raiseError("unknown option $option"); - } - - // }}} - // {{{ getOption() - - /** - * Returns the value of an option - * - * @param string $option the option name you're curious about - * - * @return mixed the option's value - */ - public function getOption($option) - { - if (isset($this->options[$option])) { - return $this->options[$option]; - } - return $this->raiseError("unknown option $option"); - } - - // }}} - // {{{ prepare() - - /** - * Prepares a query for multiple execution with execute() - * - * Creates a query that can be run multiple times. Each time it is run, - * the placeholders, if any, will be replaced by the contents of - * execute()'s $data argument. - * - * Three types of placeholders can be used: - * + ? scalar value (i.e. strings, integers). The system - * will automatically quote and escape the data. - * + ! value is inserted 'as is' - * + & requires a file name. The file's contents get - * inserted into the query (i.e. saving binary - * data in a db) - * - * Example 1. - * - * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); - * $data = array( - * "John's text", - * "'it''s good'", - * 'filename.txt' - * ); - * $res = $db->execute($sth, $data); - * - * - * Use backslashes to escape placeholder characters if you don't want - * them to be interpreted as placeholders: - *
-     *    "UPDATE foo SET col=? WHERE col='over \& under'"
-     * 
- * - * With some database backends, this is emulated. - * - * {@internal ibase and oci8 have their own prepare() methods.}} - * - * @param string $query the query to be prepared - * - * @return mixed DB statement resource on success. A DB_Error object - * on failure. - * - * @see DB_common::execute() - */ - public function prepare($query) - { - $tokens = preg_split( - '/((?prepare_tokens[] = &$newtokens; - end($this->prepare_tokens); - - $k = key($this->prepare_tokens); - $this->prepare_types[$k] = $types; - $this->prepared_queries[$k] = implode(' ', $newtokens); - - return $k; - } - - // }}} - // {{{ autoPrepare() + DB_PORTABILITY_LOWERCASE | + DB_PORTABILITY_NUMROWS; + break; + case 'fbsql': + case 'mysql': + case 'mysqli': + case 'sqlite': + $this->options['portability'] = + DB_PORTABILITY_DELETE_COUNT; + break; + } + } else { + $this->options['portability'] = DB_PORTABILITY_NONE; + } + } - /** - * Automaticaly generates an insert or update query and pass it to prepare() - * - * @param string $table the table name - * @param array $table_fields the array of field names - * @param int $mode a type of query to make: - * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE - * @param string $where for update queries: the WHERE clause to - * append to the SQL statement. Don't - * include the "WHERE" keyword. - * - * @return resource the query handle - * - * @uses DB_common::prepare(), DB_common::buildManipSQL() - */ - public function autoPrepare( - $table, - $table_fields, - $mode = DB_AUTOQUERY_INSERT, - $where = false - ) { - $query = $this->buildManipSQL($table, $table_fields, $mode, $where); - if (DB::isError($query)) { - return $query; + return DB_OK; } - return $this->prepare($query); + return $this->raiseError("unknown option $option"); } // }}} - // {{{ autoExecute() + // {{{ prepare() /** * Automaticaly generates an insert or update query and call prepare() * and execute() with it * - * @param string $table the table name - * @param array $fields_values the associative array where $key is a + * @param string $table the table name + * @param array $fields_values the associative array where $key is a * field name and $value its value - * @param int $mode a type of query to make: + * @param int $mode a type of query to make: * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE - * @param string $where for update queries: the WHERE clause to + * @param bool $where for update queries: the WHERE clause to * append to the SQL statement. Don't * include the "WHERE" keyword. * @@ -896,7 +848,8 @@ class DB_common extends PEAR $fields_values, $mode = DB_AUTOQUERY_INSERT, $where = false - ) { + ) + { $sth = $this->autoPrepare( $table, array_keys($fields_values), @@ -912,7 +865,39 @@ class DB_common extends PEAR } // }}} - // {{{ buildManipSQL() + // {{{ autoPrepare() + + /** + * Automaticaly generates an insert or update query and pass it to prepare() + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param bool $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return resource|string + * + * @uses DB_common::prepare(), DB_common::buildManipSQL() + */ + public function autoPrepare( + $table, + $table_fields, + $mode = DB_AUTOQUERY_INSERT, + $where = false + ) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (DB::isError($query)) { + return $query; + } + return $this->prepare($query); + } + + // }}} + // {{{ autoExecute() /** * Produces an SQL query string for autoPrepare() @@ -934,11 +919,11 @@ class DB_common extends PEAR * - Be carefull! If you don't give a $where param with an UPDATE * query, all the records of the table will be updated! * - * @param string $table the table name - * @param array $table_fields the array of field names - * @param int $mode a type of query to make: + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE - * @param string $where for update queries: the WHERE clause to + * @param bool $where for update queries: the WHERE clause to * append to the SQL statement. Don't * include the "WHERE" keyword. * @@ -985,6 +970,90 @@ class DB_common extends PEAR } } + // }}} + // {{{ buildManipSQL() + + /** + * Prepares a query for multiple execution with execute() + * + * Creates a query that can be run multiple times. Each time it is run, + * the placeholders, if any, will be replaced by the contents of + * execute()'s $data argument. + * + * Three types of placeholders can be used: + * + ? scalar value (i.e. strings, integers). The system + * will automatically quote and escape the data. + * + ! value is inserted 'as is' + * + & requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Example 1. + * + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res = $db->execute($sth, $data); + * + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders: + *
+     *    "UPDATE foo SET col=? WHERE col='over \& under'"
+     * 
+ * + * With some database backends, this is emulated. + * + * {@internal ibase and oci8 have their own prepare() methods.}} + * + * @param string $query the query to be prepared + * + * @return mixed DB statement resource on success. A DB_Error object + * on failure. + * + * @see DB_common::execute() + */ + public function prepare($query) + { + $tokens = preg_split( + '/((?prepare_tokens[] = &$newtokens; + end($this->prepare_tokens); + + $k = key($this->prepare_tokens); + $this->prepare_types[$k] = $types; + $this->prepared_queries[$k] = implode(' ', $newtokens); + + return $k; + } + // }}} // {{{ execute() @@ -1002,8 +1071,8 @@ class DB_common extends PEAR * $res = $db->execute($sth, $data); * * - * @param resource $stmt a DB statement resource returned from prepare() - * @param mixed $data array, string or numeric data to be used in + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -1039,8 +1108,8 @@ class DB_common extends PEAR /** * Emulates executing prepared statements if the DBMS not support them * - * @param resource $stmt a DB statement resource returned from execute() - * @param mixed $data array, string or numeric data to be used in + * @param resource $stmt a DB statement resource returned from execute() + * @param mixed $data array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -1089,6 +1158,33 @@ class DB_common extends PEAR // }}} // {{{ executeMultiple() + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_common::prepare() + */ + public function freePrepared($stmt, $free_resource = true) + { + $stmt = (int)$stmt; + if (isset($this->prepare_tokens[$stmt])) { + unset($this->prepare_tokens[$stmt]); + unset($this->prepare_types[$stmt]); + unset($this->prepared_queries[$stmt]); + return true; + } + return false; + } + + // }}} + // {{{ freePrepared() + /** * Performs several execute() calls on the same statement handle * @@ -1098,8 +1194,8 @@ class DB_common extends PEAR * If an error occurs during execute(), executeMultiple() does not * execute the unfinished rows, but rather returns that error. * - * @param resource $stmt query handle from prepare() - * @param array $data numeric array containing the + * @param resource $stmt query handle from prepare() + * @param array $data numeric array containing the * data to insert into the query * * @return int DB_OK on success. A DB_Error object on failure. @@ -1117,33 +1213,6 @@ class DB_common extends PEAR return DB_OK; } - // }}} - // {{{ freePrepared() - - /** - * Frees the internal resources associated with a prepared query - * - * @param resource $stmt the prepared statement's PHP resource - * @param bool $free_resource should the PHP resource be freed too? - * Use false if you need to get data - * from the result set later. - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_common::prepare() - */ - public function freePrepared($stmt, $free_resource = true) - { - $stmt = (int)$stmt; - if (isset($this->prepare_tokens[$stmt])) { - unset($this->prepare_tokens[$stmt]); - unset($this->prepare_types[$stmt]); - unset($this->prepared_queries[$stmt]); - return true; - } - return false; - } - // }}} // {{{ modifyQuery() @@ -1152,7 +1221,7 @@ class DB_common extends PEAR * * It is defined here to ensure all drivers have this method available. * - * @param string $query the query string to modify + * @param string $query the query string to modify * * @return string the modified query string * @@ -1168,16 +1237,49 @@ class DB_common extends PEAR // }}} // {{{ modifyLimitQuery() + /** + * Generates and executes a LIMIT query + * + * @param string $query the query + * @param intr $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + */ + public function &limitQuery($query, $from, $count, $params = array()) + { + $query = $this->modifyLimitQuery($query, $from, $count, $params); + if (DB::isError($query)) { + return $query; + } + $result = $this->query($query, $params); + if (is_object($result) && is_a($result, 'DB_result')) { + $result->setOption('limit_from', $from); + $result->setOption('limit_count', $count); + } + return $result; + } + + // }}} + // {{{ query() + /** * Adds LIMIT clauses to a query string according to current DBMS standards * * It is defined here to assure that all implementations * have this method defined. * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -1193,7 +1295,7 @@ class DB_common extends PEAR } // }}} - // {{{ query() + // {{{ limitQuery() /** * Sends a query to the database server @@ -1202,8 +1304,8 @@ class DB_common extends PEAR * to the server OR if $params are passed the query can have * placeholders and it will be passed through prepare() and execute(). * - * @param string $query the SQL query or the statement to prepare - * @param mixed $params array, string or numeric data to be used in + * @param string $query the SQL query or the statement to prepare + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -1231,43 +1333,10 @@ class DB_common extends PEAR if ($result === DB_OK || DB::isError($result)) { return $result; } else { - $tmp = new DB_result($this, $result); - return $tmp; - } - } - } - - // }}} - // {{{ limitQuery() - - /** - * Generates and executes a LIMIT query - * - * @param string $query the query - * @param intr $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return mixed a new DB_result object for successful SELECT queries - * or DB_OK for successul data manipulation queries. - * A DB_Error object on failure. - */ - public function &limitQuery($query, $from, $count, $params = array()) - { - $query = $this->modifyLimitQuery($query, $from, $count, $params); - if (DB::isError($query)) { - return $query; - } - $result = $this->query($query, $params); - if (is_object($result) && is_a($result, 'DB_result')) { - $result->setOption('limit_from', $from); - $result->setOption('limit_count', $count); + $tmp = new DB_result($this, $result); + return $tmp; + } } - return $result; } // }}} @@ -1278,8 +1347,8 @@ class DB_common extends PEAR * * Takes care of doing the query and freeing the results when finished. * - * @param string $query the SQL query - * @param mixed $params array, string or numeric data to be used in + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -1325,13 +1394,13 @@ class DB_common extends PEAR * * Takes care of doing the query and freeing the results when finished. * - * @param string $query the SQL query - * @param mixed $params array, string or numeric data to be used in + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array * parameters or 1 placeholder per array element. - * @param int $fetchmode the fetch mode to use + * @param int $fetchmode the fetch mode to use * * @return array the first row of results as an array. * A DB_Error object on failure. @@ -1340,7 +1409,8 @@ class DB_common extends PEAR $query, $params = array(), $fetchmode = DB_FETCHMODE_DEFAULT - ) { + ) + { // compat check, the params and fetchmode parameters used to // have the opposite order if (!is_array($params)) { @@ -1387,70 +1457,6 @@ class DB_common extends PEAR // }}} // {{{ getCol() - /** - * Fetches a single column from a query result and returns it as an - * indexed array - * - * @param string $query the SQL query - * @param mixed $col which column to return (integer [column number, - * starting at 0] or string [column name]) - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return array the results as an array. A DB_Error object on failure. - * - * @see DB_common::query() - */ - public function &getCol($query, $col = 0, $params = array()) - { - $params = (array)$params; - if (sizeof($params) > 0) { - $sth = $this->prepare($query); - - if (DB::isError($sth)) { - return $sth; - } - - $res = $this->execute($sth, $params); - $this->freePrepared($sth); - } else { - $res = $this->query($query); - } - - if (DB::isError($res)) { - return $res; - } - - $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; - - if (!is_array($row = $res->fetchRow($fetchmode))) { - $ret = array(); - } else { - if (!array_key_exists($col, $row)) { - $ret = $this->raiseError(DB_ERROR_NOSUCHFIELD); - } else { - $ret = array($row[$col]); - while (is_array($row = $res->fetchRow($fetchmode))) { - $ret[] = $row[$col]; - } - } - } - - $res->free(); - - if (DB::isError($row)) { - $ret = $row; - } - - return $ret; - } - - // }}} - // {{{ getAssoc() - /** * Fetches an entire query result and returns it as an * associative array using the first column as the key @@ -1513,26 +1519,26 @@ class DB_common extends PEAR * Keep in mind that database functions in PHP usually return string * values for results regardless of the database's internal type. * - * @param string $query the SQL query - * @param bool $force_array used only when the query returns + * @param string $query the SQL query + * @param bool $force_array used only when the query returns * exactly two columns. If true, the values * of the returned array will be one-element * arrays instead of scalars. - * @param mixed $params array, string or numeric data to be used in + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of * items passed must match quantity of * placeholders in query: meaning 1 * placeholder for non-array parameters or * 1 placeholder per array element. - * @param int $fetchmode the fetch mode to use - * @param bool $group if true, the values of the returned array + * @param int $fetchmode the fetch mode to use + * @param bool $group if true, the values of the returned array * is wrapped in another array. If the same * key value (in the first column) repeats * itself, the values will be appended to * this array instead of overwriting the * existing values. * - * @return array the associative array containing the query results. + * @return array|object * A DB_Error object on failure. */ public function &getAssoc( @@ -1541,7 +1547,8 @@ class DB_common extends PEAR $params = array(), $fetchmode = DB_FETCHMODE_DEFAULT, $group = false - ) { + ) + { $params = (array)$params; if (sizeof($params) > 0) { $sth = $this->prepare($query); @@ -1630,31 +1637,32 @@ class DB_common extends PEAR } // }}} - // {{{ getAll() + // {{{ getAssoc() /** * Fetches all of the rows from a query result * - * @param string $query the SQL query - * @param mixed $params array, string or numeric data to be used in + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of * items passed must match quantity of * placeholders in query: meaning 1 * placeholder for non-array parameters or * 1 placeholder per array element. - * @param int $fetchmode the fetch mode to use: + * @param int $fetchmode the fetch mode to use: * + DB_FETCHMODE_ORDERED * + DB_FETCHMODE_ASSOC * + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED * + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED * - * @return array the nested array. A DB_Error object on failure. + * @return array|object */ public function &getAll( $query, $params = array(), $fetchmode = DB_FETCHMODE_DEFAULT - ) { + ) + { // compat check, the params and fetchmode parameters used to // have the opposite order if (!is_array($params)) { @@ -1710,14 +1718,14 @@ class DB_common extends PEAR } // }}} - // {{{ autoCommit() + // {{{ getAll() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * - * @return int DB_OK on success. A DB_Error object if the driver + * @return int|object * doesn't support auto-committing transactions. */ public function autoCommit($onoff = false) @@ -1726,12 +1734,12 @@ class DB_common extends PEAR } // }}} - // {{{ commit() + // {{{ autoCommit() /** * Commits the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function commit() { @@ -1739,12 +1747,12 @@ class DB_common extends PEAR } // }}} - // {{{ rollback() + // {{{ commit() /** * Reverts the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function rollback() { @@ -1752,14 +1760,14 @@ class DB_common extends PEAR } // }}} - // {{{ numRows() + // {{{ rollback() /** * Determines the number of rows in a query result * - * @param resource $result the query result idenifier produced by PHP + * @param resource $result the query result idenifier produced by PHP * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object */ public function numRows($result) { @@ -1767,14 +1775,14 @@ class DB_common extends PEAR } // }}} - // {{{ affectedRows() + // {{{ numRows() /** * Determines the number of rows affected by a data maniuplation query * * 0 is returned for queries that don't manipulate data. * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object */ public function affectedRows() { @@ -1782,7 +1790,7 @@ class DB_common extends PEAR } // }}} - // {{{ getSequenceName() + // {{{ affectedRows() /** * Generates the name used inside the database for a sequence @@ -1790,7 +1798,7 @@ class DB_common extends PEAR * The createSequence() docblock contains notes about storing sequence * names. * - * @param string $sqn the sequence's public name + * @param string $sqn the sequence's public name * * @return string the sequence's name in the backend * @@ -1806,17 +1814,35 @@ class DB_common extends PEAR ); } + // }}} + // {{{ getSequenceName() + + /** + * Returns the value of an option + * + * @param string $option the option name you're curious about + * + * @return mixed the option's value + */ + public function getOption($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return $this->raiseError("unknown option $option"); + } + // }}} // {{{ nextId() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::createSequence(), DB_common::dropSequence(), @@ -1840,9 +1866,9 @@ class DB_common extends PEAR * * seqname_format is set via setOption(). * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object * * @see DB_common::dropSequence(), DB_common::getSequenceName(), * DB_common::nextID() @@ -1858,9 +1884,9 @@ class DB_common extends PEAR /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object * * @see DB_common::createSequence(), DB_common::getSequenceName(), * DB_common::nextID() @@ -1873,88 +1899,6 @@ class DB_common extends PEAR // }}} // {{{ raiseError() - /** - * Communicates an error and invoke error callbacks, etc - * - * Basically a wrapper for PEAR::raiseError without the message string. - * - * @param mixed integer error code, or a PEAR error object (all - * other parameters are ignored if this parameter is - * an object - * @param int error mode, see PEAR_Error docs - * @param mixed if error mode is PEAR_ERROR_TRIGGER, this is the - * error level (E_USER_NOTICE etc). If error mode is - * PEAR_ERROR_CALLBACK, this is the callback function, - * either as a function name, or as an array of an - * object and method name. For other error modes this - * parameter is ignored. - * @param string extra debug information. Defaults to the last - * query and native error code. - * @param mixed native error code, integer or string depending the - * backend - * @param mixed dummy parameter for E_STRICT compatibility with - * PEAR::raiseError - * @param mixed dummy parameter for E_STRICT compatibility with - * PEAR::raiseError - * - * @return object the PEAR_Error object - * - * @see PEAR_Error - */ - public function &raiseError( - $code = DB_ERROR, - $mode = null, - $options = null, - $userinfo = null, - $nativecode = null, - $dummy1 = null, - $dummy2 = null - ) { - // The error is yet a DB error object - if (is_object($code)) { - // because we the static PEAR::raiseError, our global - // handler should be used if it is set - if ($mode === null && !empty($this->_default_error_mode)) { - $mode = $this->_default_error_mode; - $options = $this->_default_error_options; - } - $tmp = PEAR::raiseError( - $code, - null, - $mode, - $options, - null, - null, - true - ); - return $tmp; - } - - if ($userinfo === null) { - $userinfo = $this->last_query; - } - - if ($nativecode) { - $userinfo .= ' [nativecode=' . trim($nativecode) . ']'; - } else { - $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']'; - } - - $tmp = PEAR::raiseError( - null, - $code, - $mode, - $options, - $userinfo, - 'DB_Error', - true - ); - return $tmp; - } - - // }}} - // {{{ errorNative() - /** * Gets the DBMS' native error code produced by the last query * @@ -1966,14 +1910,14 @@ class DB_common extends PEAR } // }}} - // {{{ errorCode() + // {{{ errorNative() /** * Maps native error codes to DB's portable ones * * Uses the $errorcode_map property defined in each driver. * - * @param string|int $nativecode the error code returned by the DBMS + * @param string|int $nativecode the error code returned by the DBMS * * @return int the portable DB error code. Return DB_ERROR if the * current driver doesn't have a mapping for the @@ -1989,12 +1933,12 @@ class DB_common extends PEAR } // }}} - // {{{ errorMessage() + // {{{ errorCode() /** * Maps a DB error code to a textual message * - * @param integer $dbcode the DB error code + * @param integer $dbcode the DB error code * * @return string the error message corresponding to the error code * submitted. FALSE if the error code is unknown. @@ -2007,7 +1951,7 @@ class DB_common extends PEAR } // }}} - // {{{ tableInfo() + // {{{ errorMessage() /** * Returns information about a table or a result set @@ -2112,19 +2056,19 @@ class DB_common extends PEAR * If the 'portability' option has DB_PORTABILITY_LOWERCASE * turned on, the names of tables and fields will be lowercased. * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode either unused or one of the tableInfo modes: + * @param int $mode either unused or one of the tableInfo modes: * DB_TABLEINFO_ORDERTABLE, * DB_TABLEINFO_ORDER or * DB_TABLEINFO_FULL (which does both). * These are bitwise, so the first two can be * combined using |. * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::setOption() @@ -2140,7 +2084,7 @@ class DB_common extends PEAR } // }}} - // {{{ getTables() + // {{{ tableInfo() /** * Lists the tables in the current database @@ -2155,17 +2099,17 @@ class DB_common extends PEAR } // }}} - // {{{ getListOf() + // {{{ getTables() /** * Lists internal database information * - * @param string $type type of information being sought. + * @param string $type type of information being sought. * Common items being sought are: * tables, databases, users, views, functions * Each DBMS's has its own capabilities. * - * @return array an array listing the items sought. + * @return array|object * A DB DB_Error object on failure. */ public function getListOf($type) @@ -2186,12 +2130,12 @@ class DB_common extends PEAR } // }}} - // {{{ getSpecialQuery() + // {{{ getListOf() /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested @@ -2204,6 +2148,70 @@ class DB_common extends PEAR return $this->raiseError(DB_ERROR_UNSUPPORTED); } + // }}} + // {{{ getSpecialQuery() + + /** + * Fetches a single column from a query result and returns it as an + * indexed array + * + * @param string $query the SQL query + * @param mixed $col which column to return (integer [column number, + * starting at 0] or string [column name]) + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return array the results as an array. A DB_Error object on failure. + * + * @see DB_common::query() + */ + public function &getCol($query, $col = 0, $params = array()) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; + + if (!is_array($row = $res->fetchRow($fetchmode))) { + $ret = array(); + } else { + if (!array_key_exists($col, $row)) { + $ret = $this->raiseError(DB_ERROR_NOSUCHFIELD); + } else { + $ret = array($row[$col]); + while (is_array($row = $res->fetchRow($fetchmode))) { + $ret[] = $row[$col]; + } + } + } + + $res->free(); + + if (DB::isError($row)) { + $ret = $row; + } + + return $ret; + } + // }}} // {{{ nextQueryIsManip() @@ -2247,7 +2255,6 @@ class DB_common extends PEAR } $this->_next_query_manip = false; return $this->_last_query_manip; - $manip = $this->_next_query_manip; } // }}} @@ -2256,7 +2263,7 @@ class DB_common extends PEAR /** * Right-trims all strings in an array * - * @param array $array the array to be trimmed (passed by reference) + * @param array $array the array to be trimmed (passed by reference) * * @return void * @@ -2277,7 +2284,7 @@ class DB_common extends PEAR /** * Converts all null values in an array to empty strings * - * @param array $array the array to be de-nullified (passed by reference) + * @param array $array the array to be de-nullified (passed by reference) * * @return void * diff --git a/extlib/DB/dbase.php b/extlib/DB/dbase.php index d500ea5afd..f72540d894 100644 --- a/extlib/DB/dbase.php +++ b/extlib/DB/dbase.php @@ -27,7 +27,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's dbase extension @@ -74,21 +75,20 @@ class DB_dbase extends DB_common * @var array */ public $features = array( - 'limit' => false, - 'new_link' => false, - 'numrows' => true, - 'pconnect' => false, - 'prepare' => false, - 'ssl' => false, - 'transactions' => false, + 'limit' => false, + 'new_link' => false, + 'numrows' => true, + 'pconnect' => false, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, ); /** * A mapping of native error codes to DB error codes * @var array */ - public $errorcode_map = array( - ); + public $errorcode_map = array(); /** * The raw database connection created by PHP @@ -189,15 +189,15 @@ class DB_dbase extends DB_common * ); * * $db = DB::connect($dsn, $options); - * if (PEAR::isError($db)) { + * if ((new PEAR)->isError($db)) { * die($db->getMessage()); * } * * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -226,9 +226,9 @@ class DB_dbase extends DB_common null, null, 'the dbase file does not exist and ' - . 'it could not be created because ' - . 'the "fields" element of the DSN ' - . 'is not properly set' + . 'it could not be created because ' + . 'the "fields" element of the DSN ' + . 'is not properly set' ); } $this->connection = @dbase_create( @@ -242,8 +242,8 @@ class DB_dbase extends DB_common null, null, 'the dbase file does not exist and ' - . 'the attempt to create it failed: ' - . $php_errormsg + . 'the attempt to create it failed: ' + . $php_errormsg ); } } else { @@ -306,10 +306,10 @@ class DB_dbase extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -350,7 +350,7 @@ class DB_dbase extends DB_common * This method is a no-op for dbase, as there aren't result resources in * the same sense as most other database backends. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -371,8 +371,7 @@ class DB_dbase extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource - * + * @param $foo * @return int the number of columns. A DB_Error object on failure. * * @see DB_result::numCols() @@ -392,8 +391,7 @@ class DB_dbase extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource - * + * @param $foo * @return int the number of rows. A DB_Error object on failure. * * @see DB_result::numRows() @@ -419,18 +417,18 @@ class DB_dbase extends DB_common { return $boolean ? 'T' : 'F'; } - + // }}} // {{{ tableInfo() /** * Returns information about the current database * - * @param mixed $result THIS IS UNUSED IN DBASE. The current database + * @param mixed $result THIS IS UNUSED IN DBASE. The current database * is examined regardless of what is provided here. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -467,7 +465,7 @@ class DB_dbase extends DB_common } $id = array(); - $i = 0; + $i = 0; $line = fread($db, 32); while (!feof($db)) { @@ -478,8 +476,8 @@ class DB_dbase extends DB_common $pos = strpos(substr($line, 0, 10), chr(0)); $pos = ($pos == 0 ? 10 : $pos); $id[$i] = array( - 'name' => substr($line, 0, $pos), - 'type' => $this->types[substr($line, 11, 1)], + 'name' => substr($line, 0, $pos), + 'type' => $this->types[substr($line, 11, 1)], 'length' => ord(substr($line, 16, 1)), 'precision' => ord(substr($line, 17, 1)), ); @@ -496,7 +494,7 @@ class DB_dbase extends DB_common $case_func = 'strval'; } - $res = array(); + $res = array(); $count = count($id); if ($mode) { @@ -506,9 +504,9 @@ class DB_dbase extends DB_common for ($i = 0; $i < $count; $i++) { $res[$i] = array( 'table' => $this->dsn['database'], - 'name' => $case_func($id[$i]['name']), - 'type' => $id[$i]['type'], - 'len' => $id[$i]['length'], + 'name' => $case_func($id[$i]['name']), + 'type' => $id[$i]['type'], + 'len' => $id[$i]['length'], 'flags' => '' ); if ($mode & DB_TABLEINFO_ORDER) { diff --git a/extlib/DB/fbsql.php b/extlib/DB/fbsql.php index d3579fe82f..3c1c3f6651 100644 --- a/extlib/DB/fbsql.php +++ b/extlib/DB/fbsql.php @@ -27,7 +27,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's fbsql extension @@ -75,13 +76,13 @@ class DB_fbsql extends DB_common * @var array */ public $features = array( - 'limit' => 'alter', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, ); /** @@ -89,8 +90,8 @@ class DB_fbsql extends DB_common * @var array */ public $errorcode_map = array( - 22 => DB_ERROR_SYNTAX, - 85 => DB_ERROR_ALREADY_EXISTS, + 22 => DB_ERROR_SYNTAX, + 85 => DB_ERROR_ALREADY_EXISTS, 108 => DB_ERROR_SYNTAX, 116 => DB_ERROR_NOSUCHTABLE, 124 => DB_ERROR_VALUE_COUNT_ON_ROW, @@ -141,10 +142,10 @@ class DB_fbsql extends DB_common * * Don't call this method directly. Use DB::connect() instead. * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -203,6 +204,35 @@ class DB_fbsql extends DB_common // }}} // {{{ disconnect() + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_fbsql::errorNative(), DB_common::errorCode() + */ + public function fbsqlRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode(fbsql_errno($this->connection)); + } + return $this->raiseError( + $errno, + null, + null, + null, + @fbsql_error($this->connection) + ); + } + + // }}} + // {{{ simpleQuery() + /** * Disconnects from the database server * @@ -216,7 +246,7 @@ class DB_fbsql extends DB_common } // }}} - // {{{ simpleQuery() + // {{{ nextResult() /** * Sends a query to the database server @@ -244,7 +274,7 @@ class DB_fbsql extends DB_common } // }}} - // {{{ nextResult() + // {{{ fetchInto() /** * Move the internal fbsql result pointer to the next available result @@ -261,7 +291,7 @@ class DB_fbsql extends DB_common } // }}} - // {{{ fetchInto() + // {{{ freeResult() /** * Places a row from the result set into the given array @@ -273,10 +303,10 @@ class DB_fbsql extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -311,7 +341,7 @@ class DB_fbsql extends DB_common } // }}} - // {{{ freeResult() + // {{{ autoCommit() /** * Deletes the result set and frees the memory occupied by the result set @@ -320,7 +350,7 @@ class DB_fbsql extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -332,27 +362,28 @@ class DB_fbsql extends DB_common } // }}} - // {{{ autoCommit() + // {{{ commit() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * * @return int DB_OK on success. A DB_Error object if the driver * doesn't support auto-committing transactions. */ - public function autoCommit($onoff=false) + public function autoCommit($onoff = false) { if ($onoff) { $this->query("SET COMMIT TRUE"); } else { $this->query("SET COMMIT FALSE"); } + return null; } // }}} - // {{{ commit() + // {{{ rollback() /** * Commits the current transaction @@ -362,10 +393,11 @@ class DB_fbsql extends DB_common public function commit() { @fbsql_commit($this->connection); + return 0; } // }}} - // {{{ rollback() + // {{{ numCols() /** * Reverts the current transaction @@ -375,10 +407,11 @@ class DB_fbsql extends DB_common public function rollback() { @fbsql_rollback($this->connection); + return 0; } // }}} - // {{{ numCols() + // {{{ numRows() /** * Gets the number of columns in a result set @@ -387,9 +420,9 @@ class DB_fbsql extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -403,7 +436,7 @@ class DB_fbsql extends DB_common } // }}} - // {{{ numRows() + // {{{ affectedRows() /** * Gets the number of rows in a result set @@ -412,9 +445,9 @@ class DB_fbsql extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object * * @see DB_result::numRows() */ @@ -428,7 +461,7 @@ class DB_fbsql extends DB_common } // }}} - // {{{ affectedRows() + // {{{ nextId() /** * Determines the number of rows affected by a data maniuplation query @@ -447,17 +480,14 @@ class DB_fbsql extends DB_common return $result; } - // }}} - // {{{ nextId() - /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -489,10 +519,13 @@ class DB_fbsql extends DB_common return $tmp[0]; } + // }}} + // {{{ dropSequence() + /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -503,8 +536,8 @@ class DB_fbsql extends DB_common { $seqname = $this->getSequenceName($seq_name); $res = $this->query('CREATE TABLE ' . $seqname - . ' (id INTEGER NOT NULL,' - . ' PRIMARY KEY(id))'); + . ' (id INTEGER NOT NULL,' + . ' PRIMARY KEY(id))'); if ($res) { $res = $this->query('SET UNIQUE = 0 FOR ' . $seqname); } @@ -512,12 +545,12 @@ class DB_fbsql extends DB_common } // }}} - // {{{ dropSequence() + // {{{ modifyLimitQuery() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -527,19 +560,19 @@ class DB_fbsql extends DB_common public function dropSequence($seq_name) { return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name) - . ' RESTRICT'); + . ' RESTRICT'); } // }}} - // {{{ modifyLimitQuery() + // {{{ quoteBoolean() /** * Adds LIMIT clauses to a query string according to current DBMS standards * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -567,7 +600,7 @@ class DB_fbsql extends DB_common } // }}} - // {{{ quoteBoolean() + // {{{ quoteFloat() /** * Formats a boolean value for use within a query in a locale-independent @@ -582,9 +615,9 @@ class DB_fbsql extends DB_common { return $boolean ? 'TRUE' : 'FALSE'; } - + // }}} - // {{{ quoteFloat() + // {{{ fbsqlRaiseError() /** * Formats a float value for use within a query in a locale-independent @@ -599,35 +632,6 @@ class DB_fbsql extends DB_common { return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); } - - // }}} - // {{{ fbsqlRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_fbsql::errorNative(), DB_common::errorCode() - */ - public function fbsqlRaiseError($errno = null) - { - if ($errno === null) { - $errno = $this->errorCode(fbsql_errno($this->connection)); - } - return $this->raiseError( - $errno, - null, - null, - null, - @fbsql_error($this->connection) - ); - } // }}} // {{{ errorNative() @@ -648,14 +652,14 @@ class DB_fbsql extends DB_common /** * Returns information about a table or a result set * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -701,7 +705,7 @@ class DB_fbsql extends DB_common } $count = @fbsql_num_fields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -710,9 +714,9 @@ class DB_fbsql extends DB_common for ($i = 0; $i < $count; $i++) { $res[$i] = array( 'table' => $case_func(@fbsql_field_table($id, $i)), - 'name' => $case_func(@fbsql_field_name($id, $i)), - 'type' => @fbsql_field_type($id, $i), - 'len' => @fbsql_field_len($id, $i), + 'name' => $case_func(@fbsql_field_name($id, $i)), + 'type' => @fbsql_field_type($id, $i), + 'len' => @fbsql_field_len($id, $i), 'flags' => @fbsql_field_flags($id, $i), ); if ($mode & DB_TABLEINFO_ORDER) { @@ -736,7 +740,7 @@ class DB_fbsql extends DB_common /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested @@ -749,32 +753,32 @@ class DB_fbsql extends DB_common switch ($type) { case 'tables': return 'SELECT "table_name" FROM information_schema.tables' - . ' t0, information_schema.schemata t1' - . ' WHERE t0.schema_pk=t1.schema_pk AND' - . ' "table_type" = \'BASE TABLE\'' - . ' AND "schema_name" = current_schema'; + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk AND' + . ' "table_type" = \'BASE TABLE\'' + . ' AND "schema_name" = current_schema'; case 'views': return 'SELECT "table_name" FROM information_schema.tables' - . ' t0, information_schema.schemata t1' - . ' WHERE t0.schema_pk=t1.schema_pk AND' - . ' "table_type" = \'VIEW\'' - . ' AND "schema_name" = current_schema'; + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk AND' + . ' "table_type" = \'VIEW\'' + . ' AND "schema_name" = current_schema'; case 'users': return 'SELECT "user_name" from information_schema.users'; case 'functions': return 'SELECT "routine_name" FROM' - . ' information_schema.psm_routines' - . ' t0, information_schema.schemata t1' - . ' WHERE t0.schema_pk=t1.schema_pk' - . ' AND "routine_kind"=\'FUNCTION\'' - . ' AND "schema_name" = current_schema'; + . ' information_schema.psm_routines' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk' + . ' AND "routine_kind"=\'FUNCTION\'' + . ' AND "schema_name" = current_schema'; case 'procedures': return 'SELECT "routine_name" FROM' - . ' information_schema.psm_routines' - . ' t0, information_schema.schemata t1' - . ' WHERE t0.schema_pk=t1.schema_pk' - . ' AND "routine_kind"=\'PROCEDURE\'' - . ' AND "schema_name" = current_schema'; + . ' information_schema.psm_routines' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk' + . ' AND "routine_kind"=\'PROCEDURE\'' + . ' AND "schema_name" = current_schema'; default: return null; } diff --git a/extlib/DB/ibase.php b/extlib/DB/ibase.php index 940462a592..a91bd35772 100644 --- a/extlib/DB/ibase.php +++ b/extlib/DB/ibase.php @@ -30,7 +30,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's interbase extension @@ -85,13 +86,13 @@ class DB_ibase extends DB_common * @var array */ public $features = array( - 'limit' => false, - 'new_link' => false, - 'numrows' => 'emulate', - 'pconnect' => true, - 'prepare' => true, - 'ssl' => false, - 'transactions' => true, + 'limit' => false, + 'new_link' => false, + 'numrows' => 'emulate', + 'pconnect' => true, + 'prepare' => true, + 'ssl' => false, + 'transactions' => true, ); /** @@ -207,10 +208,10 @@ class DB_ibase extends DB_common * Functional only with InterBase 6 and up. * + role Functional only with InterBase 5 and up. * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -228,14 +229,14 @@ class DB_ibase extends DB_common $params = array( $dsn['hostspec'] - ? ($dsn['hostspec'] . ':' . $dsn['database']) - : $dsn['database'], + ? ($dsn['hostspec'] . ':' . $dsn['database']) + : $dsn['database'], $dsn['username'] ? $dsn['username'] : null, $dsn['password'] ? $dsn['password'] : null, isset($dsn['charset']) ? $dsn['charset'] : null, isset($dsn['buffers']) ? $dsn['buffers'] : null, isset($dsn['dialect']) ? $dsn['dialect'] : null, - isset($dsn['role']) ? $dsn['role'] : null, + isset($dsn['role']) ? $dsn['role'] : null, ); $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect'; @@ -250,6 +251,112 @@ class DB_ibase extends DB_common // }}} // {{{ disconnect() + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_ibase::errorNative(), DB_ibase::errorCode() + */ + public function &ibaseRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode($this->errorNative()); + } + $tmp = $this->raiseError($errno, null, null, null, @ibase_errmsg()); + return $tmp; + } + + // }}} + // {{{ simpleQuery() + + /** + * Maps native error codes to DB's portable ones + * + * @param int $nativecode the error code returned by the DBMS + * + * @return int the portable DB error code. Return DB_ERROR if the + * current driver doesn't have a mapping for the + * $nativecode submitted. + * + * @since Method available since Release 1.7.0 + */ + public function errorCode($nativecode = null) + { + if (isset($this->errorcode_map[$nativecode])) { + return $this->errorcode_map[$nativecode]; + } + + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/generator .* is not defined/' + => DB_ERROR_SYNTAX, // for compat. w ibase_errcode() + '/violation of [\w ]+ constraint/i' + => DB_ERROR_CONSTRAINT, + '/table.*(not exist|not found|unknown)/i' + => DB_ERROR_NOSUCHTABLE, + '/table .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/unsuccessful metadata update .* failed attempt to store duplicate value/i' + => DB_ERROR_ALREADY_EXISTS, + '/unsuccessful metadata update .* not found/i' + => DB_ERROR_NOT_FOUND, + '/validation error for column .* value "\*\*\* null/i' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/conversion error from string/i' + => DB_ERROR_INVALID_NUMBER, + '/no permission for/i' + => DB_ERROR_ACCESS_VIOLATION, + '/arithmetic exception, numeric overflow, or string truncation/i' + => DB_ERROR_INVALID, + '/feature is not supported/i' + => DB_ERROR_NOT_CAPABLE, + ); + } + + $errormsg = @ibase_errmsg(); + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code. NULL if there is no error code. + * + * @since Method available since Release 1.7.0 + */ + public function errorNative() + { + if (function_exists('ibase_errcode')) { + return @ibase_errcode(); + } + if (preg_match( + '/^Dynamic SQL Error SQL error code = ([0-9-]+)/i', + @ibase_errmsg(), + $m + )) { + return (int)$m[1]; + } + return null; + } + + // }}} + // {{{ nextResult() + /** * Disconnects from the database server * @@ -263,7 +370,7 @@ class DB_ibase extends DB_common } // }}} - // {{{ simpleQuery() + // {{{ fetchInto() /** * Sends a query to the database server @@ -297,17 +404,17 @@ class DB_ibase extends DB_common } // }}} - // {{{ modifyLimitQuery() + // {{{ freeResult() /** * Adds LIMIT clauses to a query string according to current DBMS standards * * Only works with Firebird. * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -330,7 +437,7 @@ class DB_ibase extends DB_common } // }}} - // {{{ nextResult() + // {{{ freeQuery() /** * Move the internal ibase result pointer to the next available result @@ -347,7 +454,7 @@ class DB_ibase extends DB_common } // }}} - // {{{ fetchInto() + // {{{ affectedRows() /** * Places a row from the result set into the given array @@ -359,10 +466,10 @@ class DB_ibase extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -399,7 +506,7 @@ class DB_ibase extends DB_common } // }}} - // {{{ freeResult() + // {{{ numCols() /** * Deletes the result set and frees the memory occupied by the result set @@ -408,7 +515,7 @@ class DB_ibase extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -420,7 +527,7 @@ class DB_ibase extends DB_common } // }}} - // {{{ freeQuery() + // {{{ prepare() public function freeQuery($query) { @@ -428,14 +535,14 @@ class DB_ibase extends DB_common } // }}} - // {{{ affectedRows() + // {{{ execute() /** * Determines the number of rows affected by a data maniuplation query * * 0 is returned for queries that don't manipulate data. * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object */ public function affectedRows() { @@ -445,9 +552,6 @@ class DB_ibase extends DB_common return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE); } - // }}} - // {{{ numCols() - /** * Gets the number of columns in a result set * @@ -455,9 +559,9 @@ class DB_ibase extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -471,7 +575,7 @@ class DB_ibase extends DB_common } // }}} - // {{{ prepare() + // {{{ autoCommit() /** * Prepares a query for multiple execution with execute(). @@ -497,14 +601,14 @@ class DB_ibase extends DB_common */ public function prepare($query) { - $tokens = preg_split( + $tokens = preg_split( '/((? $val) { @@ -527,26 +631,26 @@ class DB_ibase extends DB_common $newquery = substr($newquery, 0, -1); $this->last_query = $query; $newquery = $this->modifyQuery($newquery); - $stmt = @ibase_prepare($this->connection, $newquery); + $stmt = @ibase_prepare(/*$this->connection,*/ $newquery); if ($stmt === false) { $stmt = $this->ibaseRaiseError(); } else { $this->prepare_types[(int)$stmt] = $types; - $this->manip_query[(int)$stmt] = DB::isManip($query); + $this->manip_query[(int)$stmt] = DB::isManip($query); } return $stmt; } // }}} - // {{{ execute() + // {{{ commit() /** * Executes a DB statement prepared with prepare(). * - * @param resource $stmt a DB statement resource returned from prepare() - * @param mixed $data array, string or numeric data to be used in + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 for non-array items or the @@ -613,11 +717,14 @@ class DB_ibase extends DB_common return $tmp; } + // }}} + // {{{ rollback() + /** * Frees the internal resources associated with a prepared query * - * @param resource $stmt the prepared statement's PHP resource - * @param bool $free_resource should the PHP resource be freed too? + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? * Use false if you need to get data * from the result set later. * @@ -640,12 +747,12 @@ class DB_ibase extends DB_common } // }}} - // {{{ autoCommit() + // {{{ transactionInit() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * * @return int DB_OK on success. A DB_Error object if the driver * doesn't support auto-committing transactions. @@ -657,7 +764,7 @@ class DB_ibase extends DB_common } // }}} - // {{{ commit() + // {{{ nextId() /** * Commits the current transaction @@ -670,7 +777,7 @@ class DB_ibase extends DB_common } // }}} - // {{{ rollback() + // {{{ createSequence() /** * Reverts the current transaction @@ -683,26 +790,26 @@ class DB_ibase extends DB_common } // }}} - // {{{ transactionInit() + // {{{ dropSequence() public function transactionInit($trans_args = 0) { return $trans_args - ? @ibase_trans($trans_args, $this->connection) - : @ibase_trans(); + ? @ibase_trans($trans_args, $this->connection) + : @ibase_trans(); } // }}} - // {{{ nextId() + // {{{ _ibaseFieldFlags() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -715,8 +822,8 @@ class DB_ibase extends DB_common do { $this->pushErrorHandling(PEAR_ERROR_RETURN); $result = $this->query("SELECT GEN_ID(${sqn}, 1) " - . 'FROM RDB$GENERATORS ' - . "WHERE RDB\$GENERATOR_NAME='${sqn}'"); + . 'FROM RDB$GENERATORS ' + . "WHERE RDB\$GENERATOR_NAME='${sqn}'"); $this->popErrorHandling(); if ($ondemand && DB::isError($result)) { $repeat = 1; @@ -737,12 +844,12 @@ class DB_ibase extends DB_common } // }}} - // {{{ createSequence() + // {{{ ibaseRaiseError() /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -760,12 +867,12 @@ class DB_ibase extends DB_common } // }}} - // {{{ dropSequence() + // {{{ errorNative() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -775,206 +882,28 @@ class DB_ibase extends DB_common public function dropSequence($seq_name) { return $this->query('DELETE FROM RDB$GENERATORS ' - . "WHERE RDB\$GENERATOR_NAME='" - . strtoupper($this->getSequenceName($seq_name)) - . "'"); - } - - // }}} - // {{{ _ibaseFieldFlags() - - /** - * Get the column's flags - * - * Supports "primary_key", "unique_key", "not_null", "default", - * "computed" and "blob". - * - * @param string $field_name the name of the field - * @param string $table_name the name of the table - * - * @return string the flags - * - * @access private - */ - public function _ibaseFieldFlags($field_name, $table_name) - { - $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE' - .' FROM RDB$INDEX_SEGMENTS I' - .' JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME' - .' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\'' - .' AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''; - - $result = @ibase_query($this->connection, $sql); - if (!$result) { - return $this->ibaseRaiseError(); - } - - $flags = ''; - if ($obj = @ibase_fetch_object($result)) { - @ibase_free_result($result); - if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'PRIMARY KEY') { - $flags .= 'primary_key '; - } - if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'UNIQUE') { - $flags .= 'unique_key '; - } - } - - $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,' - .' R.RDB$DEFAULT_SOURCE AS DSOURCE,' - .' F.RDB$FIELD_TYPE AS FTYPE,' - .' F.RDB$COMPUTED_SOURCE AS CSOURCE' - .' FROM RDB$RELATION_FIELDS R ' - .' JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME' - .' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'' - .' AND R.RDB$FIELD_NAME=\'' . $field_name . '\''; - - $result = @ibase_query($this->connection, $sql); - if (!$result) { - return $this->ibaseRaiseError(); - } - if ($obj = @ibase_fetch_object($result)) { - @ibase_free_result($result); - if (isset($obj->NFLAG)) { - $flags .= 'not_null '; - } - if (isset($obj->DSOURCE)) { - $flags .= 'default '; - } - if (isset($obj->CSOURCE)) { - $flags .= 'computed '; - } - if (isset($obj->FTYPE) && $obj->FTYPE == 261) { - $flags .= 'blob '; - } - } - - return trim($flags); - } - - // }}} - // {{{ ibaseRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_ibase::errorNative(), DB_ibase::errorCode() - */ - public function &ibaseRaiseError($errno = null) - { - if ($errno === null) { - $errno = $this->errorCode($this->errorNative()); - } - $tmp = $this->raiseError($errno, null, null, null, @ibase_errmsg()); - return $tmp; - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return int the DBMS' error code. NULL if there is no error code. - * - * @since Method available since Release 1.7.0 - */ - public function errorNative() - { - if (function_exists('ibase_errcode')) { - return @ibase_errcode(); - } - if (preg_match( - '/^Dynamic SQL Error SQL error code = ([0-9-]+)/i', - @ibase_errmsg(), - $m - )) { - return (int)$m[1]; - } - return null; + . "WHERE RDB\$GENERATOR_NAME='" + . strtoupper($this->getSequenceName($seq_name)) + . "'"); } // }}} // {{{ errorCode() - /** - * Maps native error codes to DB's portable ones - * - * @param int $nativecode the error code returned by the DBMS - * - * @return int the portable DB error code. Return DB_ERROR if the - * current driver doesn't have a mapping for the - * $nativecode submitted. - * - * @since Method available since Release 1.7.0 - */ - public function errorCode($nativecode = null) - { - if (isset($this->errorcode_map[$nativecode])) { - return $this->errorcode_map[$nativecode]; - } - - static $error_regexps; - if (!isset($error_regexps)) { - $error_regexps = array( - '/generator .* is not defined/' - => DB_ERROR_SYNTAX, // for compat. w ibase_errcode() - '/violation of [\w ]+ constraint/i' - => DB_ERROR_CONSTRAINT, - '/table.*(not exist|not found|unknown)/i' - => DB_ERROR_NOSUCHTABLE, - '/table .* already exists/i' - => DB_ERROR_ALREADY_EXISTS, - '/unsuccessful metadata update .* failed attempt to store duplicate value/i' - => DB_ERROR_ALREADY_EXISTS, - '/unsuccessful metadata update .* not found/i' - => DB_ERROR_NOT_FOUND, - '/validation error for column .* value "\*\*\* null/i' - => DB_ERROR_CONSTRAINT_NOT_NULL, - '/conversion error from string/i' - => DB_ERROR_INVALID_NUMBER, - '/no permission for/i' - => DB_ERROR_ACCESS_VIOLATION, - '/arithmetic exception, numeric overflow, or string truncation/i' - => DB_ERROR_INVALID, - '/feature is not supported/i' - => DB_ERROR_NOT_CAPABLE, - ); - } - - $errormsg = @ibase_errmsg(); - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - return DB_ERROR; - } - - // }}} - // {{{ tableInfo() - /** * Returns information about a table or a result set * * NOTE: only supports 'table' and 'flags' if $result * is a table name. * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -1019,7 +948,7 @@ class DB_ibase extends DB_common } $count = @ibase_num_fields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -1029,12 +958,12 @@ class DB_ibase extends DB_common $info = @ibase_field_info($id, $i); $res[$i] = array( 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func($info['name']), - 'type' => $info['type'], - 'len' => $info['length'], + 'name' => $case_func($info['name']), + 'type' => $info['type'], + 'len' => $info['length'], 'flags' => ($got_string) - ? $this->_ibaseFieldFlags($info['name'], $result) - : '', + ? $this->_ibaseFieldFlags($info['name'], $result) + : '', ); if ($mode & DB_TABLEINFO_ORDER) { $res['order'][$res[$i]['name']] = $i; @@ -1051,13 +980,85 @@ class DB_ibase extends DB_common return $res; } + // }}} + // {{{ tableInfo() + + /** + * Get the column's flags + * + * Supports "primary_key", "unique_key", "not_null", "default", + * "computed" and "blob". + * + * @param string $field_name the name of the field + * @param string $table_name the name of the table + * + * @return string the flags + * + * @access private + */ + public function _ibaseFieldFlags($field_name, $table_name) + { + $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE' + . ' FROM RDB$INDEX_SEGMENTS I' + . ' JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME' + . ' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\'' + . ' AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''; + + $result = @ibase_query($this->connection, $sql); + if (!$result) { + return $this->ibaseRaiseError(); + } + + $flags = ''; + if ($obj = @ibase_fetch_object($result)) { + @ibase_free_result($result); + if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'PRIMARY KEY') { + $flags .= 'primary_key '; + } + if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'UNIQUE') { + $flags .= 'unique_key '; + } + } + + $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,' + . ' R.RDB$DEFAULT_SOURCE AS DSOURCE,' + . ' F.RDB$FIELD_TYPE AS FTYPE,' + . ' F.RDB$COMPUTED_SOURCE AS CSOURCE' + . ' FROM RDB$RELATION_FIELDS R ' + . ' JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME' + . ' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'' + . ' AND R.RDB$FIELD_NAME=\'' . $field_name . '\''; + + $result = @ibase_query($this->connection, $sql); + if (!$result) { + return $this->ibaseRaiseError(); + } + if ($obj = @ibase_fetch_object($result)) { + @ibase_free_result($result); + if (isset($obj->NFLAG)) { + $flags .= 'not_null '; + } + if (isset($obj->DSOURCE)) { + $flags .= 'default '; + } + if (isset($obj->CSOURCE)) { + $flags .= 'computed '; + } + if (isset($obj->FTYPE) && $obj->FTYPE == 261) { + $flags .= 'blob '; + } + } + + return trim($flags); + } + // }}} // {{{ getSpecialQuery() /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested @@ -1070,7 +1071,7 @@ class DB_ibase extends DB_common switch ($type) { case 'tables': return 'SELECT DISTINCT R.RDB$RELATION_NAME FROM ' - . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0'; + . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0'; case 'views': return 'SELECT DISTINCT RDB$VIEW_NAME from RDB$VIEW_RELATIONS'; case 'users': diff --git a/extlib/DB/ifx.php b/extlib/DB/ifx.php index 87e5d48b9d..d398dd2693 100644 --- a/extlib/DB/ifx.php +++ b/extlib/DB/ifx.php @@ -27,7 +27,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's ifx extension @@ -81,13 +82,13 @@ class DB_ifx extends DB_common * @var array */ public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => 'emulate', - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => 'emulate', + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, ); /** @@ -95,33 +96,33 @@ class DB_ifx extends DB_common * @var array */ public $errorcode_map = array( - '-201' => DB_ERROR_SYNTAX, - '-206' => DB_ERROR_NOSUCHTABLE, - '-217' => DB_ERROR_NOSUCHFIELD, - '-236' => DB_ERROR_VALUE_COUNT_ON_ROW, - '-239' => DB_ERROR_CONSTRAINT, - '-253' => DB_ERROR_SYNTAX, - '-268' => DB_ERROR_CONSTRAINT, - '-292' => DB_ERROR_CONSTRAINT_NOT_NULL, - '-310' => DB_ERROR_ALREADY_EXISTS, - '-316' => DB_ERROR_ALREADY_EXISTS, - '-319' => DB_ERROR_NOT_FOUND, - '-329' => DB_ERROR_NODBSELECTED, - '-346' => DB_ERROR_CONSTRAINT, - '-386' => DB_ERROR_CONSTRAINT_NOT_NULL, - '-391' => DB_ERROR_CONSTRAINT_NOT_NULL, - '-554' => DB_ERROR_SYNTAX, - '-691' => DB_ERROR_CONSTRAINT, - '-692' => DB_ERROR_CONSTRAINT, - '-703' => DB_ERROR_CONSTRAINT_NOT_NULL, - '-1202' => DB_ERROR_DIVZERO, - '-1204' => DB_ERROR_INVALID_DATE, - '-1205' => DB_ERROR_INVALID_DATE, - '-1206' => DB_ERROR_INVALID_DATE, - '-1209' => DB_ERROR_INVALID_DATE, - '-1210' => DB_ERROR_INVALID_DATE, - '-1212' => DB_ERROR_INVALID_DATE, - '-1213' => DB_ERROR_INVALID_NUMBER, + '-201' => DB_ERROR_SYNTAX, + '-206' => DB_ERROR_NOSUCHTABLE, + '-217' => DB_ERROR_NOSUCHFIELD, + '-236' => DB_ERROR_VALUE_COUNT_ON_ROW, + '-239' => DB_ERROR_CONSTRAINT, + '-253' => DB_ERROR_SYNTAX, + '-268' => DB_ERROR_CONSTRAINT, + '-292' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-310' => DB_ERROR_ALREADY_EXISTS, + '-316' => DB_ERROR_ALREADY_EXISTS, + '-319' => DB_ERROR_NOT_FOUND, + '-329' => DB_ERROR_NODBSELECTED, + '-346' => DB_ERROR_CONSTRAINT, + '-386' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-391' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-554' => DB_ERROR_SYNTAX, + '-691' => DB_ERROR_CONSTRAINT, + '-692' => DB_ERROR_CONSTRAINT, + '-703' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-1202' => DB_ERROR_DIVZERO, + '-1204' => DB_ERROR_INVALID_DATE, + '-1205' => DB_ERROR_INVALID_DATE, + '-1206' => DB_ERROR_INVALID_DATE, + '-1209' => DB_ERROR_INVALID_DATE, + '-1210' => DB_ERROR_INVALID_DATE, + '-1212' => DB_ERROR_INVALID_DATE, + '-1213' => DB_ERROR_INVALID_NUMBER, ); /** @@ -184,10 +185,10 @@ class DB_ifx extends DB_common * * Don't call this method directly. Use DB::connect() instead. * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -218,6 +219,72 @@ class DB_ifx extends DB_common // }}} // {{{ disconnect() + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_ifx::errorNative(), DB_ifx::errorCode() + */ + public function ifxRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode(ifx_error()); + } + return $this->raiseError( + $errno, + null, + null, + null, + $this->errorNative() + ); + } + + // }}} + // {{{ simpleQuery() + + /** + * Maps native error codes to DB's portable ones. + * + * Requires that the DB implementation's constructor fills + * in the $errorcode_map property. + * + * @param string $nativecode error code returned by the database + * @return int a portable DB error code, or DB_ERROR if this DB + * implementation has no mapping for the given error code. + */ + public function errorCode($nativecode) + { + if (preg_match('/SQLCODE=(.*)]/', $nativecode, $match)) { + $code = $match[1]; + if (isset($this->errorcode_map[$code])) { + return $this->errorcode_map[$code]; + } + } + return DB_ERROR; + } + + // }}} + // {{{ nextResult() + + /** + * Gets the DBMS' native error code and message produced by the last query + * + * @return string the DBMS' error code and message + */ + public function errorNative() + { + return @ifx_error() . ' ' . @ifx_errormsg(); + } + + // }}} + // {{{ affectedRows() + /** * Disconnects from the database server * @@ -231,7 +298,7 @@ class DB_ifx extends DB_common } // }}} - // {{{ simpleQuery() + // {{{ fetchInto() /** * Sends a query to the database server @@ -246,7 +313,7 @@ class DB_ifx extends DB_common { $ismanip = $this->_checkManip($query); $this->last_query = $query; - $this->affected = null; + $this->affected = null; if (preg_match('/(SELECT|EXECUTE)/i', $query)) { //TESTME: Use !DB::isManip()? // the scroll is needed for fetching absolute row numbers // in a select query result @@ -282,7 +349,7 @@ class DB_ifx extends DB_common } // }}} - // {{{ nextResult() + // {{{ numCols() /** * Move the internal ifx result pointer to the next available result @@ -299,7 +366,7 @@ class DB_ifx extends DB_common } // }}} - // {{{ affectedRows() + // {{{ freeResult() /** * Determines the number of rows affected by a data maniuplation query @@ -318,7 +385,7 @@ class DB_ifx extends DB_common } // }}} - // {{{ fetchInto() + // {{{ autoCommit() /** * Places a row from the result set into the given array @@ -330,10 +397,10 @@ class DB_ifx extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -359,14 +426,14 @@ class DB_ifx extends DB_common return null; } if ($fetchmode !== DB_FETCHMODE_ASSOC) { - $i=0; + $i = 0; $order = array(); foreach ($arr as $val) { $order[$i++] = $val; } $arr = $order; } elseif ($fetchmode == DB_FETCHMODE_ASSOC && - $this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $this->options['portability'] & DB_PORTABILITY_LOWERCASE) { $arr = array_change_key_case($arr, CASE_LOWER); } if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { @@ -379,7 +446,7 @@ class DB_ifx extends DB_common } // }}} - // {{{ numCols() + // {{{ commit() /** * Gets the number of columns in a result set @@ -388,9 +455,9 @@ class DB_ifx extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -403,7 +470,7 @@ class DB_ifx extends DB_common } // }}} - // {{{ freeResult() + // {{{ rollback() /** * Deletes the result set and frees the memory occupied by the result set @@ -412,7 +479,7 @@ class DB_ifx extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -424,12 +491,12 @@ class DB_ifx extends DB_common } // }}} - // {{{ autoCommit() + // {{{ ifxRaiseError() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * * @return int DB_OK on success. A DB_Error object if the driver * doesn't support auto-committing transactions. @@ -443,12 +510,12 @@ class DB_ifx extends DB_common } // }}} - // {{{ commit() + // {{{ errorNative() /** * Commits the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function commit() { @@ -463,12 +530,12 @@ class DB_ifx extends DB_common } // }}} - // {{{ rollback() + // {{{ errorCode() /** * Reverts the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function rollback() { @@ -482,72 +549,6 @@ class DB_ifx extends DB_common return DB_OK; } - // }}} - // {{{ ifxRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_ifx::errorNative(), DB_ifx::errorCode() - */ - public function ifxRaiseError($errno = null) - { - if ($errno === null) { - $errno = $this->errorCode(ifx_error()); - } - return $this->raiseError( - $errno, - null, - null, - null, - $this->errorNative() - ); - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error code and message produced by the last query - * - * @return string the DBMS' error code and message - */ - public function errorNative() - { - return @ifx_error() . ' ' . @ifx_errormsg(); - } - - // }}} - // {{{ errorCode() - - /** - * Maps native error codes to DB's portable ones. - * - * Requires that the DB implementation's constructor fills - * in the $errorcode_map property. - * - * @param string $nativecode error code returned by the database - * @return int a portable DB error code, or DB_ERROR if this DB - * implementation has no mapping for the given error code. - */ - public function errorCode($nativecode) - { - if (preg_match('/SQLCODE=(.*)]/', $nativecode, $match)) { - $code = $match[1]; - if (isset($this->errorcode_map[$code])) { - return $this->errorcode_map[$code]; - } - } - return DB_ERROR; - } - // }}} // {{{ tableInfo() @@ -560,14 +561,14 @@ class DB_ifx extends DB_common * an error will be raised saying * can't distinguish duplicate field names. * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -618,7 +619,7 @@ class DB_ifx extends DB_common $case_func = 'strval'; } - $i = 0; + $i = 0; $res = array(); if ($mode) { @@ -629,9 +630,9 @@ class DB_ifx extends DB_common $props = explode(';', $value); $res[$i] = array( 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func($key), - 'type' => $props[0], - 'len' => $props[1], + 'name' => $case_func($key), + 'type' => $props[0], + 'len' => $props[1], 'flags' => $props[4] == 'N' ? 'not_null' : '', ); if ($mode & DB_TABLEINFO_ORDER) { @@ -656,7 +657,7 @@ class DB_ifx extends DB_common /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested diff --git a/extlib/DB/msql.php b/extlib/DB/msql.php index 399215bc36..b583b5c635 100644 --- a/extlib/DB/msql.php +++ b/extlib/DB/msql.php @@ -30,7 +30,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's msql extension @@ -81,21 +82,20 @@ class DB_msql extends DB_common * @var array */ public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => false, + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, ); /** * A mapping of native error codes to DB error codes * @var array */ - public $errorcode_map = array( - ); + public $errorcode_map = array(); /** * The raw database connection created by PHP @@ -154,15 +154,15 @@ class DB_msql extends DB_common * ); * * $db = DB::connect($dsn, $options); - * if (PEAR::isError($db)) { + * if ((new PEAR)->isError($db)) { * die($db->getMessage()); * } * * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -178,8 +178,8 @@ class DB_msql extends DB_common $params = array(); if ($dsn['hostspec']) { $params[] = $dsn['port'] - ? $dsn['hostspec'] . ',' . $dsn['port'] - : $dsn['hostspec']; + ? $dsn['hostspec'] . ',' . $dsn['port'] + : $dsn['hostspec']; } $connect_function = $persistent ? 'msql_pconnect' : 'msql_connect'; @@ -229,6 +229,127 @@ class DB_msql extends DB_common // }}} // {{{ disconnect() + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_msql::errorNative(), DB_msql::errorCode() + */ + public function msqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ simpleQuery() + + /** + * Gets the DBMS' native error message produced by the last query + * + * @return string the DBMS' error message + */ + public function errorNative() + { + return @msql_error(); + } + + + // }}} + // {{{ nextResult() + + /** + * Determines PEAR::DB error code from the database's text error message + * + * @param string $errormsg the error message returned from the database + * + * @return integer the error number from a DB_ERROR* constant + */ + public function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^msql[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/^Access to database denied/i' + => DB_ERROR_ACCESS_VIOLATION, + '/^Bad index name/i' + => DB_ERROR_ALREADY_EXISTS, + '/^Bad order field/i' + => DB_ERROR_SYNTAX, + '/^Bad type for comparison/i' + => DB_ERROR_SYNTAX, + '/^Can\'t perform LIKE on/i' + => DB_ERROR_SYNTAX, + '/^Can\'t use TEXT fields in LIKE comparison/i' + => DB_ERROR_SYNTAX, + '/^Couldn\'t create temporary table/i' + => DB_ERROR_CANNOT_CREATE, + '/^Error creating table file/i' + => DB_ERROR_CANNOT_CREATE, + '/^Field .* cannot be null$/i' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/^Index (field|condition) .* cannot be null$/i' + => DB_ERROR_SYNTAX, + '/^Invalid date format/i' + => DB_ERROR_INVALID_DATE, + '/^Invalid time format/i' + => DB_ERROR_INVALID, + '/^Literal value for .* is wrong type$/i' + => DB_ERROR_INVALID_NUMBER, + '/^No Database Selected/i' + => DB_ERROR_NODBSELECTED, + '/^No value specified for field/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + '/^Non unique value for unique index/i' + => DB_ERROR_CONSTRAINT, + '/^Out of memory for temporary table/i' + => DB_ERROR_CANNOT_CREATE, + '/^Permission denied/i' + => DB_ERROR_ACCESS_VIOLATION, + '/^Reference to un-selected table/i' + => DB_ERROR_SYNTAX, + '/^syntax error/i' + => DB_ERROR_SYNTAX, + '/^Table .* exists$/i' + => DB_ERROR_ALREADY_EXISTS, + '/^Unknown database/i' + => DB_ERROR_NOSUCHDB, + '/^Unknown field/i' + => DB_ERROR_NOSUCHFIELD, + '/^Unknown (index|system variable)/i' + => DB_ERROR_NOT_FOUND, + '/^Unknown table/i' + => DB_ERROR_NOSUCHTABLE, + '/^Unqualified field/i' + => DB_ERROR_SYNTAX, + ); + } + + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ fetchInto() + /** * Disconnects from the database server * @@ -242,7 +363,7 @@ class DB_msql extends DB_common } // }}} - // {{{ simpleQuery() + // {{{ freeResult() /** * Sends a query to the database server @@ -272,9 +393,8 @@ class DB_msql extends DB_common } } - // }}} - // {{{ nextResult() + // {{{ numCols() /** * Move the internal msql result pointer to the next available result @@ -291,7 +411,7 @@ class DB_msql extends DB_common } // }}} - // {{{ fetchInto() + // {{{ numRows() /** * Places a row from the result set into the given array @@ -307,10 +427,10 @@ class DB_msql extends DB_common * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds * those versions. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -345,7 +465,7 @@ class DB_msql extends DB_common } // }}} - // {{{ freeResult() + // {{{ affected() /** * Deletes the result set and frees the memory occupied by the result set @@ -354,7 +474,7 @@ class DB_msql extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -366,7 +486,7 @@ class DB_msql extends DB_common } // }}} - // {{{ numCols() + // {{{ nextId() /** * Gets the number of columns in a result set @@ -375,9 +495,9 @@ class DB_msql extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -391,7 +511,7 @@ class DB_msql extends DB_common } // }}} - // {{{ numRows() + // {{{ createSequence() /** * Gets the number of rows in a result set @@ -400,9 +520,9 @@ class DB_msql extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object * * @see DB_result::numRows() */ @@ -416,7 +536,7 @@ class DB_msql extends DB_common } // }}} - // {{{ affected() + // {{{ dropSequence() /** * Determines the number of rows affected by a data maniuplation query @@ -434,16 +554,16 @@ class DB_msql extends DB_common } // }}} - // {{{ nextId() + // {{{ quoteIdentifier() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -479,7 +599,7 @@ class DB_msql extends DB_common } // }}} - // {{{ createSequence() + // {{{ quoteFloat() /** * Creates a new sequence @@ -487,7 +607,7 @@ class DB_msql extends DB_common * Also creates a new table to associate the sequence with. Uses * a separate table to ensure portability with other drivers. * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -498,7 +618,7 @@ class DB_msql extends DB_common { $seqname = $this->getSequenceName($seq_name); $res = $this->query('CREATE TABLE ' . $seqname - . ' (id INTEGER NOT NULL)'); + . ' (id INTEGER NOT NULL)'); if (DB::isError($res)) { return $res; } @@ -507,12 +627,12 @@ class DB_msql extends DB_common } // }}} - // {{{ dropSequence() + // {{{ escapeSimple() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -525,12 +645,12 @@ class DB_msql extends DB_common } // }}} - // {{{ quoteIdentifier() + // {{{ msqlRaiseError() /** * mSQL does not support delimited identifiers * - * @param string $str the identifier name to be quoted + * @param string $str the identifier name to be quoted * * @return object a DB_Error object * @@ -543,7 +663,7 @@ class DB_msql extends DB_common } // }}} - // {{{ quoteFloat() + // {{{ errorNative() /** * Formats a float value for use within a query in a locale-independent @@ -558,14 +678,14 @@ class DB_msql extends DB_common { return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); } - + // }}} - // {{{ escapeSimple() + // {{{ errorCode() /** * Escapes a string according to the current DBMS's standards * - * @param string $str the string to be escaped + * @param string $str the string to be escaped * * @return string the escaped string * @@ -577,140 +697,20 @@ class DB_msql extends DB_common return addslashes($str); } - // }}} - // {{{ msqlRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_msql::errorNative(), DB_msql::errorCode() - */ - public function msqlRaiseError($errno = null) - { - $native = $this->errorNative(); - if ($errno === null) { - $errno = $this->errorCode($native); - } - return $this->raiseError($errno, null, null, null, $native); - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error message produced by the last query - * - * @return string the DBMS' error message - */ - public function errorNative() - { - return @msql_error(); - } - - // }}} - // {{{ errorCode() - - /** - * Determines PEAR::DB error code from the database's text error message - * - * @param string $errormsg the error message returned from the database - * - * @return integer the error number from a DB_ERROR* constant - */ - public function errorCode($errormsg) - { - static $error_regexps; - - // PHP 5.2+ prepends the function name to $php_errormsg, so we need - // this hack to work around it, per bug #9599. - $errormsg = preg_replace('/^msql[a-z_]+\(\): /', '', $errormsg); - - if (!isset($error_regexps)) { - $error_regexps = array( - '/^Access to database denied/i' - => DB_ERROR_ACCESS_VIOLATION, - '/^Bad index name/i' - => DB_ERROR_ALREADY_EXISTS, - '/^Bad order field/i' - => DB_ERROR_SYNTAX, - '/^Bad type for comparison/i' - => DB_ERROR_SYNTAX, - '/^Can\'t perform LIKE on/i' - => DB_ERROR_SYNTAX, - '/^Can\'t use TEXT fields in LIKE comparison/i' - => DB_ERROR_SYNTAX, - '/^Couldn\'t create temporary table/i' - => DB_ERROR_CANNOT_CREATE, - '/^Error creating table file/i' - => DB_ERROR_CANNOT_CREATE, - '/^Field .* cannot be null$/i' - => DB_ERROR_CONSTRAINT_NOT_NULL, - '/^Index (field|condition) .* cannot be null$/i' - => DB_ERROR_SYNTAX, - '/^Invalid date format/i' - => DB_ERROR_INVALID_DATE, - '/^Invalid time format/i' - => DB_ERROR_INVALID, - '/^Literal value for .* is wrong type$/i' - => DB_ERROR_INVALID_NUMBER, - '/^No Database Selected/i' - => DB_ERROR_NODBSELECTED, - '/^No value specified for field/i' - => DB_ERROR_VALUE_COUNT_ON_ROW, - '/^Non unique value for unique index/i' - => DB_ERROR_CONSTRAINT, - '/^Out of memory for temporary table/i' - => DB_ERROR_CANNOT_CREATE, - '/^Permission denied/i' - => DB_ERROR_ACCESS_VIOLATION, - '/^Reference to un-selected table/i' - => DB_ERROR_SYNTAX, - '/^syntax error/i' - => DB_ERROR_SYNTAX, - '/^Table .* exists$/i' - => DB_ERROR_ALREADY_EXISTS, - '/^Unknown database/i' - => DB_ERROR_NOSUCHDB, - '/^Unknown field/i' - => DB_ERROR_NOSUCHFIELD, - '/^Unknown (index|system variable)/i' - => DB_ERROR_NOT_FOUND, - '/^Unknown table/i' - => DB_ERROR_NOSUCHTABLE, - '/^Unqualified field/i' - => DB_ERROR_SYNTAX, - ); - } - - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - return DB_ERROR; - } - // }}} // {{{ tableInfo() /** * Returns information about a table or a result set * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::setOption() @@ -755,7 +755,7 @@ class DB_msql extends DB_common } $count = @msql_num_fields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -775,9 +775,9 @@ class DB_msql extends DB_common $res[$i] = array( 'table' => $case_func($tmp->table), - 'name' => $case_func($tmp->name), - 'type' => $tmp->type, - 'len' => msql_field_len($id, $i), + 'name' => $case_func($tmp->name), + 'type' => $tmp->type, + 'len' => msql_field_len($id, $i), 'flags' => $flags, ); @@ -802,9 +802,9 @@ class DB_msql extends DB_common /** * Obtain a list of a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * - * @return array the array containing the list of objects requested + * @return array|object * * @access protected * @see DB_common::getListOf() diff --git a/extlib/DB/mssql.php b/extlib/DB/mssql.php index 94a5a83af6..4a764100ef 100644 --- a/extlib/DB/mssql.php +++ b/extlib/DB/mssql.php @@ -27,7 +27,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's mssql extension @@ -82,13 +83,13 @@ class DB_mssql extends DB_common * @var array */ public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, ); /** @@ -97,39 +98,39 @@ class DB_mssql extends DB_common */ // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX public $errorcode_map = array( - 102 => DB_ERROR_SYNTAX, - 110 => DB_ERROR_VALUE_COUNT_ON_ROW, - 155 => DB_ERROR_NOSUCHFIELD, - 156 => DB_ERROR_SYNTAX, - 170 => DB_ERROR_SYNTAX, - 207 => DB_ERROR_NOSUCHFIELD, - 208 => DB_ERROR_NOSUCHTABLE, - 245 => DB_ERROR_INVALID_NUMBER, - 319 => DB_ERROR_SYNTAX, - 321 => DB_ERROR_NOSUCHFIELD, - 325 => DB_ERROR_SYNTAX, - 336 => DB_ERROR_SYNTAX, - 515 => DB_ERROR_CONSTRAINT_NOT_NULL, - 547 => DB_ERROR_CONSTRAINT, - 1018 => DB_ERROR_SYNTAX, - 1035 => DB_ERROR_SYNTAX, - 1913 => DB_ERROR_ALREADY_EXISTS, - 2209 => DB_ERROR_SYNTAX, - 2223 => DB_ERROR_SYNTAX, - 2248 => DB_ERROR_SYNTAX, - 2256 => DB_ERROR_SYNTAX, - 2257 => DB_ERROR_SYNTAX, - 2627 => DB_ERROR_CONSTRAINT, - 2714 => DB_ERROR_ALREADY_EXISTS, - 3607 => DB_ERROR_DIVZERO, - 3701 => DB_ERROR_NOSUCHTABLE, - 7630 => DB_ERROR_SYNTAX, - 8134 => DB_ERROR_DIVZERO, - 9303 => DB_ERROR_SYNTAX, - 9317 => DB_ERROR_SYNTAX, - 9318 => DB_ERROR_SYNTAX, - 9331 => DB_ERROR_SYNTAX, - 9332 => DB_ERROR_SYNTAX, + 102 => DB_ERROR_SYNTAX, + 110 => DB_ERROR_VALUE_COUNT_ON_ROW, + 155 => DB_ERROR_NOSUCHFIELD, + 156 => DB_ERROR_SYNTAX, + 170 => DB_ERROR_SYNTAX, + 207 => DB_ERROR_NOSUCHFIELD, + 208 => DB_ERROR_NOSUCHTABLE, + 245 => DB_ERROR_INVALID_NUMBER, + 319 => DB_ERROR_SYNTAX, + 321 => DB_ERROR_NOSUCHFIELD, + 325 => DB_ERROR_SYNTAX, + 336 => DB_ERROR_SYNTAX, + 515 => DB_ERROR_CONSTRAINT_NOT_NULL, + 547 => DB_ERROR_CONSTRAINT, + 1018 => DB_ERROR_SYNTAX, + 1035 => DB_ERROR_SYNTAX, + 1913 => DB_ERROR_ALREADY_EXISTS, + 2209 => DB_ERROR_SYNTAX, + 2223 => DB_ERROR_SYNTAX, + 2248 => DB_ERROR_SYNTAX, + 2256 => DB_ERROR_SYNTAX, + 2257 => DB_ERROR_SYNTAX, + 2627 => DB_ERROR_CONSTRAINT, + 2714 => DB_ERROR_ALREADY_EXISTS, + 3607 => DB_ERROR_DIVZERO, + 3701 => DB_ERROR_NOSUCHTABLE, + 7630 => DB_ERROR_SYNTAX, + 8134 => DB_ERROR_DIVZERO, + 9303 => DB_ERROR_SYNTAX, + 9317 => DB_ERROR_SYNTAX, + 9318 => DB_ERROR_SYNTAX, + 9331 => DB_ERROR_SYNTAX, + 9332 => DB_ERROR_SYNTAX, 15253 => DB_ERROR_SYNTAX, ); @@ -196,10 +197,10 @@ class DB_mssql extends DB_common * * Don't call this method directly. Use DB::connect() instead. * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -220,7 +221,7 @@ class DB_mssql extends DB_common ); if ($dsn['port']) { $params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':') - . $dsn['port']; + . $dsn['port']; } $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect'; @@ -307,6 +308,80 @@ class DB_mssql extends DB_common // }}} // {{{ nextResult() + /** + * Produces a DB_Error object regarding the current problem + * + * @param null $code + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mssql::errorNative(), DB_mssql::errorCode() + */ + public function mssqlRaiseError($code = null) + { + $message = @mssql_get_last_message(); + if (!$code) { + $code = $this->errorNative(); + } + return $this->raiseError( + $this->errorCode($code, $message), + null, + null, + null, + "$code - $message" + ); + } + + // }}} + // {{{ fetchInto() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + public function errorNative() + { + $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection); + if (!$res) { + return DB_ERROR; + } + $row = @mssql_fetch_row($res); + return $row[0]; + } + + // }}} + // {{{ freeResult() + + /** + * Determines PEAR::DB error code from mssql's native codes. + * + * If $nativecode isn't known yet, it will be looked up. + * + * @param mixed $nativecode mssql error code, if known + * @param string $msg + * @return integer an error number from a DB error constant + * @see errorNative() + */ + public function errorCode($nativecode = null, $msg = '') + { + if (!$nativecode) { + $nativecode = $this->errorNative(); + } + if (isset($this->errorcode_map[$nativecode])) { + if ($nativecode == 3701 + && preg_match('/Cannot drop the index/i', $msg)) { + return DB_ERROR_NOT_FOUND; + } + return $this->errorcode_map[$nativecode]; + } else { + return DB_ERROR; + } + } + + // }}} + // {{{ numCols() + /** * Move the internal mssql result pointer to the next available result * @@ -322,7 +397,7 @@ class DB_mssql extends DB_common } // }}} - // {{{ fetchInto() + // {{{ numRows() /** * Places a row from the result set into the given array @@ -334,10 +409,10 @@ class DB_mssql extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -372,7 +447,7 @@ class DB_mssql extends DB_common } // }}} - // {{{ freeResult() + // {{{ autoCommit() /** * Deletes the result set and frees the memory occupied by the result set @@ -381,7 +456,7 @@ class DB_mssql extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -393,7 +468,7 @@ class DB_mssql extends DB_common } // }}} - // {{{ numCols() + // {{{ commit() /** * Gets the number of columns in a result set @@ -402,9 +477,9 @@ class DB_mssql extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -418,7 +493,7 @@ class DB_mssql extends DB_common } // }}} - // {{{ numRows() + // {{{ rollback() /** * Gets the number of rows in a result set @@ -427,9 +502,9 @@ class DB_mssql extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object * * @see DB_result::numRows() */ @@ -443,12 +518,12 @@ class DB_mssql extends DB_common } // }}} - // {{{ autoCommit() + // {{{ affectedRows() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * * @return int DB_OK on success. A DB_Error object if the driver * doesn't support auto-committing transactions. @@ -462,12 +537,12 @@ class DB_mssql extends DB_common } // }}} - // {{{ commit() + // {{{ nextId() /** * Commits the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function commit() { @@ -484,13 +559,10 @@ class DB_mssql extends DB_common return DB_OK; } - // }}} - // {{{ rollback() - /** * Reverts the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function rollback() { @@ -508,14 +580,14 @@ class DB_mssql extends DB_common } // }}} - // {{{ affectedRows() + // {{{ dropSequence() /** * Determines the number of rows affected by a data maniuplation query * * 0 is returned for queries that don't manipulate data. * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object */ public function affectedRows() { @@ -538,16 +610,16 @@ class DB_mssql extends DB_common } // }}} - // {{{ nextId() + // {{{ escapeSimple() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -593,10 +665,13 @@ class DB_mssql extends DB_common return $result[0]; } + // }}} + // {{{ quoteIdentifier() + /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -606,18 +681,18 @@ class DB_mssql extends DB_common public function createSequence($seq_name) { return $this->query('CREATE TABLE ' - . $this->getSequenceName($seq_name) - . ' ([id] [int] IDENTITY (1, 1) NOT NULL,' - . ' [vapor] [int] NULL)'); + . $this->getSequenceName($seq_name) + . ' ([id] [int] IDENTITY (1, 1) NOT NULL,' + . ' [vapor] [int] NULL)'); } // }}} - // {{{ dropSequence() + // {{{ mssqlRaiseError() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -630,12 +705,12 @@ class DB_mssql extends DB_common } // }}} - // {{{ escapeSimple() + // {{{ errorNative() /** * Escapes a string in a manner suitable for SQL Server. * - * @param string $str the string to be escaped + * @param string $str the string to be escaped * @return string the escaped string * * @see DB_common::quoteSmart() @@ -651,12 +726,12 @@ class DB_mssql extends DB_common } // }}} - // {{{ quoteIdentifier() + // {{{ errorCode() /** * Quotes a string so it can be safely used as a table or column name * - * @param string $str identifier name to be quoted + * @param string $str identifier name to be quoted * * @return string quoted identifier string * @@ -668,82 +743,6 @@ class DB_mssql extends DB_common return '[' . str_replace(']', ']]', $str) . ']'; } - // }}} - // {{{ mssqlRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_mssql::errorNative(), DB_mssql::errorCode() - */ - public function mssqlRaiseError($code = null) - { - $message = @mssql_get_last_message(); - if (!$code) { - $code = $this->errorNative(); - } - return $this->raiseError( - $this->errorCode($code, $message), - null, - null, - null, - "$code - $message" - ); - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return int the DBMS' error code - */ - public function errorNative() - { - $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection); - if (!$res) { - return DB_ERROR; - } - $row = @mssql_fetch_row($res); - return $row[0]; - } - - // }}} - // {{{ errorCode() - - /** - * Determines PEAR::DB error code from mssql's native codes. - * - * If $nativecode isn't known yet, it will be looked up. - * - * @param mixed $nativecode mssql error code, if known - * @return integer an error number from a DB error constant - * @see errorNative() - */ - public function errorCode($nativecode = null, $msg = '') - { - if (!$nativecode) { - $nativecode = $this->errorNative(); - } - if (isset($this->errorcode_map[$nativecode])) { - if ($nativecode == 3701 - && preg_match('/Cannot drop the index/i', $msg)) { - return DB_ERROR_NOT_FOUND; - } - return $this->errorcode_map[$nativecode]; - } else { - return DB_ERROR; - } - } - // }}} // {{{ tableInfo() @@ -753,14 +752,14 @@ class DB_mssql extends DB_common * NOTE: only supports 'table' and 'flags' if $result * is a table name. * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -808,7 +807,7 @@ class DB_mssql extends DB_common } $count = @mssql_num_fields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -829,9 +828,9 @@ class DB_mssql extends DB_common $res[$i] = array( 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func(@mssql_field_name($id, $i)), - 'type' => @mssql_field_type($id, $i), - 'len' => @mssql_field_length($id, $i), + 'name' => $case_func(@mssql_field_name($id, $i)), + 'type' => @mssql_field_type($id, $i), + 'len' => @mssql_field_length($id, $i), 'flags' => $flags, ); if ($mode & DB_TABLEINFO_ORDER) { @@ -864,10 +863,10 @@ class DB_mssql extends DB_common * not useful at all - is the behaviour of mysql_field_flags that primary * keys are alway unique? is the interpretation of multiple_key correct? * - * @param string $table the table name - * @param string $column the field name + * @param string $table the table name + * @param string $column the field name * - * @return string the flags + * @return array|string * * @access private * @author Joern Barthel @@ -928,7 +927,7 @@ class DB_mssql extends DB_common } if (array_key_exists($column, $flags)) { - return(implode(' ', $flags[$column])); + return (implode(' ', $flags[$column])); } return ''; } @@ -940,8 +939,8 @@ class DB_mssql extends DB_common * Adds a string to the flags array if the flag is not yet in there * - if there is no flag present the array is created * - * @param array &$array the reference to the flag-array - * @param string $value the flag value + * @param array &$array the reference to the flag-array + * @param string $value the flag value * * @return void * @@ -963,7 +962,7 @@ class DB_mssql extends DB_common /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested @@ -976,7 +975,7 @@ class DB_mssql extends DB_common switch ($type) { case 'tables': return "SELECT name FROM sysobjects WHERE type = 'U'" - . ' ORDER BY name'; + . ' ORDER BY name'; case 'views': return "SELECT name FROM sysobjects WHERE type = 'V'"; default: diff --git a/extlib/DB/mysql.php b/extlib/DB/mysql.php index a541cb7a14..a992e28ade 100644 --- a/extlib/DB/mysql.php +++ b/extlib/DB/mysql.php @@ -27,7 +27,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's mysql extension @@ -74,13 +75,13 @@ class DB_mysql extends DB_common * @var array */ public $features = array( - 'limit' => 'alter', - 'new_link' => '4.2.0', - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, + 'limit' => 'alter', + 'new_link' => '4.2.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, ); /** @@ -188,10 +189,10 @@ class DB_mysql extends DB_common * Only used if PHP is at version 4.3.0 or greater. * Available since PEAR DB 1.7.0. * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -209,7 +210,7 @@ class DB_mysql extends DB_common $params[0] = ':' . $dsn['socket']; } else { $params[0] = $dsn['hostspec'] ? $dsn['hostspec'] - : 'localhost'; + : 'localhost'; if ($dsn['port']) { $params[0] .= ':' . $dsn['port']; } @@ -227,7 +228,7 @@ class DB_mysql extends DB_common } if (version_compare(phpversion(), '4.3.0', '>=')) { $params[] = isset($dsn['client_flags']) - ? $dsn['client_flags'] : null; + ? $dsn['client_flags'] : null; } $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; @@ -281,6 +282,46 @@ class DB_mysql extends DB_common // }}} // {{{ disconnect() + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysql::errorNative(), DB_common::errorCode() + */ + public function mysqlRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysql_errno($this->connection)); + } + return $this->raiseError( + $errno, + null, + null, + null, + @mysql_errno($this->connection) . ' ** ' . + @mysql_error($this->connection) + ); + } + + // }}} + // {{{ simpleQuery() + /** * Disconnects from the database server * @@ -294,7 +335,7 @@ class DB_mysql extends DB_common } // }}} - // {{{ simpleQuery() + // {{{ nextResult() /** * Sends a query to the database server @@ -344,7 +385,40 @@ class DB_mysql extends DB_common } // }}} - // {{{ nextResult() + // {{{ fetchInto() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + public function modifyQuery($query) + { + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace( + '/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', + $query + ); + } + } + return $query; + } + + // }}} + // {{{ freeResult() /** * Move the internal mysql result pointer to the next available result @@ -361,7 +435,7 @@ class DB_mysql extends DB_common } // }}} - // {{{ fetchInto() + // {{{ numCols() /** * Places a row from the result set into the given array @@ -373,10 +447,10 @@ class DB_mysql extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -416,7 +490,7 @@ class DB_mysql extends DB_common } // }}} - // {{{ freeResult() + // {{{ numRows() /** * Deletes the result set and frees the memory occupied by the result set @@ -425,7 +499,7 @@ class DB_mysql extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -437,7 +511,7 @@ class DB_mysql extends DB_common } // }}} - // {{{ numCols() + // {{{ autoCommit() /** * Gets the number of columns in a result set @@ -446,9 +520,9 @@ class DB_mysql extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -462,7 +536,7 @@ class DB_mysql extends DB_common } // }}} - // {{{ numRows() + // {{{ commit() /** * Gets the number of rows in a result set @@ -471,9 +545,9 @@ class DB_mysql extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object * * @see DB_result::numRows() */ @@ -487,12 +561,12 @@ class DB_mysql extends DB_common } // }}} - // {{{ autoCommit() + // {{{ rollback() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * * @return int DB_OK on success. A DB_Error object if the driver * doesn't support auto-committing transactions. @@ -506,12 +580,12 @@ class DB_mysql extends DB_common } // }}} - // {{{ commit() + // {{{ affectedRows() /** * Commits the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function commit() { @@ -532,12 +606,12 @@ class DB_mysql extends DB_common } // }}} - // {{{ rollback() + // {{{ nextId() /** * Reverts the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function rollback() { @@ -558,7 +632,7 @@ class DB_mysql extends DB_common } // }}} - // {{{ affectedRows() + // {{{ createSequence() /** * Determines the number of rows affected by a data maniuplation query @@ -577,16 +651,16 @@ class DB_mysql extends DB_common } // }}} - // {{{ nextId() + // {{{ dropSequence() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -598,8 +672,8 @@ class DB_mysql extends DB_common do { $repeat = 0; $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("UPDATE ${seqname} ". - 'SET id=LAST_INSERT_ID(id+1)'); + $result = $this->query("UPDATE ${seqname} " . + 'SET id=LAST_INSERT_ID(id+1)'); $this->popErrorHandling(); if ($result === DB_OK) { // COMMON CASE @@ -627,7 +701,7 @@ class DB_mysql extends DB_common // Release the lock $result = $this->getOne('SELECT RELEASE_LOCK(' - . "'${seqname}_lock')"); + . "'${seqname}_lock')"); if (DB::isError($result)) { return $this->raiseError($result); } @@ -643,7 +717,7 @@ class DB_mysql extends DB_common $repeat = 1; } } elseif (DB::isError($result) && - $result->getCode() == DB_ERROR_ALREADY_EXISTS) { + $result->getCode() == DB_ERROR_ALREADY_EXISTS) { // BACKWARDS COMPAT // see _BCsequence() comment $result = $this->_BCsequence($seqname); @@ -658,12 +732,12 @@ class DB_mysql extends DB_common } // }}} - // {{{ createSequence() + // {{{ _BCsequence() /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -674,8 +748,8 @@ class DB_mysql extends DB_common { $seqname = $this->getSequenceName($seq_name); $res = $this->query('CREATE TABLE ' . $seqname - . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' - . ' PRIMARY KEY(id))'); + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); if (DB::isError($res)) { return $res; } @@ -689,33 +763,15 @@ class DB_mysql extends DB_common } // }}} - // {{{ dropSequence() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_mysql::nextID(), DB_mysql::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ _BCsequence() + // {{{ quoteIdentifier() /** * Backwards compatibility with old sequence emulation implementation * (clean up the dupes) * - * @param string $seqname the sequence name to clean up + * @param string $seqname the sequence name to clean up * - * @return bool true on success. A DB_Error object on failure. + * @return bool|object * * @access private */ @@ -742,7 +798,7 @@ class DB_mysql extends DB_common // We should probably do something if $highest_id isn't // numeric, but I'm at a loss as how to handle that... $result = $this->query('DELETE FROM ' . $seqname - . " WHERE id <> $highest_id"); + . " WHERE id <> $highest_id"); if (DB::isError($result)) { return $result; } @@ -758,7 +814,25 @@ class DB_mysql extends DB_common } // }}} - // {{{ quoteIdentifier() + // {{{ escapeSimple() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + public function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ modifyQuery() /** * Quotes a string so it can be safely used as a table or column name @@ -767,7 +841,7 @@ class DB_mysql extends DB_common * WARNING: Older versions of MySQL can't handle the backtick * character (`) in table or column names. * - * @param string $str identifier name to be quoted + * @param string $str identifier name to be quoted * * @return string quoted identifier string * @@ -780,12 +854,12 @@ class DB_mysql extends DB_common } // }}} - // {{{ escapeSimple() + // {{{ modifyLimitQuery() /** * Escapes a string according to the current DBMS's standards * - * @param string $str the string to be escaped + * @param string $str the string to be escaped * * @return string the escaped string * @@ -802,48 +876,15 @@ class DB_mysql extends DB_common } // }}} - // {{{ modifyQuery() - - /** - * Changes a query string for various DBMS specific reasons - * - * This little hack lets you know how many rows were deleted - * when running a "DELETE FROM table" query. Only implemented - * if the DB_PORTABILITY_DELETE_COUNT portability option is on. - * - * @param string $query the query string to modify - * - * @return string the modified query string - * - * @access protected - * @see DB_common::setOption() - */ - public function modifyQuery($query) - { - if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { - // "DELETE FROM table" gives 0 affected rows in MySQL. - // This little hack lets you know how many rows were deleted. - if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { - $query = preg_replace( - '/^\s*DELETE\s+FROM\s+(\S+)\s*$/', - 'DELETE FROM \1 WHERE 1=1', - $query - ); - } - } - return $query; - } - - // }}} - // {{{ modifyLimitQuery() + // {{{ mysqlRaiseError() /** * Adds LIMIT clauses to a query string according to current DBMS standards * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -862,46 +903,6 @@ class DB_mysql extends DB_common } } - // }}} - // {{{ mysqlRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_mysql::errorNative(), DB_common::errorCode() - */ - public function mysqlRaiseError($errno = null) - { - if ($errno === null) { - if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { - $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; - $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; - $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; - } else { - // Doing this in case mode changes during runtime. - $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; - $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; - $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; - } - $errno = $this->errorCode(mysql_errno($this->connection)); - } - return $this->raiseError( - $errno, - null, - null, - null, - @mysql_errno($this->connection) . ' ** ' . - @mysql_error($this->connection) - ); - } - // }}} // {{{ errorNative() @@ -921,14 +922,14 @@ class DB_mysql extends DB_common /** * Returns information about a table or a result set * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -942,7 +943,7 @@ class DB_mysql extends DB_common return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); } } - + /* * Probably received a table name. * Create a result resource identifier. @@ -980,7 +981,7 @@ class DB_mysql extends DB_common } $count = @mysql_num_fields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -989,9 +990,9 @@ class DB_mysql extends DB_common for ($i = 0; $i < $count; $i++) { $res[$i] = array( 'table' => $case_func(@mysql_field_table($id, $i)), - 'name' => $case_func(@mysql_field_name($id, $i)), - 'type' => @mysql_field_type($id, $i), - 'len' => @mysql_field_len($id, $i), + 'name' => $case_func(@mysql_field_name($id, $i)), + 'type' => @mysql_field_type($id, $i), + 'len' => @mysql_field_len($id, $i), 'flags' => @mysql_field_flags($id, $i), ); if ($mode & DB_TABLEINFO_ORDER) { @@ -1015,7 +1016,7 @@ class DB_mysql extends DB_common /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested diff --git a/extlib/DB/mysqli.php b/extlib/DB/mysqli.php index 42ba23e4f9..cc59c11570 100644 --- a/extlib/DB/mysqli.php +++ b/extlib/DB/mysqli.php @@ -26,7 +26,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's mysqli extension @@ -77,13 +78,13 @@ class DB_mysqli extends DB_common * @var array */ public $features = array( - 'limit' => 'alter', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => false, - 'prepare' => false, - 'ssl' => true, - 'transactions' => true, + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => false, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, ); /** @@ -167,19 +168,19 @@ class DB_mysqli extends DB_common * @since Property available since Release 1.6.5 */ public $mysqli_flags = array( - MYSQLI_NOT_NULL_FLAG => 'not_null', - MYSQLI_PRI_KEY_FLAG => 'primary_key', - MYSQLI_UNIQUE_KEY_FLAG => 'unique_key', - MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key', - MYSQLI_BLOB_FLAG => 'blob', - MYSQLI_UNSIGNED_FLAG => 'unsigned', - MYSQLI_ZEROFILL_FLAG => 'zerofill', - MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment', - MYSQLI_TIMESTAMP_FLAG => 'timestamp', - MYSQLI_SET_FLAG => 'set', + MYSQLI_NOT_NULL_FLAG => 'not_null', + MYSQLI_PRI_KEY_FLAG => 'primary_key', + MYSQLI_UNIQUE_KEY_FLAG => 'unique_key', + MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key', + MYSQLI_BLOB_FLAG => 'blob', + MYSQLI_UNSIGNED_FLAG => 'unsigned', + MYSQLI_ZEROFILL_FLAG => 'zerofill', + MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment', + MYSQLI_TIMESTAMP_FLAG => 'timestamp', + MYSQLI_SET_FLAG => 'set', // MYSQLI_NUM_FLAG => 'numeric', // unnecessary // MYSQLI_PART_KEY_FLAG => 'multiple_key', // duplicatvie - MYSQLI_GROUP_FLAG => 'group_by' + MYSQLI_GROUP_FLAG => 'group_by' ); /** @@ -189,34 +190,34 @@ class DB_mysqli extends DB_common * @since Property available since Release 1.6.5 */ public $mysqli_types = array( - MYSQLI_TYPE_DECIMAL => 'decimal', - MYSQLI_TYPE_TINY => 'tinyint', - MYSQLI_TYPE_SHORT => 'int', - MYSQLI_TYPE_LONG => 'int', - MYSQLI_TYPE_FLOAT => 'float', - MYSQLI_TYPE_DOUBLE => 'double', + MYSQLI_TYPE_DECIMAL => 'decimal', + MYSQLI_TYPE_TINY => 'tinyint', + MYSQLI_TYPE_SHORT => 'int', + MYSQLI_TYPE_LONG => 'int', + MYSQLI_TYPE_FLOAT => 'float', + MYSQLI_TYPE_DOUBLE => 'double', // MYSQLI_TYPE_NULL => 'DEFAULT NULL', // let flags handle it - MYSQLI_TYPE_TIMESTAMP => 'timestamp', - MYSQLI_TYPE_LONGLONG => 'bigint', - MYSQLI_TYPE_INT24 => 'mediumint', - MYSQLI_TYPE_DATE => 'date', - MYSQLI_TYPE_TIME => 'time', - MYSQLI_TYPE_DATETIME => 'datetime', - MYSQLI_TYPE_YEAR => 'year', - MYSQLI_TYPE_NEWDATE => 'date', - MYSQLI_TYPE_ENUM => 'enum', - MYSQLI_TYPE_SET => 'set', - MYSQLI_TYPE_TINY_BLOB => 'tinyblob', + MYSQLI_TYPE_TIMESTAMP => 'timestamp', + MYSQLI_TYPE_LONGLONG => 'bigint', + MYSQLI_TYPE_INT24 => 'mediumint', + MYSQLI_TYPE_DATE => 'date', + MYSQLI_TYPE_TIME => 'time', + MYSQLI_TYPE_DATETIME => 'datetime', + MYSQLI_TYPE_YEAR => 'year', + MYSQLI_TYPE_NEWDATE => 'date', + MYSQLI_TYPE_ENUM => 'enum', + MYSQLI_TYPE_SET => 'set', + MYSQLI_TYPE_TINY_BLOB => 'tinyblob', MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob', - MYSQLI_TYPE_LONG_BLOB => 'longblob', - MYSQLI_TYPE_BLOB => 'blob', - MYSQLI_TYPE_VAR_STRING => 'varchar', - MYSQLI_TYPE_STRING => 'char', - MYSQLI_TYPE_GEOMETRY => 'geometry', + MYSQLI_TYPE_LONG_BLOB => 'longblob', + MYSQLI_TYPE_BLOB => 'blob', + MYSQLI_TYPE_VAR_STRING => 'varchar', + MYSQLI_TYPE_STRING => 'char', + MYSQLI_TYPE_GEOMETRY => 'geometry', /* These constants are conditionally compiled in ext/mysqli, so we'll * define them by number rather than constant. */ - 16 => 'bit', - 246 => 'decimal', + 16 => 'bit', + 246 => 'decimal', ); @@ -272,15 +273,15 @@ class DB_mysqli extends DB_common * ); * * $db = DB::connect($dsn, $options); - * if (PEAR::isError($db)) { + * if ((new PEAR)->isError($db)) { * die($db->getMessage()); * } * * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -297,13 +298,13 @@ class DB_mysqli extends DB_common @ini_set('track_errors', 1); $php_errormsg = ''; - if (((int) $this->getOption('ssl')) === 1) { + if (((int)$this->getOption('ssl')) === 1) { $init = mysqli_init(); mysqli_ssl_set( $init, - empty($dsn['key']) ? null : $dsn['key'], - empty($dsn['cert']) ? null : $dsn['cert'], - empty($dsn['ca']) ? null : $dsn['ca'], + empty($dsn['key']) ? null : $dsn['key'], + empty($dsn['cert']) ? null : $dsn['cert'], + empty($dsn['ca']) ? null : $dsn['ca'], empty($dsn['capath']) ? null : $dsn['capath'], empty($dsn['cipher']) ? null : $dsn['cipher'] ); @@ -418,6 +419,46 @@ class DB_mysqli extends DB_common // }}} // {{{ nextResult() + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysqli::errorNative(), DB_common::errorCode() + */ + public function mysqliRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysqli_errno($this->connection)); + } + return $this->raiseError( + $errno, + null, + null, + null, + @mysqli_errno($this->connection) . ' ** ' . + @mysqli_error($this->connection) + ); + } + + // }}} + // {{{ fetchInto() + /** * Move the internal mysql result pointer to the next available result. * @@ -433,7 +474,7 @@ class DB_mysqli extends DB_common } // }}} - // {{{ fetchInto() + // {{{ freeResult() /** * Places a row from the result set into the given array @@ -445,10 +486,10 @@ class DB_mysqli extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -488,7 +529,7 @@ class DB_mysqli extends DB_common } // }}} - // {{{ freeResult() + // {{{ numCols() /** * Deletes the result set and frees the memory occupied by the result set @@ -497,7 +538,7 @@ class DB_mysqli extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -505,7 +546,7 @@ class DB_mysqli extends DB_common */ public function freeResult($result) { - if (! $result instanceof mysqli_result) { + if (!$result instanceof mysqli_result) { return false; } mysqli_free_result($result); @@ -513,7 +554,7 @@ class DB_mysqli extends DB_common } // }}} - // {{{ numCols() + // {{{ numRows() /** * Gets the number of columns in a result set @@ -522,9 +563,9 @@ class DB_mysqli extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -538,7 +579,7 @@ class DB_mysqli extends DB_common } // }}} - // {{{ numRows() + // {{{ autoCommit() /** * Gets the number of rows in a result set @@ -547,9 +588,9 @@ class DB_mysqli extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object * * @see DB_result::numRows() */ @@ -563,12 +604,12 @@ class DB_mysqli extends DB_common } // }}} - // {{{ autoCommit() + // {{{ commit() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * * @return int DB_OK on success. A DB_Error object if the driver * doesn't support auto-committing transactions. @@ -582,12 +623,12 @@ class DB_mysqli extends DB_common } // }}} - // {{{ commit() + // {{{ rollback() /** * Commits the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function commit() { @@ -608,12 +649,12 @@ class DB_mysqli extends DB_common } // }}} - // {{{ rollback() + // {{{ affectedRows() /** * Reverts the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function rollback() { @@ -634,7 +675,7 @@ class DB_mysqli extends DB_common } // }}} - // {{{ affectedRows() + // {{{ nextId() /** * Determines the number of rows affected by a data maniuplation query @@ -652,17 +693,14 @@ class DB_mysqli extends DB_common } } - // }}} - // {{{ nextId() - /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -675,7 +713,7 @@ class DB_mysqli extends DB_common $repeat = 0; $this->pushErrorHandling(PEAR_ERROR_RETURN); $result = $this->query('UPDATE ' . $seqname - . ' SET id = LAST_INSERT_ID(id + 1)'); + . ' SET id = LAST_INSERT_ID(id + 1)'); $this->popErrorHandling(); if ($result === DB_OK) { // COMMON CASE @@ -689,7 +727,7 @@ class DB_mysqli extends DB_common // so fill it and return 1 // Obtain a user-level lock $result = $this->getOne('SELECT GET_LOCK(' - . "'${seqname}_lock', 10)"); + . "'${seqname}_lock', 10)"); if (DB::isError($result)) { return $this->raiseError($result); } @@ -699,14 +737,14 @@ class DB_mysqli extends DB_common // add the default value $result = $this->query('REPLACE INTO ' . $seqname - . ' (id) VALUES (0)'); + . ' (id) VALUES (0)'); if (DB::isError($result)) { return $this->raiseError($result); } // Release the lock $result = $this->getOne('SELECT RELEASE_LOCK(' - . "'${seqname}_lock')"); + . "'${seqname}_lock')"); if (DB::isError($result)) { return $this->raiseError($result); } @@ -726,7 +764,7 @@ class DB_mysqli extends DB_common return 1; } } elseif (DB::isError($result) && - $result->getCode() == DB_ERROR_ALREADY_EXISTS) { + $result->getCode() == DB_ERROR_ALREADY_EXISTS) { // BACKWARDS COMPAT // see _BCsequence() comment $result = $this->_BCsequence($seqname); @@ -740,10 +778,13 @@ class DB_mysqli extends DB_common return $this->raiseError($result); } + // }}} + // {{{ dropSequence() + /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -754,8 +795,8 @@ class DB_mysqli extends DB_common { $seqname = $this->getSequenceName($seq_name); $res = $this->query('CREATE TABLE ' . $seqname - . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' - . ' PRIMARY KEY(id))'); + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); if (DB::isError($res)) { return $res; } @@ -763,24 +804,6 @@ class DB_mysqli extends DB_common return $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); } - // }}} - // {{{ dropSequence() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_mysql::nextID(), DB_mysql::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - // }}} // {{{ _BCsequence() @@ -788,9 +811,9 @@ class DB_mysqli extends DB_common * Backwards compatibility with old sequence emulation implementation * (clean up the dupes) * - * @param string $seqname the sequence name to clean up + * @param string $seqname the sequence name to clean up * - * @return bool true on success. A DB_Error object on failure. + * @return bool|object * * @access private */ @@ -818,7 +841,7 @@ class DB_mysqli extends DB_common // We should probably do something if $highest_id isn't // numeric, but I'm at a loss as how to handle that... $result = $this->query('DELETE FROM ' . $seqname - . " WHERE id <> $highest_id"); + . " WHERE id <> $highest_id"); if (DB::isError($result)) { return $result; } @@ -836,6 +859,24 @@ class DB_mysqli extends DB_common // }}} // {{{ quoteIdentifier() + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + public function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ escapeSimple() + /** * Quotes a string so it can be safely used as a table or column name * (WARNING: using names that require this is a REALLY BAD IDEA) @@ -843,7 +884,7 @@ class DB_mysqli extends DB_common * WARNING: Older versions of MySQL can't handle the backtick * character (`) in table or column names. * - * @param string $str identifier name to be quoted + * @param string $str identifier name to be quoted * * @return string quoted identifier string * @@ -856,12 +897,12 @@ class DB_mysqli extends DB_common } // }}} - // {{{ escapeSimple() + // {{{ modifyLimitQuery() /** * Escapes a string according to the current DBMS's standards * - * @param string $str the string to be escaped + * @param string $str the string to be escaped * * @return string the escaped string * @@ -874,15 +915,15 @@ class DB_mysqli extends DB_common } // }}} - // {{{ modifyLimitQuery() + // {{{ mysqliRaiseError() /** * Adds LIMIT clauses to a query string according to current DBMS standards * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -901,46 +942,6 @@ class DB_mysqli extends DB_common } } - // }}} - // {{{ mysqliRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_mysqli::errorNative(), DB_common::errorCode() - */ - public function mysqliRaiseError($errno = null) - { - if ($errno === null) { - if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { - $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; - $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; - $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; - } else { - // Doing this in case mode changes during runtime. - $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; - $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; - $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; - } - $errno = $this->errorCode(mysqli_errno($this->connection)); - } - return $this->raiseError( - $errno, - null, - null, - null, - @mysqli_errno($this->connection) . ' ** ' . - @mysqli_error($this->connection) - ); - } - // }}} // {{{ errorNative() @@ -960,14 +961,14 @@ class DB_mysqli extends DB_common /** * Returns information about a table or a result set * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::setOption() @@ -1019,7 +1020,7 @@ class DB_mysqli extends DB_common } $count = @mysqli_num_fields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -1041,16 +1042,16 @@ class DB_mysqli extends DB_common $res[$i] = array( 'table' => $case_func($tmp->table), - 'name' => $case_func($tmp->name), - 'type' => isset($this->mysqli_types[$tmp->type]) - ? $this->mysqli_types[$tmp->type] - : 'unknown', + 'name' => $case_func($tmp->name), + 'type' => isset($this->mysqli_types[$tmp->type]) + ? $this->mysqli_types[$tmp->type] + : 'unknown', // http://bugs.php.net/?id=36579 // Doc Bug #36579: mysqli_fetch_field length handling // https://bugs.php.net/bug.php?id=62426 // Bug #62426: mysqli_fetch_field_direct returns incorrect // length on UTF8 fields - 'len' => $tmp->length, + 'len' => $tmp->length, 'flags' => $flags, ); @@ -1075,7 +1076,7 @@ class DB_mysqli extends DB_common /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested @@ -1097,7 +1098,8 @@ class DB_mysqli extends DB_common } } - public function getVersion() { + public function getVersion() + { return mysqli_get_server_version($this->connection); } diff --git a/extlib/DB/oci8.php b/extlib/DB/oci8.php index a485ba2fed..8fba3cb0f2 100644 --- a/extlib/DB/oci8.php +++ b/extlib/DB/oci8.php @@ -27,7 +27,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's oci8 extension @@ -80,13 +81,13 @@ class DB_oci8 extends DB_common * @var array */ public $features = array( - 'limit' => 'alter', - 'new_link' => '5.0.0', - 'numrows' => 'subquery', - 'pconnect' => true, - 'prepare' => true, - 'ssl' => false, - 'transactions' => true, + 'limit' => 'alter', + 'new_link' => '5.0.0', + 'numrows' => 'subquery', + 'pconnect' => true, + 'prepare' => true, + 'ssl' => false, + 'transactions' => true, ); /** @@ -94,24 +95,24 @@ class DB_oci8 extends DB_common * @var array */ public $errorcode_map = array( - 1 => DB_ERROR_CONSTRAINT, - 900 => DB_ERROR_SYNTAX, - 904 => DB_ERROR_NOSUCHFIELD, - 913 => DB_ERROR_VALUE_COUNT_ON_ROW, - 921 => DB_ERROR_SYNTAX, - 923 => DB_ERROR_SYNTAX, - 942 => DB_ERROR_NOSUCHTABLE, - 955 => DB_ERROR_ALREADY_EXISTS, - 1400 => DB_ERROR_CONSTRAINT_NOT_NULL, - 1401 => DB_ERROR_INVALID, - 1407 => DB_ERROR_CONSTRAINT_NOT_NULL, - 1418 => DB_ERROR_NOT_FOUND, - 1476 => DB_ERROR_DIVZERO, - 1722 => DB_ERROR_INVALID_NUMBER, - 2289 => DB_ERROR_NOSUCHTABLE, - 2291 => DB_ERROR_CONSTRAINT, - 2292 => DB_ERROR_CONSTRAINT, - 2449 => DB_ERROR_CONSTRAINT, + 1 => DB_ERROR_CONSTRAINT, + 900 => DB_ERROR_SYNTAX, + 904 => DB_ERROR_NOSUCHFIELD, + 913 => DB_ERROR_VALUE_COUNT_ON_ROW, + 921 => DB_ERROR_SYNTAX, + 923 => DB_ERROR_SYNTAX, + 942 => DB_ERROR_NOSUCHTABLE, + 955 => DB_ERROR_ALREADY_EXISTS, + 1400 => DB_ERROR_CONSTRAINT_NOT_NULL, + 1401 => DB_ERROR_INVALID, + 1407 => DB_ERROR_CONSTRAINT_NOT_NULL, + 1418 => DB_ERROR_NOT_FOUND, + 1476 => DB_ERROR_DIVZERO, + 1722 => DB_ERROR_INVALID_NUMBER, + 2289 => DB_ERROR_NOSUCHTABLE, + 2291 => DB_ERROR_CONSTRAINT, + 2292 => DB_ERROR_CONSTRAINT, + 2449 => DB_ERROR_CONSTRAINT, 12899 => DB_ERROR_INVALID, ); @@ -208,10 +209,10 @@ class DB_oci8 extends DB_common * not portable to other DBMS's. * Available since PEAR DB 1.7.0. * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -237,10 +238,10 @@ class DB_oci8 extends DB_common $connect_function = 'oci_new_connect'; } else { $connect_function = $persistent ? 'oci_pconnect' - : 'oci_connect'; + : 'oci_connect'; } if (isset($this->dsn['port']) && $this->dsn['port']) { - $db = '//'.$db.':'.$this->dsn['port']; + $db = '//' . $db . ':' . $this->dsn['port']; } $char = empty($dsn['charset']) ? null : $dsn['charset']; @@ -355,6 +356,68 @@ class DB_oci8 extends DB_common // }}} // {{{ nextResult() + /** + * Changes a query string for various DBMS specific reasons + * + * "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + */ + public function modifyQuery($query) + { + if (preg_match('/^\s*SELECT/i', $query) && + !preg_match('/\sFROM\s/i', $query)) { + $query .= ' FROM dual'; + } + return $query; + } + + // }}} + // {{{ fetchInto() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_oci8::errorNative(), DB_oci8::errorCode() + */ + public function oci8RaiseError($errno = null) + { + if ($errno === null) { + $error = @OCIError($this->connection); + return $this->raiseError( + $this->errorCode($error['code']), + null, + null, + null, + $error['message'] + ); + } elseif (is_resource($errno)) { + $error = @OCIError($errno); + return $this->raiseError( + $this->errorCode($error['code']), + null, + null, + null, + $error['message'] + ); + } + return $this->raiseError($this->errorCode($errno)); + } + + // }}} + // {{{ freeResult() + /** * Move the internal oracle result pointer to the next available result * @@ -369,9 +432,6 @@ class DB_oci8 extends DB_common return false; } - // }}} - // {{{ fetchInto() - /** * Places a row from the result set into the given array * @@ -382,10 +442,10 @@ class DB_oci8 extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -398,13 +458,13 @@ class DB_oci8 extends DB_common return $this->raiseError(DB_ERROR_NOT_CAPABLE); } if ($fetchmode & DB_FETCHMODE_ASSOC) { - $moredata = @OCIFetchInto($result, $arr, OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS); + $moredata = @OCIFetchInto($result, $arr, OCI_ASSOC + OCI_RETURN_NULLS + OCI_RETURN_LOBS); if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $moredata) { $arr = array_change_key_case($arr, CASE_LOWER); } } else { - $moredata = OCIFetchInto($result, $arr, OCI_RETURN_NULLS+OCI_RETURN_LOBS); + $moredata = OCIFetchInto($result, $arr, OCI_RETURN_NULLS + OCI_RETURN_LOBS); } if (!$moredata) { return null; @@ -419,7 +479,7 @@ class DB_oci8 extends DB_common } // }}} - // {{{ freeResult() + // {{{ numRows() /** * Deletes the result set and frees the memory occupied by the result set @@ -428,7 +488,7 @@ class DB_oci8 extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -439,11 +499,14 @@ class DB_oci8 extends DB_common return is_resource($result) ? OCIFreeStatement($result) : false; } + // }}} + // {{{ numCols() + /** * Frees the internal resources associated with a prepared query * - * @param resource $stmt the prepared statement's resource - * @param bool $free_resource should the PHP resource be freed too? + * @param resource $stmt the prepared statement's resource + * @param bool $free_resource should the PHP resource be freed too? * Use false if you need to get data * from the result set later. * @@ -470,7 +533,7 @@ class DB_oci8 extends DB_common } // }}} - // {{{ numRows() + // {{{ prepare() /** * Gets the number of rows in a result set @@ -482,9 +545,9 @@ class DB_oci8 extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object * * @see DB_result::numRows(), DB_common::setOption() */ @@ -493,7 +556,7 @@ class DB_oci8 extends DB_common // emulate numRows for Oracle. yuck. if ($this->options['portability'] & DB_PORTABILITY_NUMROWS && $result === $this->last_stmt) { - $countquery = 'SELECT COUNT(*) FROM ('.$this->last_query.')'; + $countquery = 'SELECT COUNT(*) FROM (' . $this->last_query . ')'; $save_query = $this->last_query; $save_stmt = $this->last_stmt; @@ -502,7 +565,7 @@ class DB_oci8 extends DB_common // Restore the last query and statement. $this->last_query = $save_query; $this->last_stmt = $save_stmt; - + if (DB::isError($count) || DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED))) { return $this->raiseError(DB_ERROR_NOT_CAPABLE); @@ -514,7 +577,7 @@ class DB_oci8 extends DB_common } // }}} - // {{{ numCols() + // {{{ execute() /** * Gets the number of columns in a result set @@ -523,9 +586,9 @@ class DB_oci8 extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -539,7 +602,145 @@ class DB_oci8 extends DB_common } // }}} - // {{{ prepare() + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + public function autoCommit($onoff = false) + { + $this->autocommit = (bool)$onoff;; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int|object + */ + public function commit() + { + $result = @OCICommit($this->connection); + if (!$result) { + return $this->oci8RaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int|object + */ + public function rollback() + { + $result = @OCIRollback($this->connection); + if (!$result) { + return $this->oci8RaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int|object + */ + public function affectedRows() + { + if ($this->last_stmt === false) { + return $this->oci8RaiseError(); + } + $result = @OCIRowCount($this->last_stmt); + if ($result === false) { + return $this->oci8RaiseError($this->last_stmt); + } + return $result; + } + + // }}} + // {{{ modifyQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + public function modifyLimitQuery($query, $from, $count, $params = array()) + { + // Let Oracle return the name of the columns instead of + // coding a "home" SQL parser + + if (count($params)) { + $result = $this->prepare("SELECT * FROM ($query) " + . 'WHERE NULL = NULL'); + $tmp = $this->execute($result, $params); + } else { + $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL"; + + if (!$result = @OCIParse($this->connection, $q_fields)) { + $this->last_query = $q_fields; + return $this->oci8RaiseError(); + } + if (!@OCIExecute($result, OCI_DEFAULT)) { + $this->last_query = $q_fields; + return $this->oci8RaiseError($result); + } + } + + $ncols = OCINumCols($result); + $cols = array(); + for ($i = 1; $i <= $ncols; $i++) { + $cols[] = '"' . OCIColumnName($result, $i) . '"'; + } + $fields = implode(', ', $cols); + // XXX Test that (tip by John Lim) + //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) { + // // Introduce the FIRST_ROWS Oracle query optimizer + // $query = substr($query, strlen($match[0]), strlen($query)); + // $query = "SELECT /* +FIRST_ROWS */ " . $query; + //} + + // Construct the query + // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2 + // Perhaps this could be optimized with the use of Unions + $query = "SELECT $fields FROM" . + " (SELECT rownum as linenum, $fields FROM" . + " ($query)" . + ' WHERE rownum <= ' . ($from + $count) . + ') WHERE linenum >= ' . ++$from; + return $query; + } + + // }}} + // {{{ modifyLimitQuery() /** * Prepares a query for multiple execution with execute(). @@ -562,7 +763,7 @@ class DB_oci8 extends DB_common * "UPDATE foo SET col=? WHERE col='over \& under'" * * - * @param string $query the query to be prepared + * @param string $query the query to be prepared * * @return mixed DB statement resource on success. DB_Error on failure. * @@ -570,15 +771,15 @@ class DB_oci8 extends DB_common */ public function prepare($query) { - $tokens = preg_split( + $tokens = preg_split( '/((? $val) { @@ -617,7 +818,7 @@ class DB_oci8 extends DB_common } // }}} - // {{{ execute() + // {{{ nextId() /** * Executes a DB statement prepared with prepare(). @@ -626,8 +827,8 @@ class DB_oci8 extends DB_common * ocisetprefetch(), see the "result_buffering" option in setOptions(). * This option was added in Release 1.7.0. * - * @param resource $stmt a DB statement resource returned from prepare() - * @param mixed $data array, string or numeric data to be used in + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 for non-array items or the @@ -713,179 +914,31 @@ class DB_oci8 extends DB_common return $tmp; } - // }}} - // {{{ autoCommit() - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - $this->autocommit = (bool)$onoff; - ; - return DB_OK; - } - - // }}} - // {{{ commit() - - /** - * Commits the current transaction - * - * @return int DB_OK on success. A DB_Error object on failure. - */ - public function commit() - { - $result = @OCICommit($this->connection); - if (!$result) { - return $this->oci8RaiseError(); - } - return DB_OK; - } - - // }}} - // {{{ rollback() - - /** - * Reverts the current transaction - * - * @return int DB_OK on success. A DB_Error object on failure. - */ - public function rollback() - { - $result = @OCIRollback($this->connection); - if (!$result) { - return $this->oci8RaiseError(); - } - return DB_OK; - } - - // }}} - // {{{ affectedRows() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - if ($this->last_stmt === false) { - return $this->oci8RaiseError(); - } - $result = @OCIRowCount($this->last_stmt); - if ($result === false) { - return $this->oci8RaiseError($this->last_stmt); - } - return $result; - } - - // }}} - // {{{ modifyQuery() - - /** - * Changes a query string for various DBMS specific reasons - * - * "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle. - * - * @param string $query the query string to modify - * - * @return string the modified query string - * - * @access protected - */ - public function modifyQuery($query) - { - if (preg_match('/^\s*SELECT/i', $query) && - !preg_match('/\sFROM\s/i', $query)) { - $query .= ' FROM dual'; - } - return $query; - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added + * Formats a float value for use within a query in a locale-independent + * manner. * - * @access protected + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. */ - public function modifyLimitQuery($query, $from, $count, $params = array()) + public function quoteFloat($float) { - // Let Oracle return the name of the columns instead of - // coding a "home" SQL parser - - if (count($params)) { - $result = $this->prepare("SELECT * FROM ($query) " - . 'WHERE NULL = NULL'); - $tmp = $this->execute($result, $params); - } else { - $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL"; - - if (!$result = @OCIParse($this->connection, $q_fields)) { - $this->last_query = $q_fields; - return $this->oci8RaiseError(); - } - if (!@OCIExecute($result, OCI_DEFAULT)) { - $this->last_query = $q_fields; - return $this->oci8RaiseError($result); - } - } - - $ncols = OCINumCols($result); - $cols = array(); - for ($i = 1; $i <= $ncols; $i++) { - $cols[] = '"' . OCIColumnName($result, $i) . '"'; - } - $fields = implode(', ', $cols); - // XXX Test that (tip by John Lim) - //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) { - // // Introduce the FIRST_ROWS Oracle query optimizer - // $query = substr($query, strlen($match[0]), strlen($query)); - // $query = "SELECT /* +FIRST_ROWS */ " . $query; - //} - - // Construct the query - // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2 - // Perhaps this could be optimized with the use of Unions - $query = "SELECT $fields FROM". - " (SELECT rownum as linenum, $fields FROM". - " ($query)". - ' WHERE rownum <= '. ($from + $count) . - ') WHERE linenum >= ' . ++$from; - return $query; + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); } // }}} - // {{{ nextId() + // {{{ dropSequence() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -917,10 +970,13 @@ class DB_oci8 extends DB_common return $arr[0]; } + // }}} + // {{{ oci8RaiseError() + /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -930,16 +986,16 @@ class DB_oci8 extends DB_common public function createSequence($seq_name) { return $this->query('CREATE SEQUENCE ' - . $this->getSequenceName($seq_name)); + . $this->getSequenceName($seq_name)); } // }}} - // {{{ dropSequence() + // {{{ errorNative() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -949,50 +1005,11 @@ class DB_oci8 extends DB_common public function dropSequence($seq_name) { return $this->query('DROP SEQUENCE ' - . $this->getSequenceName($seq_name)); + . $this->getSequenceName($seq_name)); } // }}} - // {{{ oci8RaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_oci8::errorNative(), DB_oci8::errorCode() - */ - public function oci8RaiseError($errno = null) - { - if ($errno === null) { - $error = @OCIError($this->connection); - return $this->raiseError( - $this->errorCode($error['code']), - null, - null, - null, - $error['message'] - ); - } elseif (is_resource($errno)) { - $error = @OCIError($errno); - return $this->raiseError( - $this->errorCode($error['code']), - null, - null, - null, - $error['message'] - ); - } - return $this->raiseError($this->errorCode($errno)); - } - - // }}} - // {{{ errorNative() + // {{{ tableInfo() /** * Gets the DBMS' native error code produced by the last query @@ -1014,7 +1031,7 @@ class DB_oci8 extends DB_common } // }}} - // {{{ tableInfo() + // {{{ getSpecialQuery() /** * Returns information about a table or a result set @@ -1024,14 +1041,14 @@ class DB_oci8 extends DB_common * * NOTE: flags won't contain index information. * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -1053,9 +1070,9 @@ class DB_oci8 extends DB_common */ $result = strtoupper($result); $q_fields = 'SELECT column_name, data_type, data_length, ' - . 'nullable ' - . 'FROM user_tab_columns ' - . "WHERE table_name='$result' ORDER BY column_id"; + . 'nullable ' + . 'FROM user_tab_columns ' + . "WHERE table_name='$result' ORDER BY column_id"; $this->last_query = $q_fields; @@ -1065,14 +1082,14 @@ class DB_oci8 extends DB_common if (!@OCIExecute($stmt, OCI_DEFAULT)) { return $this->oci8RaiseError($stmt); } - + $i = 0; while (@OCIFetch($stmt)) { $res[$i] = array( 'table' => $case_func($result), - 'name' => $case_func(@OCIResult($stmt, 1)), - 'type' => @OCIResult($stmt, 2), - 'len' => @OCIResult($stmt, 3), + 'name' => $case_func(@OCIResult($stmt, 1)), + 'type' => @OCIResult($stmt, 2), + 'len' => @OCIResult($stmt, 3), 'flags' => (@OCIResult($stmt, 4) == 'N') ? 'not_null' : '', ); if ($mode & DB_TABLEINFO_ORDER) { @@ -1107,9 +1124,9 @@ class DB_oci8 extends DB_common for ($i = 0; $i < $count; $i++) { $res[$i] = array( 'table' => '', - 'name' => $case_func(@OCIColumnName($result, $i+1)), - 'type' => @OCIColumnType($result, $i+1), - 'len' => @OCIColumnSize($result, $i+1), + 'name' => $case_func(@OCIColumnName($result, $i + 1)), + 'type' => @OCIColumnType($result, $i + 1), + 'len' => @OCIColumnSize($result, $i + 1), 'flags' => '', ); if ($mode & DB_TABLEINFO_ORDER) { @@ -1127,12 +1144,12 @@ class DB_oci8 extends DB_common } // }}} - // {{{ getSpecialQuery() + // {{{ quoteFloat() /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested @@ -1154,23 +1171,6 @@ class DB_oci8 extends DB_common } } - // }}} - // {{{ quoteFloat() - - /** - * Formats a float value for use within a query in a locale-independent - * manner. - * - * @param float the float value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteFloat($float) - { - return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); - } - // }}} } diff --git a/extlib/DB/odbc.php b/extlib/DB/odbc.php index abd689ef0e..0d1e11a479 100644 --- a/extlib/DB/odbc.php +++ b/extlib/DB/odbc.php @@ -27,7 +27,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's odbc extension @@ -82,13 +83,13 @@ class DB_odbc extends DB_common * @var array */ public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => false, + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, ); /** @@ -173,10 +174,10 @@ class DB_odbc extends DB_common * PEAR DB's odbc driver supports the following extra DSN options: * + cursor The type of cursor to be used for this connection. * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -242,10 +243,26 @@ class DB_odbc extends DB_common // }}} // {{{ disconnect() + /** + * Gets the DBMS' native error code and message produced by the last query + * + * @return string the DBMS' error code and message + */ + public function errorNative() + { + if (!is_resource($this->connection)) { + return @odbc_error() . ' ' . @odbc_errormsg(); + } + return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection); + } + + // }}} + // {{{ simpleQuery() + /** * Disconnects from the database server * - * @return bool TRUE on success, FALSE on failure + * @return bool|void */ public function disconnect() { @@ -255,7 +272,7 @@ class DB_odbc extends DB_common } // }}} - // {{{ simpleQuery() + // {{{ nextResult() /** * Sends a query to the database server @@ -285,7 +302,75 @@ class DB_odbc extends DB_common } // }}} - // {{{ nextResult() + // {{{ fetchInto() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_odbc::errorNative(), DB_common::errorCode() + */ + public function odbcRaiseError($errno = null) + { + if ($errno === null) { + switch ($this->dbsyntax) { + case 'access': + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map['07001'] = DB_ERROR_MISMATCH; + } + + $native_code = odbc_error($this->connection); + + // S1000 is for "General Error." Let's be more specific. + if ($native_code == 'S1000') { + $errormsg = odbc_errormsg($this->connection); + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/includes related records.$/i' => DB_ERROR_CONSTRAINT, + '/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $this->raiseError( + $code, + null, + null, + null, + $native_code . ' ' . $errormsg + ); + } + } + $errno = DB_ERROR; + } else { + $errno = $this->errorCode($native_code); + } + break; + default: + $errno = $this->errorCode(odbc_error($this->connection)); + } + } + return $this->raiseError( + $errno, + null, + null, + null, + $this->errorNative() + ); + } + + // }}} + // {{{ freeResult() /** * Move the internal odbc result pointer to the next available result @@ -302,7 +387,7 @@ class DB_odbc extends DB_common } // }}} - // {{{ fetchInto() + // {{{ numCols() /** * Places a row from the result set into the given array @@ -314,10 +399,10 @@ class DB_odbc extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -342,7 +427,7 @@ class DB_odbc extends DB_common } if ($fetchmode !== DB_FETCHMODE_ORDERED) { for ($i = 0; $i < count($arr); $i++) { - $colName = @odbc_field_name($result, $i+1); + $colName = @odbc_field_name($result, $i + 1); $a[$colName] = $arr[$i]; } if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { @@ -360,7 +445,7 @@ class DB_odbc extends DB_common } // }}} - // {{{ freeResult() + // {{{ affectedRows() /** * Deletes the result set and frees the memory occupied by the result set @@ -369,7 +454,7 @@ class DB_odbc extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -381,7 +466,7 @@ class DB_odbc extends DB_common } // }}} - // {{{ numCols() + // {{{ numRows() /** * Gets the number of columns in a result set @@ -390,9 +475,9 @@ class DB_odbc extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -406,14 +491,14 @@ class DB_odbc extends DB_common } // }}} - // {{{ affectedRows() + // {{{ quoteIdentifier() /** * Determines the number of rows affected by a data maniuplation query * * 0 is returned for queries that don't manipulate data. * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object */ public function affectedRows() { @@ -428,7 +513,7 @@ class DB_odbc extends DB_common } // }}} - // {{{ numRows() + // {{{ nextId() /** * Gets the number of rows in a result set @@ -440,9 +525,9 @@ class DB_odbc extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object * * @see DB_result::numRows() */ @@ -458,16 +543,13 @@ class DB_odbc extends DB_common return $nrows; } - // }}} - // {{{ quoteIdentifier() - /** * Quotes a string so it can be safely used as a table or column name * * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked * "Use ANSI quoted identifiers" when setting up the ODBC data source. * - * @param string $str identifier name to be quoted + * @param string $str identifier name to be quoted * * @return string quoted identifier string * @@ -491,16 +573,16 @@ class DB_odbc extends DB_common } // }}} - // {{{ nextId() + // {{{ dropSequence() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -546,10 +628,13 @@ class DB_odbc extends DB_common return $row[0]; } + // }}} + // {{{ autoCommit() + /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -559,18 +644,18 @@ class DB_odbc extends DB_common public function createSequence($seq_name) { return $this->query('CREATE TABLE ' - . $this->getSequenceName($seq_name) - . ' (id integer NOT NULL,' - . ' PRIMARY KEY(id))'); + . $this->getSequenceName($seq_name) + . ' (id integer NOT NULL,' + . ' PRIMARY KEY(id))'); } // }}} - // {{{ dropSequence() + // {{{ commit() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -583,14 +668,14 @@ class DB_odbc extends DB_common } // }}} - // {{{ autoCommit() + // {{{ rollback() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * - * @return int DB_OK on success. A DB_Error object if the driver + * @return int|object * doesn't support auto-committing transactions. */ public function autoCommit($onoff = false) @@ -602,12 +687,12 @@ class DB_odbc extends DB_common } // }}} - // {{{ commit() + // {{{ odbcRaiseError() /** * Commits the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function commit() { @@ -618,12 +703,12 @@ class DB_odbc extends DB_common } // }}} - // {{{ rollback() + // {{{ errorNative() /** * Reverts the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function rollback() { @@ -633,104 +718,20 @@ class DB_odbc extends DB_common return DB_OK; } - // }}} - // {{{ odbcRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_odbc::errorNative(), DB_common::errorCode() - */ - public function odbcRaiseError($errno = null) - { - if ($errno === null) { - switch ($this->dbsyntax) { - case 'access': - if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { - $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD; - } else { - // Doing this in case mode changes during runtime. - $this->errorcode_map['07001'] = DB_ERROR_MISMATCH; - } - - $native_code = odbc_error($this->connection); - - // S1000 is for "General Error." Let's be more specific. - if ($native_code == 'S1000') { - $errormsg = odbc_errormsg($this->connection); - static $error_regexps; - if (!isset($error_regexps)) { - $error_regexps = array( - '/includes related records.$/i' => DB_ERROR_CONSTRAINT, - '/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL, - ); - } - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $this->raiseError( - $code, - null, - null, - null, - $native_code . ' ' . $errormsg - ); - } - } - $errno = DB_ERROR; - } else { - $errno = $this->errorCode($native_code); - } - break; - default: - $errno = $this->errorCode(odbc_error($this->connection)); - } - } - return $this->raiseError( - $errno, - null, - null, - null, - $this->errorNative() - ); - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error code and message produced by the last query - * - * @return string the DBMS' error code and message - */ - public function errorNative() - { - if (!is_resource($this->connection)) { - return @odbc_error() . ' ' . @odbc_errormsg(); - } - return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection); - } - // }}} // {{{ tableInfo() /** * Returns information about a table or a result set * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -776,7 +777,7 @@ class DB_odbc extends DB_common } $count = @odbc_num_fields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -786,9 +787,9 @@ class DB_odbc extends DB_common $col = $i + 1; $res[$i] = array( 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func(@odbc_field_name($id, $col)), - 'type' => @odbc_field_type($id, $col), - 'len' => @odbc_field_len($id, $col), + 'name' => $case_func(@odbc_field_name($id, $col)), + 'type' => @odbc_field_type($id, $col), + 'len' => @odbc_field_len($id, $col), 'flags' => '', ); if ($mode & DB_TABLEINFO_ORDER) { @@ -814,9 +815,9 @@ class DB_odbc extends DB_common * * Thanks to symbol1@gmail.com and Philippe.Jausions@11abacus.com. * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * - * @return string the list of objects requested + * @return array|string * * @access protected * @see DB_common::getListOf() @@ -859,7 +860,7 @@ class DB_odbc extends DB_common * in the odbc_tables() call because some backends choke on this: * odbc_tables($this->connection, '', '', '', 'TABLE') */ - $res = @odbc_tables($this->connection); + $res = @odbc_tables($this->connection); if (!$res) { return $this->odbcRaiseError(); } diff --git a/extlib/DB/pgsql.php b/extlib/DB/pgsql.php index a10774cc69..251f9d7a89 100644 --- a/extlib/DB/pgsql.php +++ b/extlib/DB/pgsql.php @@ -28,7 +28,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's pgsql extension @@ -76,21 +77,20 @@ class DB_pgsql extends DB_common * @var array */ public $features = array( - 'limit' => 'alter', - 'new_link' => '4.3.0', - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => true, - 'transactions' => true, + 'limit' => 'alter', + 'new_link' => '4.3.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, ); /** * A mapping of native error codes to DB error codes * @var array */ - public $errorcode_map = array( - ); + public $errorcode_map = array(); /** * The raw database connection created by PHP @@ -194,15 +194,15 @@ class DB_pgsql extends DB_common * ); * * $db = DB::connect($dsn, $options); - * if (PEAR::isError($db)) { + * if ((new PEAR)->isError($db)) { * die($db->getMessage()); * } * * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object * * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT */ @@ -381,6 +381,161 @@ class DB_pgsql extends DB_common // }}} // {{{ nextResult() + /** + * Checks if the given query is a manipulation query. This also takes into + * account the _next_query_manip flag and sets the _last_query_manip flag + * (and resets _next_query_manip) according to the result. + * + * @param string The query to check. + * + * @return boolean true if the query is a manipulation query, false + * otherwise + * + * @access protected + */ + public function _checkManip($query) + { + return (preg_match('/^\s*(SAVEPOINT|RELEASE)\s+/i', $query) + || parent::_checkManip($query)); + } + + // }}} + // {{{ fetchInto() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_pgsql::errorNative(), DB_pgsql::errorCode() + */ + public function pgsqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if (!$native) { + $native = 'Database connection has been lost.'; + $errno = DB_ERROR_CONNECT_FAILED; + } + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ freeResult() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal Error messages are used instead of error codes + * in order to support older versions of PostgreSQL.}} + * + * @return string the DBMS' error message + */ + public function errorNative() + { + return @pg_errormessage($this->connection); + } + + // }}} + // {{{ quoteBoolean() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + public function errorCode($errormsg) + { + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/column .* (of relation .*)?does not exist/i' + => DB_ERROR_NOSUCHFIELD, + '/(relation|sequence|table).*does not exist|class .* not found/i' + => DB_ERROR_NOSUCHTABLE, + '/index .* does not exist/' + => DB_ERROR_NOT_FOUND, + '/relation .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/(divide|division) by zero$/i' + => DB_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /i' + => DB_ERROR_INVALID_NUMBER, + '/invalid input syntax for( type)? (integer|numeric)/i' + => DB_ERROR_INVALID_NUMBER, + '/value .* is out of range for type \w*int/i' + => DB_ERROR_INVALID_NUMBER, + '/integer out of range/i' + => DB_ERROR_INVALID_NUMBER, + '/value too long for type character/i' + => DB_ERROR_INVALID, + '/attribute .* not found|relation .* does not have attribute/i' + => DB_ERROR_NOSUCHFIELD, + '/column .* specified in USING clause does not exist in (left|right) table/i' + => DB_ERROR_NOSUCHFIELD, + '/parser: parse error at or near/i' + => DB_ERROR_SYNTAX, + '/syntax error at/' + => DB_ERROR_SYNTAX, + '/column reference .* is ambiguous/i' + => DB_ERROR_SYNTAX, + '/permission denied/' + => DB_ERROR_ACCESS_VIOLATION, + '/violates not-null constraint/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/violates [\w ]+ constraint/' + => DB_ERROR_CONSTRAINT, + '/referential integrity violation/' + => DB_ERROR_CONSTRAINT, + '/more expressions than target columns/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ escapeSimple() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int|object + * + * @see DB_result::numRows() + */ + public function numRows($result) + { + $rows = @pg_numrows($result); + if ($rows === null) { + return $this->pgsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ numCols() + /** * Move the internal pgsql result pointer to the next available result * @@ -396,7 +551,7 @@ class DB_pgsql extends DB_common } // }}} - // {{{ fetchInto() + // {{{ numRows() /** * Places a row from the result set into the given array @@ -408,10 +563,10 @@ class DB_pgsql extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -447,7 +602,7 @@ class DB_pgsql extends DB_common } // }}} - // {{{ freeResult() + // {{{ autoCommit() /** * Deletes the result set and frees the memory occupied by the result set @@ -456,7 +611,7 @@ class DB_pgsql extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -474,7 +629,7 @@ class DB_pgsql extends DB_common } // }}} - // {{{ quoteBoolean() + // {{{ commit() /** * Formats a boolean value for use within a query in a locale-independent @@ -489,9 +644,9 @@ class DB_pgsql extends DB_common { return $boolean ? 'TRUE' : 'FALSE'; } - + // }}} - // {{{ escapeSimple() + // {{{ rollback() /** * Escapes a string according to the current DBMS's standards @@ -499,7 +654,7 @@ class DB_pgsql extends DB_common * {@internal PostgreSQL treats a backslash as an escape character, * so they are escaped as well. * - * @param string $str the string to be escaped + * @param string $str the string to be escaped * * @return string the escaped string * @@ -526,7 +681,7 @@ class DB_pgsql extends DB_common } // }}} - // {{{ numCols() + // {{{ affectedRows() /** * Gets the number of columns in a result set @@ -535,9 +690,9 @@ class DB_pgsql extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -551,37 +706,12 @@ class DB_pgsql extends DB_common } // }}} - // {{{ numRows() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int the number of rows. A DB_Error object on failure. - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @pg_numrows($result); - if ($rows === null) { - return $this->pgsqlRaiseError(); - } - return $rows; - } - - // }}} - // {{{ autoCommit() + // {{{ nextId() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * * @return int DB_OK on success. A DB_Error object if the driver * doesn't support auto-committing transactions. @@ -595,12 +725,12 @@ class DB_pgsql extends DB_common } // }}} - // {{{ commit() + // {{{ createSequence() /** * Commits the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function commit() { @@ -617,12 +747,12 @@ class DB_pgsql extends DB_common } // }}} - // {{{ rollback() + // {{{ dropSequence() /** * Reverts the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function rollback() { @@ -637,7 +767,7 @@ class DB_pgsql extends DB_common } // }}} - // {{{ affectedRows() + // {{{ modifyLimitQuery() /** * Determines the number of rows affected by a data maniuplation query @@ -652,16 +782,16 @@ class DB_pgsql extends DB_common } // }}} - // {{{ nextId() + // {{{ pgsqlRaiseError() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -697,12 +827,12 @@ class DB_pgsql extends DB_common } // }}} - // {{{ createSequence() + // {{{ errorNative() /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -717,12 +847,12 @@ class DB_pgsql extends DB_common } // }}} - // {{{ dropSequence() + // {{{ errorCode() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -732,19 +862,19 @@ class DB_pgsql extends DB_common public function dropSequence($seq_name) { return $this->query('DROP SEQUENCE ' - . $this->getSequenceName($seq_name)); + . $this->getSequenceName($seq_name)); } // }}} - // {{{ modifyLimitQuery() + // {{{ tableInfo() /** * Adds LIMIT clauses to a query string according to current DBMS standards * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -760,116 +890,7 @@ class DB_pgsql extends DB_common } // }}} - // {{{ pgsqlRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_pgsql::errorNative(), DB_pgsql::errorCode() - */ - public function pgsqlRaiseError($errno = null) - { - $native = $this->errorNative(); - if (!$native) { - $native = 'Database connection has been lost.'; - $errno = DB_ERROR_CONNECT_FAILED; - } - if ($errno === null) { - $errno = $this->errorCode($native); - } - return $this->raiseError($errno, null, null, null, $native); - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error message produced by the last query - * - * {@internal Error messages are used instead of error codes - * in order to support older versions of PostgreSQL.}} - * - * @return string the DBMS' error message - */ - public function errorNative() - { - return @pg_errormessage($this->connection); - } - - // }}} - // {{{ errorCode() - - /** - * Determines PEAR::DB error code from the database's text error message. - * - * @param string $errormsg error message returned from the database - * @return integer an error number from a DB error constant - */ - public function errorCode($errormsg) - { - static $error_regexps; - if (!isset($error_regexps)) { - $error_regexps = array( - '/column .* (of relation .*)?does not exist/i' - => DB_ERROR_NOSUCHFIELD, - '/(relation|sequence|table).*does not exist|class .* not found/i' - => DB_ERROR_NOSUCHTABLE, - '/index .* does not exist/' - => DB_ERROR_NOT_FOUND, - '/relation .* already exists/i' - => DB_ERROR_ALREADY_EXISTS, - '/(divide|division) by zero$/i' - => DB_ERROR_DIVZERO, - '/pg_atoi: error in .*: can\'t parse /i' - => DB_ERROR_INVALID_NUMBER, - '/invalid input syntax for( type)? (integer|numeric)/i' - => DB_ERROR_INVALID_NUMBER, - '/value .* is out of range for type \w*int/i' - => DB_ERROR_INVALID_NUMBER, - '/integer out of range/i' - => DB_ERROR_INVALID_NUMBER, - '/value too long for type character/i' - => DB_ERROR_INVALID, - '/attribute .* not found|relation .* does not have attribute/i' - => DB_ERROR_NOSUCHFIELD, - '/column .* specified in USING clause does not exist in (left|right) table/i' - => DB_ERROR_NOSUCHFIELD, - '/parser: parse error at or near/i' - => DB_ERROR_SYNTAX, - '/syntax error at/' - => DB_ERROR_SYNTAX, - '/column reference .* is ambiguous/i' - => DB_ERROR_SYNTAX, - '/permission denied/' - => DB_ERROR_ACCESS_VIOLATION, - '/violates not-null constraint/' - => DB_ERROR_CONSTRAINT_NOT_NULL, - '/violates [\w ]+ constraint/' - => DB_ERROR_CONSTRAINT, - '/referential integrity violation/' - => DB_ERROR_CONSTRAINT, - '/more expressions than target columns/i' - => DB_ERROR_VALUE_COUNT_ON_ROW, - ); - } - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - // Fall back to DB_ERROR if there was no mapping. - return DB_ERROR; - } - - // }}} - // {{{ tableInfo() + // {{{ _pgFieldFlags() /** * Returns information about a table or a result set @@ -877,14 +898,14 @@ class DB_pgsql extends DB_common * NOTE: only supports 'table' and 'flags' if $result * is a table name. * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -926,7 +947,7 @@ class DB_pgsql extends DB_common } $count = @pg_numfields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -935,12 +956,12 @@ class DB_pgsql extends DB_common for ($i = 0; $i < $count; $i++) { $res[$i] = array( 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func(@pg_fieldname($id, $i)), - 'type' => @pg_fieldtype($id, $i), - 'len' => @pg_fieldsize($id, $i), + 'name' => $case_func(@pg_fieldname($id, $i)), + 'type' => @pg_fieldtype($id, $i), + 'len' => @pg_fieldsize($id, $i), 'flags' => $got_string - ? $this->_pgFieldFlags($id, $i, $result) - : '', + ? $this->_pgFieldFlags($id, $i, $result) + : '', ); if ($mode & DB_TABLEINFO_ORDER) { $res['order'][$res[$i]['name']] = $i; @@ -958,7 +979,7 @@ class DB_pgsql extends DB_common } // }}} - // {{{ _pgFieldFlags() + // {{{ getSpecialQuery() /** * Get a column's flags @@ -967,9 +988,10 @@ class DB_pgsql extends DB_common * and "multiple_key". The default value is passed through * rawurlencode() in case there are spaces in it. * - * @param int $resource the PostgreSQL result identifier - * @param int $num_field the field number + * @param int $resource the PostgreSQL result identifier + * @param int $num_field the field number * + * @param $table_name * @return string the flags * * @access private @@ -997,7 +1019,7 @@ class DB_pgsql extends DB_common AND $tableWhere"); if (@pg_numrows($result) > 0) { $row = @pg_fetch_row($result, 0); - $flags = ($row[0] == 't') ? 'not_null ' : ''; + $flags = ($row[0] == 't') ? 'not_null ' : ''; if ($row[1] == 't') { $result = @pg_exec($this->connection, "SELECT a.adsrc @@ -1021,7 +1043,7 @@ class DB_pgsql extends DB_common AND $tableWhere"); $count = @pg_numrows($result); - for ($i = 0; $i < $count ; $i++) { + for ($i = 0; $i < $count; $i++) { $row = @pg_fetch_row($result, $i); $keys = explode(' ', $row[2]); @@ -1038,12 +1060,12 @@ class DB_pgsql extends DB_common } // }}} - // {{{ getSpecialQuery() + // {{{ _checkManip() /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested @@ -1056,37 +1078,37 @@ class DB_pgsql extends DB_common switch ($type) { case 'tables': return 'SELECT c.relname AS "Name"' - . ' FROM pg_class c, pg_user u' - . ' WHERE c.relowner = u.usesysid' - . " AND c.relkind = 'r'" - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_views' - . ' WHERE viewname = c.relname)' - . " AND c.relname !~ '^(pg_|sql_)'" - . ' UNION' - . ' SELECT c.relname AS "Name"' - . ' FROM pg_class c' - . " WHERE c.relkind = 'r'" - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_views' - . ' WHERE viewname = c.relname)' - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_user' - . ' WHERE usesysid = c.relowner)' - . " AND c.relname !~ '^pg_'"; + . ' FROM pg_class c, pg_user u' + . ' WHERE c.relowner = u.usesysid' + . " AND c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . " AND c.relname !~ '^(pg_|sql_)'" + . ' UNION' + . ' SELECT c.relname AS "Name"' + . ' FROM pg_class c' + . " WHERE c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_user' + . ' WHERE usesysid = c.relowner)' + . " AND c.relname !~ '^pg_'"; case 'schema.tables': return "SELECT schemaname || '.' || tablename" - . ' AS "Name"' - . ' FROM pg_catalog.pg_tables' - . ' WHERE schemaname NOT IN' - . " ('pg_catalog', 'information_schema', 'pg_toast')"; + . ' AS "Name"' + . ' FROM pg_catalog.pg_tables' + . ' WHERE schemaname NOT IN' + . " ('pg_catalog', 'information_schema', 'pg_toast')"; case 'schema.views': return "SELECT schemaname || '.' || viewname from pg_views WHERE schemaname" - . " NOT IN ('information_schema', 'pg_catalog')"; + . " NOT IN ('information_schema', 'pg_catalog')"; case 'views': // Table cols: viewname | viewowner | definition return 'SELECT viewname from pg_views WHERE schemaname' - . " NOT IN ('information_schema', 'pg_catalog')"; + . " NOT IN ('information_schema', 'pg_catalog')"; case 'users': // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil return 'SELECT usename FROM pg_user'; @@ -1099,27 +1121,6 @@ class DB_pgsql extends DB_common return null; } } - - // }}} - // {{{ _checkManip() - - /** - * Checks if the given query is a manipulation query. This also takes into - * account the _next_query_manip flag and sets the _last_query_manip flag - * (and resets _next_query_manip) according to the result. - * - * @param string The query to check. - * - * @return boolean true if the query is a manipulation query, false - * otherwise - * - * @access protected - */ - public function _checkManip($query) - { - return (preg_match('/^\s*(SAVEPOINT|RELEASE)\s+/i', $query) - || parent::_checkManip($query)); - } } /* diff --git a/extlib/DB/sqlite.php b/extlib/DB/sqlite.php index 33d36060a4..5e7c67b21a 100644 --- a/extlib/DB/sqlite.php +++ b/extlib/DB/sqlite.php @@ -28,7 +28,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's sqlite extension @@ -80,13 +81,13 @@ class DB_sqlite extends DB_common * @var array */ public $features = array( - 'limit' => 'alter', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => false, + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, ); /** @@ -98,8 +99,7 @@ class DB_sqlite extends DB_common * * @var array */ - public $errorcode_map = array( - ); + public $errorcode_map = array(); /** * The raw database connection created by PHP @@ -122,22 +122,22 @@ class DB_sqlite extends DB_common * @var array */ public $keywords = array( - 'BLOB' => '', - 'BOOLEAN' => '', + 'BLOB' => '', + 'BOOLEAN' => '', 'CHARACTER' => '', - 'CLOB' => '', - 'FLOAT' => '', - 'INTEGER' => '', - 'KEY' => '', - 'NATIONAL' => '', - 'NUMERIC' => '', - 'NVARCHAR' => '', - 'PRIMARY' => '', - 'TEXT' => '', + 'CLOB' => '', + 'FLOAT' => '', + 'INTEGER' => '', + 'KEY' => '', + 'NATIONAL' => '', + 'NUMERIC' => '', + 'NVARCHAR' => '', + 'PRIMARY' => '', + 'TEXT' => '', 'TIMESTAMP' => '', - 'UNIQUE' => '', - 'VARCHAR' => '', - 'VARYING' => '', + 'UNIQUE' => '', + 'VARCHAR' => '', + 'VARYING' => '', ); /** @@ -183,15 +183,15 @@ class DB_sqlite extends DB_common * ); * * $db = DB::connect($dsn, $options); - * if (PEAR::isError($db)) { + * if ((new PEAR)->isError($db)) { * die($db->getMessage()); * } * * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -255,10 +255,98 @@ class DB_sqlite extends DB_common // }}} // {{{ disconnect() + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_sqlite::errorNative(), DB_sqlite::errorCode() + */ + public function sqliteRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + + $errorcode = @sqlite_last_error($this->connection); + $userinfo = "$errorcode ** $this->last_query"; + + return $this->raiseError($errno, null, null, $userinfo, $native); + } + + // }}} + // {{{ simpleQuery() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal This is used to retrieve more meaningfull error messages + * because sqlite_last_error() does not provide adequate info.}} + * + * @return string the DBMS' error message + */ + public function errorNative() + { + return $this->_lasterror; + } + + // }}} + // {{{ nextResult() + + /** + * Determines PEAR::DB error code from the database's text error message + * + * @param string $errormsg the error message returned from the database + * + * @return integer the DB error number + */ + public function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^sqlite[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/^no such table:/' => DB_ERROR_NOSUCHTABLE, + '/^no such index:/' => DB_ERROR_NOT_FOUND, + '/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS, + '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT, + '/is not unique/' => DB_ERROR_CONSTRAINT, + '/columns .* are not unique/i' => DB_ERROR_CONSTRAINT, + '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT, + '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL, + '/^no such column:/' => DB_ERROR_NOSUCHFIELD, + '/no column named/' => DB_ERROR_NOSUCHFIELD, + '/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD, + '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX, + '/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ fetchInto() + /** * Disconnects from the database server * - * @return bool TRUE on success, FALSE on failure + * @return bool|void */ public function disconnect() { @@ -268,7 +356,7 @@ class DB_sqlite extends DB_common } // }}} - // {{{ simpleQuery() + // {{{ freeResult() /** * Sends a query to the database server @@ -313,12 +401,68 @@ class DB_sqlite extends DB_common } // }}} - // {{{ nextResult() + // {{{ numCols() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + public function modifyQuery($query) + { + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace( + '/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', + $query + ); + } + } + return $query; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int|object + * + * @see DB_result::numRows() + */ + public function numRows($result) + { + $rows = @sqlite_num_rows($result); + if ($rows === null) { + return $this->sqliteRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affected() /** * Move the internal sqlite result pointer to the next available result * - * @param resource $result the valid sqlite result resource + * @param resource $result the valid sqlite result resource * * @return bool true if a result is available otherwise return false */ @@ -328,7 +472,7 @@ class DB_sqlite extends DB_common } // }}} - // {{{ fetchInto() + // {{{ dropSequence() /** * Places a row from the result set into the given array @@ -340,10 +484,10 @@ class DB_sqlite extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -392,9 +536,6 @@ class DB_sqlite extends DB_common return DB_OK; } - // }}} - // {{{ freeResult() - /** * Deletes the result set and frees the memory occupied by the result set * @@ -402,7 +543,7 @@ class DB_sqlite extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -419,7 +560,7 @@ class DB_sqlite extends DB_common } // }}} - // {{{ numCols() + // {{{ nextId() /** * Gets the number of columns in a result set @@ -428,9 +569,9 @@ class DB_sqlite extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -444,32 +585,7 @@ class DB_sqlite extends DB_common } // }}} - // {{{ numRows() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int the number of rows. A DB_Error object on failure. - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @sqlite_num_rows($result); - if ($rows === null) { - return $this->sqliteRaiseError(); - } - return $rows; - } - - // }}} - // {{{ affected() + // {{{ getDbFileStats() /** * Determines the number of rows affected by a data maniuplation query @@ -484,12 +600,12 @@ class DB_sqlite extends DB_common } // }}} - // {{{ dropSequence() + // {{{ escapeSimple() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -501,46 +617,17 @@ class DB_sqlite extends DB_common return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); } - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_sqlite::nextID(), DB_sqlite::dropSequence() - */ - public function createSequence($seq_name) - { - $seqname = $this->getSequenceName($seq_name); - $query = 'CREATE TABLE ' . $seqname . - ' (id INTEGER UNSIGNED PRIMARY KEY) '; - $result = $this->query($query); - if (DB::isError($result)) { - return($result); - } - $query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname - BEGIN - DELETE FROM $seqname WHERE idquery($query); - if (DB::isError($result)) { - return($result); - } - } - // }}} - // {{{ nextId() + // {{{ modifyLimitQuery() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -561,7 +648,7 @@ class DB_sqlite extends DB_common return $id; } } elseif ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $result->getCode() == DB_ERROR_NOSUCHTABLE) { $result = $this->createSequence($seq_name); if (DB::isError($result)) { return $this->raiseError($result); @@ -575,7 +662,39 @@ class DB_sqlite extends DB_common } // }}} - // {{{ getDbFileStats() + // {{{ modifyQuery() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_sqlite::nextID(), DB_sqlite::dropSequence() + */ + public function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $query = 'CREATE TABLE ' . $seqname . + ' (id INTEGER UNSIGNED PRIMARY KEY) '; + $result = $this->query($query); + if (DB::isError($result)) { + return ($result); + } + $query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname + BEGIN + DELETE FROM $seqname WHERE idquery($query); + //if (DB::isError($result)) { + return ($result); + //} + } + + // }}} + // {{{ sqliteRaiseError() /** * Get the file stats for the current database @@ -584,7 +703,7 @@ class DB_sqlite extends DB_common * atime, mtime, ctime, blksize, blocks or a numeric key between * 0 and 12. * - * @param string $arg the array key for stats() + * @param string $arg the array key for stats() * * @return mixed an array on an unspecified key, integer on a passed * arg and false at a stats error @@ -600,17 +719,17 @@ class DB_sqlite extends DB_common if (((int)$arg <= 12) & ((int)$arg >= 0)) { return false; } - return $stats[$arg ]; + return $stats[$arg]; } if (array_key_exists(trim($arg), $stats)) { - return $stats[$arg ]; + return $stats[$arg]; } } return $stats; } // }}} - // {{{ escapeSimple() + // {{{ errorNative() /** * Escapes a string according to the current DBMS's standards @@ -620,7 +739,7 @@ class DB_sqlite extends DB_common * containing binary data. See the * {@link http://php.net/sqlite_escape_string PHP manual} for more info. * - * @param string $str the string to be escaped + * @param string $str the string to be escaped * * @return string the escaped string * @@ -633,15 +752,15 @@ class DB_sqlite extends DB_common } // }}} - // {{{ modifyLimitQuery() + // {{{ errorCode() /** * Adds LIMIT clauses to a query string according to current DBMS standards * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in * execution of the statement. Quantity of items * passed must match quantity of placeholders in * query: meaning 1 placeholder for non-array @@ -656,135 +775,16 @@ class DB_sqlite extends DB_common return "$query LIMIT $count OFFSET $from"; } - // }}} - // {{{ modifyQuery() - - /** - * Changes a query string for various DBMS specific reasons - * - * This little hack lets you know how many rows were deleted - * when running a "DELETE FROM table" query. Only implemented - * if the DB_PORTABILITY_DELETE_COUNT portability option is on. - * - * @param string $query the query string to modify - * - * @return string the modified query string - * - * @access protected - * @see DB_common::setOption() - */ - public function modifyQuery($query) - { - if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { - if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { - $query = preg_replace( - '/^\s*DELETE\s+FROM\s+(\S+)\s*$/', - 'DELETE FROM \1 WHERE 1=1', - $query - ); - } - } - return $query; - } - - // }}} - // {{{ sqliteRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_sqlite::errorNative(), DB_sqlite::errorCode() - */ - public function sqliteRaiseError($errno = null) - { - $native = $this->errorNative(); - if ($errno === null) { - $errno = $this->errorCode($native); - } - - $errorcode = @sqlite_last_error($this->connection); - $userinfo = "$errorcode ** $this->last_query"; - - return $this->raiseError($errno, null, null, $userinfo, $native); - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error message produced by the last query - * - * {@internal This is used to retrieve more meaningfull error messages - * because sqlite_last_error() does not provide adequate info.}} - * - * @return string the DBMS' error message - */ - public function errorNative() - { - return $this->_lasterror; - } - - // }}} - // {{{ errorCode() - - /** - * Determines PEAR::DB error code from the database's text error message - * - * @param string $errormsg the error message returned from the database - * - * @return integer the DB error number - */ - public function errorCode($errormsg) - { - static $error_regexps; - - // PHP 5.2+ prepends the function name to $php_errormsg, so we need - // this hack to work around it, per bug #9599. - $errormsg = preg_replace('/^sqlite[a-z_]+\(\): /', '', $errormsg); - - if (!isset($error_regexps)) { - $error_regexps = array( - '/^no such table:/' => DB_ERROR_NOSUCHTABLE, - '/^no such index:/' => DB_ERROR_NOT_FOUND, - '/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS, - '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT, - '/is not unique/' => DB_ERROR_CONSTRAINT, - '/columns .* are not unique/i' => DB_ERROR_CONSTRAINT, - '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT, - '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL, - '/^no such column:/' => DB_ERROR_NOSUCHFIELD, - '/no column named/' => DB_ERROR_NOSUCHFIELD, - '/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD, - '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX, - '/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW, - ); - } - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - // Fall back to DB_ERROR if there was no mapping. - return DB_ERROR; - } - // }}} // {{{ tableInfo() /** * Returns information about a table * - * @param string $result a string containing the name of a table - * @param int $mode a valid tableInfo mode + * @param string $result a string containing the name of a table + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -811,7 +811,7 @@ class DB_sqlite extends DB_common null, null, 'This DBMS can not obtain tableInfo' . - ' from result sets' + ' from result sets' ); } @@ -822,7 +822,7 @@ class DB_sqlite extends DB_common } $count = count($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -832,10 +832,10 @@ class DB_sqlite extends DB_common if (strpos($id[$i]['type'], '(') !== false) { $bits = explode('(', $id[$i]['type']); $type = $bits[0]; - $len = rtrim($bits[1], ')'); + $len = rtrim($bits[1], ')'); } else { $type = $id[$i]['type']; - $len = 0; + $len = 0; } $flags = ''; @@ -855,9 +855,9 @@ class DB_sqlite extends DB_common $res[$i] = array( 'table' => $case_func($result), - 'name' => $case_func($id[$i]['name']), - 'type' => $type, - 'len' => $len, + 'name' => $case_func($id[$i]['name']), + 'type' => $type, + 'len' => $len, 'flags' => $flags, ); @@ -878,8 +878,8 @@ class DB_sqlite extends DB_common /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve - * @param array $args SQLITE DRIVER ONLY: a private array of arguments + * @param string $type the kind of objects you want to retrieve + * @param array $args SQLITE DRIVER ONLY: a private array of arguments * used by the getSpecialQuery(). Do not use * this directly. * @@ -906,13 +906,13 @@ class DB_sqlite extends DB_common return 'SELECT * FROM sqlite_master;'; case 'tables': return "SELECT name FROM sqlite_master WHERE type='table' " - . 'UNION ALL SELECT name FROM sqlite_temp_master ' - . "WHERE type='table' ORDER BY name;"; + . 'UNION ALL SELECT name FROM sqlite_temp_master ' + . "WHERE type='table' ORDER BY name;"; case 'schema': return 'SELECT sql FROM (SELECT * FROM sqlite_master ' - . 'UNION ALL SELECT * FROM sqlite_temp_master) ' - . "WHERE type!='meta' " - . 'ORDER BY tbl_name, type DESC, name;'; + . 'UNION ALL SELECT * FROM sqlite_temp_master) ' + . "WHERE type!='meta' " + . 'ORDER BY tbl_name, type DESC, name;'; case 'schemax': case 'schema_x': /* @@ -921,10 +921,10 @@ class DB_sqlite extends DB_common * array('table' => 'table3'))); */ return 'SELECT sql FROM (SELECT * FROM sqlite_master ' - . 'UNION ALL SELECT * FROM sqlite_temp_master) ' - . "WHERE tbl_name LIKE '{$args['table']}' " - . "AND type!='meta' " - . 'ORDER BY type DESC, name;'; + . 'UNION ALL SELECT * FROM sqlite_temp_master) ' + . "WHERE tbl_name LIKE '{$args['table']}' " + . "AND type!='meta' " + . 'ORDER BY type DESC, name;'; case 'alter': /* * SQLite does not support ALTER TABLE; this is a helper query diff --git a/extlib/DB/storage.php b/extlib/DB/storage.php index 8a06403eca..2ddc4c0b49 100644 --- a/extlib/DB/storage.php +++ b/extlib/DB/storage.php @@ -46,33 +46,33 @@ class DB_storage extends PEAR // {{{ properties /** the name of the table (or view, if the backend database supports - updates in views) we hold data from */ + * updates in views) we hold data from */ public $_table = null; /** which column(s) in the table contains primary keys, can be a - string for single-column primary keys, or an array of strings - for multiple-column primary keys */ + * string for single-column primary keys, or an array of strings + * for multiple-column primary keys */ public $_keycolumn = null; /** DB connection handle used for all transactions */ public $_dbh = null; /** an assoc with the names of database fields stored as properties - in this object */ + * in this object */ public $_properties = array(); /** an assoc with the names of the properties in this object that - have been changed since they were fetched from the database */ + * have been changed since they were fetched from the database */ public $_changes = array(); /** flag that decides if data in this object can be changed. - objects that don't have their table's key column in their - property lists will be flagged as read-only. */ + * objects that don't have their table's key column in their + * property lists will be flagged as read-only. */ public $_readonly = false; /** function or method that implements a validator for fields that - are set, this validator function returns true if the field is - valid, false if not */ + * are set, this validator function returns true if the field is + * valid, false if not */ public $_validator = null; // }}} @@ -108,49 +108,34 @@ class DB_storage extends PEAR // {{{ _makeWhere() /** - * Utility method to build a "WHERE" clause to locate ourselves in - * the table. - * - * XXX future improvement: use rowids? - * - * @access private + * Create a new (empty) row in the configured table for this + * object. + * @param $newpk + * @return |null */ - public function _makeWhere($keyval = null) + public function insert($newpk) { if (is_array($this->_keycolumn)) { - if ($keyval === null) { - for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { - $keyval[] = $this->{$this->_keycolumn[$i]}; - } - } - $whereclause = ''; - for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { - if ($i > 0) { - $whereclause .= ' AND '; - } - $whereclause .= $this->_keycolumn[$i]; - if (is_null($keyval[$i])) { - // there's not much point in having a NULL key, - // but we support it anyway - $whereclause .= ' IS NULL'; - } else { - $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); - } - } + $primarykey = $this->_keycolumn; } else { - if ($keyval === null) { - $keyval = @$this->{$this->_keycolumn}; - } - $whereclause = $this->_keycolumn; - if (is_null($keyval)) { - // there's not much point in having a NULL key, - // but we support it anyway - $whereclause .= ' IS NULL'; - } else { - $whereclause .= ' = ' . $this->_dbh->quote($keyval); - } + $primarykey = array($this->_keycolumn); } - return $whereclause; + settype($newpk, "array"); + for ($i = 0; $i < sizeof($primarykey); $i++) { + $pkvals[] = $this->_dbh->quote($newpk[$i]); + } + + $sth = $this->_dbh->query("INSERT INTO $this->_table (" . + implode(",", $primarykey) . ") VALUES(" . + implode(",", $pkvals) . ")"); + if (DB::isError($sth)) { + return $sth; + } + if (sizeof($newpk) == 1) { + $newpk = $newpk[0]; + } + $this->setup($newpk); + return null; } // }}} @@ -162,7 +147,7 @@ class DB_storage extends PEAR * * @param $keyval mixed the key[s] of the row to fetch (string or array) * - * @return int DB_OK on success, a DB error if not + * @return int|object */ public function setup($keyval) { @@ -198,31 +183,51 @@ class DB_storage extends PEAR // {{{ insert() /** - * Create a new (empty) row in the configured table for this - * object. + * Utility method to build a "WHERE" clause to locate ourselves in + * the table. + * + * XXX future improvement: use rowids? + * + * @access private + * @param null $keyval + * @return mixed|string|null */ - public function insert($newpk) + public function _makeWhere($keyval = null) { if (is_array($this->_keycolumn)) { - $primarykey = $this->_keycolumn; + if ($keyval === null) { + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + $keyval[] = $this->{$this->_keycolumn[$i]}; + } + } + $whereclause = ''; + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + if ($i > 0) { + $whereclause .= ' AND '; + } + $whereclause .= $this->_keycolumn[$i]; + if (is_null($keyval[$i])) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); + } + } } else { - $primarykey = array($this->_keycolumn); - } - settype($newpk, "array"); - for ($i = 0; $i < sizeof($primarykey); $i++) { - $pkvals[] = $this->_dbh->quote($newpk[$i]); - } - - $sth = $this->_dbh->query("INSERT INTO $this->_table (" . - implode(",", $primarykey) . ") VALUES(" . - implode(",", $pkvals) . ")"); - if (DB::isError($sth)) { - return $sth; - } - if (sizeof($newpk) == 1) { - $newpk = $newpk[0]; + if ($keyval === null) { + $keyval = @$this->{$this->_keycolumn}; + } + $whereclause = $this->_keycolumn; + if (is_null($keyval)) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval); + } } - $this->setup($newpk); + return $whereclause; } // }}} @@ -293,6 +298,7 @@ class DB_storage extends PEAR /** * Static method used to create new DB storage objects. + * @param $table * @param $data assoc. array where the keys are the names * of properties/columns * @return object a new instance of DB_storage or a subclass of it @@ -365,6 +371,9 @@ class DB_storage extends PEAR /** * Modify an attriute value. + * @param $property + * @param $newvalue + * @return bool|object */ public function set($property, $newvalue) { @@ -469,7 +478,7 @@ class DB_storage extends PEAR /** * Stores changes to this object in the database. * - * @return DB_OK or a DB error + * @return DB_OK|int */ public function store() { @@ -514,7 +523,7 @@ class DB_storage extends PEAR true ); } - $query = 'DELETE FROM ' . $this->_table .' WHERE '. + $query = 'DELETE FROM ' . $this->_table . ' WHERE ' . $this->_makeWhere(); $res = $this->_dbh->query($query); if (DB::isError($res)) { diff --git a/extlib/DB/sybase.php b/extlib/DB/sybase.php index c2108059dd..01f2121d48 100644 --- a/extlib/DB/sybase.php +++ b/extlib/DB/sybase.php @@ -17,7 +17,7 @@ * @category Database * @package DB * @author Sterling Hughes - * @author Antônio Carlos Venâncio Júnior + * @author Ant�nio Carlos Ven�ncio J�nior * @author Daniel Convissor * @copyright 1997-2007 The PHP Group * @license http://www.php.net/license/3_0.txt PHP License 3.0 @@ -28,7 +28,8 @@ /** * Obtain the DB_common class so it can be extended from */ -require_once 'DB/common.php'; +//require_once 'DB/common.php'; +require_once 'common.php'; /** * The methods PEAR DB uses to interact with PHP's sybase extension @@ -42,7 +43,7 @@ require_once 'DB/common.php'; * @category Database * @package DB * @author Sterling Hughes - * @author Antônio Carlos Venâncio Júnior + * @author Ant�nio Carlos Ven�ncio J�nior * @author Daniel Convissor * @copyright 1997-2007 The PHP Group * @license http://www.php.net/license/3_0.txt PHP License 3.0 @@ -79,21 +80,20 @@ class DB_sybase extends DB_common * @var array */ public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, ); /** * A mapping of native error codes to DB error codes * @var array */ - public $errorcode_map = array( - ); + public $errorcode_map = array(); /** * The raw database connection created by PHP @@ -164,10 +164,10 @@ class DB_sybase extends DB_common * + charset The character set to use on this connection. * Available since PEAR DB 1.7.0. * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function connect($dsn, $persistent = false) { @@ -291,6 +291,105 @@ class DB_sybase extends DB_common // }}} // {{{ nextResult() + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_sybase::errorNative(), DB_sybase::errorCode() + */ + public function sybaseRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ fetchInto() + + /** + * Gets the DBMS' native error message produced by the last query + * + * @return string the DBMS' error message + */ + public function errorNative() + { + return @sybase_get_last_message(); + } + + // }}} + // {{{ freeResult() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + public function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^sybase[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/Incorrect syntax near/' + => DB_ERROR_SYNTAX, + '/^Unclosed quote before the character string [\"\'].*[\"\']\./' + => DB_ERROR_SYNTAX, + '/Implicit conversion (from datatype|of NUMERIC value)/i' + => DB_ERROR_INVALID_NUMBER, + '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./' + => DB_ERROR_NOSUCHTABLE, + '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./' + => DB_ERROR_ACCESS_VIOLATION, + '/^.+ permission denied on object .+, database .+, owner .+/' + => DB_ERROR_ACCESS_VIOLATION, + '/^.* permission denied, database .+, owner .+/' + => DB_ERROR_ACCESS_VIOLATION, + '/[^.*] not found\./' + => DB_ERROR_NOSUCHTABLE, + '/There is already an object named/' + => DB_ERROR_ALREADY_EXISTS, + '/Invalid column name/' + => DB_ERROR_NOSUCHFIELD, + '/does not allow null values/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/Command has been aborted/' + => DB_ERROR_CONSTRAINT, + '/^Cannot drop the index .* because it doesn\'t exist/i' + => DB_ERROR_NOT_FOUND, + '/^There is already an index/i' + => DB_ERROR_ALREADY_EXISTS, + '/^There are fewer columns in the INSERT statement than values specified/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + '/Divide by zero/i' + => DB_ERROR_DIVZERO, + ); + } + + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ numCols() + /** * Move the internal sybase result pointer to the next available result * @@ -306,7 +405,7 @@ class DB_sybase extends DB_common } // }}} - // {{{ fetchInto() + // {{{ numRows() /** * Places a row from the result set into the given array @@ -318,10 +417,10 @@ class DB_sybase extends DB_common * DB_result::fetchInto() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) * * @return mixed DB_OK on success, NULL when the end of a result set is * reached or on failure @@ -366,7 +465,7 @@ class DB_sybase extends DB_common } // }}} - // {{{ freeResult() + // {{{ affectedRows() /** * Deletes the result set and frees the memory occupied by the result set @@ -375,7 +474,7 @@ class DB_sybase extends DB_common * DB_result::free() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * * @return bool TRUE on success, FALSE if $result is invalid * @@ -387,7 +486,7 @@ class DB_sybase extends DB_common } // }}} - // {{{ numCols() + // {{{ nextId() /** * Gets the number of columns in a result set @@ -396,9 +495,9 @@ class DB_sybase extends DB_common * DB_result::numCols() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of columns. A DB_Error object on failure. + * @return int|object * * @see DB_result::numCols() */ @@ -411,9 +510,6 @@ class DB_sybase extends DB_common return $cols; } - // }}} - // {{{ numRows() - /** * Gets the number of rows in a result set * @@ -421,9 +517,9 @@ class DB_sybase extends DB_common * DB_result::numRows() instead. It can't be declared "protected" * because DB_result is a separate object. * - * @param resource $result PHP's query result resource + * @param resource $result PHP's query result resource * - * @return int the number of rows. A DB_Error object on failure. + * @return int|object * * @see DB_result::numRows() */ @@ -437,7 +533,7 @@ class DB_sybase extends DB_common } // }}} - // {{{ affectedRows() + // {{{ dropSequence() /** * Determines the number of rows affected by a data maniuplation query @@ -457,16 +553,16 @@ class DB_sybase extends DB_common } // }}} - // {{{ nextId() + // {{{ quoteFloat() /** * Returns the next free id in a sequence * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically * created if it does not exist * - * @return int the next id number in the sequence. + * @return int|object * A DB_Error object on failure. * * @see DB_common::nextID(), DB_common::getSequenceName(), @@ -504,10 +600,13 @@ class DB_sybase extends DB_common return $result[0]; } + // }}} + // {{{ autoCommit() + /** * Creates a new sequence * - * @param string $seq_name name of the new sequence + * @param string $seq_name name of the new sequence * * @return int DB_OK on success. A DB_Error object on failure. * @@ -517,18 +616,18 @@ class DB_sybase extends DB_common public function createSequence($seq_name) { return $this->query('CREATE TABLE ' - . $this->getSequenceName($seq_name) - . ' (id numeric(10, 0) IDENTITY NOT NULL,' - . ' vapor int NULL)'); + . $this->getSequenceName($seq_name) + . ' (id numeric(10, 0) IDENTITY NOT NULL,' + . ' vapor int NULL)'); } // }}} - // {{{ dropSequence() + // {{{ commit() /** * Deletes a sequence * - * @param string $seq_name name of the sequence to be deleted + * @param string $seq_name name of the sequence to be deleted * * @return int DB_OK on success. A DB_Error object on failure. * @@ -541,7 +640,7 @@ class DB_sybase extends DB_common } // }}} - // {{{ quoteFloat() + // {{{ rollback() /** * Formats a float value for use within a query in a locale-independent @@ -556,14 +655,14 @@ class DB_sybase extends DB_common { return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); } - + // }}} - // {{{ autoCommit() + // {{{ sybaseRaiseError() /** * Enables or disables automatic commits * - * @param bool $onoff true turns it on, false turns it off + * @param bool $onoff true turns it on, false turns it off * * @return int DB_OK on success. A DB_Error object if the driver * doesn't support auto-committing transactions. @@ -577,12 +676,12 @@ class DB_sybase extends DB_common } // }}} - // {{{ commit() + // {{{ errorNative() /** * Commits the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function commit() { @@ -600,12 +699,12 @@ class DB_sybase extends DB_common } // }}} - // {{{ rollback() + // {{{ errorCode() /** * Reverts the current transaction * - * @return int DB_OK on success. A DB_Error object on failure. + * @return int|object */ public function rollback() { @@ -622,105 +721,6 @@ class DB_sybase extends DB_common return DB_OK; } - // }}} - // {{{ sybaseRaiseError() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_sybase::errorNative(), DB_sybase::errorCode() - */ - public function sybaseRaiseError($errno = null) - { - $native = $this->errorNative(); - if ($errno === null) { - $errno = $this->errorCode($native); - } - return $this->raiseError($errno, null, null, null, $native); - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error message produced by the last query - * - * @return string the DBMS' error message - */ - public function errorNative() - { - return @sybase_get_last_message(); - } - - // }}} - // {{{ errorCode() - - /** - * Determines PEAR::DB error code from the database's text error message. - * - * @param string $errormsg error message returned from the database - * @return integer an error number from a DB error constant - */ - public function errorCode($errormsg) - { - static $error_regexps; - - // PHP 5.2+ prepends the function name to $php_errormsg, so we need - // this hack to work around it, per bug #9599. - $errormsg = preg_replace('/^sybase[a-z_]+\(\): /', '', $errormsg); - - if (!isset($error_regexps)) { - $error_regexps = array( - '/Incorrect syntax near/' - => DB_ERROR_SYNTAX, - '/^Unclosed quote before the character string [\"\'].*[\"\']\./' - => DB_ERROR_SYNTAX, - '/Implicit conversion (from datatype|of NUMERIC value)/i' - => DB_ERROR_INVALID_NUMBER, - '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./' - => DB_ERROR_NOSUCHTABLE, - '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./' - => DB_ERROR_ACCESS_VIOLATION, - '/^.+ permission denied on object .+, database .+, owner .+/' - => DB_ERROR_ACCESS_VIOLATION, - '/^.* permission denied, database .+, owner .+/' - => DB_ERROR_ACCESS_VIOLATION, - '/[^.*] not found\./' - => DB_ERROR_NOSUCHTABLE, - '/There is already an object named/' - => DB_ERROR_ALREADY_EXISTS, - '/Invalid column name/' - => DB_ERROR_NOSUCHFIELD, - '/does not allow null values/' - => DB_ERROR_CONSTRAINT_NOT_NULL, - '/Command has been aborted/' - => DB_ERROR_CONSTRAINT, - '/^Cannot drop the index .* because it doesn\'t exist/i' - => DB_ERROR_NOT_FOUND, - '/^There is already an index/i' - => DB_ERROR_ALREADY_EXISTS, - '/^There are fewer columns in the INSERT statement than values specified/i' - => DB_ERROR_VALUE_COUNT_ON_ROW, - '/Divide by zero/i' - => DB_ERROR_DIVZERO, - ); - } - - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - return DB_ERROR; - } - // }}} // {{{ tableInfo() @@ -730,14 +730,14 @@ class DB_sybase extends DB_common * NOTE: only supports 'table' and 'flags' if $result * is a table name. * - * @param object|string $result DB_result object from a query or a + * @param object|string $result DB_result object from a query or a * string containing the name of a table. * While this also accepts a query result * resource identifier, this behavior is * deprecated. - * @param int $mode a valid tableInfo mode + * @param int $mode a valid tableInfo mode * - * @return array an associative array with the information requested. + * @return array|object * A DB_Error object on failure. * * @see DB_common::tableInfo() @@ -786,7 +786,7 @@ class DB_sybase extends DB_common } $count = @sybase_num_fields($id); - $res = array(); + $res = array(); if ($mode) { $res['num_fields'] = $count; @@ -797,11 +797,11 @@ class DB_sybase extends DB_common // column_source is often blank $res[$i] = array( 'table' => $got_string - ? $case_func($result) - : $case_func($f->column_source), - 'name' => $case_func($f->name), - 'type' => $f->type, - 'len' => $f->max_length, + ? $case_func($result) + : $case_func($f->column_source), + 'name' => $case_func($f->name), + 'type' => $f->type, + 'len' => $f->max_length, 'flags' => '', ); if ($res[$i]['table']) { @@ -835,8 +835,8 @@ class DB_sybase extends DB_common * + unique_key (unique index, unique check or primary_key) * + multiple_key (multi-key index) * - * @param string $table the table name - * @param string $column the field name + * @param string $table the table name + * @param string $column the field name * * @return string space delimited string of flags. Empty string if none. * @@ -888,7 +888,7 @@ class DB_sybase extends DB_common } if (array_key_exists($column, $flags)) { - return(implode(' ', $flags[$column])); + return (implode(' ', $flags[$column])); } return ''; @@ -901,8 +901,8 @@ class DB_sybase extends DB_common * Adds a string to the flags array if the flag is not yet in there * - if there is no flag present the array is created * - * @param array $array reference of flags array to add a value to - * @param mixed $value value to add to the flag array + * @param array $array reference of flags array to add a value to + * @param mixed $value value to add to the flag array * * @return void * @@ -923,7 +923,7 @@ class DB_sybase extends DB_common /** * Obtains the query string needed for listing a given type of objects * - * @param string $type the kind of objects you want to retrieve + * @param string $type the kind of objects you want to retrieve * * @return string the SQL query string or null if the driver doesn't * support the object type requested @@ -936,7 +936,7 @@ class DB_sybase extends DB_common switch ($type) { case 'tables': return "SELECT name FROM sysobjects WHERE type = 'U'" - . ' ORDER BY name'; + . ' ORDER BY name'; case 'views': return "SELECT name FROM sysobjects WHERE type = 'V'"; default: