From 47e1cf515e8d151415032d1cc4de91d47d645c78 Mon Sep 17 00:00:00 2001 From: doctrine Date: Tue, 23 May 2006 12:12:54 +0000 Subject: [PATCH] DQL : Multiple join bug fixed DQL : Changed cartesian product fetching to inner join fetching --- classes/Query.class.php | 62 +++++++++++++++++++++++------------ classes/Record.class.php | 20 +++++++---- tests/QueryTestCase.class.php | 30 +++++++++++++---- tests/UnitTestCase.class.php | 4 +-- 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/classes/Query.class.php b/classes/Query.class.php index f017cfba8..80b527146 100644 --- a/classes/Query.class.php +++ b/classes/Query.class.php @@ -22,6 +22,8 @@ class Doctrine_Query extends Doctrine_Access { private $collections = array(); private $joined = array(); + + private $joins = array(); /** * @var array $data fetched data */ @@ -101,6 +103,7 @@ class Doctrine_Query extends Doctrine_Access { $this->connectors = array(); $this->collections = array(); $this->joined = array(); + $this->joins = array(); } /** * loadFields @@ -167,6 +170,9 @@ class Doctrine_Query extends Doctrine_Access { $this->parts[$name] = $args[0]; break; + case "from": + $this->parts['columns'] = array(); + $this->joins = array(); default: $this->parts[$name] = array(); $this->$method($args[0]); @@ -209,6 +215,9 @@ class Doctrine_Query extends Doctrine_Access { $this->parts[$name] = $value; break; + case "from": + $this->parts['columns'] = array(); + $this->joins = array(); default: $this->parts[$name] = array(); $this->$method($value); @@ -230,14 +239,15 @@ class Doctrine_Query extends Doctrine_Access { // build the basic query $q = "SELECT ".implode(", ",$this->parts["columns"]). " FROM "; + foreach($this->parts["from"] as $tname => $bool) { - $str = $tname; - if(isset($this->parts["join"][$tname])) - $str .= " ".$this->parts["join"][$tname]; - - $a[] = $str; + $a[] = $tname; } $q .= implode(", ",$a); + + if( ! empty($this->parts['join'])) + $q .= " ".implode(' ', $this->parts["join"]); + $this->applyInheritance(); if( ! empty($this->parts["where"])) $q .= " WHERE ".implode(" ",$this->parts["where"]); @@ -345,7 +355,7 @@ class Doctrine_Query extends Doctrine_Access { final public function getData($key) { if(isset($this->data[$key]) && is_array($this->data[$key])) return $this->data[$key]; - + return array(); } /** @@ -395,17 +405,18 @@ class Doctrine_Query extends Doctrine_Access { $previd = array(); - $coll = $this->getCollection($root); + $coll = $this->getCollection($root); + $prev[$root] = $coll; $array = $this->parseData($stmt); + $colls = array(); - foreach($array as $data): - + foreach($array as $data) { /** * remove duplicated data rows and map data into objects */ - foreach($data as $key => $row): + foreach($data as $key => $row) { if(empty($row)) continue; @@ -416,24 +427,31 @@ class Doctrine_Query extends Doctrine_Access { if($previd[$name] !== $row) { + // set internal data $this->tables[$name]->setData($row); + + // initialize a new record $record = $this->tables[$name]->getRecord(); if($name == $root) { + // add record into root collection $coll->add($record); } else { - $last = $coll->getLast(); - - if( ! $last->hasReference($name)) - $last->initReference($this->getCollection($name),$this->connectors[$name]); + $pointer = $this->joins[$name]; + + $last = $prev[$pointer]->getLast(); + if( ! $last->hasReference($name)) { + $prev[$name] = $this->getCollection($name); + $last->initReference($prev[$name],$this->connectors[$name]); + } $last->addReference($record); } } $previd[$name] = $row; - endforeach; - endforeach; + } + } return $coll; endswitch; @@ -866,18 +884,20 @@ class Doctrine_Query extends Doctrine_Access { switch($fk->getType()): case Doctrine_Relation::ONE_AGGREGATE: case Doctrine_Relation::ONE_COMPOSITE: - - $this->parts["where"][] = "(".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign().")"; - $this->parts["from"][$tname] = true; - $this->parts["from"][$tname2] = true; + //$this->parts["where"][] = "(".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign().")"; + //$this->parts["from"][$tname] = true; + //$this->parts["from"][$tname2] = true; + $this->parts["join"][$tname] = "INNER JOIN ".$tname2." ON ".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign(); break; case Doctrine_Relation::MANY_AGGREGATE: case Doctrine_Relation::MANY_COMPOSITE: $this->parts["join"][$tname] = "LEFT JOIN ".$tname2." ON ".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign(); + $this->joined[] = $tname2; - $this->parts["from"][$tname] = true; break; endswitch; + $c = $objTable->getComponentName(); + $this->joins[$name] = $c; } elseif($fk instanceof Doctrine_Association) { $asf = $fk->getAssociationFactory(); diff --git a/classes/Record.class.php b/classes/Record.class.php index 1e941d09f..f4c3b85ba 100644 --- a/classes/Record.class.php +++ b/classes/Record.class.php @@ -205,7 +205,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite case Doctrine_Identifier::SEQUENCE: if($exists) { $name = $this->table->getIdentifier(); - + if(isset($this->data[$name])) $this->id = $this->data[$name]; @@ -246,14 +246,16 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite $this->data[$k] = array(); } return array_keys(get_object_vars($this)); + } /** - * __wakeup + * unseralize * this method is automatically called everytime a Doctrine_Record object is unserialized * * @return void */ public function __wakeup() { + $this->modified = array(); $this->state = Doctrine_Record::STATE_CLEAN; @@ -301,21 +303,27 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite } /** * hasCollections - * @return boolean whether or not this dao is part of a collection + * whether or not this record is part of a collection + * + * @return boolean */ final public function hasCollections() { return (! empty($this->collections)); } /** * getState + * returns the current state of the object + * * @see Doctrine_Record::STATE_* constants - * @return integer the current state + * @return integer */ final public function getState() { return $this->state; } /** - * refresh refresh internal data from the database + * refresh + * refresh internal data from the database + * * @return boolean */ final public function refresh() { @@ -377,7 +385,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite } /** * get - * returns a value of a property or related component + * returns a value of a property or a related component * * @param $name name of the property or related component * @throws InvalidKeyException diff --git a/tests/QueryTestCase.class.php b/tests/QueryTestCase.class.php index 020bca1c7..a17516b31 100644 --- a/tests/QueryTestCase.class.php +++ b/tests/QueryTestCase.class.php @@ -32,13 +32,15 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $thread = $board->Threads[0]; $thread->Entries[0]->topic = "My first topic"; + $thread->Entries[1]->topic = "My second topic"; $this->assertEqual($thread->Entries[0]->topic, "My first topic"); $this->assertEqual($thread->Entries[0]->getState(), Doctrine_Record::STATE_TDIRTY); $this->assertTrue($thread->Entries[0] instanceof Forum_Entry); $this->session->flush(); - + $q = new Doctrine_Query($this->session); $board->getTable()->clear(); + $board = $board->getTable()->find($board->getID()); $this->assertEqual($board->Threads->count(), 1); $this->assertEqual($board->name, "Doctrine Forum"); @@ -48,7 +50,7 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $this->assertTrue($board->Threads[0] instanceof Forum_Thread); - $q = new Doctrine_Query($this->session); + $q->from("Forum_Board"); $coll = $q->execute(); $this->assertEqual($coll->count(), 1); @@ -56,6 +58,20 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $q->from("Forum_Board, Forum_Board.Threads"); $coll = $q->execute(); $this->assertEqual($coll->count(), 1); + + + $q->from("Forum_Board-l, Forum_Board.Threads-l"); + $this->assertEqual($q->getQuery(), "SELECT forum_board.id AS Forum_Board__id, forum_thread.id AS Forum_Thread__id FROM forum_board LEFT JOIN forum_thread ON forum_board.id = forum_thread.board_id"); + + $q->from("Forum_Board-l, Forum_Board.Threads-l, Forum_Board.Threads.Entries-l"); + $this->assertEqual($q->getQuery(), "SELECT forum_board.id AS Forum_Board__id, forum_thread.id AS Forum_Thread__id, forum_entry.id AS Forum_Entry__id FROM forum_board LEFT JOIN forum_thread ON forum_board.id = forum_thread.board_id LEFT JOIN forum_entry ON forum_thread.id = forum_entry.thread_id"); + $boards = $q->execute(); + $this->assertEqual($boards->count(), 1); + $count = count($this->dbh); + $this->assertEqual($boards[0]->Threads->count(), 1); + $this->assertEqual(count($this->dbh), $count); + $this->assertEqual($boards[0]->Threads[0]->Entries->count(), 1); + $this->assertEqual(count($this->dbh), $count); } public function testQueryWithAliases() { @@ -141,9 +157,10 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { } public function testOrderBy() { $query = new Doctrine_Query($this->session); - $users = $query->query("FROM User-b ORDER BY User.name ASC, User.Email.address"); + $query->from("User-b")->orderby("User.name ASC, User.Email.address"); + $users = $query->execute(); $this->assertEqual(trim($query->getQuery()), - "SELECT entity.id AS User__id FROM entity, email WHERE (entity.email_id = email.id) AND (entity.type = 0) ORDER BY entity.name ASC, email.address"); + "SELECT entity.id AS User__id FROM entity INNER JOIN email ON entity.email_id = email.id WHERE (entity.type = 0) ORDER BY entity.name ASC, email.address"); $this->assertEqual($users->count(),8); $this->assertTrue($users[0]->name == "Arnold Schwarzenegger"); } @@ -286,11 +303,12 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $users = $query->query("FROM User-b, User.Email-b"); $this->assertEqual(trim($query->getQuery()), - "SELECT entity.id AS User__id, email.id AS Email__id FROM entity, email WHERE (entity.email_id = email.id) AND (entity.type = 0)"); + "SELECT entity.id AS User__id, email.id AS Email__id FROM entity INNER JOIN email ON entity.email_id = email.id WHERE (entity.type = 0)"); $this->assertEqual($users->count(),8); $users = $query->query("FROM Email-b WHERE Email.address LIKE '%@example%'"); + $this->assertEqual($query->getQuery(), "SELECT email.id AS Email__id FROM email WHERE (email.address LIKE '%@example%')"); $this->assertEqual($users->count(),8); @@ -323,7 +341,7 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $this->assertTrue(is_array($values)); $this->assertTrue(isset($values['users'])); $this->assertTrue(isset($values['max'])); - } + } } ?> diff --git a/tests/UnitTestCase.class.php b/tests/UnitTestCase.class.php index 876928c2f..b2325acda 100644 --- a/tests/UnitTestCase.class.php +++ b/tests/UnitTestCase.class.php @@ -52,10 +52,8 @@ class Doctrine_UnitTestCase extends UnitTestCase { $this->prepareTables(); $this->prepareData(); } - public function prepareTables() { - + public function prepareTables() { foreach($this->tables as $name) { - $this->dbh->query("DROP TABLE IF EXISTS ".strtolower($name)); }