[2.0][DDC-482] Added support to INSTANCE OF in DQL.
This commit is contained in:
parent
d2740f0e77
commit
c1fec32f58
@ -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);
|
||||
}
|
||||
@ -314,6 +314,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.
|
||||
*
|
||||
|
49
lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php
Normal file
49
lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
@ -75,32 +75,33 @@ 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.
|
||||
|
@ -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]
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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 ?
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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)');
|
||||
|
@ -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()
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user