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
* @var Doctrine_Null $null used for extremely fast null value testing
*/
protected static $null;
protected $aggregateValues = array();
/**
* constructor
......@@ -117,7 +120,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
* @param string $name
* @return mixed
*/
public function getAggregateValue() {
public function getAggregateValue($name) {
return $this->aggregateValues[$name];
}
/**
......
......@@ -82,6 +82,12 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
* @var array $tableIndexes
*/
protected $tableIndexes = array();
protected $components = array();
protected $pendingAggregates = array();
protected $aggregateMap = array();
/**
* @var array $parts SQL query string parts
*/
......@@ -143,6 +149,14 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
return $this;
}
public function getPathAlias($path) {
$s = array_search($path, $this->compAliases);
if($s === false)
return $path;
return $s;
}
/**
* createSubquery
*
......@@ -264,6 +278,9 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
*/
private function getCollection($name) {
$table = $this->tables[$name];
if( ! isset($this->fetchModes[$name]))
return new Doctrine_Collection($table);
switch($this->fetchModes[$name]):
case Doctrine::FETCH_BATCH:
$coll = new Doctrine_Collection_Batch($table);
......@@ -344,7 +361,6 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
if($return == Doctrine::FETCH_ARRAY)
return $array;
foreach($array as $data) {
......@@ -355,12 +371,30 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
if(empty($row))
continue;
$ids = $this->tables[$key]->getIdentifier();
$name = $key;
if($this->isIdentifiable($row, $ids)) {
$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;
}
......@@ -384,7 +418,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
unset($previd);
} else {
$prev = $this->addRelated($prev, $name, $record);
$prev = $this->addRelated($prev, $name, $record);
}
// following statement is needed to ensure that mappings
......@@ -476,7 +511,6 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
* @return boolean
*/
public function isIdentifiable(array $row, $ids) {
$emptyID = false;
if(is_array($ids)) {
foreach($ids as $id) {
if($row[$id] == null)
......@@ -544,10 +578,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access {
$e = explode("__",$key);
$field = strtolower( array_pop($e) );
$component = strtolower( implode("__",$e) );
$data[$component][$field] = $value;
unset($data[$key]);
......
......@@ -45,6 +45,9 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
private $relationStack = array();
private $isDistinct = false;
private $pendingFields = array();
/**
* create
* returns a new Doctrine_Query object
......@@ -69,21 +72,97 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
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()) {
$this->remove('select');
$join = $this->join;
$where = $this->where;
$having = $this->having;
$q = "SELECT COUNT(DISTINCT ".$table->getTableName().'.'.$table->getIdentifier().") FROM ".$table->getTableName()." ";
foreach($join as $j) {
$q .= " ".implode(" ",$j);
}
public function count(Doctrine_Table $table, $params = array()) {
$this->remove('select');
$join = $this->join;
$where = $this->where;
$having = $this->having;
$q = "SELECT COUNT(DISTINCT ".$table->getTableName().'.'.$table->getIdentifier().") FROM ".$table->getTableName()." ";
foreach($join as $j) {
$q .= implode(" ",$j);
}
$string = $this->applyInheritance();
if( ! empty($where)) {
......@@ -94,13 +173,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
if( ! empty($string))
$q .= " WHERE (".$string.")";
}
if( ! empty($having))
$q .= " HAVING ".implode(' AND ',$having);
if( ! empty($having))
$q .= " HAVING ".implode(' AND ',$having);
$a = $this->getConnection()->execute($q, $params)->fetch(PDO::FETCH_NUM);
return $a[0];
}
$a = $this->getConnection()->execute($q, $params)->fetch(PDO::FETCH_NUM);
return $a[0];
}
/**
* loadFields
* loads fields for a given table and
......@@ -203,7 +282,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
$class = "Doctrine_Query_".ucwords($name);
$parser = new $class($this);
$parser->parse($args[0]);
break;
case "where":
......@@ -697,10 +776,6 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
foreach($e as $key => $fullname) {
try {
$copy = $e;
$e2 = preg_split("/[-(]/",$fullname);
$name = $e2[0];
......@@ -751,7 +826,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
else
$aliasString = $original;
switch($mark):
switch($mark) {
case ":":
$join = 'INNER JOIN ';
break;
......@@ -760,23 +835,17 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
break;
default:
throw new Doctrine_Exception("Unknown operator '$mark'");
endswitch;
}
if($fk->getType() == Doctrine_Relation::MANY_AGGREGATE ||
$fk->getType() == Doctrine_Relation::MANY_COMPOSITE) {
if( ! $fk->isOneToOne()) {
if( ! $loadFields) {
$this->subqueryAliases[] = $tname2;
}
$this->needsSubquery = true;
}
if($fk instanceof Doctrine_Relation_ForeignKey ||
$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) {
if($fk instanceof Doctrine_Relation_Association) {
$asf = $fk->getAssociationFactory();
$assocTableName = $asf->getTableName();
......@@ -784,8 +853,11 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
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.".".$table->getIdentifier()." = ".$assocTableName.".".$fk->getForeign();
$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();
} else {
$this->parts["join"][$tname][$tname2] = $join.$aliasString .' ON '.$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign();
}
$this->joins[$tname2] = $prevTable;
......@@ -798,13 +870,31 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
$this->relationStack[] = $fk;
}
$this->components[$currPath] = $table;
$this->tableStack[] = $table;
if( ! isset($this->tables[$tableName])) {
$this->tables[$tableName] = $table;
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 {
}
}
}
......@@ -86,11 +86,6 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable {
* @var Doctrine_Table_Repository $repository record repository
*/
private $repository;
/**
* @var Doctrine_Cache $cache second level cache
*/
private $cache;
/**
* @var array $columns an array of column definitions
*/
......@@ -733,8 +728,7 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable {
* finds a record by its identifier
*
* @param $id database row id
* @throws Doctrine_Find_Exception
* @return Doctrine_Record a record for given database identifier
* @return Doctrine_Record|false a record for given database identifier
*/
public function find($id) {
if($id !== null) {
......
<?php
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");
require_once("QueryConditionTestCase.php");
require_once("QueryComponentAliasTestCase.php");
require_once("QuerySubqueryTestCase.php");
require_once("QuerySelectTestCase.php");
require_once("DBTestCase.php");
require_once("SchemaTestCase.php");
......@@ -52,12 +53,11 @@ error_reporting(E_ALL);
print "<pre>";
$test = new GroupTest("Doctrine Framework Unit Tests");
$test->addTestCase(new Doctrine_RecordTestCase());
$test->addTestCase(new Doctrine_ValidatorTestCase());
$test->addTestCase(new Doctrine_Query_MultiJoin_TestCase());
$test->addTestCase(new Doctrine_Relation_TestCase());
......@@ -130,6 +130,7 @@ $test->addTestCase(new Doctrine_Query_Where_TestCase());
$test->addTestCase(new Doctrine_Query_From_TestCase());
$test->addTestCase(new Doctrine_Query_Select_TestCase());
//$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