From 9d87bb45f44b62ab503f88cf2775535eced96d06 Mon Sep 17 00:00:00 2001 From: lsmith Date: Fri, 29 Dec 2006 21:46:14 +0000 Subject: [PATCH] - remove trailing whitespace --- lib/Doctrine/Collection.php | 8 +- lib/Doctrine/Collection/Batch.php | 2 +- lib/Doctrine/Configurable.php | 8 +- lib/Doctrine/DataDict/Firebird.php | 4 +- lib/Doctrine/DataDict/Informix.php | 4 +- lib/Doctrine/DataDict/Mssql.php | 4 +- lib/Doctrine/DataDict/Mysql.php | 8 +- lib/Doctrine/DataDict/Oracle.php | 4 +- lib/Doctrine/DataDict/Pgsql.php | 6 +- lib/Doctrine/DataDict/Sqlite.php | 4 +- lib/Doctrine/Db.php | 2 +- lib/Doctrine/Query.php | 2776 ++++++++++---------- lib/Doctrine/Query/Where.php | 2 +- lib/Doctrine/RawSql.php | 2 +- lib/Doctrine/Record.php | 18 +- lib/Doctrine/Relation/Association.php | 2 +- lib/Doctrine/Relation/Association/Self.php | 2 +- lib/Doctrine/Table.php | 12 +- 18 files changed, 1434 insertions(+), 1434 deletions(-) diff --git a/lib/Doctrine/Collection.php b/lib/Doctrine/Collection.php index 9b382187e..6addc946a 100644 --- a/lib/Doctrine/Collection.php +++ b/lib/Doctrine/Collection.php @@ -321,17 +321,17 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator if ( ! $this->expandable) { return false; } - + if ( ! isset($this->reference)) { return false; } - + $id = $this->reference->obtainIdentifier(); - + if (empty($id)) { return false; } - + switch (get_class($this)) { case "Doctrine_Collection_Immediate": $fields = implode(", ",$this->table->getColumnNames()); diff --git a/lib/Doctrine/Collection/Batch.php b/lib/Doctrine/Collection/Batch.php index acedae59d..c7d757030 100644 --- a/lib/Doctrine/Collection/Batch.php +++ b/lib/Doctrine/Collection/Batch.php @@ -159,7 +159,7 @@ class Doctrine_Collection_Batch extends Doctrine_Collection // Doctrine_Record didn't exist in cache $this->table->setData($this->data[$key]); $this->data[$key] = $this->table->getProxy(); - + $this->data[$key]->addCollection($this); break; }; diff --git a/lib/Doctrine/Configurable.php b/lib/Doctrine/Configurable.php index a3541a2a7..f37f5dc0f 100644 --- a/lib/Doctrine/Configurable.php +++ b/lib/Doctrine/Configurable.php @@ -69,7 +69,7 @@ abstract class Doctrine_Configurable throw new Doctrine_Exception("Batch size should be greater than or equal to zero"); } break; - + case Doctrine::ATTR_FETCHMODE: if ($value < 0) { throw new Doctrine_Exception("Unknown fetchmode. See Doctrine::FETCH_* constants."); @@ -98,11 +98,11 @@ abstract class Doctrine_Configurable break; case Doctrine::ATTR_ACCESSORS: $accessors = array('none','get','set','both'); - + // if ( ! in_array($value,$accessors)) { // throw new Doctrine_Exception(); // } - + break; case Doctrine::ATTR_COLL_LIMIT: if ($value < 1) { @@ -126,7 +126,7 @@ abstract class Doctrine_Configurable case Doctrine::ATTR_DEFAULT_TABLE_TYPE: case Doctrine::ATTR_ACCESSOR_PREFIX_GET: case Doctrine::ATTR_ACCESSOR_PREFIX_SET: - + break; case Doctrine::ATTR_SEQCOL_NAME: if ( ! is_string($value)) { diff --git a/lib/Doctrine/DataDict/Firebird.php b/lib/Doctrine/DataDict/Firebird.php index dbdd8a6d0..c79ba34b0 100644 --- a/lib/Doctrine/DataDict/Firebird.php +++ b/lib/Doctrine/DataDict/Firebird.php @@ -65,9 +65,9 @@ class Doctrine_DataDict_Firebird extends Doctrine_DataDict case 'text': $length = !empty($field['length']) ? $field['length'] : 16777215; // TODO: $db->options['default_text_field_length']; - + $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; - + return $fixed ? 'CHAR('.$length.')' : 'VARCHAR('.$length.')'; case 'clob': return 'BLOB SUB_TYPE 1'; diff --git a/lib/Doctrine/DataDict/Informix.php b/lib/Doctrine/DataDict/Informix.php index ce6bb0bbc..b11d6cac8 100644 --- a/lib/Doctrine/DataDict/Informix.php +++ b/lib/Doctrine/DataDict/Informix.php @@ -65,10 +65,10 @@ class Doctrine_DataDict_Informix extends Doctrine_DataDict if (empty($field['length']) && array_key_exists('default', $field)) { $field['length'] = $this->conn->varchar_max_length; } - + $length = (! empty($field['length'])) ? $field['length'] : false; $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; - + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)') : ($length ? 'VARCHAR('.$length.')' : 'NVARCHAR'); case 'clob': diff --git a/lib/Doctrine/DataDict/Mssql.php b/lib/Doctrine/DataDict/Mssql.php index 7f8e6fa5e..528c9dde7 100644 --- a/lib/Doctrine/DataDict/Mssql.php +++ b/lib/Doctrine/DataDict/Mssql.php @@ -67,9 +67,9 @@ class Doctrine_DataDict_Mssql extends Doctrine_DataDict case 'string': $length = !empty($field['length']) ? $field['length'] : false; - + $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; - + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); case 'clob': diff --git a/lib/Doctrine/DataDict/Mysql.php b/lib/Doctrine/DataDict/Mysql.php index 91c22e035..89c88796a 100644 --- a/lib/Doctrine/DataDict/Mysql.php +++ b/lib/Doctrine/DataDict/Mysql.php @@ -136,13 +136,13 @@ class Doctrine_DataDict_Mysql extends Doctrine_DataDict switch ($field['type']) { case 'char': $length = (! empty($field['length'])) ? $field['length'] : false; - + return $length ? 'CHAR('.$length.')' : 'CHAR(255)'; case 'varchar': case 'array': case 'object': case 'string': - + if ( ! isset($field['length'])) { if (array_key_exists('default', $field)) { $field['length'] = $this->conn->varchar_max_length; @@ -150,10 +150,10 @@ class Doctrine_DataDict_Mysql extends Doctrine_DataDict $field['length'] = false; } } - + $length = ($field['length'] < $this->conn->varchar_max_length) ? $field['length'] : false; $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; - + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)') : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); case 'clob': diff --git a/lib/Doctrine/DataDict/Oracle.php b/lib/Doctrine/DataDict/Oracle.php index ba6bc5d35..979089ce7 100644 --- a/lib/Doctrine/DataDict/Oracle.php +++ b/lib/Doctrine/DataDict/Oracle.php @@ -62,9 +62,9 @@ class Doctrine_DataDict_Oracle extends Doctrine_DataDict case 'varchar': $length = !empty($field['length']) ? $field['length'] : 16777215; // TODO: $db->options['default_text_field_length']; - + $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; - + return $fixed ? 'CHAR('.$length.')' : 'VARCHAR2('.$length.')'; case 'clob': return 'CLOB'; diff --git a/lib/Doctrine/DataDict/Pgsql.php b/lib/Doctrine/DataDict/Pgsql.php index 848c80d06..0a0172ef2 100644 --- a/lib/Doctrine/DataDict/Pgsql.php +++ b/lib/Doctrine/DataDict/Pgsql.php @@ -366,12 +366,12 @@ class Doctrine_DataDict_Pgsql extends Doctrine_DataDict case 'varchar': $length = (isset($field['length']) && $field['length']) ? $field['length'] : null; // TODO: $db->options['default_text_field_length']; - + $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; - + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); - + case 'clob': return 'TEXT'; case 'blob': diff --git a/lib/Doctrine/DataDict/Sqlite.php b/lib/Doctrine/DataDict/Sqlite.php index ae446fe33..072f5e9ff 100644 --- a/lib/Doctrine/DataDict/Sqlite.php +++ b/lib/Doctrine/DataDict/Sqlite.php @@ -65,9 +65,9 @@ class Doctrine_DataDict_Sqlite extends Doctrine_DataDict case 'gzip': case 'varchar': $length = (isset($field['length']) && $field['length']) ? $field['length'] : null; - + $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; - + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$this->conn->getAttribute(Doctrine::ATTR_DEFAULT_TEXTFLD_LENGTH).')') : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); case 'clob': diff --git a/lib/Doctrine/Db.php b/lib/Doctrine/Db.php index adb01f602..8e4147037 100644 --- a/lib/Doctrine/Db.php +++ b/lib/Doctrine/Db.php @@ -252,7 +252,7 @@ class Doctrine_Db implements Countable, IteratorAggregate, Doctrine_Adapter_Inte $parts['database'] = ':memory:'; $parts['dsn'] = 'sqlite::memory:'; } - + break; case 'mysql': case 'informix': diff --git a/lib/Doctrine/Query.php b/lib/Doctrine/Query.php index 911238b12..2092d0690 100644 --- a/lib/Doctrine/Query.php +++ b/lib/Doctrine/Query.php @@ -1,1388 +1,1388 @@ -. - */ -Doctrine::autoload('Doctrine_Hydrate'); -/** - * Doctrine_Query - * - * @package Doctrine - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @category Object Relational Mapping - * @link www.phpdoctrine.com - * @since 1.0 - * @version $Revision$ - * @author Konsta Vesterinen - */ -class Doctrine_Query extends Doctrine_Hydrate implements Countable { - /** - * QUERY TYPE CONSTANTS - */ - - /** - * constant for SELECT queries - */ - const SELECT = 0; - /** - * constant for DELETE queries - */ - const DELETE = 1; - /** - * constant for UPDATE queries - */ - const UPDATE = 2; - /** - * @param array $subqueryAliases the table aliases needed in some LIMIT subqueries - */ - private $subqueryAliases = array(); - /** - * @param boolean $needsSubquery - */ - private $needsSubquery = false; - /** - * @param boolean $limitSubqueryUsed - */ - private $limitSubqueryUsed = false; - - private $tableStack; - - private $relationStack = array(); - - private $isDistinct = false; - /** - * @var array $pendingFields - */ - private $pendingFields = array(); - /** - * @var integer $type the query type - * - * @see Doctrine_Query::* constants - */ - protected $type = self::SELECT; - - /** - * create - * returns a new Doctrine_Query object - * - * @return Doctrine_Query - */ - public static function create() - { - return new Doctrine_Query(); - } - - public function getTableStack() - { - return $this->tableStack; - } - - public function getRelationStack() - { - return $this->relationStack; - } - - public function isDistinct($distinct = null) - { - if(isset($distinct)) - $this->isDistinct = (bool) $distinct; - - return $this->isDistinct; - } - - public function processPendingFields($componentAlias) - { - $tableAlias = $this->getTableAlias($componentAlias); - - if( ! isset($this->tables[$tableAlias])) - throw new Doctrine_Query_Exception('Unknown component path '.$componentPath); - - $table = $this->tables[$tableAlias]; - - if(isset($this->pendingFields[$componentAlias])) { - $fields = $this->pendingFields[$componentAlias]; - - if(in_array('*', $fields)) - $fields = $table->getColumnNames(); - else - $fields = array_unique(array_merge($table->getPrimaryKeys(), $fields)); - } - foreach($fields as $name) { - $this->parts["select"][] = $tableAlias . '.' .$name . ' AS ' . $tableAlias . '__' . $name; - } - - } - public function parseSelect($dql) - { - $refs = Doctrine_Query::bracketExplode($dql, ','); - - foreach($refs as $reference) { - if(strpos($reference, '(') !== false) { - $this->parseAggregateFunction2($reference); - } else { - - $e = explode('.', $reference); - if(count($e) > 2) - $this->pendingFields[] = $reference; - else - $this->pendingFields[$e[0]][] = $e[1]; - } - } - } - public function parseAggregateFunction2($func) - { - $e = Doctrine_Query::bracketExplode($func, ' '); - $func = $e[0]; - - $pos = strpos($func, '('); - $name = substr($func, 0, $pos); - - - if(method_exists($this->conn->expression, $name)) { - - $argStr = substr($func, ($pos + 1), -1); - - $args = explode(',', $argStr); - - $e2 = explode(' ', $args[0]); - - $distinct = ''; - if(count($e2) > 1) { - if(strtoupper($e2[0]) == 'DISTINCT') - $distinct = 'DISTINCT '; - - $args[0] = $e2[1]; - } - - - - $parts = explode('.', $args[0]); - $owner = $parts[0]; - $alias = (isset($e[1])) ? $e[1] : $name; - - $e3 = explode('.', $alias); - - if(count($e3) > 1) { - $alias = $e3[1]; - $owner = $e3[0]; - } - - $this->pendingAggregates[$owner][] = array($name, $args, $distinct, $alias); - } else { - throw new Doctrine_Query_Exception('Unknown aggregate function '.$name); - } - } - public function processPendingAggregates($componentAlias) - { - $tableAlias = $this->getTableAlias($componentAlias); - - if( ! isset($this->tables[$tableAlias])) - throw new Doctrine_Query_Exception('Unknown component path '.$componentPath); - - $table = $this->tables[$tableAlias]; - - foreach($this->pendingAggregates[$componentAlias] as $parts) { - list($name, $args, $distinct, $alias) = $parts; - - $arglist = array(); - foreach($args as $arg) { - $e = explode('.', $arg); - - - if(count($e) > 1) { - $tableAlias = $this->getTableAlias($e[0]); - $table = $this->tables[$tableAlias]; - - if( ! $table->hasColumn($e[1])) { - throw new Doctrine_Query_Exception('Unknown column ' . $e[1]); - } - - $arglist[] = $tableAlias . '.' . $e[1]; - } else { - $arglist[] = $e[0]; - } - } - - $this->parts['select'][] = $name . '(' . $distinct . implode(', ', $arglist) . ') AS ' . $tableAlias . '__' . count($this->aggregateMap); - - $this->aggregateMap[] = $table; - } - } - /** - * count - * - * @param array $params - * @return integer - */ - public function count($params = array()) - { - $this->remove('select'); - $join = $this->join; - $where = $this->where; - $having = $this->having; - $table = reset($this->tables); - - $q = 'SELECT COUNT(DISTINCT ' . $this->aliasHandler->getShortAlias($table->getTableName()) - . '.' . $table->getIdentifier() - . ') FROM ' . $table->getTableName() . ' ' . $this->aliasHandler->getShortAlias($table->getTableName()); - - foreach($join as $j) { - $q .= ' '.implode(' ',$j); - } - $string = $this->applyInheritance(); - - if( ! empty($where)) { - $q .= ' WHERE ' . implode(' AND ', $where); - if( ! empty($string)) - $q .= ' AND (' . $string . ')'; - } else { - if( ! empty($string)) - $q .= ' WHERE (' . $string . ')'; - } - - if( ! empty($having)) - $q .= ' HAVING ' . implode(' AND ',$having); - - if( ! is_array($params)) - $params = array($params); - - $params = array_merge($this->params, $params); - - $a = $this->getConnection()->execute($q, $params)->fetch(PDO::FETCH_NUM); - return $a[0]; - } - /** - * loadFields - * loads fields for a given table and - * constructs a little bit of sql for every field - * - * fields of the tables become: [tablename].[fieldname] as [tablename]__[fieldname] - * - * @access private - * @param object Doctrine_Table $table a Doctrine_Table object - * @param integer $fetchmode fetchmode the table is using eg. Doctrine::FETCH_LAZY - * @param array $names fields to be loaded (only used in lazy property loading) - * @return void - */ - protected function loadFields(Doctrine_Table $table, $fetchmode, array $names, $cpath) - { - $name = $table->getComponentName(); - - switch($fetchmode): - case Doctrine::FETCH_OFFSET: - $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT); - case Doctrine::FETCH_IMMEDIATE: - if( ! empty($names)) - $names = array_unique(array_merge($table->getPrimaryKeys(), $names)); - else - $names = $table->getColumnNames(); - break; - case Doctrine::FETCH_LAZY_OFFSET: - $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT); - case Doctrine::FETCH_LAZY: - case Doctrine::FETCH_BATCH: - $names = array_unique(array_merge($table->getPrimaryKeys(), $names)); - break; - default: - throw new Doctrine_Exception("Unknown fetchmode."); - endswitch; - - $component = $table->getComponentName(); - $tablename = $this->tableAliases[$cpath]; - - $this->fetchModes[$tablename] = $fetchmode; - - $count = count($this->tables); - - foreach($names as $name) { - if($count == 0) { - $this->parts['select'][] = $tablename . '.' . $name; - } else { - $this->parts['select'][] = $tablename . '.' . $name . ' AS ' . $tablename . '__' . $name; - } - } - } - /** - * addFrom - * - * @param strint $from - * @return Doctrine_Query - */ - public function addFrom($from) - { - $class = 'Doctrine_Query_From'; - $parser = new $class($this); - $parser->parse($from); - - return $this; - } - /** - * leftJoin - * - * @param strint $join - * @return Doctrine_Query - */ - public function leftJoin($join) - { - $class = 'Doctrine_Query_From'; - $parser = new $class($this); - $parser->parse('LEFT JOIN ' . $join); - - return $this; - } - /** - * innerJoin - * - * @param strint $join - * @return Doctrine_Query - */ - public function innerJoin($join) - { - $class = 'Doctrine_Query_From'; - $parser = new $class($this); - $parser->parse('INNER JOIN ' . $join); - - return $this; - } - /** - * addOrderBy - * - * @param strint $orderby - * @return Doctrine_Query - */ - public function addOrderBy($orderby) - { - $class = 'Doctrine_Query_Orderby'; - $parser = new $class($this); - $this->parts['orderby'][] = $parser->parse($orderby); - - return $this; - } - /** - * addWhere - * - * @param string $where - * @param mixed $params - */ - public function addWhere($where, $params = array()) - { - $class = 'Doctrine_Query_Where'; - $parser = new $class($this); - $this->parts['where'][] = $parser->parse($where); - - if(is_array($params)) { - $this->params = array_merge($this->params, $params); - } else { - $this->params[] = $params; - } - } - /** - * sets a query part - * - * @param string $name - * @param array $args - * @return void - */ - public function __call($name, $args) - { - $name = strtolower($name); - - $method = 'parse' . ucwords($name); - - switch($name) { - case 'select': - $this->type = self::SELECT; - - if( ! isset($args[0])) - throw new Doctrine_Query_Exception('Empty select part'); - - $this->parseSelect($args[0]); - break; - case 'delete': - $this->type = self::DELETE; - break; - case 'update': - $this->type = self::UPDATE; - $name = 'from'; - case 'from': - $this->parts['from'] = array(); - $this->parts['select'] = array(); - $this->parts['join'] = array(); - $this->joins = array(); - $this->tables = array(); - $this->fetchModes = array(); - $this->tableIndexes = array(); - $this->tableAliases = array(); - $this->aliasHandler->clear(); - - $class = "Doctrine_Query_".ucwords($name); - $parser = new $class($this); - - $parser->parse($args[0]); - break; - case 'where': - if(isset($args[1])) { - if(is_array($args[1])) { - $this->params = $args[1]; - } else { - $this->params = array($args[1]); - } - } - case 'having': - case 'orderby': - case 'groupby': - $class = "Doctrine_Query_".ucwords($name); - $parser = new $class($this); - - $this->parts[$name] = array($parser->parse($args[0])); - break; - case 'limit': - case 'offset': - if($args[0] == null) - $args[0] = false; - - $this->parts[$name] = $args[0]; - break; - default: - $this->parts[$name] = array(); - $this->$method($args[0]); - - throw new Doctrine_Query_Exception("Unknown overload method"); - } - - - return $this; - } - /** - * returns a query part - * - * @param $name query part name - * @return mixed - */ - public function get($name) - { - if( ! isset($this->parts[$name])) - return false; - - return $this->parts[$name]; - } - /** - * set - * sets a query SET part - * this method should only be used with UPDATE queries - * - * @param $name name of the field - * @param $value field value - * @return Doctrine_Query - */ - public function set($name, $value) - { - $class = new Doctrine_Query_Set($this); - $this->parts['set'][] = $class->parse($name . ' = ' . $value); - - return $this; - } - /** - * @return boolean - */ - public function isLimitSubqueryUsed() { - return $this->limitSubqueryUsed; - } - /** - * getQueryBase - * returns the base of the generated sql query - * On mysql driver special strategy has to be used for DELETE statements - * - * @return string the base of the generated sql query - */ - public function getQueryBase() - { - switch($this->type) { - case self::DELETE: - if($this->conn->getName() == 'mysql') - $q = 'DELETE '.end($this->tableAliases).' FROM '; - else - $q = 'DELETE FROM '; - break; - case self::UPDATE: - $q = 'UPDATE '; - break; - case self::SELECT: - $distinct = ($this->isDistinct()) ? 'DISTINCT ' : ''; - - $q = 'SELECT '.$distinct.implode(', ', $this->parts['select']).' FROM '; - break; - } - return $q; - } - /** - * builds the sql query from the given parameters and applies things such as - * column aggregation inheritance and limit subqueries if needed - * - * @param array $params an array of prepared statement params (needed only in mysql driver - * when limit subquery algorithm is used) - * @return string the built sql query - */ - public function getQuery($params = array()) - { - if(empty($this->parts["select"]) || empty($this->parts["from"])) - return false; - - $needsSubQuery = false; - $subquery = ''; - $k = array_keys($this->tables); - $table = $this->tables[$k[0]]; - - if( ! empty($this->parts['limit']) && $this->needsSubquery && $table->getAttribute(Doctrine::ATTR_QUERY_LIMIT) == Doctrine::LIMIT_RECORDS) { - $needsSubQuery = true; - $this->limitSubqueryUsed = true; - } - - // build the basic query - - $str = ''; - if($this->isDistinct()) - $str = 'DISTINCT '; - - $q = $this->getQueryBase(); - - $q .= $this->parts['from']; - - if( ! empty($this->parts['join'])) { - foreach($this->parts['join'] as $part) { - $q .= ' '.implode(' ', $part); - } - } - - if( ! empty($this->parts['set'])) { - $q .= ' SET ' . implode(', ', $this->parts['set']); - } - - $string = $this->applyInheritance(); - - if( ! empty($string)) - $this->parts['where'][] = '('.$string.')'; - - - - $modifyLimit = true; - if( ! empty($this->parts["limit"]) || ! empty($this->parts["offset"])) { - - if($needsSubQuery) { - $subquery = $this->getLimitSubquery(); - - - switch(strtolower($this->conn->getName())) { - case 'mysql': - // mysql doesn't support LIMIT in subqueries - $params = array_merge($this->params, $params); - $list = $this->conn->execute($subquery, $params)->fetchAll(PDO::FETCH_COLUMN); - $subquery = implode(', ', $list); - break; - case 'pgsql': - // pgsql needs special nested LIMIT subquery - $subquery = 'SELECT doctrine_subquery_alias.' . $table->getIdentifier(). ' FROM (' . $subquery . ') AS doctrine_subquery_alias'; - break; - } - - $field = $this->aliasHandler->getShortAlias($table->getTableName()) . '.' . $table->getIdentifier(); - - // only append the subquery if it actually contains something - if($subquery !== '') - array_unshift($this->parts['where'], $field. ' IN (' . $subquery . ')'); - - $modifyLimit = false; - } - } - - $q .= ( ! empty($this->parts['where']))? ' WHERE ' . implode(' AND ', $this->parts['where']):''; - $q .= ( ! empty($this->parts['groupby']))? ' GROUP BY ' . implode(', ', $this->parts['groupby']):''; - $q .= ( ! empty($this->parts['having']))? ' HAVING ' . implode(' AND ', $this->parts['having']):''; - $q .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(', ', $this->parts['orderby']):''; - - if($modifyLimit) - $q = $this->conn->modifyLimitQuery($q, $this->parts['limit'], $this->parts['offset']); - - // return to the previous state - if( ! empty($string)) - array_pop($this->parts['where']); - if($needsSubQuery) - array_shift($this->parts['where']); - - return $q; - } - /** - * this is method is used by the record limit algorithm - * - * when fetching one-to-many, many-to-many associated data with LIMIT clause - * an additional subquery is needed for limiting the number of returned records instead - * of limiting the number of sql result set rows - * - * @return string the limit subquery - */ - public function getLimitSubquery() - { - $k = array_keys($this->tables); - $table = $this->tables[$k[0]]; - - // get short alias - $alias = $this->aliasHandler->getShortAlias($table->getTableName()); - $primaryKey = $alias . '.' . $table->getIdentifier(); - - // initialize the base of the subquery - $subquery = 'SELECT DISTINCT ' . $primaryKey; - - if($this->conn->getDBH()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') { - // pgsql needs the order by fields to be preserved in select clause - - foreach($this->parts['orderby'] as $part) { - $e = explode(' ', $part); - - // don't add primarykey column (its already in the select clause) - if($e[0] !== $primaryKey) - $subquery .= ', ' . $e[0]; - } - } - - $subquery .= ' FROM ' . $this->conn->quoteIdentifier($table->getTableName()) . ' ' . $alias; - - foreach($this->parts['join'] as $parts) { - foreach($parts as $part) { - // preserve LEFT JOINs only if needed - if(substr($part,0,9) === 'LEFT JOIN') { - $e = explode(' ', $part); - - if( ! in_array($e[3], $this->subqueryAliases) && - ! in_array($e[2], $this->subqueryAliases)) { - continue; - } - - } - - $subquery .= ' '.$part; - } - } - - - // all conditions must be preserved in subquery - $subquery .= ( ! empty($this->parts['where']))? ' WHERE ' . implode(' AND ',$this->parts['where']):''; - $subquery .= ( ! empty($this->parts['groupby']))? ' GROUP BY ' . implode(', ',$this->parts['groupby']):''; - $subquery .= ( ! empty($this->parts['having']))? ' HAVING ' . implode(' AND ',$this->parts['having']):''; - $subquery .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(', ', $this->parts['orderby']):''; - - // add driver specific limit clause - $subquery = $this->conn->modifyLimitQuery($subquery, $this->parts['limit'], $this->parts['offset']); - - $parts = self::quoteExplode($subquery, ' ', "'", "'"); - - foreach($parts as $k => $part) { - if(strpos($part, "'") !== false) { - continue; - } - - if($this->aliasHandler->hasAliasFor($part)) { - $parts[$k] = $this->aliasHandler->generateNewAlias($part); - } - - if(strpos($part, '.') !== false) { - $e = explode('.', $part); - - $trimmed = ltrim($e[0], '( '); - $pos = strpos($e[0], $trimmed); - - $e[0] = substr($e[0], 0, $pos) . $this->aliasHandler->generateNewAlias($trimmed); - $parts[$k] = implode('.', $e); - } - } - $subquery = implode(' ', $parts); - - return $subquery; - } - /** - * query the database with DQL (Doctrine Query Language) - * - * @param string $query DQL query - * @param array $params parameters - */ - public function query($query,$params = array()) - { - $this->parseQuery($query); - - if($this->aggregate) { - $keys = array_keys($this->tables); - $query = $this->getQuery(); - $stmt = $this->tables[$keys[0]]->getConnection()->select($query, $this->parts["limit"], $this->parts["offset"]); - $data = $stmt->fetch(PDO::FETCH_ASSOC); - if(count($data) == 1) { - return current($data); - } else { - return $data; - } - } else { - return $this->execute($params); - } - } - /** - * splitQuery - * splits the given dql query into an array where keys - * represent different query part names and values are - * arrays splitted using sqlExplode method - * - * example: - * - * parameter: - * $query = "SELECT u.* FROM User u WHERE u.name LIKE ?" - * returns: - * array('select' => array('u.*'), - * 'from' => array('User', 'u'), - * 'where' => array('u.name', 'LIKE', '?')) - * - * @param string $query DQL query - * @throws Doctrine_Query_Exception if some generic parsing error occurs - * @return array an array containing the query string parts - */ - public function splitQuery($query) - { - $e = self::sqlExplode($query, ' '); - - foreach($e as $k=>$part) { - $part = trim($part); - switch(strtolower($part)) { - case 'delete': - case 'update': - case 'select': - case 'set': - case 'from': - case 'where': - case 'limit': - case 'offset': - case 'having': - $p = $part; - $parts[$part] = array(); - break; - case 'order': - case 'group': - $i = ($k + 1); - if(isset($e[$i]) && strtolower($e[$i]) === "by") { - $p = $part; - $parts[$part] = array(); - } else - $parts[$p][] = $part; - break; - case "by": - continue; - default: - if( ! isset($p)) - throw new Doctrine_Query_Exception("Couldn't parse query."); - - $parts[$p][] = $part; - } - } - return $parts; - } - /** - * DQL PARSER - * parses a DQL query - * first splits the query in parts and then uses individual - * parsers for each part - * - * @param string $query DQL query - * @param boolean $clear whether or not to clear the aliases - * @throws Doctrine_Query_Exception if some generic parsing error occurs - * @return Doctrine_Query - */ - public function parseQuery($query, $clear = true) - { - if($clear) - $this->clear(); - - $query = trim($query); - $query = str_replace("\n", ' ', $query); - $query = str_replace("\r", ' ', $query); - - $parts = $this->splitQuery($query); - - foreach($parts as $k => $part) { - $part = implode(" ",$part); - switch(strtoupper($k)) { - case 'DELETE': - $this->type = self::DELETE; - break; - case 'SELECT': - $this->type = self::SELECT; - $this->parseSelect($part); - break; - case 'UPDATE': - $this->type = self::UPDATE; - $k = 'FROM'; - - case 'FROM': - $class = 'Doctrine_Query_' . ucwords(strtolower($k)); - $parser = new $class($this); - $parser->parse($part); - break; - case 'SET': - $class = 'Doctrine_Query_' . ucwords(strtolower($k)); - $parser = new $class($this); - $this->parts['set'][] = $parser->parse($part); - break; - case 'GROUP': - case 'ORDER': - $k .= 'by'; - case 'WHERE': - case 'HAVING': - $class = 'Doctrine_Query_' . ucwords(strtolower($k)); - $parser = new $class($this); - - $name = strtolower($k); - $this->parts[$name][] = $parser->parse($part); - break; - case 'LIMIT': - $this->parts['limit'] = trim($part); - break; - case 'OFFSET': - $this->parts['offset'] = trim($part); - break; - } - } - - return $this; - } - /** - * DQL ORDER BY PARSER - * parses the order by part of the query string - * - * @param string $str - * @return void - */ - final public function parseOrderBy($str) - { - $parser = new Doctrine_Query_Part_Orderby($this); - return $parser->parse($str); - } - /** - * returns Doctrine::FETCH_* constant - * - * @param string $mode - * @return integer - */ - final public function parseFetchMode($mode) - { - switch(strtolower($mode)): - case "i": - case "immediate": - $fetchmode = Doctrine::FETCH_IMMEDIATE; - break; - case "b": - case "batch": - $fetchmode = Doctrine::FETCH_BATCH; - break; - case "l": - case "lazy": - $fetchmode = Doctrine::FETCH_LAZY; - break; - case "o": - case "offset": - $fetchmode = Doctrine::FETCH_OFFSET; - break; - case "lo": - case "lazyoffset": - $fetchmode = Doctrine::FETCH_LAZYOFFSET; - default: - throw new Doctrine_Query_Exception("Unknown fetchmode '$mode'. The availible fetchmodes are 'i', 'b' and 'l'."); - endswitch; - return $fetchmode; - } - /** - * trims brackets - * - * @param string $str - * @param string $e1 the first bracket, usually '(' - * @param string $e2 the second bracket, usually ')' - */ - public static function bracketTrim($str,$e1 = '(',$e2 = ')') - { - if(substr($str,0,1) == $e1 && substr($str,-1) == $e2) - return substr($str,1,-1); - else - return $str; - } - /** - * bracketExplode - * - * example: - * - * parameters: - * $str = (age < 20 AND age > 18) AND email LIKE 'John@example.com' - * $d = ' AND ' - * $e1 = '(' - * $e2 = ')' - * - * would return an array: - * array("(age < 20 AND age > 18)", - * "email LIKE 'John@example.com'") - * - * @param string $str - * @param string $d the delimeter which explodes the string - * @param string $e1 the first bracket, usually '(' - * @param string $e2 the second bracket, usually ')' - * - */ - public static function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')') - { - if(is_array($d)) { - $a = preg_split('/('.implode('|', $d).')/', $str); - $d = stripslashes($d[0]); - } else - $a = explode("$d",$str); - - $i = 0; - $term = array(); - foreach($a as $key=>$val) { - if (empty($term[$i])) { - $term[$i] = trim($val); - $s1 = substr_count($term[$i], "$e1"); - $s2 = substr_count($term[$i], "$e2"); - if($s1 == $s2) $i++; - } else { - $term[$i] .= "$d".trim($val); - $c1 = substr_count($term[$i], "$e1"); - $c2 = substr_count($term[$i], "$e2"); - if($c1 == $c2) $i++; - } - } - return $term; - } - /** - * quoteExplode - * - * example: - * - * parameters: - * $str = email LIKE 'John@example.com' - * $d = ' AND ' - * $e1 = '(' - * $e2 = ')' - * - * would return an array: - * array("email", "LIKE", "'John@example.com'") - * - * @param string $str - * @param string $d the delimeter which explodes the string - */ - public static function quoteExplode($str, $d = ' ') - { - if(is_array($d)) { - $a = preg_split('/('.implode('|', $d).')/', $str); - $d = stripslashes($d[0]); - } else - $a = explode("$d",$str); - - $i = 0; - $term = array(); - foreach($a as $key => $val) { - if (empty($term[$i])) { - $term[$i] = trim($val); - - if( ! (substr_count($term[$i], "'") & 1)) - $i++; - } else { - $term[$i] .= "$d".trim($val); - - if( ! (substr_count($term[$i], "'") & 1)) - $i++; - } - } - return $term; - } - /** - * sqlExplode - * - * explodes a string into array using custom brackets and - * quote delimeters - * - * - * example: - * - * parameters: - * $str = "(age < 20 AND age > 18) AND name LIKE 'John Doe'" - * $d = ' ' - * $e1 = '(' - * $e2 = ')' - * - * would return an array: - * array('(age < 20 AND age > 18)', - * 'name', - * 'LIKE', - * 'John Doe') - * - * @param string $str - * @param string $d the delimeter which explodes the string - * @param string $e1 the first bracket, usually '(' - * @param string $e2 the second bracket, usually ')' - * - * @return array - */ - public static function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')') - { - if(is_array($d)) { - $str = preg_split('/('.implode('|', $d).')/', $str); - $d = stripslashes($d[0]); - } else - $str = explode("$d",$str); - - $i = 0; - $term = array(); - foreach($str as $key => $val) { - if (empty($term[$i])) { - $term[$i] = trim($val); - - $s1 = substr_count($term[$i],"$e1"); - $s2 = substr_count($term[$i],"$e2"); - - if(substr($term[$i],0,1) == "(") { - if($s1 == $s2) { - $i++; - } - } else { - if( ! (substr_count($term[$i], "'") & 1) && - ! (substr_count($term[$i], "\"") & 1) && - ! (substr_count($term[$i], "´") & 1) - ) { $i++; } - } - } else { - $term[$i] .= "$d".trim($val); - $c1 = substr_count($term[$i],"$e1"); - $c2 = substr_count($term[$i],"$e2"); - - if(substr($term[$i],0,1) == "(") { - if($c1 == $c2) { - $i++; - } - } else { - if( ! (substr_count($term[$i], "'") & 1) && - ! (substr_count($term[$i], "\"") & 1) && - ! (substr_count($term[$i], "´") & 1) - ) { $i++; } - } - } - } - return $term; - } - /** - * generateAlias - * - * @param string $tableName - * @return string - */ - public function generateAlias($tableName) - { - if(isset($this->tableIndexes[$tableName])) { - return $tableName.++$this->tableIndexes[$tableName]; - } else { - $this->tableIndexes[$tableName] = 1; - return $tableName; - } - } - - /** - * loads a component - * - * @param string $path the path of the loadable component - * @param integer $fetchmode optional fetchmode, if not set the components default fetchmode will be used - * @throws Doctrine_Query_Exception - * @return Doctrine_Table - */ - final public function load($path, $loadFields = true) - { - $tmp = explode(' ',$path); - $componentAlias = (count($tmp) > 1) ? end($tmp) : false; - - $e = preg_split("/[.:]/", $tmp[0], -1); - - - if(isset($this->compAliases[$e[0]])) { - $end = substr($tmp[0], strlen($e[0])); - $path = $this->compAliases[$e[0]] . $end; - $e = preg_split("/[.:]/", $path, -1); - } else { - $path = $tmp[0]; - } - - - - $index = 0; - $currPath = ''; - $this->tableStack = array(); - - foreach($e as $key => $fullname) { - try { - $e2 = preg_split("/[-(]/",$fullname); - $name = $e2[0]; - - $currPath .= '.' . $name; - - if($key == 0) { - $currPath = substr($currPath,1); - - $this->conn = Doctrine_Manager::getInstance() - ->getConnectionForComponent($name); - - $table = $this->conn->getTable($name); - - - $tname = $this->aliasHandler->getShortAlias($table->getTableName()); - - if( ! isset($this->tableAliases[$currPath])) { - $this->tableIndexes[$tname] = 1; - } - - - $this->parts["from"] = $this->conn->quoteIdentifier($table->getTableName()) . ' ' . $tname; - - $this->tableAliases[$currPath] = $tname; - - $tableName = $tname; - - } else { - - $index += strlen($e[($key - 1)]) + 1; - // the mark here is either '.' or ':' - $mark = substr($path,($index - 1),1); - - if(isset($this->tableAliases[$prevPath])) { - $tname = $this->tableAliases[$prevPath]; - } else { - $tname = $this->aliasHandler->getShortAlias($table->getTableName()); - } - - $fk = $table->getRelation($name); - $name = $fk->getTable()->getComponentName(); - $original = $fk->getTable()->getTableName(); - - - - if(isset($this->tableAliases[$currPath])) { - $tname2 = $this->tableAliases[$currPath]; - } else - $tname2 = $this->aliasHandler->generateShortAlias($original); - - $aliasString = $this->conn->quoteIdentifier($original) . ' ' . $tname2; - - switch($mark) { - case ':': - $join = 'INNER JOIN '; - break; - case '.': - $join = 'LEFT JOIN '; - break; - default: - throw new Doctrine_Exception("Unknown operator '$mark'"); - } - - if( ! $fk->isOneToOne()) { - $this->needsSubquery = true; - } - - if( ! $loadFields || $fk->getTable()->usesInheritanceMap()) { - $this->subqueryAliases[] = $tname2; - } - - if($fk instanceof Doctrine_Relation_Association) { - $asf = $fk->getAssociationFactory(); - - $assocTableName = $asf->getTableName(); - - if( ! $loadFields) { - $this->subqueryAliases[] = $assocTableName; - } - $this->parts["join"][$tname][$assocTableName] = $join . $assocTableName . ' ON ' . $tname . '.' - . $table->getIdentifier() . ' = ' - . $assocTableName . '.' . $fk->getLocal(); - - $this->parts["join"][$tname][$tname2] = $join . $aliasString . ' ON ' . $tname2 . '.' - . $fk->getTable()->getIdentifier() . ' = ' - . $assocTableName . '.' . $fk->getForeign(); - - } else { - $this->parts["join"][$tname][$tname2] = $join . $aliasString - . ' ON ' . $tname . '.' - . $fk->getLocal() . ' = ' . $tname2 . '.' . $fk->getForeign(); - } - - - $this->joins[$tname2] = $prevTable; - - - $table = $fk->getTable(); - - $this->tableAliases[$currPath] = $tname2; - - $tableName = $tname2; - - $this->relationStack[] = $fk; - } - - $this->components[$currPath] = $table; - - $this->tableStack[] = $table; - - if( ! isset($this->tables[$tableName])) { - $this->tables[$tableName] = $table; - - if($loadFields) { - - $skip = false; - - if( ! empty($this->pendingFields)) - $skip = true; - - if($componentAlias) { - $this->compAliases[$componentAlias] = $currPath; - - if(isset($this->pendingFields[$componentAlias])) { - $this->processPendingFields($componentAlias); - $skip = true; - } - if(isset($this->pendingAggregates[$componentAlias])) { - $this->processPendingAggregates($componentAlias); - $skip = true; - } - } - - if( ! $skip) { - $this->parseFields($fullname, $tableName, $e2, $currPath); - } - } - } - - - $prevPath = $currPath; - $prevTable = $tableName; - } catch(Exception $e) { - throw new Doctrine_Query_Exception($e->__toString()); - } - } - - if($componentAlias !== false) { - $this->compAliases[$componentAlias] = $currPath; - } - - return $table; - } - /** - * parseFields - * - * @param string $fullName - * @param string $tableName - * @param array $exploded - * @param string $currPath - * @return void - */ - final public function parseFields($fullName, $tableName, array $exploded, $currPath) - { - $table = $this->tables[$tableName]; - - $fields = array(); - - if(strpos($fullName, '-') === false) { - $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); - - if(isset($exploded[1])) { - if(count($exploded) > 2) { - $fields = $this->parseAggregateValues($fullName, $tableName, $exploded, $currPath); - } elseif(count($exploded) == 2) { - $fields = explode(',',substr($exploded[1],0,-1)); - } - } - } else { - if(isset($exploded[1])) { - $fetchmode = $this->parseFetchMode($exploded[1]); - } else - $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); - - if(isset($exploded[2])) { - if(substr_count($exploded[2], ')') > 1) { - - } else { - $fields = explode(',', substr($exploded[2],0,-1)); - } - } - - } - if( ! $this->aggregate) - $this->loadFields($table, $fetchmode, $fields, $currPath); - } - /** - * parseAggregateFunction - * - * @param string $func - * @param string $reference - * @return string - */ - public function parseAggregateFunction($func,$reference) - { - $pos = strpos($func, '('); - - if($pos !== false) { - $funcs = array(); - - $name = substr($func, 0, $pos); - $func = substr($func, ($pos + 1), -1); - $params = Doctrine_Query::bracketExplode($func, ',', '(', ')'); - - foreach($params as $k => $param) { - $params[$k] = $this->parseAggregateFunction($param,$reference); - } - - $funcs = $name . '(' . implode(', ', $params). ')'; - - return $funcs; - - } else { - if( ! is_numeric($func)) { - - $func = $this->getTableAlias($reference).'.'.$func; - - return $func; - } else { - - return $func; - } - } - } - /** - * parseAggregateValues - */ - public function parseAggregateValues($fullName, $tableName, array $exploded, $currPath) - { - $this->aggregate = true; - $pos = strpos($fullName, '('); - $name = substr($fullName, 0, $pos); - $string = substr($fullName, ($pos + 1), -1); - - $exploded = Doctrine_Query::bracketExplode($string, ','); - foreach($exploded as $k => $value) { - $func = $this->parseAggregateFunction($value, $currPath); - $exploded[$k] = $func; - - $this->parts['select'][] = $exploded[$k]; - } - } -} - +. + */ +Doctrine::autoload('Doctrine_Hydrate'); +/** + * Doctrine_Query + * + * @package Doctrine + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @category Object Relational Mapping + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision$ + * @author Konsta Vesterinen + */ +class Doctrine_Query extends Doctrine_Hydrate implements Countable { + /** + * QUERY TYPE CONSTANTS + */ + + /** + * constant for SELECT queries + */ + const SELECT = 0; + /** + * constant for DELETE queries + */ + const DELETE = 1; + /** + * constant for UPDATE queries + */ + const UPDATE = 2; + /** + * @param array $subqueryAliases the table aliases needed in some LIMIT subqueries + */ + private $subqueryAliases = array(); + /** + * @param boolean $needsSubquery + */ + private $needsSubquery = false; + /** + * @param boolean $limitSubqueryUsed + */ + private $limitSubqueryUsed = false; + + private $tableStack; + + private $relationStack = array(); + + private $isDistinct = false; + /** + * @var array $pendingFields + */ + private $pendingFields = array(); + /** + * @var integer $type the query type + * + * @see Doctrine_Query::* constants + */ + protected $type = self::SELECT; + + /** + * create + * returns a new Doctrine_Query object + * + * @return Doctrine_Query + */ + public static function create() + { + return new Doctrine_Query(); + } + + public function getTableStack() + { + return $this->tableStack; + } + + public function getRelationStack() + { + return $this->relationStack; + } + + public function isDistinct($distinct = null) + { + if(isset($distinct)) + $this->isDistinct = (bool) $distinct; + + return $this->isDistinct; + } + + public function processPendingFields($componentAlias) + { + $tableAlias = $this->getTableAlias($componentAlias); + + if( ! isset($this->tables[$tableAlias])) + throw new Doctrine_Query_Exception('Unknown component path '.$componentPath); + + $table = $this->tables[$tableAlias]; + + if(isset($this->pendingFields[$componentAlias])) { + $fields = $this->pendingFields[$componentAlias]; + + if(in_array('*', $fields)) + $fields = $table->getColumnNames(); + else + $fields = array_unique(array_merge($table->getPrimaryKeys(), $fields)); + } + foreach($fields as $name) { + $this->parts["select"][] = $tableAlias . '.' .$name . ' AS ' . $tableAlias . '__' . $name; + } + + } + public function parseSelect($dql) + { + $refs = Doctrine_Query::bracketExplode($dql, ','); + + foreach($refs as $reference) { + if(strpos($reference, '(') !== false) { + $this->parseAggregateFunction2($reference); + } else { + + $e = explode('.', $reference); + if(count($e) > 2) + $this->pendingFields[] = $reference; + else + $this->pendingFields[$e[0]][] = $e[1]; + } + } + } + public function parseAggregateFunction2($func) + { + $e = Doctrine_Query::bracketExplode($func, ' '); + $func = $e[0]; + + $pos = strpos($func, '('); + $name = substr($func, 0, $pos); + + + if(method_exists($this->conn->expression, $name)) { + + $argStr = substr($func, ($pos + 1), -1); + + $args = explode(',', $argStr); + + $e2 = explode(' ', $args[0]); + + $distinct = ''; + if(count($e2) > 1) { + if(strtoupper($e2[0]) == 'DISTINCT') + $distinct = 'DISTINCT '; + + $args[0] = $e2[1]; + } + + + + $parts = explode('.', $args[0]); + $owner = $parts[0]; + $alias = (isset($e[1])) ? $e[1] : $name; + + $e3 = explode('.', $alias); + + if(count($e3) > 1) { + $alias = $e3[1]; + $owner = $e3[0]; + } + + $this->pendingAggregates[$owner][] = array($name, $args, $distinct, $alias); + } else { + throw new Doctrine_Query_Exception('Unknown aggregate function '.$name); + } + } + public function processPendingAggregates($componentAlias) + { + $tableAlias = $this->getTableAlias($componentAlias); + + if( ! isset($this->tables[$tableAlias])) + throw new Doctrine_Query_Exception('Unknown component path '.$componentPath); + + $table = $this->tables[$tableAlias]; + + foreach($this->pendingAggregates[$componentAlias] as $parts) { + list($name, $args, $distinct, $alias) = $parts; + + $arglist = array(); + foreach($args as $arg) { + $e = explode('.', $arg); + + + if(count($e) > 1) { + $tableAlias = $this->getTableAlias($e[0]); + $table = $this->tables[$tableAlias]; + + if( ! $table->hasColumn($e[1])) { + throw new Doctrine_Query_Exception('Unknown column ' . $e[1]); + } + + $arglist[] = $tableAlias . '.' . $e[1]; + } else { + $arglist[] = $e[0]; + } + } + + $this->parts['select'][] = $name . '(' . $distinct . implode(', ', $arglist) . ') AS ' . $tableAlias . '__' . count($this->aggregateMap); + + $this->aggregateMap[] = $table; + } + } + /** + * count + * + * @param array $params + * @return integer + */ + public function count($params = array()) + { + $this->remove('select'); + $join = $this->join; + $where = $this->where; + $having = $this->having; + $table = reset($this->tables); + + $q = 'SELECT COUNT(DISTINCT ' . $this->aliasHandler->getShortAlias($table->getTableName()) + . '.' . $table->getIdentifier() + . ') FROM ' . $table->getTableName() . ' ' . $this->aliasHandler->getShortAlias($table->getTableName()); + + foreach($join as $j) { + $q .= ' '.implode(' ',$j); + } + $string = $this->applyInheritance(); + + if( ! empty($where)) { + $q .= ' WHERE ' . implode(' AND ', $where); + if( ! empty($string)) + $q .= ' AND (' . $string . ')'; + } else { + if( ! empty($string)) + $q .= ' WHERE (' . $string . ')'; + } + + if( ! empty($having)) + $q .= ' HAVING ' . implode(' AND ',$having); + + if( ! is_array($params)) + $params = array($params); + + $params = array_merge($this->params, $params); + + $a = $this->getConnection()->execute($q, $params)->fetch(PDO::FETCH_NUM); + return $a[0]; + } + /** + * loadFields + * loads fields for a given table and + * constructs a little bit of sql for every field + * + * fields of the tables become: [tablename].[fieldname] as [tablename]__[fieldname] + * + * @access private + * @param object Doctrine_Table $table a Doctrine_Table object + * @param integer $fetchmode fetchmode the table is using eg. Doctrine::FETCH_LAZY + * @param array $names fields to be loaded (only used in lazy property loading) + * @return void + */ + protected function loadFields(Doctrine_Table $table, $fetchmode, array $names, $cpath) + { + $name = $table->getComponentName(); + + switch($fetchmode): + case Doctrine::FETCH_OFFSET: + $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT); + case Doctrine::FETCH_IMMEDIATE: + if( ! empty($names)) + $names = array_unique(array_merge($table->getPrimaryKeys(), $names)); + else + $names = $table->getColumnNames(); + break; + case Doctrine::FETCH_LAZY_OFFSET: + $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT); + case Doctrine::FETCH_LAZY: + case Doctrine::FETCH_BATCH: + $names = array_unique(array_merge($table->getPrimaryKeys(), $names)); + break; + default: + throw new Doctrine_Exception("Unknown fetchmode."); + endswitch; + + $component = $table->getComponentName(); + $tablename = $this->tableAliases[$cpath]; + + $this->fetchModes[$tablename] = $fetchmode; + + $count = count($this->tables); + + foreach($names as $name) { + if($count == 0) { + $this->parts['select'][] = $tablename . '.' . $name; + } else { + $this->parts['select'][] = $tablename . '.' . $name . ' AS ' . $tablename . '__' . $name; + } + } + } + /** + * addFrom + * + * @param strint $from + * @return Doctrine_Query + */ + public function addFrom($from) + { + $class = 'Doctrine_Query_From'; + $parser = new $class($this); + $parser->parse($from); + + return $this; + } + /** + * leftJoin + * + * @param strint $join + * @return Doctrine_Query + */ + public function leftJoin($join) + { + $class = 'Doctrine_Query_From'; + $parser = new $class($this); + $parser->parse('LEFT JOIN ' . $join); + + return $this; + } + /** + * innerJoin + * + * @param strint $join + * @return Doctrine_Query + */ + public function innerJoin($join) + { + $class = 'Doctrine_Query_From'; + $parser = new $class($this); + $parser->parse('INNER JOIN ' . $join); + + return $this; + } + /** + * addOrderBy + * + * @param strint $orderby + * @return Doctrine_Query + */ + public function addOrderBy($orderby) + { + $class = 'Doctrine_Query_Orderby'; + $parser = new $class($this); + $this->parts['orderby'][] = $parser->parse($orderby); + + return $this; + } + /** + * addWhere + * + * @param string $where + * @param mixed $params + */ + public function addWhere($where, $params = array()) + { + $class = 'Doctrine_Query_Where'; + $parser = new $class($this); + $this->parts['where'][] = $parser->parse($where); + + if(is_array($params)) { + $this->params = array_merge($this->params, $params); + } else { + $this->params[] = $params; + } + } + /** + * sets a query part + * + * @param string $name + * @param array $args + * @return void + */ + public function __call($name, $args) + { + $name = strtolower($name); + + $method = 'parse' . ucwords($name); + + switch($name) { + case 'select': + $this->type = self::SELECT; + + if( ! isset($args[0])) + throw new Doctrine_Query_Exception('Empty select part'); + + $this->parseSelect($args[0]); + break; + case 'delete': + $this->type = self::DELETE; + break; + case 'update': + $this->type = self::UPDATE; + $name = 'from'; + case 'from': + $this->parts['from'] = array(); + $this->parts['select'] = array(); + $this->parts['join'] = array(); + $this->joins = array(); + $this->tables = array(); + $this->fetchModes = array(); + $this->tableIndexes = array(); + $this->tableAliases = array(); + $this->aliasHandler->clear(); + + $class = "Doctrine_Query_".ucwords($name); + $parser = new $class($this); + + $parser->parse($args[0]); + break; + case 'where': + if(isset($args[1])) { + if(is_array($args[1])) { + $this->params = $args[1]; + } else { + $this->params = array($args[1]); + } + } + case 'having': + case 'orderby': + case 'groupby': + $class = "Doctrine_Query_".ucwords($name); + $parser = new $class($this); + + $this->parts[$name] = array($parser->parse($args[0])); + break; + case 'limit': + case 'offset': + if($args[0] == null) + $args[0] = false; + + $this->parts[$name] = $args[0]; + break; + default: + $this->parts[$name] = array(); + $this->$method($args[0]); + + throw new Doctrine_Query_Exception("Unknown overload method"); + } + + + return $this; + } + /** + * returns a query part + * + * @param $name query part name + * @return mixed + */ + public function get($name) + { + if( ! isset($this->parts[$name])) + return false; + + return $this->parts[$name]; + } + /** + * set + * sets a query SET part + * this method should only be used with UPDATE queries + * + * @param $name name of the field + * @param $value field value + * @return Doctrine_Query + */ + public function set($name, $value) + { + $class = new Doctrine_Query_Set($this); + $this->parts['set'][] = $class->parse($name . ' = ' . $value); + + return $this; + } + /** + * @return boolean + */ + public function isLimitSubqueryUsed() { + return $this->limitSubqueryUsed; + } + /** + * getQueryBase + * returns the base of the generated sql query + * On mysql driver special strategy has to be used for DELETE statements + * + * @return string the base of the generated sql query + */ + public function getQueryBase() + { + switch($this->type) { + case self::DELETE: + if($this->conn->getName() == 'mysql') + $q = 'DELETE '.end($this->tableAliases).' FROM '; + else + $q = 'DELETE FROM '; + break; + case self::UPDATE: + $q = 'UPDATE '; + break; + case self::SELECT: + $distinct = ($this->isDistinct()) ? 'DISTINCT ' : ''; + + $q = 'SELECT '.$distinct.implode(', ', $this->parts['select']).' FROM '; + break; + } + return $q; + } + /** + * builds the sql query from the given parameters and applies things such as + * column aggregation inheritance and limit subqueries if needed + * + * @param array $params an array of prepared statement params (needed only in mysql driver + * when limit subquery algorithm is used) + * @return string the built sql query + */ + public function getQuery($params = array()) + { + if(empty($this->parts["select"]) || empty($this->parts["from"])) + return false; + + $needsSubQuery = false; + $subquery = ''; + $k = array_keys($this->tables); + $table = $this->tables[$k[0]]; + + if( ! empty($this->parts['limit']) && $this->needsSubquery && $table->getAttribute(Doctrine::ATTR_QUERY_LIMIT) == Doctrine::LIMIT_RECORDS) { + $needsSubQuery = true; + $this->limitSubqueryUsed = true; + } + + // build the basic query + + $str = ''; + if($this->isDistinct()) + $str = 'DISTINCT '; + + $q = $this->getQueryBase(); + + $q .= $this->parts['from']; + + if( ! empty($this->parts['join'])) { + foreach($this->parts['join'] as $part) { + $q .= ' '.implode(' ', $part); + } + } + + if( ! empty($this->parts['set'])) { + $q .= ' SET ' . implode(', ', $this->parts['set']); + } + + $string = $this->applyInheritance(); + + if( ! empty($string)) + $this->parts['where'][] = '('.$string.')'; + + + + $modifyLimit = true; + if( ! empty($this->parts["limit"]) || ! empty($this->parts["offset"])) { + + if($needsSubQuery) { + $subquery = $this->getLimitSubquery(); + + + switch(strtolower($this->conn->getName())) { + case 'mysql': + // mysql doesn't support LIMIT in subqueries + $params = array_merge($this->params, $params); + $list = $this->conn->execute($subquery, $params)->fetchAll(PDO::FETCH_COLUMN); + $subquery = implode(', ', $list); + break; + case 'pgsql': + // pgsql needs special nested LIMIT subquery + $subquery = 'SELECT doctrine_subquery_alias.' . $table->getIdentifier(). ' FROM (' . $subquery . ') AS doctrine_subquery_alias'; + break; + } + + $field = $this->aliasHandler->getShortAlias($table->getTableName()) . '.' . $table->getIdentifier(); + + // only append the subquery if it actually contains something + if($subquery !== '') + array_unshift($this->parts['where'], $field. ' IN (' . $subquery . ')'); + + $modifyLimit = false; + } + } + + $q .= ( ! empty($this->parts['where']))? ' WHERE ' . implode(' AND ', $this->parts['where']):''; + $q .= ( ! empty($this->parts['groupby']))? ' GROUP BY ' . implode(', ', $this->parts['groupby']):''; + $q .= ( ! empty($this->parts['having']))? ' HAVING ' . implode(' AND ', $this->parts['having']):''; + $q .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(', ', $this->parts['orderby']):''; + + if($modifyLimit) + $q = $this->conn->modifyLimitQuery($q, $this->parts['limit'], $this->parts['offset']); + + // return to the previous state + if( ! empty($string)) + array_pop($this->parts['where']); + if($needsSubQuery) + array_shift($this->parts['where']); + + return $q; + } + /** + * this is method is used by the record limit algorithm + * + * when fetching one-to-many, many-to-many associated data with LIMIT clause + * an additional subquery is needed for limiting the number of returned records instead + * of limiting the number of sql result set rows + * + * @return string the limit subquery + */ + public function getLimitSubquery() + { + $k = array_keys($this->tables); + $table = $this->tables[$k[0]]; + + // get short alias + $alias = $this->aliasHandler->getShortAlias($table->getTableName()); + $primaryKey = $alias . '.' . $table->getIdentifier(); + + // initialize the base of the subquery + $subquery = 'SELECT DISTINCT ' . $primaryKey; + + if($this->conn->getDBH()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') { + // pgsql needs the order by fields to be preserved in select clause + + foreach($this->parts['orderby'] as $part) { + $e = explode(' ', $part); + + // don't add primarykey column (its already in the select clause) + if($e[0] !== $primaryKey) + $subquery .= ', ' . $e[0]; + } + } + + $subquery .= ' FROM ' . $this->conn->quoteIdentifier($table->getTableName()) . ' ' . $alias; + + foreach($this->parts['join'] as $parts) { + foreach($parts as $part) { + // preserve LEFT JOINs only if needed + if(substr($part,0,9) === 'LEFT JOIN') { + $e = explode(' ', $part); + + if( ! in_array($e[3], $this->subqueryAliases) && + ! in_array($e[2], $this->subqueryAliases)) { + continue; + } + + } + + $subquery .= ' '.$part; + } + } + + + // all conditions must be preserved in subquery + $subquery .= ( ! empty($this->parts['where']))? ' WHERE ' . implode(' AND ',$this->parts['where']):''; + $subquery .= ( ! empty($this->parts['groupby']))? ' GROUP BY ' . implode(', ',$this->parts['groupby']):''; + $subquery .= ( ! empty($this->parts['having']))? ' HAVING ' . implode(' AND ',$this->parts['having']):''; + $subquery .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(', ', $this->parts['orderby']):''; + + // add driver specific limit clause + $subquery = $this->conn->modifyLimitQuery($subquery, $this->parts['limit'], $this->parts['offset']); + + $parts = self::quoteExplode($subquery, ' ', "'", "'"); + + foreach($parts as $k => $part) { + if(strpos($part, "'") !== false) { + continue; + } + + if($this->aliasHandler->hasAliasFor($part)) { + $parts[$k] = $this->aliasHandler->generateNewAlias($part); + } + + if(strpos($part, '.') !== false) { + $e = explode('.', $part); + + $trimmed = ltrim($e[0], '( '); + $pos = strpos($e[0], $trimmed); + + $e[0] = substr($e[0], 0, $pos) . $this->aliasHandler->generateNewAlias($trimmed); + $parts[$k] = implode('.', $e); + } + } + $subquery = implode(' ', $parts); + + return $subquery; + } + /** + * query the database with DQL (Doctrine Query Language) + * + * @param string $query DQL query + * @param array $params parameters + */ + public function query($query,$params = array()) + { + $this->parseQuery($query); + + if($this->aggregate) { + $keys = array_keys($this->tables); + $query = $this->getQuery(); + $stmt = $this->tables[$keys[0]]->getConnection()->select($query, $this->parts["limit"], $this->parts["offset"]); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + if(count($data) == 1) { + return current($data); + } else { + return $data; + } + } else { + return $this->execute($params); + } + } + /** + * splitQuery + * splits the given dql query into an array where keys + * represent different query part names and values are + * arrays splitted using sqlExplode method + * + * example: + * + * parameter: + * $query = "SELECT u.* FROM User u WHERE u.name LIKE ?" + * returns: + * array('select' => array('u.*'), + * 'from' => array('User', 'u'), + * 'where' => array('u.name', 'LIKE', '?')) + * + * @param string $query DQL query + * @throws Doctrine_Query_Exception if some generic parsing error occurs + * @return array an array containing the query string parts + */ + public function splitQuery($query) + { + $e = self::sqlExplode($query, ' '); + + foreach($e as $k=>$part) { + $part = trim($part); + switch(strtolower($part)) { + case 'delete': + case 'update': + case 'select': + case 'set': + case 'from': + case 'where': + case 'limit': + case 'offset': + case 'having': + $p = $part; + $parts[$part] = array(); + break; + case 'order': + case 'group': + $i = ($k + 1); + if(isset($e[$i]) && strtolower($e[$i]) === "by") { + $p = $part; + $parts[$part] = array(); + } else + $parts[$p][] = $part; + break; + case "by": + continue; + default: + if( ! isset($p)) + throw new Doctrine_Query_Exception("Couldn't parse query."); + + $parts[$p][] = $part; + } + } + return $parts; + } + /** + * DQL PARSER + * parses a DQL query + * first splits the query in parts and then uses individual + * parsers for each part + * + * @param string $query DQL query + * @param boolean $clear whether or not to clear the aliases + * @throws Doctrine_Query_Exception if some generic parsing error occurs + * @return Doctrine_Query + */ + public function parseQuery($query, $clear = true) + { + if($clear) + $this->clear(); + + $query = trim($query); + $query = str_replace("\n", ' ', $query); + $query = str_replace("\r", ' ', $query); + + $parts = $this->splitQuery($query); + + foreach($parts as $k => $part) { + $part = implode(" ",$part); + switch(strtoupper($k)) { + case 'DELETE': + $this->type = self::DELETE; + break; + case 'SELECT': + $this->type = self::SELECT; + $this->parseSelect($part); + break; + case 'UPDATE': + $this->type = self::UPDATE; + $k = 'FROM'; + + case 'FROM': + $class = 'Doctrine_Query_' . ucwords(strtolower($k)); + $parser = new $class($this); + $parser->parse($part); + break; + case 'SET': + $class = 'Doctrine_Query_' . ucwords(strtolower($k)); + $parser = new $class($this); + $this->parts['set'][] = $parser->parse($part); + break; + case 'GROUP': + case 'ORDER': + $k .= 'by'; + case 'WHERE': + case 'HAVING': + $class = 'Doctrine_Query_' . ucwords(strtolower($k)); + $parser = new $class($this); + + $name = strtolower($k); + $this->parts[$name][] = $parser->parse($part); + break; + case 'LIMIT': + $this->parts['limit'] = trim($part); + break; + case 'OFFSET': + $this->parts['offset'] = trim($part); + break; + } + } + + return $this; + } + /** + * DQL ORDER BY PARSER + * parses the order by part of the query string + * + * @param string $str + * @return void + */ + final public function parseOrderBy($str) + { + $parser = new Doctrine_Query_Part_Orderby($this); + return $parser->parse($str); + } + /** + * returns Doctrine::FETCH_* constant + * + * @param string $mode + * @return integer + */ + final public function parseFetchMode($mode) + { + switch(strtolower($mode)): + case "i": + case "immediate": + $fetchmode = Doctrine::FETCH_IMMEDIATE; + break; + case "b": + case "batch": + $fetchmode = Doctrine::FETCH_BATCH; + break; + case "l": + case "lazy": + $fetchmode = Doctrine::FETCH_LAZY; + break; + case "o": + case "offset": + $fetchmode = Doctrine::FETCH_OFFSET; + break; + case "lo": + case "lazyoffset": + $fetchmode = Doctrine::FETCH_LAZYOFFSET; + default: + throw new Doctrine_Query_Exception("Unknown fetchmode '$mode'. The availible fetchmodes are 'i', 'b' and 'l'."); + endswitch; + return $fetchmode; + } + /** + * trims brackets + * + * @param string $str + * @param string $e1 the first bracket, usually '(' + * @param string $e2 the second bracket, usually ')' + */ + public static function bracketTrim($str,$e1 = '(',$e2 = ')') + { + if(substr($str,0,1) == $e1 && substr($str,-1) == $e2) + return substr($str,1,-1); + else + return $str; + } + /** + * bracketExplode + * + * example: + * + * parameters: + * $str = (age < 20 AND age > 18) AND email LIKE 'John@example.com' + * $d = ' AND ' + * $e1 = '(' + * $e2 = ')' + * + * would return an array: + * array("(age < 20 AND age > 18)", + * "email LIKE 'John@example.com'") + * + * @param string $str + * @param string $d the delimeter which explodes the string + * @param string $e1 the first bracket, usually '(' + * @param string $e2 the second bracket, usually ')' + * + */ + public static function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')') + { + if(is_array($d)) { + $a = preg_split('/('.implode('|', $d).')/', $str); + $d = stripslashes($d[0]); + } else + $a = explode("$d",$str); + + $i = 0; + $term = array(); + foreach($a as $key=>$val) { + if (empty($term[$i])) { + $term[$i] = trim($val); + $s1 = substr_count($term[$i], "$e1"); + $s2 = substr_count($term[$i], "$e2"); + if($s1 == $s2) $i++; + } else { + $term[$i] .= "$d".trim($val); + $c1 = substr_count($term[$i], "$e1"); + $c2 = substr_count($term[$i], "$e2"); + if($c1 == $c2) $i++; + } + } + return $term; + } + /** + * quoteExplode + * + * example: + * + * parameters: + * $str = email LIKE 'John@example.com' + * $d = ' AND ' + * $e1 = '(' + * $e2 = ')' + * + * would return an array: + * array("email", "LIKE", "'John@example.com'") + * + * @param string $str + * @param string $d the delimeter which explodes the string + */ + public static function quoteExplode($str, $d = ' ') + { + if(is_array($d)) { + $a = preg_split('/('.implode('|', $d).')/', $str); + $d = stripslashes($d[0]); + } else + $a = explode("$d",$str); + + $i = 0; + $term = array(); + foreach($a as $key => $val) { + if (empty($term[$i])) { + $term[$i] = trim($val); + + if( ! (substr_count($term[$i], "'") & 1)) + $i++; + } else { + $term[$i] .= "$d".trim($val); + + if( ! (substr_count($term[$i], "'") & 1)) + $i++; + } + } + return $term; + } + /** + * sqlExplode + * + * explodes a string into array using custom brackets and + * quote delimeters + * + * + * example: + * + * parameters: + * $str = "(age < 20 AND age > 18) AND name LIKE 'John Doe'" + * $d = ' ' + * $e1 = '(' + * $e2 = ')' + * + * would return an array: + * array('(age < 20 AND age > 18)', + * 'name', + * 'LIKE', + * 'John Doe') + * + * @param string $str + * @param string $d the delimeter which explodes the string + * @param string $e1 the first bracket, usually '(' + * @param string $e2 the second bracket, usually ')' + * + * @return array + */ + public static function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')') + { + if(is_array($d)) { + $str = preg_split('/('.implode('|', $d).')/', $str); + $d = stripslashes($d[0]); + } else + $str = explode("$d",$str); + + $i = 0; + $term = array(); + foreach($str as $key => $val) { + if (empty($term[$i])) { + $term[$i] = trim($val); + + $s1 = substr_count($term[$i],"$e1"); + $s2 = substr_count($term[$i],"$e2"); + + if(substr($term[$i],0,1) == "(") { + if($s1 == $s2) { + $i++; + } + } else { + if( ! (substr_count($term[$i], "'") & 1) && + ! (substr_count($term[$i], "\"") & 1) && + ! (substr_count($term[$i], "´") & 1) + ) { $i++; } + } + } else { + $term[$i] .= "$d".trim($val); + $c1 = substr_count($term[$i],"$e1"); + $c2 = substr_count($term[$i],"$e2"); + + if(substr($term[$i],0,1) == "(") { + if($c1 == $c2) { + $i++; + } + } else { + if( ! (substr_count($term[$i], "'") & 1) && + ! (substr_count($term[$i], "\"") & 1) && + ! (substr_count($term[$i], "´") & 1) + ) { $i++; } + } + } + } + return $term; + } + /** + * generateAlias + * + * @param string $tableName + * @return string + */ + public function generateAlias($tableName) + { + if(isset($this->tableIndexes[$tableName])) { + return $tableName.++$this->tableIndexes[$tableName]; + } else { + $this->tableIndexes[$tableName] = 1; + return $tableName; + } + } + + /** + * loads a component + * + * @param string $path the path of the loadable component + * @param integer $fetchmode optional fetchmode, if not set the components default fetchmode will be used + * @throws Doctrine_Query_Exception + * @return Doctrine_Table + */ + final public function load($path, $loadFields = true) + { + $tmp = explode(' ',$path); + $componentAlias = (count($tmp) > 1) ? end($tmp) : false; + + $e = preg_split("/[.:]/", $tmp[0], -1); + + + if(isset($this->compAliases[$e[0]])) { + $end = substr($tmp[0], strlen($e[0])); + $path = $this->compAliases[$e[0]] . $end; + $e = preg_split("/[.:]/", $path, -1); + } else { + $path = $tmp[0]; + } + + + + $index = 0; + $currPath = ''; + $this->tableStack = array(); + + foreach($e as $key => $fullname) { + try { + $e2 = preg_split("/[-(]/",$fullname); + $name = $e2[0]; + + $currPath .= '.' . $name; + + if($key == 0) { + $currPath = substr($currPath,1); + + $this->conn = Doctrine_Manager::getInstance() + ->getConnectionForComponent($name); + + $table = $this->conn->getTable($name); + + + $tname = $this->aliasHandler->getShortAlias($table->getTableName()); + + if( ! isset($this->tableAliases[$currPath])) { + $this->tableIndexes[$tname] = 1; + } + + + $this->parts["from"] = $this->conn->quoteIdentifier($table->getTableName()) . ' ' . $tname; + + $this->tableAliases[$currPath] = $tname; + + $tableName = $tname; + + } else { + + $index += strlen($e[($key - 1)]) + 1; + // the mark here is either '.' or ':' + $mark = substr($path,($index - 1),1); + + if(isset($this->tableAliases[$prevPath])) { + $tname = $this->tableAliases[$prevPath]; + } else { + $tname = $this->aliasHandler->getShortAlias($table->getTableName()); + } + + $fk = $table->getRelation($name); + $name = $fk->getTable()->getComponentName(); + $original = $fk->getTable()->getTableName(); + + + + if(isset($this->tableAliases[$currPath])) { + $tname2 = $this->tableAliases[$currPath]; + } else + $tname2 = $this->aliasHandler->generateShortAlias($original); + + $aliasString = $this->conn->quoteIdentifier($original) . ' ' . $tname2; + + switch($mark) { + case ':': + $join = 'INNER JOIN '; + break; + case '.': + $join = 'LEFT JOIN '; + break; + default: + throw new Doctrine_Exception("Unknown operator '$mark'"); + } + + if( ! $fk->isOneToOne()) { + $this->needsSubquery = true; + } + + if( ! $loadFields || $fk->getTable()->usesInheritanceMap()) { + $this->subqueryAliases[] = $tname2; + } + + if($fk instanceof Doctrine_Relation_Association) { + $asf = $fk->getAssociationFactory(); + + $assocTableName = $asf->getTableName(); + + if( ! $loadFields) { + $this->subqueryAliases[] = $assocTableName; + } + $this->parts["join"][$tname][$assocTableName] = $join . $assocTableName . ' ON ' . $tname . '.' + . $table->getIdentifier() . ' = ' + . $assocTableName . '.' . $fk->getLocal(); + + $this->parts["join"][$tname][$tname2] = $join . $aliasString . ' ON ' . $tname2 . '.' + . $fk->getTable()->getIdentifier() . ' = ' + . $assocTableName . '.' . $fk->getForeign(); + + } else { + $this->parts["join"][$tname][$tname2] = $join . $aliasString + . ' ON ' . $tname . '.' + . $fk->getLocal() . ' = ' . $tname2 . '.' . $fk->getForeign(); + } + + + $this->joins[$tname2] = $prevTable; + + + $table = $fk->getTable(); + + $this->tableAliases[$currPath] = $tname2; + + $tableName = $tname2; + + $this->relationStack[] = $fk; + } + + $this->components[$currPath] = $table; + + $this->tableStack[] = $table; + + if( ! isset($this->tables[$tableName])) { + $this->tables[$tableName] = $table; + + if($loadFields) { + + $skip = false; + + if( ! empty($this->pendingFields)) + $skip = true; + + if($componentAlias) { + $this->compAliases[$componentAlias] = $currPath; + + if(isset($this->pendingFields[$componentAlias])) { + $this->processPendingFields($componentAlias); + $skip = true; + } + if(isset($this->pendingAggregates[$componentAlias])) { + $this->processPendingAggregates($componentAlias); + $skip = true; + } + } + + if( ! $skip) { + $this->parseFields($fullname, $tableName, $e2, $currPath); + } + } + } + + + $prevPath = $currPath; + $prevTable = $tableName; + } catch(Exception $e) { + throw new Doctrine_Query_Exception($e->__toString()); + } + } + + if($componentAlias !== false) { + $this->compAliases[$componentAlias] = $currPath; + } + + return $table; + } + /** + * parseFields + * + * @param string $fullName + * @param string $tableName + * @param array $exploded + * @param string $currPath + * @return void + */ + final public function parseFields($fullName, $tableName, array $exploded, $currPath) + { + $table = $this->tables[$tableName]; + + $fields = array(); + + if(strpos($fullName, '-') === false) { + $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); + + if(isset($exploded[1])) { + if(count($exploded) > 2) { + $fields = $this->parseAggregateValues($fullName, $tableName, $exploded, $currPath); + } elseif(count($exploded) == 2) { + $fields = explode(',',substr($exploded[1],0,-1)); + } + } + } else { + if(isset($exploded[1])) { + $fetchmode = $this->parseFetchMode($exploded[1]); + } else + $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); + + if(isset($exploded[2])) { + if(substr_count($exploded[2], ')') > 1) { + + } else { + $fields = explode(',', substr($exploded[2],0,-1)); + } + } + + } + if( ! $this->aggregate) + $this->loadFields($table, $fetchmode, $fields, $currPath); + } + /** + * parseAggregateFunction + * + * @param string $func + * @param string $reference + * @return string + */ + public function parseAggregateFunction($func,$reference) + { + $pos = strpos($func, '('); + + if($pos !== false) { + $funcs = array(); + + $name = substr($func, 0, $pos); + $func = substr($func, ($pos + 1), -1); + $params = Doctrine_Query::bracketExplode($func, ',', '(', ')'); + + foreach($params as $k => $param) { + $params[$k] = $this->parseAggregateFunction($param,$reference); + } + + $funcs = $name . '(' . implode(', ', $params). ')'; + + return $funcs; + + } else { + if( ! is_numeric($func)) { + + $func = $this->getTableAlias($reference).'.'.$func; + + return $func; + } else { + + return $func; + } + } + } + /** + * parseAggregateValues + */ + public function parseAggregateValues($fullName, $tableName, array $exploded, $currPath) + { + $this->aggregate = true; + $pos = strpos($fullName, '('); + $name = substr($fullName, 0, $pos); + $string = substr($fullName, ($pos + 1), -1); + + $exploded = Doctrine_Query::bracketExplode($string, ','); + foreach($exploded as $k => $value) { + $func = $this->parseAggregateFunction($value, $currPath); + $exploded[$k] = $func; + + $this->parts['select'][] = $exploded[$k]; + } + } +} + diff --git a/lib/Doctrine/Query/Where.php b/lib/Doctrine/Query/Where.php index 0e00ad292..e79b9f219 100644 --- a/lib/Doctrine/Query/Where.php +++ b/lib/Doctrine/Query/Where.php @@ -68,7 +68,7 @@ class Doctrine_Query_Where extends Doctrine_Query_Condition case 'regexp': case 'like': $operator = $this->getOperator($func); - + if (empty($relation)) { throw new Doctrine_Query_Exception('DQL functions contains/regexp/like can only be used for fields of related components'); } diff --git a/lib/Doctrine/RawSql.php b/lib/Doctrine/RawSql.php index 6a827ad31..59eca7b13 100644 --- a/lib/Doctrine/RawSql.php +++ b/lib/Doctrine/RawSql.php @@ -105,7 +105,7 @@ class Doctrine_RawSql extends Doctrine_Hydrate $p = $low; $p .= "by"; $parts[$low."by"] = array(); - + } else { $parts[$p][] = $part; } diff --git a/lib/Doctrine/Record.php b/lib/Doctrine/Record.php index 6b2502a98..372e1c80c 100644 --- a/lib/Doctrine/Record.php +++ b/lib/Doctrine/Record.php @@ -378,7 +378,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite if ($tmp[$name] !== self::$null) { if (is_string($tmp[$name])) { $value = unserialize($tmp[$name]); - + if ($value === false) throw new Doctrine_Record_Exception("Unserialization of $name failed."); } else { @@ -390,10 +390,10 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite case "gzip": if ($tmp[$name] !== self::$null) { $value = gzuncompress($tmp[$name]); - + if ($value === false) throw new Doctrine_Record_Exception("Uncompressing of $name failed."); - + $this->_data[$name] = $value; } break; @@ -422,27 +422,27 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite case Doctrine_Identifier::AUTO_INCREMENT: case Doctrine_Identifier::SEQUENCE: $name = $this->_table->getIdentifier(); - + if ($exists) { if (isset($this->_data[$name]) && $this->_data[$name] !== self::$null) { $this->_id[$name] = $this->_data[$name]; } } - + unset($this->_data[$name]); - + break; case Doctrine_Identifier::NORMAL: $this->_id = array(); $name = $this->_table->getIdentifier(); - + if (isset($this->_data[$name]) && $this->_data[$name] !== self::$null) { $this->_id[$name] = $this->_data[$name]; } break; case Doctrine_Identifier::COMPOSITE: $names = $this->_table->getIdentifier(); - + foreach ($names as $name) { if ($this->_data[$name] === self::$null) { $this->_id[$name] = null; @@ -1021,7 +1021,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite default: if ($this->_data[$v] instanceof Doctrine_Record) $this->_data[$v] = $this->_data[$v]->getIncremented(); - + $a[$v] = $this->_data[$v]; } } diff --git a/lib/Doctrine/Relation/Association.php b/lib/Doctrine/Relation/Association.php index 2c6d9677e..ecaca74fe 100644 --- a/lib/Doctrine/Relation/Association.php +++ b/lib/Doctrine/Relation/Association.php @@ -113,7 +113,7 @@ class Doctrine_Relation_Association extends Doctrine_Relation ' WHERE ' . $this->local. ' IN (' . substr(str_repeat("?, ", $count),0,-2) . ')'; - + $dql = "FROM ".$this->table->getComponentName(); $dql .= ".".$this->associationTable->getComponentName(); $dql .= " WHERE ".$this->table->getComponentName().".".$this->table->getIdentifier()." IN ($sub)"; diff --git a/lib/Doctrine/Relation/Association/Self.php b/lib/Doctrine/Relation/Association/Self.php index fcff48e9c..0868a737d 100644 --- a/lib/Doctrine/Relation/Association/Self.php +++ b/lib/Doctrine/Relation/Association/Self.php @@ -50,7 +50,7 @@ class Doctrine_Relation_Association_Self extends Doctrine_Relation_Association ' FROM '.$this->associationTable->getTableName(). ' WHERE '.$this->foreign. ' = ?'; - + $dql = 'FROM '.$this->table->getComponentName(); $dql .= '.'.$this->associationTable->getComponentName(); $dql .= ' WHERE '.$this->table->getComponentName().'.'.$this->table->getIdentifier().' IN ('.$sub.')'; diff --git a/lib/Doctrine/Table.php b/lib/Doctrine/Table.php index 2b5bc7884..e4a69dbd0 100644 --- a/lib/Doctrine/Table.php +++ b/lib/Doctrine/Table.php @@ -196,7 +196,7 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable ) ) ), $this->columns); - + $this->primaryKeys[] = 'id'; $this->identifier = 'id'; $this->identifierType = Doctrine_Identifier::AUTO_INCREMENT; @@ -206,19 +206,19 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable if (count($this->primaryKeys) > 1) { $this->identifier = $this->primaryKeys; $this->identifierType = Doctrine_Identifier::COMPOSITE; - + } else { foreach ($this->primaryKeys as $pk) { $e = $this->columns[$pk][2]; - + $found = false; - + foreach ($e as $option => $value) { if ($found) break; - + $e2 = explode(":",$option); - + switch (strtolower($e2[0])) { case "autoincrement": $this->identifierType = Doctrine_Identifier::AUTO_INCREMENT;