From 2c99ecf5862ee36864d1427e54d7f74d58470fe7 Mon Sep 17 00:00:00 2001 From: Lukasz Cybula Date: Thu, 11 Oct 2012 15:25:39 +0200 Subject: [PATCH 1/4] Extended TreeWalker interface with getQueryComponenets() and setQueryComponent() which are used by the Parser class --- lib/Doctrine/ORM/Query/Parser.php | 1 + lib/Doctrine/ORM/Query/SqlWalker.php | 16 ++- lib/Doctrine/ORM/Query/TreeWalker.php | 15 +++ lib/Doctrine/ORM/Query/TreeWalkerAdapter.php | 27 +++++ lib/Doctrine/ORM/Query/TreeWalkerChain.php | 28 +++++ .../Functional/CustomTreeWalkersJoinTest.php | 112 ++++++++++++++++++ 6 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index f6994c05b..a205eb71f 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -358,6 +358,7 @@ class Parser default: $treeWalkerChain->walkSelectStatement($AST); } + $this->queryComponents = $treeWalkerChain->getQueryComponents(); } $outputWalkerClass = $this->customOutputWalker ?: __NAMESPACE__ . '\SqlWalker'; diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 26135bc75..fc15370c8 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -140,7 +140,7 @@ class SqlWalker implements TreeWalker /** * The DQL alias of the root class of the currently traversed query. - * + * * @var array */ private $rootAliases = array(); @@ -223,13 +223,23 @@ class SqlWalker implements TreeWalker return $this->queryComponents[$dqlAlias]; } + /** + * Return internal queryComponents array + * + * @return array + */ + public function getQueryComponents() + { + return $this->queryComponents; + } + /** * Set or override a query component for a given dql alias. * * @param string $dqlAlias The DQL alias. * @param array $queryComponent */ - protected function setQueryComponent($dqlAlias, array $queryComponent) + public function setQueryComponent($dqlAlias, array $queryComponent) { $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); @@ -1986,7 +1996,7 @@ class SqlWalker implements TreeWalker $dqlAlias = $instanceOfExpr->identificationVariable; $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata']; - + if ($class->discriminatorColumn) { $discrClass = $this->em->getClassMetadata($class->rootEntityName); } diff --git a/lib/Doctrine/ORM/Query/TreeWalker.php b/lib/Doctrine/ORM/Query/TreeWalker.php index c88ca1328..ceb117c0a 100644 --- a/lib/Doctrine/ORM/Query/TreeWalker.php +++ b/lib/Doctrine/ORM/Query/TreeWalker.php @@ -36,6 +36,21 @@ interface TreeWalker */ public function __construct($query, $parserResult, array $queryComponents); + /** + * Return internal queryComponents array + * + * @return array + */ + public function getQueryComponents(); + + /** + * Set or override a query component for a given dql alias. + * + * @param string $dqlAlias The DQL alias. + * @param array $queryComponent + */ + public function setQueryComponent($dqlAlias, array $queryComponent); + /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * diff --git a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php index 4446a85e0..32009a3c4 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php @@ -42,6 +42,33 @@ abstract class TreeWalkerAdapter implements TreeWalker $this->_queryComponents = $queryComponents; } + /** + * Return internal queryComponents array + * + * @return array: + */ + public function getQueryComponents() + { + return $this->_queryComponents; + } + + /** + * Set or override a query component for a given dql alias. + * + * @param string $dqlAlias The DQL alias. + * @param array $queryComponent + */ + public function setQueryComponent($dqlAlias, array $queryComponent) + { + $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); + + if (array_diff($requiredKeys, array_keys($queryComponent))) { + throw QueryException::invalidQueryComponent($dqlAlias); + } + + $this->_queryComponents[$dqlAlias] = $queryComponent; + } + /** * @return array */ diff --git a/lib/Doctrine/ORM/Query/TreeWalkerChain.php b/lib/Doctrine/ORM/Query/TreeWalkerChain.php index 13bbcdeef..72d087824 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerChain.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerChain.php @@ -38,6 +38,33 @@ class TreeWalkerChain implements TreeWalker /** The query components of the original query (the "symbol table") that was produced by the Parser. */ private $_queryComponents; + /** + * Return internal queryComponents array + * + * @return array + */ + public function getQueryComponents() + { + return $this->_queryComponents; + } + + /** + * Set or override a query component for a given dql alias. + * + * @param string $dqlAlias The DQL alias. + * @param array $queryComponent + */ + public function setQueryComponent($dqlAlias, array $queryComponent) + { + $requiredKeys = array('metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'); + + if (array_diff($requiredKeys, array_keys($queryComponent))) { + throw QueryException::invalidQueryComponent($dqlAlias); + } + + $this->_queryComponents[$dqlAlias] = $queryComponent; + } + /** * @inheritdoc */ @@ -67,6 +94,7 @@ class TreeWalkerChain implements TreeWalker { foreach ($this->_walkers as $walker) { $walker->walkSelectStatement($AST); + $this->_queryComponents = $walker->getQueryComponents(); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php new file mode 100644 index 000000000..cde0aadb8 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php @@ -0,0 +1,112 @@ +. + */ + +namespace Doctrine\Tests\ORM\Functional; + +use Doctrine\ORM\Query; + +require_once __DIR__ . '/../../TestInit.php'; + +/** + * Test case for custom AST walking and adding new joins. + * + * @author Lukasz Cybula + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.doctrine-project.org + */ +class CustomTreeWalkersJoinTest extends \Doctrine\Tests\OrmTestCase +{ + private $_em; + + protected function setUp() + { + $this->_em = $this->_getTestEntityManager(); + } + + public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) + { + try { + $query = $this->_em->createQuery($dqlToBeTested); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\Tests\ORM\Functional\CustomTreeWalker2')) + ->useQueryCache(false); + + $this->assertEquals($sqlToBeConfirmed, $query->getSql()); + $query->free(); + } catch (\Exception $e) { + $this->fail($e->getMessage() . ' at "' . $e->getFile() . '" on line ' . $e->getLine()); + + } + } + + public function testAddsJoin() + { + $this->assertSqlGeneration( + 'select u from Doctrine\Tests\Models\CMS\CmsUser u', + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c1_.id AS id4, c1_.country AS country5, c1_.zip AS zip6, c1_.city AS city7, c0_.email_id AS email_id8, c1_.user_id AS user_id9 FROM cms_users c0_ LEFT JOIN cms_addresses c1_ ON c0_.id = c1_.user_id" + ); + } + + public function testDoesNotAddJoin() + { + $this->assertSqlGeneration( + 'select a from Doctrine\Tests\Models\CMS\CmsAddress a', + "SELECT c0_.id AS id0, c0_.country AS country1, c0_.zip AS zip2, c0_.city AS city3, c0_.user_id AS user_id4 FROM cms_addresses c0_" + ); + } +} + +class CustomTreeWalker2 extends Query\TreeWalkerAdapter +{ + public function walkSelectStatement(Query\AST\SelectStatement $selectStatement) + { + foreach ($selectStatement->fromClause->identificationVariableDeclarations as $identificationVariableDeclaration) { + if ($identificationVariableDeclaration->rangeVariableDeclaration->abstractSchemaName == 'Doctrine\Tests\Models\CMS\CmsUser') { + $identificationVariableDeclaration->joins[] = new Query\AST\Join( + Query\AST\Join::JOIN_TYPE_LEFT, + new Query\AST\JoinAssociationDeclaration( + new Query\AST\JoinAssociationPathExpression( + $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable, + 'address' + ), + $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a', + null + ) + ); + $selectStatement->selectClause->selectExpressions[] = + new Query\AST\SelectExpression( + $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a', + null, + false + ); + $meta1 = $this->_getQuery()->getEntityManager()->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $meta = $this->_getQuery()->getEntityManager()->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'); + $this->setQueryComponent($identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a', + array( + 'metadata' => $meta, + 'parent' => $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable, + 'relation' => $meta1->getAssociationMapping('address'), + 'map' => null, + 'nestingLevel' => 0, + 'token' => null + ) + ); + } + } + } +} From 7b1d84cbdb8002428a9550264919ee5f05c13113 Mon Sep 17 00:00:00 2001 From: Lukasz Cybula Date: Fri, 12 Oct 2012 09:33:40 +0200 Subject: [PATCH 2/4] Moved CustomTreeWalkersJoinTest to proper namespace and fixed licence --- .../CustomTreeWalkersJoinTest.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) rename tests/Doctrine/Tests/ORM/{Functional => Query}/CustomTreeWalkersJoinTest.php (90%) diff --git a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php b/tests/Doctrine/Tests/ORM/Query/CustomTreeWalkersJoinTest.php similarity index 90% rename from tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php rename to tests/Doctrine/Tests/ORM/Query/CustomTreeWalkersJoinTest.php index cde0aadb8..18d34acd7 100644 --- a/tests/Doctrine/Tests/ORM/Functional/CustomTreeWalkersJoinTest.php +++ b/tests/Doctrine/Tests/ORM/Query/CustomTreeWalkersJoinTest.php @@ -13,37 +13,35 @@ * 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 + * and is licensed under the MIT license. For more information, see * . */ -namespace Doctrine\Tests\ORM\Functional; +namespace Doctrine\Tests\ORM\Query; use Doctrine\ORM\Query; -require_once __DIR__ . '/../../TestInit.php'; - /** * Test case for custom AST walking and adding new joins. * * @author Lukasz Cybula - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @license MIT * @link http://www.doctrine-project.org */ class CustomTreeWalkersJoinTest extends \Doctrine\Tests\OrmTestCase { - private $_em; + private $em; protected function setUp() { - $this->_em = $this->_getTestEntityManager(); + $this->em = $this->_getTestEntityManager(); } public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed) { try { - $query = $this->_em->createQuery($dqlToBeTested); - $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\Tests\ORM\Functional\CustomTreeWalker2')) + $query = $this->em->createQuery($dqlToBeTested); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\Tests\ORM\Query\CustomTreeWalkerJoin')) ->useQueryCache(false); $this->assertEquals($sqlToBeConfirmed, $query->getSql()); @@ -71,7 +69,7 @@ class CustomTreeWalkersJoinTest extends \Doctrine\Tests\OrmTestCase } } -class CustomTreeWalker2 extends Query\TreeWalkerAdapter +class CustomTreeWalkerJoin extends Query\TreeWalkerAdapter { public function walkSelectStatement(Query\AST\SelectStatement $selectStatement) { From 08a3423ce20a269db10bf3f05ace10e2ea4926c0 Mon Sep 17 00:00:00 2001 From: Lukasz Cybula Date: Mon, 5 Nov 2012 14:47:41 +0100 Subject: [PATCH 3/4] CS fixes --- lib/Doctrine/ORM/Query/Parser.php | 5 +++-- lib/Doctrine/ORM/Query/TreeWalkerChain.php | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index a205eb71f..d057ba754 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -358,6 +358,7 @@ class Parser default: $treeWalkerChain->walkSelectStatement($AST); } + $this->queryComponents = $treeWalkerChain->getQueryComponents(); } @@ -1751,7 +1752,7 @@ class Parser * NewObjectArg ::= ScalarExpression * * @TODO - Maybe you should support other expressions and nested "new" operator - * + * * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression */ public function NewObjectArg() @@ -2094,7 +2095,7 @@ class Parser case ($lookaheadType === Lexer::T_NEW): $expression = $this->NewObjectExpression(); break; - + default: $this->syntaxError( 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', diff --git a/lib/Doctrine/ORM/Query/TreeWalkerChain.php b/lib/Doctrine/ORM/Query/TreeWalkerChain.php index 72d087824..f20413c40 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerChain.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerChain.php @@ -94,6 +94,7 @@ class TreeWalkerChain implements TreeWalker { foreach ($this->_walkers as $walker) { $walker->walkSelectStatement($AST); + $this->_queryComponents = $walker->getQueryComponents(); } } From afdb92ff9b7d46bc1b314260b667be83ecfe9797 Mon Sep 17 00:00:00 2001 From: Lukasz Cybula Date: Mon, 12 Nov 2012 22:03:04 +0100 Subject: [PATCH 4/4] Added note about new methods in UPGRADE.md --- UPGRADE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UPGRADE.md b/UPGRADE.md index ef30c4f04..387337083 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -33,6 +33,12 @@ Also, related functions were affected: Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker, you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin. +## New methods in TreeWalker interface *BC break* + +Two methods getQueryComponents() and setQueryComponent() were added to the TreeWalker interface and all its implementations +including TreeWalkerAdapter, TreeWalkerChain and SqlWalker. If you have your own implementation not inheriting from one of the +above you must implement these new methods. + ## Metadata Drivers Metadata drivers have been rewritten to reuse code from Doctrine\Common. Anyone who is using the