From 00c44b7c41f6fe6b86f4cb9eb0c20c926b8e4276 Mon Sep 17 00:00:00 2001 From: romanb Date: Thu, 7 May 2009 13:54:01 +0000 Subject: [PATCH] [2.0] Implemented many-to-many SQL join construction, small test included. --- lib/Doctrine/DBAL/DriverManager.php | 9 +-- .../DBAL/Platforms/OraclePlatform.php | 55 +++++++++++++++++++ lib/Doctrine/ORM/PersistentCollection.php | 15 +++++ lib/Doctrine/ORM/Query/SqlWalker.php | 53 +++++++++++++++--- tests/Doctrine/Tests/Models/CMS/CmsGroup.php | 12 ++++ tests/Doctrine/Tests/Models/CMS/CmsUser.php | 17 +++++- .../ORM/Functional/BasicFunctionalTest.php | 37 ++++++++++++- 7 files changed, 183 insertions(+), 15 deletions(-) diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index a4326cdec..2ff9433aa 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -21,6 +21,7 @@ namespace Doctrine\DBAL; +use Doctrine\Common\DoctrineException; use Doctrine\Common\EventManager; /** @@ -40,7 +41,7 @@ final class DriverManager 'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', 'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', - 'pdo_oracle' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', + 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', 'pdo_mssql' => 'Doctrine\DBAL\Driver\PDOMsSql\Driver', 'pdo_firebird' => 'Doctrine\DBAL\Driver\PDOFirebird\Driver', 'pdo_informix' => 'Doctrine\DBAL\Driver\PDOInformix\Driver', @@ -107,7 +108,7 @@ final class DriverManager // check for existing pdo object if (isset($params['pdo']) && ! $params['pdo'] instanceof PDO) { - throw Exceptions\DBALException::invalidPDOInstance(); + throw DoctrineException::invalidPDOInstance(); } else if (isset($params['pdo'])) { $params['driver'] = $params['pdo']->getAttribute(PDO::ATTR_DRIVER_NAME); } else { @@ -140,14 +141,14 @@ final class DriverManager // driver if ( ! isset($params['driver']) && ! isset($params['driverClass'])) { - throw Exceptions\DBALException::driverRequired(); + throw DoctrineException::driverRequired(); } // check validity of parameters // driver if ( isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) { - throw Exceptions\DBALException::unknownDriver($params['driver']); + throw DoctrineException::unknownDriver($params['driver']); } } } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php index ddc61d5d2..dc51c2f70 100644 --- a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php +++ b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php @@ -352,6 +352,61 @@ class OraclePlatform extends AbstractPlatform } } + /** + * @override + */ + public function getIntegerTypeDeclarationSql(array $field) + { + return 'NUMBER(10)'; + } + + /** + * @override + */ + public function getBigIntTypeDeclarationSql(array $field) + { + return 'NUMBER(20)'; + } + + /** + * @override + */ + public function getSmallIntTypeDeclarationSql(array $field) + { + return 'NUMBER(5)'; + } + + /** + * @override + */ + protected function _getCommonIntegerTypeDeclarationSql(array $columnDef) + { + return ''; + } + + /** + * Gets the SQL snippet used to declare a VARCHAR column on the Oracle platform. + * + * @params array $field + * @override + */ + public function getVarcharTypeDeclarationSql(array $field) + { + if ( ! isset($field['length'])) { + if (array_key_exists('default', $field)) { + $field['length'] = $this->getVarcharMaxLength(); + } else { + $field['length'] = false; + } + } + + $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; + $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; + + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)') + : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)'); + } + /** * Whether the platform prefers sequences for ID generation. * diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index d4e4834ed..ac9d7fef6 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -276,6 +276,21 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection //TODO: Register collection as dirty with the UoW if necessary //$this->_changed(); } + + public function contains($element) + { + //TODO: Probably need to hit the database here...? + /*if ( ! $this->_initialized) { + return $this->_checkElementExistence($element); + } + return parent::contains($element);*/ + return parent::contains($element); + } + + private function _checkElementExistence($element) + { + + } /** * INTERNAL: diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 3cabe5f55..53defee11 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -99,7 +99,7 @@ class SqlWalker } else if ($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) { $sql .= ' WHERE ' . $discSql; } - //$sql .= $AST->getWhereClause() ? $this->walkWhereClause($AST->getWhereClause()) : ''; + $sql .= $AST->getGroupByClause() ? $this->walkGroupByClause($AST->getGroupByClause()) : ''; $sql .= $AST->getHavingClause() ? $this->walkHavingClause($AST->getHavingClause()) : ''; $sql .= $AST->getOrderByClause() ? $this->walkOrderByClause($AST->getOrderByClause()) : ''; @@ -247,27 +247,66 @@ class SqlWalker $targetTableAlias = $this->getSqlTableAlias($targetTableName); $sourceTableAlias = $this->getSqlTableAlias($sourceQComp['metadata']->getTableName()); - $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; - + // Ensure we got the owning side, since it has all mapping info if ( ! $targetQComp['relation']->isOwningSide()) { $assoc = $targetQComp['metadata']->getAssociationMapping($targetQComp['relation']->getMappedByFieldName()); } else { $assoc = $targetQComp['relation']; } - if ($targetQComp['relation']->isOneToOne() || $targetQComp['relation']->isOneToMany()) { + if ($assoc->isOneToOne()/* || $assoc->isOneToMany()*/) { + $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; $joinColumns = $assoc->getSourceToTargetKeyColumns(); $first = true; foreach ($joinColumns as $sourceColumn => $targetColumn) { - if ( ! $first) $sql .= ' AND '; + if ( ! $first) { + $sql .= ' AND '; + } else { + $first = false; + } if ($targetQComp['relation']->isOwningSide()) { $sql .= "$sourceTableAlias.$sourceColumn = $targetTableAlias.$targetColumn"; } else { $sql .= "$sourceTableAlias.$targetColumn = $targetTableAlias.$sourceColumn"; } } - } else { // ManyToMany - //TODO + } else if ($assoc->isManyToMany()) { + // Join relation table + $joinTable = $assoc->getJoinTable(); + $joinTableAlias = $this->getSqlTableAlias($joinTable['name']); + $sql .= $joinTable['name'] . ' ' . $joinTableAlias . ' ON '; + if ($targetQComp['relation']->isOwningSide()) { + $sourceToRelationJoinColumns = $assoc->getSourceToRelationKeyColumns(); + foreach ($sourceToRelationJoinColumns as $sourceColumn => $relationColumn) { + $sql .= "$sourceTableAlias.$sourceColumn = $joinTableAlias.$relationColumn"; + } + } else { + $targetToRelationJoinColumns = $assoc->getTargetToRelationKeyColumns(); + foreach ($targetToRelationJoinColumns as $targetColumn => $relationColumn) { + $sql .= "$sourceTableAlias.$targetColumn = $joinTableAlias.$relationColumn"; + } + } + + // Join target table + if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) { + $sql .= ' LEFT JOIN '; + } else { + $sql .= ' INNER JOIN '; + } + + $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; + + if ($targetQComp['relation']->isOwningSide()) { + $targetToRelationJoinColumns = $assoc->getTargetToRelationKeyColumns(); + foreach ($targetToRelationJoinColumns as $targetColumn => $relationColumn) { + $sql .= "$targetTableAlias.$targetColumn = $joinTableAlias.$relationColumn"; + } + } else { + $sourceToRelationJoinColumns = $assoc->getSourceToRelationKeyColumns(); + foreach ($sourceToRelationJoinColumns as $sourceColumn => $relationColumn) { + $sql .= "$targetTableAlias.$sourceColumn = $joinTableAlias.$relationColumn"; + } + } } $discrSql = $this->_generateDiscriminatorColumnConditionSql($joinedDqlAlias); diff --git a/tests/Doctrine/Tests/Models/CMS/CmsGroup.php b/tests/Doctrine/Tests/Models/CMS/CmsGroup.php index b762fe2be..e616e0c76 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsGroup.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsGroup.php @@ -29,5 +29,17 @@ class CmsGroup * @DoctrineManyToMany(targetEntity="CmsUser", mappedBy="groups") */ public $users; + + public function setName($name) { + $this->name = $name; + } + + public function getName() { + return $this->name; + } + + public function addUser(CmsUser $user) { + $this->users[] = $user; + } } diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index aa6a1a659..8209db002 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -53,12 +53,25 @@ class CmsUser */ public function addPhonenumber(CmsPhonenumber $phone) { $this->phonenumbers[] = $phone; - $phone->user = $this; + if ($phone->user !== $this) { + $phone->user = $this; + } } public function addArticle(CmsArticle $article) { $this->articles[] = $article; - $article->user = $this; + if ($article->user !== $this) { + $article->user = $this; + } + } + + public function addGroup(CmsGroup $group) { + $this->groups[] = $group; + $group->addUser($this); + } + + public function getGroups() { + return $this->groups; } public function removePhonenumber($index) { diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index a36565b4b..897b17297 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -222,7 +222,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('developer', $usersScalar[0]['u_status']); } - public function testBasicInnerJoin() + public function testBasicOneToManyInnerJoin() { $user = new CmsUser; $user->name = 'Guilherme'; @@ -238,7 +238,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(0, $users->count()); } - public function testBasicLeftJoin() + public function testBasicOneToManyLeftJoin() { $user = new CmsUser; $user->name = 'Guilherme'; @@ -259,4 +259,37 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(0, $users[0]->phonenumbers->count()); $this->assertNull($users[0]->articles); } + + public function testBasicManyToManyJoin() + { + $user = new CmsUser; + $user->name = 'Guilherme'; + $user->username = 'gblanco'; + $user->status = 'developer'; + + $group1 = new CmsGroup; + $group1->setName('Doctrine Developers'); + + $user->addGroup($group1); + + $this->_em->save($user); + $this->_em->save($group1); + + + $this->_em->flush(); + $this->_em->clear(); + + $this->assertEquals(0, $this->_em->getUnitOfWork()->size()); + + $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u join u.groups g"); + + $result = $query->getResultList(); + + $this->assertEquals(2, $this->_em->getUnitOfWork()->size()); + $this->assertTrue($result[0] instanceof CmsUser); + $this->assertEquals('Guilherme', $result[0]->name); + $this->assertEquals(1, $result[0]->getGroups()->count()); + $groups = $result[0]->getGroups(); + $this->assertEquals('Doctrine Developers', $groups[0]->getName()); + } } \ No newline at end of file