From f64347d89945766e4d8d3efd5caa980eca6993e4 Mon Sep 17 00:00:00 2001 From: romanb Date: Wed, 5 Aug 2009 15:47:41 +0000 Subject: [PATCH] [2.0] Implemented SQL generation for SIZE() function and EmptyCollectionComparisonExpression. --- lib/Doctrine/ORM/EntityManager.php | 3 - .../ORM/Query/AST/Functions/SizeFunction.php | 57 +++++++++++++++++-- lib/Doctrine/ORM/Query/Parser.php | 2 +- lib/Doctrine/ORM/Query/SqlWalker.php | 28 +++++---- .../ORM/Query/LanguageRecognitionTest.php | 10 ++++ .../ORM/Query/SelectSqlGenerationTest.php | 20 +++++++ 6 files changed, 99 insertions(+), 21 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 587f9326b..06d8258a3 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -552,9 +552,6 @@ class EntityManager case Query::HYDRATE_SINGLE_SCALAR: $this->_hydrators[$hydrationMode] = new Internal\Hydration\SingleScalarHydrator($this); break; - case Query::HYDRATE_NONE: - $this->_hydrators[$hydrationMode] = new Internal\Hydration\NoneHydrator($this); - break; default: throw DoctrineException::updateMe("No hydrator found for hydration mode '$hydrationMode'."); } diff --git a/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php b/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php index 00d9fde15..9da4c4c4b 100644 --- a/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php +++ b/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php @@ -1,7 +1,22 @@ . */ namespace Doctrine\ORM\Query\AST\Functions; @@ -9,7 +24,7 @@ namespace Doctrine\ORM\Query\AST\Functions; /** * "SIZE" "(" CollectionValuedPathExpression ")" * - * @author robo + * @author Roman Borschel */ class SizeFunction extends FunctionNode { @@ -19,14 +34,44 @@ class SizeFunction extends FunctionNode { return $this->_collectionPathExpression; } + + public function setCollectionPathExpression($collPathExpr) + { + $this->_collectionPathExpression = $collPathExpr; + } /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { - //TODO: Construct appropriate SQL - return ""; + $dqlAlias = $this->_collectionPathExpression->getIdentificationVariable(); + $qComp = $sqlWalker->getQueryComponent($dqlAlias); + $parts = $this->_collectionPathExpression->getParts(); + + $assoc = $qComp['metadata']->associationMappings[$parts[0]]; + + if ($assoc->isOneToMany()) { + $targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc->targetEntityName); + $targetAssoc = $targetClass->associationMappings[$assoc->mappedByFieldName]; + + $targetTableAlias = $sqlWalker->getSqlTableAlias($targetClass->primaryTable['name']); + $sourceTableAlias = $sqlWalker->getSqlTableAlias($qComp['metadata']->primaryTable['name'], $dqlAlias); + + $sql = "(SELECT COUNT($targetTableAlias." + . implode(", $targetTableAlias.", $targetAssoc->targetToSourceKeyColumns) + . ') FROM ' . $targetClass->primaryTable['name'] . ' ' . $targetTableAlias; + + $whereSql = ''; + foreach ($targetAssoc->targetToSourceKeyColumns as $targetKeyColumn => $sourceKeyColumn) { + if ($whereSql == '') $whereSql = ' WHERE '; else $whereSql .= ' AND '; + $whereSql .= $targetTableAlias . '.' . $sourceKeyColumn . ' = ' . $sourceTableAlias . '.' . $targetKeyColumn; + } + + $sql .= $whereSql . ')'; + } + + return $sql; } /** diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index dc5a24efd..241fbb8ac 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1970,7 +1970,7 @@ class Parser return $this->AggregateExpression(); } - return $this->FunctionsReturningStrings(); + return $this->FunctionDeclaration(); } $this->syntaxError(); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 11a3cb16e..6823f8bde 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -21,10 +21,8 @@ namespace Doctrine\ORM\Query; -use Doctrine\ORM\Query; -use Doctrine\ORM\Query\Parser; -use Doctrine\ORM\Query\AST; -use Doctrine\Common\DoctrineException; +use Doctrine\ORM\Query, + Doctrine\Common\DoctrineException; /** * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs @@ -124,6 +122,11 @@ class SqlWalker implements TreeWalker { return $this->_em; } + + public function getQueryComponent($dqlAlias) + { + return $this->_queryComponents[$dqlAlias]; + } /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. @@ -962,6 +965,7 @@ class SqlWalker implements TreeWalker $dqlParamKey = $entityExpr->isNamed() ? $entityExpr->getName() : $entityExpr->getPosition(); $entity = $this->_query->getParameter($dqlParamKey); } else { + //TODO throw DoctrineException::notImplemented(); } @@ -1046,13 +1050,14 @@ class SqlWalker implements TreeWalker * * @param EmptyCollectionComparisonExpression * @return string The SQL. - * - * @todo Finish this implementation. It is quite incomplete! */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) { - $sql = $this->walkPathExpression($emptyCollCompExpr->getExpression()); - $sql .= ' IS' . ($emptyCollCompExpr->isNot() ? ' NOT' : '') . ' EMPTY'; + $sizeFunc = new AST\Functions\SizeFunction('size'); + $sizeFunc->setCollectionPathExpression($emptyCollCompExpr->getExpression()); + + $sql = $sizeFunc->getSql($this); + $sql .= ($emptyCollCompExpr->isNot() ? ' > 0' : ' = 0'); return $sql; } @@ -1316,7 +1321,7 @@ class SqlWalker implements TreeWalker } /** - * Walks down an PathExpression AST node, thereby generating the appropriate SQL. + * Walks down a PathExpression AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. @@ -1324,8 +1329,9 @@ class SqlWalker implements TreeWalker public function walkPathExpression($pathExpr) { $sql = ''; + $pathExprType = $pathExpr->getType(); - if ($pathExpr->getType() == AST\PathExpression::TYPE_STATE_FIELD) { + if ($pathExprType == AST\PathExpression::TYPE_STATE_FIELD) { $parts = $pathExpr->getParts(); $numParts = count($parts); $dqlAlias = $pathExpr->getIdentificationVariable(); @@ -1349,7 +1355,7 @@ class SqlWalker implements TreeWalker } else { $sql .= $this->_conn->quoteIdentifier($class->getColumnName($fieldName)); } - } else if ($pathExpr->isSimpleStateFieldAssociationPathExpression()) { + } else if ($pathExprType == AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) { throw DoctrineException::updateMe("Not yet implemented."); } else { throw DoctrineException::updateMe("Encountered invalid PathExpression during SQL construction."); diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php index d6c45305e..63f4fb6be 100644 --- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php @@ -330,6 +330,16 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.phonenumbers'); } + public function testSizeFunction() + { + $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE SIZE(u.phonenumbers) > 1'); + } + + public function testEmptyCollectionComparisonExpression() + { + $this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.phonenumbers IS EMPTY'); + } + /** * This checks for invalid attempt to hydrate a proxy. It should throw an exception * diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 79babd4f5..3a1dafb10 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -362,4 +362,24 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ OFFSET 0 LIMIT 10', $q->getSql()); } + + public function testSizeFunction() + { + $this->assertSqlGeneration( + "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE SIZE(u.phonenumbers) > 1", + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (SELECT COUNT(c1_.user_id) FROM cms_phonenumbers c1_ WHERE c1_.user_id = c0_.id) > 1" + ); + } + + public function testEmptyCollectionComparisonExpression() + { + $this->assertSqlGeneration( + "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.phonenumbers IS EMPTY", + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (SELECT COUNT(c1_.user_id) FROM cms_phonenumbers c1_ WHERE c1_.user_id = c0_.id) = 0" + ); + $this->assertSqlGeneration( + "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.phonenumbers IS NOT EMPTY", + "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (SELECT COUNT(c1_.user_id) FROM cms_phonenumbers c1_ WHERE c1_.user_id = c0_.id) > 0" + ); + } }