diff --git a/lib/Doctrine/Hydrate.php b/lib/Doctrine/Hydrate.php index 4746798cc..908ce514a 100644 --- a/lib/Doctrine/Hydrate.php +++ b/lib/Doctrine/Hydrate.php @@ -392,6 +392,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { $alias = $this->getPathAlias($path); //print_r($this->pendingAggregates); + + // map each aggregate value foreach($row as $index => $value) { $agg = false; diff --git a/lib/Doctrine/Query.php b/lib/Doctrine/Query.php index f2b060156..38aa63f23 100644 --- a/lib/Doctrine/Query.php +++ b/lib/Doctrine/Query.php @@ -384,7 +384,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { $this->parts[$name] = array($this->$method($value)); break; case "limit": - case "offset": + case "offset": if($value == null) $value = false; @@ -458,10 +458,8 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { if($this->isDistinct()) $str = 'DISTINCT '; - - - $q = "SELECT ".$str.implode(", ",$this->parts["select"]). - " FROM "; + $q = 'SELECT '.$str.implode(", ",$this->parts["select"]). + ' FROM '; $q = $this->getQueryBase(); foreach($this->parts["from"] as $tname => $bool) { @@ -469,31 +467,10 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { } $q .= implode(", ",$a); - if($needsSubQuery) - $subquery = 'SELECT DISTINCT ' . $table->getTableName() - . '.' . $table->getIdentifier() - . ' FROM '.$table->getTableName(); - - if( ! empty($this->parts['join'])) { foreach($this->parts['join'] as $part) { $q .= " ".implode(' ', $part); } - - if($needsSubQuery) { - foreach($this->parts['join'] as $parts) { - foreach($parts as $part) { - if(substr($part,0,9) === 'LEFT JOIN') { - $e = explode(' ', $part); - - if( ! in_array($e[2],$this->subqueryAliases)) - continue; - } - - $subquery .= " ".$part; - } - } - } } $string = $this->applyInheritance(); @@ -501,18 +478,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { if( ! empty($string)) $this->parts['where'][] = '('.$string.')'; - if($needsSubQuery) { - // 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(" ",$this->parts["having"]):''; - } + $modifyLimit = true; if( ! empty($this->parts["limit"]) || ! empty($this->parts["offset"])) { if($needsSubQuery) { - $subquery = $this->connection->modifyLimitQuery($subquery,$this->parts["limit"],$this->parts["offset"]); + $subquery = $this->getLimitSubquery(); $dbh = $this->connection->getDBH(); // mysql doesn't support LIMIT in subqueries @@ -525,12 +497,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { } } - $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(" ",$this->parts["having"]):''; - $q .= ( ! empty($this->parts['orderby']))?" ORDER BY ".implode(" ",$this->parts["orderby"]):''; + $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(' ', $this->parts['having']):''; + $q .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(' ', $this->parts['orderby']):''; + if($modifyLimit) - $q = $this->connection->modifyLimitQuery($q,$this->parts["limit"],$this->parts["offset"]); + $q = $this->connection->modifyLimitQuery($q, $this->parts['limit'], $this->parts['offset']); // return to the previous state if( ! empty($string)) @@ -540,6 +513,47 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { 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]]; + + $subquery = 'SELECT DISTINCT ' . $table->getTableName() + . '.' . $table->getIdentifier() + . ' FROM '.$table->getTableName(); + + 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[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(' ',$this->parts['having']):''; + + // add driver specific limit clause + $subquery = $this->connection->modifyLimitQuery($subquery, $this->parts['limit'], $this->parts['offset']); + + return $subquery; + } diff --git a/lib/Doctrine/Query/Where.php b/lib/Doctrine/Query/Where.php index 4edce7163..8f0ab1860 100644 --- a/lib/Doctrine/Query/Where.php +++ b/lib/Doctrine/Query/Where.php @@ -135,6 +135,15 @@ class Doctrine_Query_Where extends Doctrine_Query_Condition { } return $where; } + /** + * parses a literal value and returns the parsed value + * + * boolean literals are parsed to integers + * components are parsed to associated table aliases + * + * @param string $value literal value to be parsed + * @return string + */ public function parseLiteralValue($value) { // check that value isn't a string if(strpos($value, '\'') === false) { @@ -161,6 +170,13 @@ class Doctrine_Query_Where extends Doctrine_Query_Condition { return $value; } + /** + * parses an EXISTS expression + * + * @param string $where query where part to be parsed + * @param boolean $negation whether or not to use the NOT keyword + * @return string + */ public function parseExists($where, $negation) { $operator = ($negation) ? 'EXISTS' : 'NOT EXISTS';