From 20990ed2046a8fd48f3fa7c5e4841bae118260de Mon Sep 17 00:00:00 2001 From: doctrine <doctrine@625475ce-881a-0410-a577-b389adb331d8> Date: Mon, 29 May 2006 08:43:21 +0000 Subject: [PATCH] DQL: direct one-to-one relation fetching bug fixed --- classes/Collection/Batch.class.php | 11 +++-- classes/IndexGenerator.php | 2 +- classes/Query.class.php | 33 +++++++++---- classes/Record.class.php | 45 ++++++++++++++--- classes/Sensei/Sensei.class.php | 9 +--- classes/Session.class.php | 40 --------------- classes/Table.class.php | 8 ++- classes/Validator/Nospace.class.php | 2 +- tests/QueryTestCase.class.php | 75 +++++++++++++++++++++++------ tests/SenseiTestCase.class.php | 7 ++- tests/UnitTestCase.class.php | 1 + tests/classes.php | 49 +++++++++++++++++++ tests/run.php | 2 - 13 files changed, 197 insertions(+), 87 deletions(-) diff --git a/classes/Collection/Batch.class.php b/classes/Collection/Batch.class.php index ed5d0ce64..bae8bcaae 100644 --- a/classes/Collection/Batch.class.php +++ b/classes/Collection/Batch.class.php @@ -28,6 +28,7 @@ class Doctrine_Collection_Batch extends Doctrine_Collection { /** * @param integer $batchSize batch size + * @return boolean */ public function setBatchSize($batchSize) { $batchSize = (int) $batchSize; @@ -38,15 +39,19 @@ class Doctrine_Collection_Batch extends Doctrine_Collection { return true; } /** + * returns the batch size of this collection + * * @return integer */ public function getBatchSize() { return $this->batchSize; } /** - * load load a specified element, by loading the batch the element is part of - * @param Doctrine_Record $record data access object - * @return boolean whether or not the load operation was successful + * load + * loads a specified element, by loading the batch the element is part of + * + * @param Doctrine_Record $record record to be loaded + * @return boolean whether or not the load operation was successful */ public function load(Doctrine_Record $record) { if(empty($this->data)) diff --git a/classes/IndexGenerator.php b/classes/IndexGenerator.php index 00600031d..01b1bcd9d 100644 --- a/classes/IndexGenerator.php +++ b/classes/IndexGenerator.php @@ -22,4 +22,4 @@ class Doctrine_IndexGenerator { return $value; } } -?> +?> diff --git a/classes/Query.class.php b/classes/Query.class.php index e3e283b25..4da8612ce 100644 --- a/classes/Query.class.php +++ b/classes/Query.class.php @@ -419,8 +419,7 @@ class Doctrine_Query extends Doctrine_Access { $array = $this->parseData($stmt); - $colls = array(); - + $colls = array(); foreach($array as $data) { /** @@ -465,14 +464,30 @@ class Doctrine_Query extends Doctrine_Access { $coll->add($record); } else { $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); + $fk = $this->tables[$pointer]->getForeignKey($this->tables[$pointer]->getAlias($name)); + + switch($fk->getType()): + case Doctrine_Relation::ONE_COMPOSITE: + case Doctrine_Relation::ONE_AGGREGATE: + $last = $prev[$pointer]->getLast(); + + $last->rawSet($this->connectors[$name]->getLocal(), $record->getID()); + + $last->initSingleReference($record); + + $prev[$name] = $record; + break; + default: + // one-to-many relation or many-to-many relation + $last = $prev[$pointer]->getLast(); + + if( ! $last->hasReference($name)) { + $prev[$name] = $this->getCollection($name); + $last->initReference($prev[$name],$this->connectors[$name]); + } + $last->addReference($record); + endswitch; } } diff --git a/classes/Record.class.php b/classes/Record.class.php index aab162589..7200846a4 100644 --- a/classes/Record.class.php +++ b/classes/Record.class.php @@ -83,6 +83,10 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite * @var integer $index this index is used for creating object identifiers */ private static $index = 1; + /** + * @var Doctrine_Null $nullObject a Doctrine_Null object used for SQL null value testing + */ + private static $nullObject; /** * @var integer $oid object identifier */ @@ -157,6 +161,12 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite $this->table->getRepository()->add($this); } } + /** + * initNullObject + */ + public static function initNullObject() { + self::$nullObject = new Doctrine_Null; + } /** * setUp * implemented by child classes @@ -402,7 +412,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite if(is_array($this->data[$name])) { // no use trying to load the data from database if the Doctrine_Record is not a proxy - if($this->state == Doctrine_Record::STATE_PROXY) { + if($this->state == Doctrine_Record::STATE_PROXY) { if( ! empty($this->collections)) { foreach($this->collections as $collection) { $collection->load($this); @@ -582,11 +592,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite $this->table->getSession()->save($this); foreach($saveLater as $fk) { - - $table = $fk->getTable(); - $foreign = $fk->getForeign(); - $local = $fk->getLocal(); - + $table = $fk->getTable(); $alias = $this->table->getAlias($table->getComponentName()); if(isset($this->references[$alias])) { @@ -836,6 +842,17 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite final public function getID() { return $this->id; } + /** + * getLast + * this method is used internally be Doctrine_Query + * it is needed to provide compatibility between + * records and collections + * + * @return Doctrine_Record + */ + public function getLast() { + return $this; + } /** * hasRefence * @param string $name @@ -845,8 +862,22 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite return isset($this->references[$name]); } /** + * initalizes a one-to-one relation + * + * @param Doctrine_Record $record + * @param Doctrine_Relation $connector + * @return void + */ + public function initSingleReference(Doctrine_Record $record) { + $name = $this->table->getAlias($record->getTable()->getComponentName()); + $this->references[$name] = $record; + } + /** + * initalizes a one-to-many / many-to-many relation + * * @param Doctrine_Collection $coll - * @param string $connectorField + * @param Doctrine_Relation $connector + * @return void */ public function initReference(Doctrine_Collection $coll, Doctrine_Relation $connector) { $name = $this->table->getAlias($coll->getTable()->getComponentName()); diff --git a/classes/Sensei/Sensei.class.php b/classes/Sensei/Sensei.class.php index e84b70766..2f4b6c9ab 100644 --- a/classes/Sensei/Sensei.class.php +++ b/classes/Sensei/Sensei.class.php @@ -1,10 +1,8 @@ <?php class Sensei_Group extends Doctrine_Record { } -//class Sensei_Company extends Sensei_Group { } - +class Sensei_Company extends Sensei_Group { } class Sensei_User extends Doctrine_Record { } -//class Sensei_Customer extends Sensei_User { } - +class Sensei_Customer extends Sensei_User { } class Sensei_Entity extends Doctrine_Record { /** * setTableDefinition @@ -65,10 +63,7 @@ class Sensei_Session extends Doctrine_Record { $this->hasColumn("created","integer"); } } - class Sensei_Exception extends Exception { } - - class Sensei extends Doctrine_Access { const ATTR_LIFESPAN = 0; /** diff --git a/classes/Session.class.php b/classes/Session.class.php index c1ac6b780..814290b37 100644 --- a/classes/Session.class.php +++ b/classes/Session.class.php @@ -246,46 +246,7 @@ abstract class Doctrine_Session extends Doctrine_Configurable implements Countab public function create($name) { return $this->getTable($name)->create(); } - public function buildFlushTree2(array $tables) { - $tree = array(); - foreach($tables as $table) { - if( ! ($table instanceof Doctrine_Table)) - $table = $this->getTable($table); - $name = $table->getComponentName(); - $index = array_search($name,$tree); - if($index === false) - $tree[] = $name; - - - foreach($table->getForeignKeys() as $rel) { - $name = $rel->getTable()->getComponentName(); - $index = array_search($name,$tree); - - if($rel instanceof Doctrine_ForeignKey) { - if($index !== false) - unset($tree[$index]); - - $tree[] = $name; - } elseif($rel instanceof Doctrine_LocalKey) { - if($index !== false) - unset($tree[$index]); - - array_unshift($tree, $name); - } elseif($rel instanceof Doctrine_Association) { - $t = $rel->getAssociationFactory(); - $n = $t->getComponentName(); - $index = array_search($n,$tree); - - if($index !== false) - unset($tree[$index]); - - $tree[] = $n; - } - } - } - return array_values($tree); - } /** * buildFlushTree @@ -573,7 +534,6 @@ abstract class Doctrine_Session extends Doctrine_Configurable implements Countab $increment = true; } - foreach($inserts as $k => $record) { $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($record); // listen the onPreInsert event diff --git a/classes/Table.class.php b/classes/Table.class.php index 7ba886b1c..3fe6601e9 100644 --- a/classes/Table.class.php +++ b/classes/Table.class.php @@ -81,6 +81,10 @@ class Doctrine_Table extends Doctrine_Configurable { * @var array $boundAliases bound relation aliases */ private $boundAliases = array(); + /** + * @var integer $columnCount cached column count + */ + private $columnCount; /** @@ -135,6 +139,8 @@ class Doctrine_Table extends Doctrine_Configurable { if(method_exists($record,"setTableDefinition")) { $record->setTableDefinition(); + $this->columnCount = count($this->columns); + if(isset($this->columns)) { $method = new ReflectionMethod($this->name,"setTableDefinition"); $class = $method->getDeclaringClass(); @@ -784,7 +790,7 @@ class Doctrine_Table extends Doctrine_Configurable { * @return integer */ final public function getColumnCount() { - return count($this->columns); + return $this->columnCount; } /** * returns all columns and their definitions diff --git a/classes/Validator/Nospace.class.php b/classes/Validator/Nospace.class.php index f7eda274d..532cccf48 100644 --- a/classes/Validator/Nospace.class.php +++ b/classes/Validator/Nospace.class.php @@ -8,7 +8,7 @@ class Doctrine_Validator_NoSpace { * @return boolean */ public function validate(Doctrine_Record $record, $key, $value, $args) { - if(preg_match("/[\s\r\t\n]/", $value)) + if(trim($value) === '') return false; return true; diff --git a/tests/QueryTestCase.class.php b/tests/QueryTestCase.class.php index 77982091a..615a8abfe 100644 --- a/tests/QueryTestCase.class.php +++ b/tests/QueryTestCase.class.php @@ -7,6 +7,46 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $this->tables[] = "Forum_Thread"; parent::prepareTables(); } + + public function testOneToOneRelationFetching() { + + $count = $this->dbh->count(); + $users = $this->query->from("User-l:Email-i")->execute(); + $this->assertEqual(($count + 1), $this->dbh->count()); + $this->assertTrue($users instanceof Doctrine_Collection_Lazy); + $count = $this->dbh->count(); + + foreach($users as $user) { + // iterate through users and test that no additional queries are needed + $user->Email->address; + $this->assertEqual($count, $this->dbh->count()); + } + $users[0]->Account->amount = 3000; + $this->assertEqual($users[0]->Account->amount, 3000); + $this->assertTrue($users[0]->Account instanceof Account); + $this->assertEqual($users[0]->id, $users[0]->Account->entity_id); + + $users[0]->Account->save(); + + $users[0]->refresh(); + $this->assertEqual($users[0]->Account->amount, 3000); + $this->assertTrue($users[0]->Account instanceof Account); + + $this->assertEqual($users[0]->id, $users[0]->Account->entity_id); + + $this->assertEqual($users[0]->Account->getState(), Doctrine_Record::STATE_CLEAN); + $users[0]->getTable()->clear(); + $users[0]->Account->getTable()->clear(); + + $count = $this->dbh->count(); + $users = $this->query->from("User-l:Account-i")->execute(); + $this->assertEqual(($count + 1), $this->dbh->count()); + $this->assertTrue($users instanceof Doctrine_Collection_Lazy); + $this->assertEqual($users->count(), 1); + + $this->assertEqual($users[0]->Account->amount,3000); + } + public function testNotValidLazyPropertyFetching() { $q = new Doctrine_Query($this->session); @@ -49,7 +89,6 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $this->assertEqual($count + 1, count($this->dbh)); } - public function testQueryWithComplexAliases() { $q = new Doctrine_Query($this->session); @@ -209,7 +248,7 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $this->assertEqual($query->offset, 3); $this->assertEqual($coll->count(), 3); } - public function testPrepared() { + public function testPreparedQuery() { $coll = $this->session->query("FROM User WHERE User.name = :name", array(":name" => "zYne")); $this->assertEqual($coll->count(), 1); } @@ -223,6 +262,25 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $this->assertEqual($users->count(),8); $this->assertTrue($users[0]->name == "Arnold Schwarzenegger"); } + public function testBatchFetching() { + $query = new Doctrine_Query($this->session); + $users = $query->query("FROM User-b"); + $this->assertEqual(trim($query->getQuery()), + "SELECT entity.id AS User__id FROM entity WHERE (entity.type = 0)"); + + $this->assertEqual($users[0]->name, "zYne"); + $this->assertTrue($users instanceof Doctrine_Collection_Batch); + } + public function testLazyFetching() { + $query = new Doctrine_Query($this->session); + $users = $query->query("FROM User-l"); + $this->assertEqual(trim($query->getQuery()), + "SELECT entity.id AS User__id FROM entity WHERE (entity.type = 0)"); + + $this->assertEqual($users[0]->name, "zYne"); + $this->assertTrue($users instanceof Doctrine_Collection_Lazy); + + } public function testQuery() { @@ -326,20 +384,8 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { $count2 = $this->session->getDBH()->count(); - $users = $query->query("FROM User-b"); - $this->assertEqual(trim($query->getQuery()), - "SELECT entity.id AS User__id FROM entity WHERE (entity.type = 0)"); - - $this->assertEqual($users[0]->name, "zYne"); - $this->assertTrue($users instanceof Doctrine_Collection_Batch); - $users = $query->query("FROM User-l"); - $this->assertEqual(trim($query->getQuery()), - "SELECT entity.id AS User__id FROM entity WHERE (entity.type = 0)"); - - $this->assertEqual($users[0]->name, "zYne"); - $this->assertTrue($users instanceof Doctrine_Collection_Lazy); //$this->clearCache(); @@ -403,5 +449,6 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { //$this->assertTrue(isset($values['max'])); } + } ?> diff --git a/tests/SenseiTestCase.class.php b/tests/SenseiTestCase.class.php index 38ca46053..b86ee90a3 100644 --- a/tests/SenseiTestCase.class.php +++ b/tests/SenseiTestCase.class.php @@ -1,5 +1,6 @@ <?php require_once("../classes/Doctrine.class.php"); + Doctrine::loadAll(); class Sensei_UnitTestCase extends UnitTestCase { @@ -13,6 +14,9 @@ class Sensei_UnitTestCase extends UnitTestCase { private $init = false; public function init() { + if(headers_sent()) + throw new Exception("'".ob_end_flush()."'"); + $this->manager = Doctrine_Manager::getInstance(); $this->manager->setAttribute(Doctrine::ATTR_CACHE, Doctrine::CACHE_NONE); @@ -40,7 +44,7 @@ class Sensei_UnitTestCase extends UnitTestCase { $entity->loginname = "Chuck Norris"; $entity->password = "toughguy"; - + $entity->save(); $this->init = true; @@ -109,6 +113,5 @@ class Sensei_UnitTestCase extends UnitTestCase { $this->assertEqual($this->record->entity_id, 0); } - } ?> diff --git a/tests/UnitTestCase.class.php b/tests/UnitTestCase.class.php index b2325acda..313b43df9 100644 --- a/tests/UnitTestCase.class.php +++ b/tests/UnitTestCase.class.php @@ -49,6 +49,7 @@ class Doctrine_UnitTestCase extends UnitTestCase { $this->manager->setAttribute(Doctrine::ATTR_LISTENER, $this->listener); } + $this->query = new Doctrine_Query($this->session); $this->prepareTables(); $this->prepareData(); } diff --git a/tests/classes.php b/tests/classes.php index 50cf51a76..64efcb0f6 100644 --- a/tests/classes.php +++ b/tests/classes.php @@ -207,4 +207,53 @@ class Forum_Thread extends Doctrine_Record { $this->ownsMany("Forum_Entry as Entries", "Forum_Entry.thread_id"); } } +class App extends Doctrine_Record { + public function setTableDefinition() { + $this->hasColumn("name", "string", 32); + $this->hasColumn("user_id", "integer", 11); + $this->hasColumn("app_category_id", "integer", 11); + } + public function setUp() { + $this->hasOne("User","User.id"); + $this->hasMany("App_Category as Category","App_Category.id"); + } +} + +class App_User extends Doctrine_Record { + public function setTableDefinition() { + $this->hasColumn("first_name", "string", 32); + $this->hasColumn("last_name", "string", 32); + $this->hasColumn("email", "string", 128, "email"); + $this->hasColumn("username", "string", 16, "unique, nospace"); + $this->hasColumn("password", "string", 128, "notblank"); + $this->hasColumn("country", "string", 2, "country"); + $this->hasColumn("zipcode", "string", 9, "nospace"); + } + public function setUp() { + $this->hasMany("App","App.user_id"); + } +} + +class App_Category extends Doctrine_Record { + public function setTableDefinition() { + $this->hasColumn("name", "string", 32); + $this->hasColumn("parent_id","integer"); + } + public function setUp() { + $this->hasMany("App","App.app_category_id"); + $this->hasMany("App_Category as Parent","App_Category.parent_id"); + } +} + +/** +$apps = $con->query("FROM App.Category"); + +if (!empty($apps)) +{ + foreach ($apps as $app) + { + print '<p>' . $app->Category[0]->name . ' => ' . $app->name . '</p>'; + } +} +*/ ?> diff --git a/tests/run.php b/tests/run.php index d7b5ba035..278bbe54c 100644 --- a/tests/run.php +++ b/tests/run.php @@ -52,8 +52,6 @@ $test->addTestCase(new Doctrine_QueryTestCase()); //$test->addTestCase(new Doctrine_Cache_SqliteTestCase()); - - print "<pre>"; $test->run(new HtmlReporter()); $cache = Doctrine_Manager::getInstance()->getCurrentSession()->getCacheHandler();