diff --git a/lib/Doctrine/Query.php b/lib/Doctrine/Query.php index cb3256821..ab3f7ebec 100644 --- a/lib/Doctrine/Query.php +++ b/lib/Doctrine/Query.php @@ -83,6 +83,10 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable 'limit' => array(), 'offset' => array(), ); + /** + * @var array $_pendingJoinConditions an array containing pending joins + */ + protected $_pendingJoinConditions = array(); /** * create @@ -109,6 +113,17 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable } $this->_options[$name] = $value; } + /** + * addPendingJoinCondition + * + * @param string $componentAlias component alias + * @param string $joinCondition dql join condition + * @return Doctrine_Query this object + */ + public function addPendingJoinCondition($componentAlias, $joinCondition) + { + $this->_pendingJoins[$componentAlias] = $joinCondition; + } /** * addEnumParam * sets input parameter as an enumerated parameter @@ -600,19 +615,16 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable } - $e = explode(' ON ', $part); - - // we can always be sure that the first join condition exists - $e2 = explode(' AND ', $e[1]); - - $part = $e[0] . ' ON ' . array_shift($e2); - - if ( ! empty($e2)) { + if (isset($this->_pendingJoinConditions[$k])) { $parser = new Doctrine_Query_JoinCondition($this); - $part .= ' AND ' . $parser->parse(implode(' AND ', $e2)); + $part .= ' AND ' . $parser->parse($this->_pendingJoinConditions[$k]); + + unset($this->_pendingJoinConditions[$k]); } $q .= ' ' . $part; + + $this->parts['from'][$k] = $part; } return $q; } @@ -937,7 +949,7 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable $joinCondition = ''; if (count($e) > 1) { - $joinCondition = ' AND ' . $e[1]; + $joinCondition = $e[1]; $path = $e[0]; } @@ -1018,34 +1030,43 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable $assocAlias = $this->getTableAlias($assocPath, $asf->getTableName()); $queryPart = $join . $assocTableName . ' ' . $assocAlias . ' ON ' . $localAlias . '.' - . $table->getIdentifier() . ' = ' - . $assocAlias . '.' . $relation->getLocal(); + . $table->getIdentifier() . ' = ' + . $assocAlias . '.' . $relation->getLocal(); + + if ($relation->isEqual()) { + $queryPart .= ' OR ' . $localAlias . '.' + . $table->getIdentifier() . ' = ' + . $assocAlias . '.' . $relation->getForeign(); - if ($relation instanceof Doctrine_Relation_Association_Self) { - $queryPart .= ' OR ' . $localAlias . '.' . $table->getIdentifier() . ' = ' - . $assocAlias . '.' . $relation->getForeign(); } $this->parts['from'][] = $queryPart; - $queryPart = $join . $foreignSql . ' ON ' . $foreignAlias . '.' - . $relation->getTable()->getIdentifier() . ' = ' - . $assocAlias . '.' . $relation->getForeign() - . $joinCondition; + $queryPart = $join . $foreignSql . ' ON '; + if ($relation->isEqual()) { + $queryPart .= '('; + } + $queryPart .= $foreignAlias . '.' + . $relation->getTable()->getIdentifier() . ' = ' + . $assocAlias . '.' . $relation->getForeign(); - if ($relation instanceof Doctrine_Relation_Association_Self) { - $queryPart .= ' OR ' . $foreignAlias . '.' . $table->getIdentifier() . ' = ' - . $assocAlias . '.' . $relation->getLocal(); + if ($relation->isEqual()) { + $queryPart .= ' OR ' . $foreignAlias . '.' . $table->getIdentifier() + . ' = ' . $assocAlias . '.' . $relation->getLocal() + . ') AND ' . $foreignAlias . '.' . $table->getIdentifier() + . ' != ' . $localAlias . '.' . $table->getIdentifier(); } } else { $queryPart = $join . $foreignSql . ' ON ' . $localAlias . '.' - . $relation->getLocal() . ' = ' . $foreignAlias . '.' . $relation->getForeign() - . $joinCondition; + . $relation->getLocal() . ' = ' . $foreignAlias . '.' . $relation->getForeign(); + } + $this->parts['from'][$componentAlias] = $queryPart; + if ( ! empty($joinCondition)) { + $this->_pendingJoinConditions[$componentAlias] = $joinCondition; } - $this->parts['from'][] = $queryPart; } if ($loadFields) { diff --git a/lib/Doctrine/Relation.php b/lib/Doctrine/Relation.php index 6670716f0..4e756d6ce 100644 --- a/lib/Doctrine/Relation.php +++ b/lib/Doctrine/Relation.php @@ -68,6 +68,7 @@ abstract class Doctrine_Relation 'onUpdate' => false, 'deferred' => false, 'constraint' => false, + 'equal' => false, ); /** * constructor @@ -130,6 +131,11 @@ abstract class Doctrine_Relation $this->definition = $def; } + + public function isEqual() + { + return $this->definition['equal']; + } /** * toArray * diff --git a/lib/Doctrine/Relation/Nest.php b/lib/Doctrine/Relation/Nest.php new file mode 100644 index 000000000..6ba0a0b58 --- /dev/null +++ b/lib/Doctrine/Relation/Nest.php @@ -0,0 +1,142 @@ +. + */ +Doctrine::autoload('Doctrine_Relation_Association'); +/** + * Doctrine_Relation_Association_Self + * + * @package Doctrine + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @category Object Relational Mapping + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision: 1434 $ + * @author Konsta Vesterinen + */ +class Doctrine_Relation_Nest extends Doctrine_Relation_Association +{ + /** + * getRelationDql + * + * @param integer $count + * @return string + */ + public function getRelationDql($count, $context = 'record') + { + switch ($context) { + case 'record': + $sub = 'SELECT '.$this->definition['foreign'] + . ' FROM '.$this->definition['refTable']->getTableName() + . ' WHERE '.$this->definition['local'] + . ' = ?'; + + $sub2 = 'SELECT '.$this->definition['local'] + . ' FROM '.$this->definition['refTable']->getTableName() + . ' WHERE '.$this->definition['foreign'] + . ' = ?'; + + $dql = 'FROM ' . $this->definition['table']->getComponentName() + . '.' . $this->definition['refTable']->getComponentName() + . ' WHERE ' . $this->definition['table']->getComponentName() + . '.' . $this->definition['table']->getIdentifier() + . ' IN (' . $sub . ')' + . ' || ' . $this->definition['table']->getComponentName() + . '.' . $this->definition['table']->getIdentifier() + . ' IN (' . $sub2 . ')'; + break; + case 'collection': + $sub = substr(str_repeat('?, ', $count),0,-2); + $dql = 'FROM '.$this->definition['refTable']->getComponentName() + . '.' . $this->definition['table']->getComponentName() + . ' WHERE '.$this->definition['refTable']->getComponentName() + . '.' . $this->definition['local'] . ' IN (' . $sub . ')'; + }; + + return $dql; + } + /** + public function fetchRelatedFor(Doctrine_Record $record) + { + $id = $record->getIncremented(); + + if (empty($id) || ! $this->definition['table']->getAttribute(Doctrine::ATTR_LOAD_REFERENCES)) { + return new Doctrine_Collection($this->getTable()); + } else { + $q = new Doctrine_Query(); + + $c = $this->getTable()->getComponentName(); + $a = substr($c, 0, 1); + $c2 = $this->getAssociationTable()->getComponentName(); + $a2 = substr($c2, 0, 1); + + $q->from($c) + ->innerJoin($c . '.' . $c2) + + $sub = 'SELECT ' . $this->getForeign() + . ' FROM ' . $c2 + . ' WHERE ' . $this->getLocal() + . ' = ?'; + } + } + */ + + public function fetchRelatedFor(Doctrine_Record $record) + { + $id = $record->getIncremented(); + + + if (empty($id) || ! $this->definition['table']->getAttribute(Doctrine::ATTR_LOAD_REFERENCES)) { + return new Doctrine_Collection($this->getTable()); + } else { + $q = new Doctrine_RawSql(); + + $assocTable = $this->getAssociationFactory()->getTableName(); + $tableName = $record->getTable()->getTableName(); + $identifier = $record->getTable()->getIdentifier(); + + $sub = 'SELECT ' . $this->getForeign() + . ' FROM ' . $assocTable + . ' WHERE ' . $this->getLocal() + . ' = ?'; + + $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub . ')'; + $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getForeign(); + + if ($this->definition['equal']) { + $sub2 = 'SELECT ' . $this->getLocal() + . ' FROM ' . $assocTable + . ' WHERE ' . $this->getForeign() + . ' = ?'; + + $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub2 . ')'; + $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getLocal(); + } + $q->select('{'.$tableName.'.*}, {'.$assocTable.'.*}') + ->from($tableName . ' INNER JOIN ' . $assocTable . ' ON ' . implode(' OR ', $joinCondition)) + ->where(implode(' OR ', $condition)); + $q->addComponent($tableName, $record->getTable()->getComponentName()); + $q->addComponent($assocTable, $record->getTable()->getComponentName(). '.' . $this->getAssociationFactory()->getComponentName()); + + $params = ($this->definition['equal']) ? array($id, $id) : array($id); + + return $q->execute($params); + } + } +} diff --git a/lib/Doctrine/Relation/Parser.php b/lib/Doctrine/Relation/Parser.php index 1b0c842ba..4bd730b23 100644 --- a/lib/Doctrine/Relation/Parser.php +++ b/lib/Doctrine/Relation/Parser.php @@ -137,7 +137,7 @@ class Doctrine_Relation_Parser 'foreign' => $def['local'])); } if (in_array($def['class'], $localClasses)) { - $rel = new Doctrine_Relation_Association_Self($def); + $rel = new Doctrine_Relation_Nest($def); } else { $rel = new Doctrine_Relation_Association($def); } diff --git a/tests/Query/JoinTestCase.php b/tests/Query/JoinTestCase.php index efc75a1a4..03de5549f 100644 --- a/tests/Query/JoinTestCase.php +++ b/tests/Query/JoinTestCase.php @@ -110,8 +110,8 @@ class Doctrine_Query_Join_TestCase extends Doctrine_UnitTestCase $q = new Doctrine_Query(); $q->select('e.name')->from('Entity e INNER JOIN e.Entity e2'); - - $this->assertEqual($q->getQuery(), 'SELECT e.id AS e__id, e.name AS e__name FROM entity e INNER JOIN entity_reference e3 ON e.id = e3.entity1 OR e.id = e3.entity2 INNER JOIN entity e2 ON e2.id = e3.entity2 OR e2.id = e3.entity1'); + + $this->assertEqual($q->getQuery(), 'SELECT e.id AS e__id, e.name AS e__name FROM entity e INNER JOIN entity_reference e3 ON e.id = e3.entity1 OR e.id = e3.entity2 INNER JOIN entity e2 ON (e2.id = e3.entity2 OR e2.id = e3.entity1) AND e2.id != e.id'); } public function testMultipleJoins() { diff --git a/tests/RecordTestCase.php b/tests/RecordTestCase.php index d881cfe22..2cac80fa1 100644 --- a/tests/RecordTestCase.php +++ b/tests/RecordTestCase.php @@ -38,7 +38,7 @@ class Doctrine_Record_TestCase extends Doctrine_UnitTestCase { $this->tables[] = "GzipTest"; parent::prepareTables(); } - /** + public function testIssetForPrimaryKey() { $this->assertTrue(isset($this->users[0]->id)); $this->assertTrue(isset($this->users[0]['id'])); @@ -51,9 +51,6 @@ class Doctrine_Record_TestCase extends Doctrine_UnitTestCase { $this->assertFalse($user->contains('id')); } - public function testUnknownColumn() { - - } public function testNotNullConstraint() { $null = new NotNullTest(); @@ -103,119 +100,8 @@ class Doctrine_Record_TestCase extends Doctrine_UnitTestCase { $this->assertTrue(is_object($test->someObject)); $this->assertEqual($test->someInt, 11); } - */ - public function testJoinTableSelfReferencingInsertingData() { - $e = new Entity(); - $e->name = "Entity test"; - - $this->assertTrue($e->Entity[0] instanceof Entity); - $this->assertTrue($e->Entity[1] instanceof Entity); - - $this->assertEqual($e->Entity[0]->state(), Doctrine_Record::STATE_TCLEAN); - $this->assertEqual($e->Entity[1]->state(), Doctrine_Record::STATE_TCLEAN); - - $e->Entity[0]->name = 'Friend 1'; - $e->Entity[1]->name = 'Friend 2'; - - $e->Entity[0]->Entity[0]->name = 'Friend 1 1'; - $e->Entity[0]->Entity[1]->name = 'Friend 1 2'; - - $e->Entity[1]->Entity[0]->name = 'Friend 2 1'; - $e->Entity[1]->Entity[1]->name = 'Friend 2 2'; - - $this->assertEqual($e->Entity[0]->name, 'Friend 1'); - $this->assertEqual($e->Entity[1]->name, 'Friend 2'); - - $this->assertEqual($e->Entity[0]->Entity[0]->name, 'Friend 1 1'); - $this->assertEqual($e->Entity[0]->Entity[1]->name, 'Friend 1 2'); - - $this->assertEqual($e->Entity[1]->Entity[0]->name, 'Friend 2 1'); - $this->assertEqual($e->Entity[1]->Entity[1]->name, 'Friend 2 2'); - $this->assertEqual($e->Entity[0]->state(), Doctrine_Record::STATE_TDIRTY); - $this->assertEqual($e->Entity[1]->state(), Doctrine_Record::STATE_TDIRTY); - - $count = count($this->dbh); - - $e->save(); - - $this->assertEqual(($count + 13), $this->dbh->count()); - $this->assertEqual($e->state(), Doctrine_Record::STATE_CLEAN); - - $this->assertTrue($e->Entity[0] instanceof Entity); - $this->assertTrue($e->Entity[1] instanceof Entity); - - $this->assertEqual($e->Entity[0]->name, 'Friend 1'); - $this->assertEqual($e->Entity[1]->name, 'Friend 2'); - - $this->assertEqual($e->Entity[0]->Entity[0]->name, 'Friend 1 1'); - $this->assertEqual($e->Entity[0]->Entity[1]->name, 'Friend 1 2'); - - $this->assertEqual($e->Entity[1]->Entity[0]->name, 'Friend 2 1'); - $this->assertEqual($e->Entity[1]->Entity[1]->name, 'Friend 2 2'); - - $this->assertEqual($e->Entity[0]->state(), Doctrine_Record::STATE_CLEAN); - $this->assertEqual($e->Entity[1]->state(), Doctrine_Record::STATE_CLEAN); - - $this->assertTrue(is_numeric($e->id)); - - $result = $this->dbh->query('SELECT * FROM entity_reference')->fetchAll(PDO::FETCH_ASSOC); - - $this->assertEqual(count($result), 6); - - //$stmt = $this->dbh->prepare($q); - - //$stmt->execute(array(18)); - //$result = $stmt->fetchAll(PDO::FETCH_ASSOC); - - //print_r($result); - - $this->connection->clear(); - - $e = $e->getTable()->find($e->id); - - $count = count($this->dbh); - - $this->assertTrue($e instanceof Entity); - - $this->assertTrue($e->Entity[0] instanceof Entity); - $this->assertTrue($e->Entity[1] instanceof Entity); - - - - $this->assertEqual(count($this->dbh), ($count + 1)); - - $this->assertEqual($e->Entity[0]->name, "Friend 1"); - $this->assertEqual($e->Entity[1]->name, "Friend 2"); - - $this->assertEqual($e->Entity[0]->Entity[0]->name, "Entity test"); - $this->assertEqual($e->Entity[0]->Entity[1]->name, "Friend 1 1"); - - $this->assertEqual(count($this->dbh), ($count + 2)); - - $this->assertEqual($e->Entity[1]->Entity[0]->name, "Entity test"); - $this->assertEqual($e->Entity[1]->Entity[1]->name, "Friend 2 1"); - - $this->assertEqual(count($this->dbh), ($count + 3)); - - $this->assertEqual($e->Entity[0]->state(), Doctrine_Record::STATE_CLEAN); - $this->assertEqual($e->Entity[1]->state(), Doctrine_Record::STATE_CLEAN); - - $coll = $this->connection->query("FROM Entity WHERE Entity.name = 'Friend 1'"); - $this->assertEqual($coll->count(), 1); - $this->assertEqual($coll[0]->state(), Doctrine_Record::STATE_CLEAN); - - $this->assertEqual($coll[0]->name, "Friend 1"); - - $query = new Doctrine_Query($this->connection); - - $query->from('Entity.Entity e')->where("e.name = 'Friend 1 1'"); - - $coll = $query->execute(); - - $this->assertEqual($coll->count(), 2); - } public function testToArray() { $user = new User(); diff --git a/tests/classes.php b/tests/classes.php index c928869a6..68e3901fc 100644 --- a/tests/classes.php +++ b/tests/classes.php @@ -6,7 +6,8 @@ class Entity extends Doctrine_Record { $this->ownsOne('Account', 'Account.entity_id'); $this->hasMany('Entity', array('local' => 'entity1', 'refClass' => 'EntityReference', - 'foreign' => 'entity2')); + 'foreign' => 'entity2', + 'equal' => true)); } public function setTableDefinition() { $this->hasColumn('id', 'integer',20, 'autoincrement|primary'); @@ -605,8 +606,17 @@ class NestTest extends Doctrine_Record } public function setUp() { - $this->hasMany('NestTest as Parents', array('local' => 'child_id', 'foreign' => 'parent_id')); - $this->hasMany('NestTest as Children', 'NestReference.child_id'); + $this->hasMany('NestTest as Parents', array('local' => 'child_id', + 'refClass' => 'NestReference', + 'foreign' => 'parent_id')); + $this->hasMany('NestTest as Children', array('local' => 'parent_id', + 'refClass' => 'NestReference', + 'foreign' => 'child_id')); + + $this->hasMany('NestTest as Relatives', array('local' => 'child_id', + 'refClass' => 'NestReference', + 'foreign' => 'parent_id', + 'equal' => true)); } } class NestReference extends Doctrine_Record diff --git a/tests/run.php b/tests/run.php index 2a7553906..eccc2ea57 100644 --- a/tests/run.php +++ b/tests/run.php @@ -65,7 +65,7 @@ $test = new GroupTest('Doctrine Framework Unit Tests'); // DATABASE ABSTRACTION tests - +/** */ // Connection drivers (not yet fully tested) $test->addTestCase(new Doctrine_Connection_Pgsql_TestCase()); $test->addTestCase(new Doctrine_Connection_Oracle_TestCase()); @@ -163,7 +163,7 @@ $test->addTestCase(new Doctrine_Relation_TestCase()); //$test->addTestCase(new Doctrine_Relation_Access_TestCase()); //$test->addTestCase(new Doctrine_Relation_ManyToMany_TestCase()); -//$test->addTestCase(new Doctrine_Relation_Nest_TestCase()); +$test->addTestCase(new Doctrine_Relation_Nest_TestCase()); $test->addTestCase(new Doctrine_Relation_OneToOne_TestCase()); @@ -240,10 +240,11 @@ $test->addTestCase(new Doctrine_Query_Limit_TestCase()); $test->addTestCase(new Doctrine_Query_IdentifierQuoting_TestCase()); $test->addTestCase(new Doctrine_Query_Update_TestCase()); $test->addTestCase(new Doctrine_Query_Delete_TestCase()); -$test->addTestCase(new Doctrine_Query_JoinCondition_TestCase()); $test->addTestCase(new Doctrine_Query_Join_TestCase()); +$test->addTestCase(new Doctrine_Record_TestCase()); + $test->addTestCase(new Doctrine_Query_Having_TestCase()); @@ -261,7 +262,7 @@ $test->addTestCase(new Doctrine_Query_AggregateValue_TestCase()); $test->addTestCase(new Doctrine_NewCore_TestCase()); // Record -$test->addTestCase(new Doctrine_Record_TestCase()); + $test->addTestCase(new Doctrine_Record_State_TestCase()); //$test->addTestCase(new Doctrine_Query_Cache_TestCase()); @@ -281,6 +282,8 @@ $test->addTestCase(new Doctrine_AuditLog_TestCase()); $test->addTestCase(new Doctrine_Query_Select_TestCase()); +$test->addTestCase(new Doctrine_Query_JoinCondition_TestCase()); + // Cache tests //$test->addTestCase(new Doctrine_Cache_Query_SqliteTestCase()); //$test->addTestCase(new Doctrine_Cache_FileTestCase());