From a5fd9b705acb0bd4b69017a319c0fee4a25a0ae3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 26 Mar 2011 14:45:15 -0400 Subject: [PATCH] upgrade DB_DataObject to 1.9.5 --- extlib/DB/DataObject.php | 342 ++++++++++++++++++++++++----- extlib/DB/DataObject/Generator.php | 126 +++++++---- 2 files changed, 376 insertions(+), 92 deletions(-) diff --git a/extlib/DB/DataObject.php b/extlib/DB/DataObject.php index a69fbae86b..811d775965 100644 --- a/extlib/DB/DataObject.php +++ b/extlib/DB/DataObject.php @@ -15,7 +15,7 @@ * @author Alan Knowles * @copyright 1997-2006 The PHP Group * @license http://www.php.net/license/3_01.txt PHP License 3.01 - * @version CVS: $Id: DataObject.php 291349 2009-11-27 09:15:18Z alan_k $ + * @version CVS: $Id: DataObject.php 301030 2010-07-07 02:26:31Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ @@ -235,7 +235,7 @@ class DB_DataObject extends DB_DataObject_Overload * @access private * @var string */ - var $_DB_DataObject_version = "1.9.0"; + var $_DB_DataObject_version = "1.9.5"; /** * The Database table (used by table extends) @@ -369,6 +369,32 @@ class DB_DataObject extends DB_DataObject_Overload return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; } + /** + * build the basic select query. + * + * @access private + */ + + function _build_select() + { + global $_DB_DATAOBJECT; + $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + if ($quoteIdentifiers) { + $this->_connect(); + $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + } + $sql = 'SELECT ' . + $this->_query['data_select'] . " \n" . + ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" . + $this->_join . " \n" . + $this->_query['condition'] . " \n" . + $this->_query['group_by'] . " \n" . + $this->_query['having'] . " \n"; + + return $sql; + } + + /** * find results, either normal or crosstable * @@ -411,20 +437,21 @@ class DB_DataObject extends DB_DataObject_Overload $query_before = $this->_query; $this->_build_condition($this->table()) ; - $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + $this->_connect(); $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - /* We are checking for method modifyLimitQuery as it is PEAR DB specific */ - $sql = 'SELECT ' . - $this->_query['data_select'] . " \n" . - ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" . - $this->_join . " \n" . - $this->_query['condition'] . " \n" . - $this->_query['group_by'] . " \n" . - $this->_query['having'] . " \n" . - $this->_query['order_by'] . " \n"; + $sql = $this->_build_select(); + + foreach ($this->_query['unions'] as $union_ar) { + $sql .= $union_ar[1] . $union_ar[0]->_build_select() . " \n"; + } + + $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 */ @@ -578,6 +605,85 @@ class DB_DataObject extends DB_DataObject_Overload 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 + */ + 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); + } + } + + $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; + } + return $ret; + + } + + /** * Adds a condition to the WHERE statement, defaults to AND * @@ -622,6 +728,47 @@ class DB_DataObject extends DB_DataObject_Overload 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 + */ + 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 ); + } + + + /** * Adds a order by condition * @@ -1175,7 +1322,7 @@ class DB_DataObject extends DB_DataObject_Overload $seq = $this->sequenceKey(); if ($seq[0] !== false) { $keys = array($seq[0]); - if (empty($this->{$keys[0]}) && $dataObject !== true) { + 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 @@ -1670,6 +1817,7 @@ class DB_DataObject extends DB_DataObject_Overload 'limit_start' => '', // the LIMIT condition 'limit_count' => '', // the LIMIT condition 'data_select' => '*', // the columns to be SELECTed + 'unions' => array(), // the added unions ); @@ -2032,9 +2180,9 @@ class DB_DataObject extends DB_DataObject_Overload $seqname = false; if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) { - $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table]; - if (strpos($usekey,':') !== false) { - list($usekey,$seqname) = explode(':',$usekey); + $seqname = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table]; + if (strpos($seqname,':') !== false) { + list($usekey,$seqname) = explode(':',$seqname); } } @@ -3068,9 +3216,9 @@ class DB_DataObject extends DB_DataObject_Overload } /** - * IS THIS SUPPORTED/USED ANYMORE???? - *return a list of options for a linked table - * + * 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..) @@ -3078,7 +3226,21 @@ class DB_DataObject extends DB_DataObject_Overload * stores it in $this->_xxxxx_yyyyy * * @access public - * @return array of results (empty array on failure) + * @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, '
'; + * } + * */ function &getLinkArray($row, $table = null) { @@ -3123,6 +3285,46 @@ class DB_DataObject extends DB_DataObject_Overload return $ret; } + /** + * 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. + */ + + 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; + } + + + /** * The JOIN condition * @@ -3248,31 +3450,46 @@ class DB_DataObject extends DB_DataObject_Overload /* otherwise see if there are any links from this table to the obj. */ //print_r($this->links()); if (($ofield === false) && ($links = $this->links())) { - foreach ($links as $k => $v) { - /* link contains {this column} = {linked table}:{linked column} */ - $ar = explode(':', $v); - // Feature Request #4266 - Allow joins with multiple keys - if (strpos($k, ',') !== false) { - $k = explode(',', $k); - } - if (strpos($ar[1], ',') !== false) { - $ar[1] = explode(',', $ar[1]); + // this enables for support for arrays of links in ini file. + // link contains this_column[] = linked_table:linked_column + // or standard way. + // link contains this_column = linked_table:linked_column + foreach ($links as $k => $linkVar) { + + if (!is_array($linkVar)) { + $linkVar = array($linkVar); } + foreach($linkVar as $v) { - if ($ar[0] == $obj->__table) { + + + /* link contains {this column} = {linked table}:{linked column} */ + $ar = explode(':', $v); + // Feature Request #4266 - Allow joins with multiple keys + if (strpos($k, ',') !== false) { + $k = explode(',', $k); + } + if (strpos($ar[1], ',') !== false) { + $ar[1] = explode(',', $ar[1]); + } + + if ($ar[0] != $obj->__table) { + continue; + } if ($joinCol !== false) { if ($k == $joinCol) { + // got it!? $tfield = $k; $ofield = $ar[1]; break; - } else { - continue; - } - } else { - $tfield = $k; - $ofield = $ar[1]; - break; - } + } + continue; + + } + $tfield = $k; + $ofield = $ar[1]; + break; + } } } @@ -3280,23 +3497,30 @@ class DB_DataObject extends DB_DataObject_Overload //print_r($obj->links()); if (!$ofield && ($olinks = $obj->links())) { - foreach ($olinks as $k => $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); + foreach ($olinks as $k => $linkVar) { + /* link contains {this column} = array ( {linked table}:{linked column} )*/ + if (!is_array($linkVar)) { + $linkVar = array($linkVar); } - - $ar_array = strpos($ar[1],','); - if ($ar_array !== false) { - $ar[1] = explode(',', $ar[1]); - } - - if ($ar[0] == $this->__table) { + 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->__table) { + continue; + } // you have explictly specified the column // and the col is listed here.. @@ -3315,6 +3539,7 @@ class DB_DataObject extends DB_DataObject_Overload $ofield = $k; $tfield = $ar[1]; break; + } } } @@ -3573,7 +3798,14 @@ class DB_DataObject extends DB_DataObject_Overload if (!$k) { continue; // ignore empty keys!!! what } - if (is_object($from) && isset($from->{sprintf($format,$k)})) { + + $chk = is_object($from) && + (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)}); diff --git a/extlib/DB/DataObject/Generator.php b/extlib/DB/DataObject/Generator.php index 17d310f57c..7ea716dc7c 100644 --- a/extlib/DB/DataObject/Generator.php +++ b/extlib/DB/DataObject/Generator.php @@ -15,7 +15,7 @@ * @author Alan Knowles * @copyright 1997-2006 The PHP Group * @license http://www.php.net/license/3_01.txt PHP License 3.01 - * @version CVS: $Id: Generator.php 289384 2009-10-09 00:17:26Z alan_k $ + * @version CVS: $Id: Generator.php 298560 2010-04-25 23:01:51Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ @@ -383,8 +383,8 @@ class DB_DataObject_Generator extends DB_DataObject return false; } $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; - if (!in_array($__DB->phptype, array('mysql','mysqli'))) { - echo "WARNING: cant handle non-mysql introspection for defaults."; + 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. } @@ -392,33 +392,72 @@ class DB_DataObject_Generator extends DB_DataObject $fk = array(); - 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()); - } + switch ($DB->phptype) { - $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) < 1) { - continue; - } - for ($i = 0; $i < count($treffer); $i++) { - $fk[$this->table][$treffer[$i][1]] = $treffer[$i][2] . ":" . $treffer[$i][3]; - } - + 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]; + } + + } + } - $links_ini = ""; foreach($fk as $table => $details) { @@ -861,10 +900,8 @@ class DB_DataObject_Generator extends DB_DataObject $body = "\n ###START_AUTOCODE\n"; $body .= " /* the code below is auto generated do not remove the above tag */\n\n"; // table - $padding = (30 - strlen($this->table)); - $padding = ($padding < 2) ? 2 : $padding; - - $p = str_repeat(' ',$padding) ; + + $p = str_repeat(' ',max(2, (18 - strlen($this->table)))) ; $options = &PEAR::getStaticProperty('DB_DataObject','options'); @@ -887,6 +924,7 @@ class DB_DataObject_Generator extends DB_DataObject // 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->table)))); $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n"; } @@ -900,6 +938,7 @@ class DB_DataObject_Generator extends DB_DataObject // show nice information! $connections = array(); $sets = array(); + foreach($defs as $t) { if (!strlen(trim($t->name))) { continue; @@ -915,19 +954,18 @@ class DB_DataObject_Generator extends DB_DataObject continue; } - - $padding = (30 - strlen($t->name)); - if ($padding < 2) $padding =2; - $p = str_repeat(' ',$padding) ; - + $p = str_repeat(' ',max(2, (30 - strlen($t->name)))); + $length = empty($t->len) ? '' : '('.$t->len.')'; $body .=" {$var} \${$t->name}; {$p}// {$t->type}$length {$t->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,$padding); + $body .= $this->derivedHookVar($t,strlen($p)); } + + $body .= $this->derivedHookPostVar($defs); // THIS IS TOTALLY BORKED old FC creation // IT WILL BE REMOVED!!!!! in DataObjects 1.6 @@ -1078,7 +1116,21 @@ class DB_DataObject_Generator extends DB_DataObject // 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. + */ + 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 *