1
0
mirror of synced 2025-01-17 22:11:41 +03:00

[2.0][DDC-482] Added support to INSTANCE OF in DQL.

This commit is contained in:
Guilherme Blanco 2010-07-30 01:30:02 -03:00
parent d2740f0e77
commit c1fec32f58
11 changed files with 290 additions and 33 deletions

View File

@ -240,13 +240,13 @@ class ClassMetadataFactory
if ($parent) {
$class->setInheritanceType($parent->inheritanceType);
$class->setDiscriminatorColumn($parent->discriminatorColumn);
$class->setIdGeneratorType($parent->generatorType);
$this->_addInheritedFields($class, $parent);
$this->_addInheritedRelations($class, $parent);
$class->setIdentifier($parent->identifier);
$class->setVersioned($parent->isVersioned);
$class->setVersionField($parent->versionField);
$this->_addInheritedDiscriminatorColumn($class, $parent);
$class->setDiscriminatorMap($parent->discriminatorMap);
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
}
@ -313,6 +313,29 @@ class ClassMetadataFactory
{
return new ClassMetadata($className);
}
/**
* Adds inherited discriminator column to the subclass mapping.
*
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
*/
private function _addInheritedDiscriminatorColumn(ClassMetadata $subClass, ClassMetadata $parentClass)
{
if ($parentClass->discriminatorColumn) {
$columnDef = $parentClass->discriminatorColumn;
if ( ! isset($columnDef['inherited'])) {
$columnDef['inherited'] = $parentClass->name;
}
if ( ! isset($columnDef['declared'])) {
$columnDef['declared'] = $parentClass->name;
}
$subClass->setDiscriminatorColumn($columnDef);
}
}
/**
* Adds inherited fields to the subclass mapping.

View File

@ -0,0 +1,49 @@
<?php
/*
* 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.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST;
/**
* InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class InstanceOfExpression extends Node
{
public $not;
public $identificationVariable;
public $value;
public function __construct($identVariable)
{
$this->identificationVariable = $identVariable;
}
public function dispatch($sqlWalker)
{
return $sqlWalker->walkInstanceOfExpression($this);
}
}

View File

@ -75,33 +75,34 @@ class Lexer extends \Doctrine\Common\Lexer
const T_IN = 121;
const T_INDEX = 122;
const T_INNER = 123;
const T_IS = 124;
const T_JOIN = 125;
const T_LEADING = 126;
const T_LEFT = 127;
const T_LIKE = 128;
const T_MAX = 129;
const T_MEMBER = 130;
const T_MIN = 131;
const T_NOT = 132;
const T_NULL = 133;
const T_OF = 134;
const T_OR = 135;
const T_ORDER = 136;
const T_OUTER = 137;
const T_SELECT = 138;
const T_SET = 139;
const T_SIZE = 140;
const T_SOME = 141;
const T_SUM = 142;
const T_TRAILING = 143;
const T_TRUE = 144;
const T_UPDATE = 145;
const T_WHERE = 146;
const T_WITH = 147;
const T_PARTIAL = 148;
const T_MOD = 149;
const T_INSTANCE = 124;
const T_IS = 125;
const T_JOIN = 126;
const T_LEADING = 127;
const T_LEFT = 128;
const T_LIKE = 129;
const T_MAX = 130;
const T_MEMBER = 131;
const T_MIN = 132;
const T_NOT = 133;
const T_NULL = 134;
const T_OF = 135;
const T_OR = 136;
const T_ORDER = 137;
const T_OUTER = 138;
const T_SELECT = 139;
const T_SET = 140;
const T_SIZE = 141;
const T_SOME = 142;
const T_SUM = 143;
const T_TRAILING = 144;
const T_TRUE = 145;
const T_UPDATE = 146;
const T_WHERE = 147;
const T_WITH = 148;
const T_PARTIAL = 149;
const T_MOD = 150;
/**
* Creates a new query scanner object.
*

View File

@ -1205,13 +1205,13 @@ class Parser
}
/**
* UpdateItem ::= IdentificationVariable "." {StateField | SingleValuedAssociationField} "=" NewValue
* UpdateItem ::= SingleValuedPathExpression "=" NewValue
*
* @return \Doctrine\ORM\Query\AST\UpdateItem
*/
public function UpdateItem()
{
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD | AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
$pathExpr = $this->SingleValuedPathExpression();
$this->match(Lexer::T_EQUALS);
@ -1797,7 +1797,8 @@ class Parser
* SimpleConditionalExpression ::=
* ComparisonExpression | BetweenExpression | LikeExpression |
* InExpression | NullComparisonExpression | ExistsExpression |
* EmptyCollectionComparisonExpression | CollectionMemberExpression
* EmptyCollectionComparisonExpression | CollectionMemberExpression |
* InstanceOfExpression
*/
public function SimpleConditionalExpression()
{
@ -1853,6 +1854,8 @@ class Parser
return $this->LikeExpression();
case Lexer::T_IN:
return $this->InExpression();
case Lexer::T_INSTANCE:
return $this->InstanceOfExpression();
case Lexer::T_IS:
if ($lookahead['type'] == Lexer::T_NULL) {
return $this->NullComparisonExpression();
@ -2386,6 +2389,38 @@ class Parser
return $inExpression;
}
/**
* InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
*
* @return \Doctrine\ORM\Query\AST\InstanceOfExpression
*/
public function InstanceOfExpression()
{
$instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$instanceOfExpression->not = true;
}
$this->match(Lexer::T_INSTANCE);
if ($this->_lexer->isNextToken(Lexer::T_OF)) {
$this->match(Lexer::T_OF);
}
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
$exprValue = new AST\InputParameter($this->_lexer->token['value']);
} else {
$exprValue = $this->AliasIdentificationVariable();
}
$instanceOfExpression->value = $exprValue;
return $instanceOfExpression;
}
/**
* LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char]
*

View File

@ -47,6 +47,11 @@ class QueryException extends \Doctrine\ORM\ORMException
return new self('[Semantical Error] ' . $message);
}
public static function invalidParameterType($expected, $received)
{
return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.');
}
public static function invalidParameterPosition($pos)
{
return new self('Invalid parameter position: ' . $pos);

View File

@ -436,9 +436,10 @@ class SqlWalker implements TreeWalker
* Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
*
* @param string $identificationVariable
* @param string $fieldName
* @return string The SQL.
*/
public function walkIdentificationVariable($identificationVariable, $fieldName)
public function walkIdentificationVariable($identificationVariable, $fieldName = null)
{
$class = $this->_queryComponents[$identificationVariable]['metadata'];
@ -1510,6 +1511,66 @@ class SqlWalker implements TreeWalker
return $sql;
}
/**
* Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
*
* @param InstanceOfExpression
* @return string The SQL.
*/
function walkInstanceOfExpression($instanceOfExpr)
{
$sql = '';
$dqlAlias = $instanceOfExpr->identificationVariable;
$discrClass = $class = $this->_queryComponents[$dqlAlias]['metadata'];
$fieldName = null;
if ($class->discriminatorColumn && isset($class->discriminatorColumn['inherited'])) {
$discrClass = $this->_em->getClassMetadata($class->discriminatorColumn['inherited']);
}
if ($this->_useSqlTableAliases) {
$sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.';
}
$sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' != ' : ' = ');
if ($instanceOfExpr->value instanceof AST\InputParameter) {
// We need to modify the parameter value to be its correspondent mapped value
$dqlParamKey = $instanceOfExpr->value->name;
$paramValue = $this->_query->getParameter($dqlParamKey);
if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) {
throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue));
}
$entityClassName = $paramValue->name;
} else {
$entityClassName = $instanceOfExpr->value;
}
$conn = $this->_em->getConnection();
if ($entityClassName == $class->name) {
$sql .= $conn->quote($class->discriminatorValue);
} else {
foreach ($class->subClasses as $subclassName) {
if ($entityClassName === $subclassName) {
$sql .= $conn->quote($this->_em->getClassMetadata($subclassName)->discriminatorValue);
break;
}
}
}
return $sql;
}
/**
* Walks down an InParameter AST node, thereby generating the appropriate SQL.
*
* @param InParameter
* @return string The SQL.
*/
public function walkInParameter($inParam)
{
return $inParam instanceof AST\InputParameter ?

View File

@ -290,6 +290,14 @@ interface TreeWalker
*/
function walkInExpression($inExpr);
/**
* Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
*
* @param InstanceOfExpression
* @return string The SQL.
*/
function walkInstanceOfExpression($instanceOfExpr);
/**
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
*

View File

@ -324,6 +324,14 @@ abstract class TreeWalkerAdapter implements TreeWalker
*/
public function walkInExpression($inExpr) {}
/**
* Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
*
* @param InstanceOfExpression
* @return string The SQL.
*/
function walkInstanceOfExpression($instanceOfExpr) {}
/**
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
*

View File

@ -472,6 +472,19 @@ class TreeWalkerChain implements TreeWalker
}
}
/**
* Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
*
* @param InstanceOfExpression
* @return string The SQL.
*/
function walkInstanceOfExpression($instanceOfExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkInstanceOfExpression($instanceOfExpr);
}
}
/**
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
*

View File

@ -134,6 +134,21 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id NOT IN (1)');
}
public function testInstanceOfExpressionSupportedInWherePart()
{
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee');
}
public function testInstanceOfExpressionWithInputParamSupportedInWherePart()
{
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1');
}
public function testNotInstanceOfExpressionSupportedInWherePart()
{
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1');
}
public function testExistsExpressionSupportedInWherePart()
{
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = 1234)');

View File

@ -15,16 +15,22 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$this->_em = $this->_getTestEntityManager();
}
public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, array $queryHints = array())
public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, array $queryHints = array(), array $queryParams = array())
{
try {
$query = $this->_em->createQuery($dqlToBeTested);
foreach ($queryParams AS $name => $value) {
$query->setParameter($name, $value);
}
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->useQueryCache(false);
foreach ($queryHints AS $name => $value) {
$query->setHint($name, $value);
}
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
$query->free();
} catch (\Exception $e) {
@ -283,6 +289,39 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
);
}
public function testSupportsInstanceOfExpressionsInWherePart()
{
$this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee",
"SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr = 'employee'"
);
}
public function testSupportsInstanceOfExpressionsInWherePartInDeeperLevel()
{
$this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyEmployee u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyManager",
"SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c0_.discr AS discr4 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id WHERE c0_.discr = 'manager'"
);
}
public function testSupportsInstanceOfExpressionsInWherePartInDeepestLevel()
{
$this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyManager u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyManager",
"SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c2_.title AS title4, c0_.discr AS discr5 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id WHERE c0_.discr = 'manager'"
);
}
public function testSupportsInstanceOfExpressionsUsingInputParameterInWherePart()
{
$this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1",
"SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr = 'employee'",
array(), array(1 => $this->_em->getClassMetadata('Doctrine\Tests\Models\Company\CompanyEmployee'))
);
}
// Ticket #973
public function testSupportsSingleValuedInExpressionWithoutSpacesInWherePart()
{