1
0
mirror of synced 2024-12-13 22:56:04 +03:00

added full support for join table self-referencing

This commit is contained in:
zYne 2007-06-11 19:27:16 +00:00
parent 5d9465870d
commit 595071f767
8 changed files with 218 additions and 150 deletions

View File

@ -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) {

View File

@ -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
*

View File

@ -0,0 +1,142 @@
<?php
/*
* $Id: Self.php 1434 2007-05-22 15:57:17Z zYne $
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
*/
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 <kvesteri@cc.hut.fi>
*/
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);
}
}
}

View File

@ -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);
}

View File

@ -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()
{

View File

@ -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();

View File

@ -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

View File

@ -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());