Commit bfcfa9f2 authored by zYne's avatar zYne

Aggregate function support added

parent 83af8189
...@@ -64,6 +64,9 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator ...@@ -64,6 +64,9 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
* @var Doctrine_Null $null used for extremely fast null value testing * @var Doctrine_Null $null used for extremely fast null value testing
*/ */
protected static $null; protected static $null;
protected $aggregateValues = array();
/** /**
* constructor * constructor
...@@ -117,7 +120,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator ...@@ -117,7 +120,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
* @param string $name * @param string $name
* @return mixed * @return mixed
*/ */
public function getAggregateValue() { public function getAggregateValue($name) {
return $this->aggregateValues[$name]; return $this->aggregateValues[$name];
} }
/** /**
......
...@@ -82,6 +82,12 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { ...@@ -82,6 +82,12 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
* @var array $tableIndexes * @var array $tableIndexes
*/ */
protected $tableIndexes = array(); protected $tableIndexes = array();
protected $components = array();
protected $pendingAggregates = array();
protected $aggregateMap = array();
/** /**
* @var array $parts SQL query string parts * @var array $parts SQL query string parts
*/ */
...@@ -143,6 +149,14 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { ...@@ -143,6 +149,14 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
return $this; return $this;
} }
public function getPathAlias($path) {
$s = array_search($path, $this->compAliases);
if($s === false)
return $path;
return $s;
}
/** /**
* createSubquery * createSubquery
* *
...@@ -264,6 +278,9 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { ...@@ -264,6 +278,9 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
*/ */
private function getCollection($name) { private function getCollection($name) {
$table = $this->tables[$name]; $table = $this->tables[$name];
if( ! isset($this->fetchModes[$name]))
return new Doctrine_Collection($table);
switch($this->fetchModes[$name]): switch($this->fetchModes[$name]):
case Doctrine::FETCH_BATCH: case Doctrine::FETCH_BATCH:
$coll = new Doctrine_Collection_Batch($table); $coll = new Doctrine_Collection_Batch($table);
...@@ -344,7 +361,6 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { ...@@ -344,7 +361,6 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
if($return == Doctrine::FETCH_ARRAY) if($return == Doctrine::FETCH_ARRAY)
return $array; return $array;
foreach($array as $data) { foreach($array as $data) {
...@@ -355,12 +371,30 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { ...@@ -355,12 +371,30 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
if(empty($row)) if(empty($row))
continue; continue;
$ids = $this->tables[$key]->getIdentifier(); $ids = $this->tables[$key]->getIdentifier();
$name = $key; $name = $key;
if($this->isIdentifiable($row, $ids)) { if($this->isIdentifiable($row, $ids)) {
$prev = $this->initRelated($prev, $name); $prev = $this->initRelated($prev, $name);
// aggregate values have numeric keys
if(isset($row[0])) {
$path = array_search($name, $this->tableAliases);
$alias = $this->getPathAlias($path);
foreach($row as $index => $value) {
$agg = false;
if(isset($this->pendingAggregates[$alias][$index]))
$agg = $this->pendingAggregates[$alias][$index][0];
$prev[$name]->setAggregateValue($agg, $value);
}
}
continue; continue;
} }
...@@ -384,7 +418,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { ...@@ -384,7 +418,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
unset($previd); unset($previd);
} else { } else {
$prev = $this->addRelated($prev, $name, $record);
$prev = $this->addRelated($prev, $name, $record);
} }
// following statement is needed to ensure that mappings // following statement is needed to ensure that mappings
...@@ -476,7 +511,6 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { ...@@ -476,7 +511,6 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
* @return boolean * @return boolean
*/ */
public function isIdentifiable(array $row, $ids) { public function isIdentifiable(array $row, $ids) {
$emptyID = false;
if(is_array($ids)) { if(is_array($ids)) {
foreach($ids as $id) { foreach($ids as $id) {
if($row[$id] == null) if($row[$id] == null)
...@@ -544,10 +578,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { ...@@ -544,10 +578,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
$e = explode("__",$key); $e = explode("__",$key);
$field = strtolower( array_pop($e) ); $field = strtolower( array_pop($e) );
$component = strtolower( implode("__",$e) ); $component = strtolower( implode("__",$e) );
$data[$component][$field] = $value; $data[$component][$field] = $value;
unset($data[$key]); unset($data[$key]);
......
...@@ -45,6 +45,9 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -45,6 +45,9 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
private $relationStack = array(); private $relationStack = array();
private $isDistinct = false; private $isDistinct = false;
private $pendingFields = array();
/** /**
* create * create
* returns a new Doctrine_Query object * returns a new Doctrine_Query object
...@@ -69,21 +72,97 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -69,21 +72,97 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
return $this->isDistinct; return $this->isDistinct;
} }
/**
* count public function processPendingFields($componentAlias) {
$tableAlias = $this->getTableAlias($componentAlias);
$componentPath = $this->compAliases[$componentAlias];
if( ! isset($this->components[$componentPath]))
throw new Doctrine_Query_Exception('Unknown component path '.$componentPath);
$table = $this->components[$componentPath];
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) {
$pos = strpos($func, '(');
$name = substr($func, 0, $pos);
switch($name) {
case 'MAX':
case 'MIN':
case 'COUNT':
case 'AVG':
$reference = substr($func, ($pos + 1), -1);
$e = explode('.', $reference);
$this->pendingAggregates[$e[0]][] = array($name, $e[1]);
break;
default:
throw new Doctrine_Query_Exception('Unknown aggregate function '.$name);
}
}
public function processPendingAggregates($componentAlias) {
$tableAlias = $this->getTableAlias($componentAlias);
$componentPath = $this->compAliases[$componentAlias];
if( ! isset($this->components[$componentPath]))
throw new Doctrine_Query_Exception('Unknown component path '.$componentPath);
$table = $this->components[$componentPath];
foreach($this->pendingAggregates[$componentAlias] as $args) {
list($name, $arg) = $args;
$this->parts["select"][] = $name . '(' . $tableAlias . '.' . $arg . ') AS ' . $tableAlias . '__' . count($this->aggregateMap);
$this->aggregateMap[] = $table;
}
}
/**
* count
* *
* @return integer * @return integer
*/ */
public function count(Doctrine_Table $table, $params = array()) { public function count(Doctrine_Table $table, $params = array()) {
$this->remove('select'); $this->remove('select');
$join = $this->join; $join = $this->join;
$where = $this->where; $where = $this->where;
$having = $this->having; $having = $this->having;
$q = "SELECT COUNT(DISTINCT ".$table->getTableName().'.'.$table->getIdentifier().") FROM ".$table->getTableName()." "; $q = "SELECT COUNT(DISTINCT ".$table->getTableName().'.'.$table->getIdentifier().") FROM ".$table->getTableName()." ";
foreach($join as $j) { foreach($join as $j) {
$q .= " ".implode(" ",$j); $q .= implode(" ",$j);
} }
$string = $this->applyInheritance(); $string = $this->applyInheritance();
if( ! empty($where)) { if( ! empty($where)) {
...@@ -94,13 +173,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -94,13 +173,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
if( ! empty($string)) if( ! empty($string))
$q .= " WHERE (".$string.")"; $q .= " WHERE (".$string.")";
} }
if( ! empty($having))
$q .= " HAVING ".implode(' AND ',$having);
if( ! empty($having)) $a = $this->getConnection()->execute($q, $params)->fetch(PDO::FETCH_NUM);
$q .= " HAVING ".implode(' AND ',$having); return $a[0];
}
$a = $this->getConnection()->execute($q, $params)->fetch(PDO::FETCH_NUM);
return $a[0];
}
/** /**
* loadFields * loadFields
* loads fields for a given table and * loads fields for a given table and
...@@ -203,7 +282,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -203,7 +282,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
$class = "Doctrine_Query_".ucwords($name); $class = "Doctrine_Query_".ucwords($name);
$parser = new $class($this); $parser = new $class($this);
$parser->parse($args[0]); $parser->parse($args[0]);
break; break;
case "where": case "where":
...@@ -697,10 +776,6 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -697,10 +776,6 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
foreach($e as $key => $fullname) { foreach($e as $key => $fullname) {
try { try {
$copy = $e;
$e2 = preg_split("/[-(]/",$fullname); $e2 = preg_split("/[-(]/",$fullname);
$name = $e2[0]; $name = $e2[0];
...@@ -751,7 +826,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -751,7 +826,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
else else
$aliasString = $original; $aliasString = $original;
switch($mark): switch($mark) {
case ":": case ":":
$join = 'INNER JOIN '; $join = 'INNER JOIN ';
break; break;
...@@ -760,23 +835,17 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -760,23 +835,17 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
break; break;
default: default:
throw new Doctrine_Exception("Unknown operator '$mark'"); throw new Doctrine_Exception("Unknown operator '$mark'");
endswitch; }
if($fk->getType() == Doctrine_Relation::MANY_AGGREGATE || if( ! $fk->isOneToOne()) {
$fk->getType() == Doctrine_Relation::MANY_COMPOSITE) {
if( ! $loadFields) { if( ! $loadFields) {
$this->subqueryAliases[] = $tname2; $this->subqueryAliases[] = $tname2;
} }
$this->needsSubquery = true; $this->needsSubquery = true;
} }
if($fk instanceof Doctrine_Relation_ForeignKey || if($fk instanceof Doctrine_Relation_Association) {
$fk instanceof Doctrine_Relation_LocalKey) {
$this->parts["join"][$tname][$tname2] = $join.$aliasString." ON ".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign();
} elseif($fk instanceof Doctrine_Relation_Association) {
$asf = $fk->getAssociationFactory(); $asf = $fk->getAssociationFactory();
$assocTableName = $asf->getTableName(); $assocTableName = $asf->getTableName();
...@@ -784,8 +853,11 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -784,8 +853,11 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
if( ! $loadFields) { if( ! $loadFields) {
$this->subqueryAliases[] = $assocTableName; $this->subqueryAliases[] = $assocTableName;
} }
$this->parts["join"][$tname][$assocTableName] = $join.$assocTableName." ON ".$tname.".".$table->getIdentifier()." = ".$assocTableName.".".$fk->getLocal(); $this->parts["join"][$tname][$assocTableName] = $join.$assocTableName .' ON '.$tname.".".$table->getIdentifier()." = ".$assocTableName.".".$fk->getLocal();
$this->parts["join"][$tname][$tname2] = $join.$aliasString." ON ".$tname2.".".$table->getIdentifier()." = ".$assocTableName.".".$fk->getForeign(); $this->parts["join"][$tname][$tname2] = $join.$aliasString .' ON '.$tname2.".".$table->getIdentifier()." = ".$assocTableName.".".$fk->getForeign();
} else {
$this->parts["join"][$tname][$tname2] = $join.$aliasString .' ON '.$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign();
} }
$this->joins[$tname2] = $prevTable; $this->joins[$tname2] = $prevTable;
...@@ -798,13 +870,31 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -798,13 +870,31 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
$this->relationStack[] = $fk; $this->relationStack[] = $fk;
} }
$this->components[$currPath] = $table;
$this->tableStack[] = $table; $this->tableStack[] = $table;
if( ! isset($this->tables[$tableName])) { if( ! isset($this->tables[$tableName])) {
$this->tables[$tableName] = $table; $this->tables[$tableName] = $table;
if($loadFields) { if($loadFields) {
$this->parseFields($fullname, $tableName, $e2, $currPath);
$skip = false;
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);
} }
} }
...@@ -919,3 +1009,4 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -919,3 +1009,4 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
} }
} }
} }
...@@ -86,11 +86,6 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable { ...@@ -86,11 +86,6 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable {
* @var Doctrine_Table_Repository $repository record repository * @var Doctrine_Table_Repository $repository record repository
*/ */
private $repository; private $repository;
/**
* @var Doctrine_Cache $cache second level cache
*/
private $cache;
/** /**
* @var array $columns an array of column definitions * @var array $columns an array of column definitions
*/ */
...@@ -733,8 +728,7 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable { ...@@ -733,8 +728,7 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable {
* finds a record by its identifier * finds a record by its identifier
* *
* @param $id database row id * @param $id database row id
* @throws Doctrine_Find_Exception * @return Doctrine_Record|false a record for given database identifier
* @return Doctrine_Record a record for given database identifier
*/ */
public function find($id) { public function find($id) {
if($id !== null) { if($id !== null) {
......
<?php <?php
class Doctrine_Query_Select_TestCase extends Doctrine_UnitTestCase { class Doctrine_Query_Select_TestCase extends Doctrine_UnitTestCase {
public function testAsteriskSelecting() { public function prepareTables() { }
public function testAggregateFunction() {
$q = new Doctrine_Query();
$q->parseQuery('SELECT COUNT(u.id) FROM User u');
$this->assertEqual($q->getQuery(), 'SELECT COUNT(entity.id) AS entity__0 FROM entity WHERE (entity.type = 0)');
}
public function testMultipleAggregateFunctions() {
$q = new Doctrine_Query();
$q->parseQuery('SELECT MAX(u.id), MIN(u.name) FROM User u');
$this->assertEqual($q->getQuery(), 'SELECT MAX(entity.id) AS entity__0, MIN(entity.name) AS entity__1 FROM entity WHERE (entity.type = 0)');
}
public function testMultipleAggregateFunctionsWithMultipleComponents() {
$q = new Doctrine_Query();
$q->parseQuery('SELECT MAX(u.id), MIN(u.name), COUNT(p.id) FROM User u, u.Phonenumber p');
$this->assertEqual($q->getQuery(), 'SELECT MAX(entity.id) AS entity__0, MIN(entity.name) AS entity__1, COUNT(phonenumber.id) AS phonenumber__2 FROM entity LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id WHERE (entity.type = 0)');
}
public function testAggregateFunctionValueHydration() {
$q = new Doctrine_Query();
$q->parseQuery('SELECT u.id, COUNT(p.id) FROM User u, u.Phonenumber p GROUP BY u.id');
$users = $q->execute();
$this->assertEqual($users[0]->Phonenumber->getAggregateValue('COUNT'), 1);
$this->assertEqual($users[1]->Phonenumber->getAggregateValue('COUNT'), 3);
$this->assertEqual($users[2]->Phonenumber->getAggregateValue('COUNT'), 1);
$this->assertEqual($users[3]->Phonenumber->getAggregateValue('COUNT'), 1);
$this->assertEqual($users[4]->Phonenumber->getAggregateValue('COUNT'), 3);
}
public function testSingleComponentWithAsterisk() {
$q = new Doctrine_Query();
$q->parseQuery('SELECT u.* FROM User u');
$this->assertEqual($q->getQuery(),'SELECT entity.id AS entity__id, entity.name AS entity__name, entity.loginname AS entity__loginname, entity.password AS entity__password, entity.type AS entity__type, entity.created AS entity__created, entity.updated AS entity__updated, entity.email_id AS entity__email_id FROM entity WHERE (entity.type = 0)');
}
public function testSingleComponentWithMultipleColumns() {
$q = new Doctrine_Query();
$q->parseQuery('SELECT u.name, u.type FROM User u');
$this->assertEqual($q->getQuery(),'SELECT entity.id AS entity__id, entity.name AS entity__name, entity.type AS entity__type FROM entity WHERE (entity.type = 0)');
}
public function testMultipleComponentsWithAsterisk() {
$q = new Doctrine_Query();
$q->parseQuery('SELECT u.*, p.* FROM User u, u.Phonenumber p');
$this->assertEqual($q->getQuery(),'SELECT entity.id AS entity__id, entity.name AS entity__name, entity.loginname AS entity__loginname, entity.password AS entity__password, entity.type AS entity__type, entity.created AS entity__created, entity.updated AS entity__updated, entity.email_id AS entity__email_id, phonenumber.id AS phonenumber__id, phonenumber.phonenumber AS phonenumber__phonenumber, phonenumber.entity_id AS phonenumber__entity_id FROM entity LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id WHERE (entity.type = 0)');
}
public function testMultipleComponentsWithMultipleColumns() {
$q = new Doctrine_Query();
$q->parseQuery('SELECT u.id, u.name, p.id FROM User u, u.Phonenumber p');
$this->assertEqual($q->getQuery(),'SELECT entity.id AS entity__id, entity.name AS entity__name, phonenumber.id AS phonenumber__id FROM entity LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id WHERE (entity.type = 0)');
} }
} }
?> ?>
...@@ -37,6 +37,7 @@ require_once("QueryFromTestCase.php"); ...@@ -37,6 +37,7 @@ require_once("QueryFromTestCase.php");
require_once("QueryConditionTestCase.php"); require_once("QueryConditionTestCase.php");
require_once("QueryComponentAliasTestCase.php"); require_once("QueryComponentAliasTestCase.php");
require_once("QuerySubqueryTestCase.php"); require_once("QuerySubqueryTestCase.php");
require_once("QuerySelectTestCase.php");
require_once("DBTestCase.php"); require_once("DBTestCase.php");
require_once("SchemaTestCase.php"); require_once("SchemaTestCase.php");
...@@ -52,12 +53,11 @@ error_reporting(E_ALL); ...@@ -52,12 +53,11 @@ error_reporting(E_ALL);
print "<pre>"; print "<pre>";
$test = new GroupTest("Doctrine Framework Unit Tests"); $test = new GroupTest("Doctrine Framework Unit Tests");
$test->addTestCase(new Doctrine_RecordTestCase()); $test->addTestCase(new Doctrine_RecordTestCase());
$test->addTestCase(new Doctrine_ValidatorTestCase()); $test->addTestCase(new Doctrine_ValidatorTestCase());
$test->addTestCase(new Doctrine_Query_MultiJoin_TestCase()); $test->addTestCase(new Doctrine_Query_MultiJoin_TestCase());
$test->addTestCase(new Doctrine_Relation_TestCase()); $test->addTestCase(new Doctrine_Relation_TestCase());
...@@ -130,6 +130,7 @@ $test->addTestCase(new Doctrine_Query_Where_TestCase()); ...@@ -130,6 +130,7 @@ $test->addTestCase(new Doctrine_Query_Where_TestCase());
$test->addTestCase(new Doctrine_Query_From_TestCase()); $test->addTestCase(new Doctrine_Query_From_TestCase());
$test->addTestCase(new Doctrine_Query_Select_TestCase());
//$test->addTestCase(new Doctrine_Cache_FileTestCase()); //$test->addTestCase(new Doctrine_Cache_FileTestCase());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment