Commit eaea971f authored by zYne's avatar zYne

fixes #307

parent cc85ccca
...@@ -197,7 +197,7 @@ class Doctrine_Db_Statement implements Doctrine_Adapter_Statement_Interface ...@@ -197,7 +197,7 @@ class Doctrine_Db_Statement implements Doctrine_Adapter_Statement_Interface
public function execute($params = null) public function execute($params = null)
{ {
$event = new Doctrine_Db_Event($this, Doctrine_Db_Event::EXECUTE, $this->stmt->queryString, $params); $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::EXECUTE, $this->stmt->queryString, $params);
//print $this->stmt->queryString . print_r($params, true) . "<br>";
$skip = $this->adapter->getListener()->onPreExecute($event); $skip = $this->adapter->getListener()->onPreExecute($event);
if ( ! $skip) { if ( ! $skip) {
......
...@@ -109,6 +109,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -109,6 +109,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
protected $tableIndexes = array(); protected $tableIndexes = array();
protected $pendingAggregates = array(); protected $pendingAggregates = array();
protected $subqueryAggregates = array();
/** /**
* @var array $aggregateMap an array containing all aggregate aliases, keys as dql aliases * @var array $aggregateMap an array containing all aggregate aliases, keys as dql aliases
* and values as sql aliases * and values as sql aliases
...@@ -403,9 +405,9 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -403,9 +405,9 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
$query = $this->view->getSelectSql(); $query = $this->view->getSelectSql();
} }
if ($this->isLimitSubqueryUsed() if ($this->isLimitSubqueryUsed() &&
&& $this->conn->getDBH()->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql' $this->conn->getDBH()->getAttribute(Doctrine::ATTR_DRIVER_NAME) !== 'mysql') {
) {
$params = array_merge($params, $params); $params = array_merge($params, $params);
} }
$stmt = $this->conn->execute($query, $params); $stmt = $this->conn->execute($query, $params);
...@@ -415,8 +417,9 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -415,8 +417,9 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
} }
if (count($this->tables) == 0) { if (count($this->tables) == 0) {
throw new Doctrine_Query_Exception("No components selected"); throw new Doctrine_Query_Exception('No components selected');
} }
$keys = array_keys($this->tables); $keys = array_keys($this->tables);
$root = $keys[0]; $root = $keys[0];
...@@ -425,13 +428,15 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -425,13 +428,15 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
$coll = $this->getCollection($root); $coll = $this->getCollection($root);
$prev[$root] = $coll; $prev[$root] = $coll;
if ($this->aggregate) if ($this->aggregate) {
$return = Doctrine::FETCH_ARRAY; $return = Doctrine::FETCH_ARRAY;
}
$array = $this->parseData($stmt); $array = $this->parseData($stmt);
if ($return == Doctrine::FETCH_ARRAY) if ($return == Doctrine::FETCH_ARRAY) {
return $array; return $array;
}
foreach ($array as $data) { foreach ($array as $data) {
/** /**
...@@ -482,6 +487,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -482,6 +487,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
if (isset($this->pendingAggregates[$alias][$index])) { if (isset($this->pendingAggregates[$alias][$index])) {
$agg = $this->pendingAggregates[$alias][$index][3]; $agg = $this->pendingAggregates[$alias][$index][3];
} elseif (isset($this->subqueryAggregates[$alias][$index])) {
$agg = $this->subqueryAggregates[$alias][$index];
} }
$record->mapValue($agg, $value); $record->mapValue($agg, $value);
...@@ -494,13 +501,14 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -494,13 +501,14 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
if ( ! isset($previd[$name])) { if ( ! isset($previd[$name])) {
$previd[$name] = array(); $previd[$name] = array();
} }
if ($previd[$name] !== $row) { if ($previd[$name] !== $row) {
// set internal data // set internal data
$this->tables[$name]->setData($row); $this->tables[$name]->setData($row);
// initialize a new record // initialize a new record
$record = $this->tables[$name]->getRecord(); $record = $this->tables[$name]->getRecord();
// aggregate values have numeric keys // aggregate values have numeric keys
...@@ -514,6 +522,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -514,6 +522,8 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
if (isset($this->pendingAggregates[$alias][$index])) { if (isset($this->pendingAggregates[$alias][$index])) {
$agg = $this->pendingAggregates[$alias][$index][3]; $agg = $this->pendingAggregates[$alias][$index][3];
} elseif (isset($this->subqueryAggregates[$alias][$index])) {
$agg = $this->subqueryAggregates[$alias][$index];
} }
$record->mapValue($agg, $value); $record->mapValue($agg, $value);
} }
...@@ -521,20 +531,21 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -521,20 +531,21 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
if ($name == $root) { if ($name == $root) {
// add record into root collection // add record into root collection
$coll->add($record); $coll->add($record);
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
// are being done properly when the result set doesn't // are being done properly when the result set doesn't
// contain the rows in 'right order' // contain the rows in 'right order'
if ($prev[$name] !== $record) if ($prev[$name] !== $record) {
$prev[$name] = $record; $prev[$name] = $record;
}
} }
$previd[$name] = $row; $previd[$name] = $row;
...@@ -682,7 +693,7 @@ abstract class Doctrine_Hydrate extends Doctrine_Access ...@@ -682,7 +693,7 @@ abstract class Doctrine_Hydrate extends Doctrine_Access
} else { } else {
$tableAlias .= '.'; $tableAlias .= '.';
} }
foreach ($maps as $map) { foreach ($maps as $map) {
$b = array(); $b = array();
foreach ($map as $field => $value) { foreach ($map as $field => $value) {
......
...@@ -62,6 +62,17 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -62,6 +62,17 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
* @var array $pendingFields * @var array $pendingFields
*/ */
private $pendingFields = array(); private $pendingFields = array();
/**
* @var array $pendingSubqueries SELECT part subqueries, these are called pending subqueries since
* they cannot be parsed directly (some queries might be correlated)
*/
private $pendingSubqueries = array();
/**
* @var boolean $subqueriesProcessed Whether or not pending subqueries have already been processed.
* Consequent calls to getQuery would result badly constructed queries
* without this variable
*/
private $subqueriesProcessed = false;
...@@ -142,7 +153,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -142,7 +153,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
if (isset($this->pendingFields[$componentAlias])) { if (isset($this->pendingFields[$componentAlias])) {
$fields = $this->pendingFields[$componentAlias]; $fields = $this->pendingFields[$componentAlias];
if(in_array('*', $fields)) { if (in_array('*', $fields)) {
$fields = $table->getColumnNames(); $fields = $table->getColumnNames();
} else { } else {
// only auto-add the primary key fields if this query object is not // only auto-add the primary key fields if this query object is not
...@@ -155,7 +166,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -155,7 +166,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
foreach ($fields as $name) { foreach ($fields as $name) {
$name = $table->getColumnName($name); $name = $table->getColumnName($name);
$this->parts["select"][] = $tableAlias . '.' .$name . ' AS ' . $tableAlias . '__' . $name; $this->parts['select'][] = $tableAlias . '.' .$name . ' AS ' . $tableAlias . '__' . $name;
} }
$this->neededTables[] = $tableAlias; $this->neededTables[] = $tableAlias;
...@@ -172,9 +183,14 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -172,9 +183,14 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
{ {
$refs = Doctrine_Query::bracketExplode($dql, ','); $refs = Doctrine_Query::bracketExplode($dql, ',');
foreach($refs as $reference) { foreach ($refs as $reference) {
if(strpos($reference, '(') !== false) { if (strpos($reference, '(') !== false) {
$this->parseAggregateFunction2($reference); if (substr($reference, 0, 1) === '(') {
// subselect found in SELECT part
$this->parseSubselect($reference);
} else {
$this->parseAggregateFunction2($reference);
}
} else { } else {
$e = explode('.', $reference); $e = explode('.', $reference);
...@@ -186,6 +202,31 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -186,6 +202,31 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
} }
} }
} }
/**
* parseSubselect
*
* parses the subquery found in DQL SELECT part and adds the
* parsed form into $pendingSubqueries stack
*
* @param string $reference
* @return void
*/
public function parseSubselect($reference)
{
$e = Doctrine_Query::bracketExplode($reference, ' ');
$alias = $e[1];
if (count($e) > 2) {
if (strtoupper($e[1]) !== 'AS') {
throw new Doctrine_Query_Exception('Syntax error near: ' . $reference);
}
$alias = $e[2];
}
$subquery = substr($e[0], 1, -1);
$this->pendingSubqueries[] = array($subquery, $alias);
}
public function parseAggregateFunction2($func) public function parseAggregateFunction2($func)
{ {
$e = Doctrine_Query::bracketExplode($func, ' '); $e = Doctrine_Query::bracketExplode($func, ' ');
...@@ -242,9 +283,39 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -242,9 +283,39 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
throw new Doctrine_Query_Exception('Unknown function '.$name); throw new Doctrine_Query_Exception('Unknown function '.$name);
} }
} }
public function processPendingSubqueries()
{
if ($this->subqueriesProcessed === true) {
return false;
}
foreach ($this->pendingSubqueries as $value) {
list($dql, $alias) = $value;
$sql = $this->createSubquery()->parseQuery($dql, false)->getQuery();
reset($this->tableAliases);
$tableAlias = current($this->tableAliases);
reset($this->compAliases);
$componentAlias = key($this->compAliases);
$sqlAlias = $tableAlias . '__' . count($this->aggregateMap);
$this->parts['select'][] = '(' . $sql . ') AS ' . $sqlAlias;
$this->aggregateMap[$alias] = $sqlAlias;
$this->subqueryAggregates[$componentAlias][] = $alias;
}
$this->subqueriesProcessed = true;
return true;
}
public function processPendingAggregates($componentAlias) public function processPendingAggregates($componentAlias)
{ {
$tableAlias = $this->getTableAlias($componentAlias); $tableAlias = $this->getTableAlias($componentAlias);
if ( ! isset($this->tables[$tableAlias])) { if ( ! isset($this->tables[$tableAlias])) {
throw new Doctrine_Query_Exception('Unknown component path ' . $componentAlias); throw new Doctrine_Query_Exception('Unknown component path ' . $componentAlias);
...@@ -670,6 +741,9 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -670,6 +741,9 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
$this->limitSubqueryUsed = true; $this->limitSubqueryUsed = true;
} }
// process all pending SELECT part subqueries
$this->processPendingSubqueries();
// build the basic query // build the basic query
$str = ''; $str = '';
...@@ -1333,7 +1407,6 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -1333,7 +1407,6 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
$this->tableAliases[$currPath] = $tname; $this->tableAliases[$currPath] = $tname;
$tableName = $tname; $tableName = $tname;
} else { } else {
$index += strlen($e[($key - 1)]) + 1; $index += strlen($e[($key - 1)]) + 1;
......
...@@ -203,7 +203,7 @@ class Doctrine_Query_Where extends Doctrine_Query_Condition ...@@ -203,7 +203,7 @@ class Doctrine_Query_Where extends Doctrine_Query_Condition
$sub = Doctrine_Query::bracketTrim(substr($where, $pos)); $sub = Doctrine_Query::bracketTrim(substr($where, $pos));
return $operator . ' ('.$this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')'; return $operator . ' (' . $this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')';
} }
/** /**
* getOperator * getOperator
......
...@@ -33,7 +33,8 @@ ...@@ -33,7 +33,8 @@
*/ */
class Doctrine_Query_Subquery_TestCase extends Doctrine_UnitTestCase class Doctrine_Query_Subquery_TestCase extends Doctrine_UnitTestCase
{ {
public function testSubqueryWithWherePartAndInExpression()
public function testSubqueryWithWherePartAndInExpression()
{ {
$q = new Doctrine_Query(); $q = new Doctrine_Query();
$q->from('User')->where("User.id NOT IN (FROM User(id) WHERE User.name = 'zYne')"); $q->from('User')->where("User.id NOT IN (FROM User(id) WHERE User.name = 'zYne')");
...@@ -46,11 +47,11 @@ class Doctrine_Query_Subquery_TestCase extends Doctrine_UnitTestCase ...@@ -46,11 +47,11 @@ class Doctrine_Query_Subquery_TestCase extends Doctrine_UnitTestCase
$this->assertEqual($users->count(), 7); $this->assertEqual($users->count(), 7);
$this->assertEqual($users[0]->name, 'Arnold Schwarzenegger'); $this->assertEqual($users[0]->name, 'Arnold Schwarzenegger');
} }
public function testSubqueryAllowsSelectingOfAnyField() public function testSubqueryAllowsSelectingOfAnyField()
{ {
$q = new Doctrine_Query(); $q = new Doctrine_Query();
$q->from('User u')->where('u.id NOT IN (SELECT g.user_id FROM Groupuser g)'); $q->from('User u')->where('u.id NOT IN (SELECT g.user_id FROM Groupuser g)');
$this->assertEqual($q->getQuery(), "SELECT e.id AS e__id, e.name AS e__name, e.loginname AS e__loginname, e.password AS e__password, e.type AS e__type, e.created AS e__created, e.updated AS e__updated, e.email_id AS e__email_id FROM entity e WHERE e.id NOT IN (SELECT g.user_id AS g__user_id FROM groupuser g) AND (e.type = 0)"); $this->assertEqual($q->getQuery(), "SELECT e.id AS e__id, e.name AS e__name, e.loginname AS e__loginname, e.password AS e__password, e.type AS e__type, e.created AS e__created, e.updated AS e__updated, e.email_id AS e__email_id FROM entity e WHERE e.id NOT IN (SELECT g.user_id AS g__user_id FROM groupuser g) AND (e.type = 0)");
} }
...@@ -58,13 +59,19 @@ class Doctrine_Query_Subquery_TestCase extends Doctrine_UnitTestCase ...@@ -58,13 +59,19 @@ class Doctrine_Query_Subquery_TestCase extends Doctrine_UnitTestCase
{ {
// ticket #307 // ticket #307
$q = new Doctrine_Query(); $q = new Doctrine_Query();
/*$q->query("SELECT u.*, (SELECT p.name FROM User p WHERE p.name = u.name) name2 FROM User u WHERE u.name = 'zYne' LIMIT 1");
$q->parseQuery("SELECT u.name, (SELECT COUNT(p.id) FROM Phonenumber p WHERE p.entity_id = u.id) pcount FROM User u WHERE u.name = 'zYne' LIMIT 1");
$this->assertEqual($q->getQuery(), "SELECT e.id AS e__id, e.name AS e__name, (SELECT COUNT(p.id) AS p__0 FROM phonenumber p WHERE p.entity_id = e.id) AS e__0 FROM entity e WHERE e.name = 'zYne' AND (e.type = 0) LIMIT 1");
// test consequent call
$this->assertEqual($q->getQuery(), "SELECT e.id AS e__id, e.name AS e__name, (SELECT COUNT(p.id) AS p__0 FROM phonenumber p WHERE p.entity_id = e.id) AS e__0 FROM entity e WHERE e.name = 'zYne' AND (e.type = 0) LIMIT 1");
$users = $q->execute(); $users = $q->execute();
$this->assertEqual($users->count(), 1); $this->assertEqual($users->count(), 1);
*/
$this->fail("Subquery support in select part. Ticket #307."); $this->assertEqual($users[0]->name, 'zYne');
$this->assertEqual($users[0]->pcount, 1);
} }
} }
?> ?>
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