Skip to content

Commit

Permalink
[2.0][DDC-482] Added support to INSTANCE OF in DQL.
Browse files Browse the repository at this point in the history
  • Loading branch information
Guilherme Blanco committed Jul 30, 2010
1 parent d2740f0 commit c1fec32
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 33 deletions.
25 changes: 24 additions & 1 deletion lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Expand Up @@ -240,13 +240,13 @@ protected function _loadMetadata($name)

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);
}
Expand Down Expand Up @@ -313,6 +313,29 @@ protected function _newClassMetadataInstance($className)
{
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.
Expand Down
49 changes: 49 additions & 0 deletions lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php
@@ -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);
}
}

55 changes: 28 additions & 27 deletions lib/Doctrine/ORM/Query/Lexer.php
Expand Up @@ -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.
*
Expand Down
41 changes: 38 additions & 3 deletions lib/Doctrine/ORM/Query/Parser.php
Expand Up @@ -1205,13 +1205,13 @@ public function Subselect()
}

/**
* 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);

Expand Down Expand Up @@ -1797,7 +1797,8 @@ public function ConditionalPrimary()
* SimpleConditionalExpression ::=
* ComparisonExpression | BetweenExpression | LikeExpression |
* InExpression | NullComparisonExpression | ExistsExpression |
* EmptyCollectionComparisonExpression | CollectionMemberExpression
* EmptyCollectionComparisonExpression | CollectionMemberExpression |
* InstanceOfExpression
*/
public function SimpleConditionalExpression()
{
Expand Down Expand Up @@ -1853,6 +1854,8 @@ public function SimpleConditionalExpression()
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();
Expand Down Expand Up @@ -2386,6 +2389,38 @@ public function InExpression()
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]
*
Expand Down
5 changes: 5 additions & 0 deletions lib/Doctrine/ORM/Query/QueryException.php
Expand Up @@ -47,6 +47,11 @@ public static function semanticalError($message)
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);
Expand Down
63 changes: 62 additions & 1 deletion lib/Doctrine/ORM/Query/SqlWalker.php
Expand Up @@ -436,9 +436,10 @@ public function walkDeleteStatement(AST\DeleteStatement $AST)
* 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'];

Expand Down Expand Up @@ -1510,6 +1511,66 @@ public function walkInExpression($inExpr)
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 ?
Expand Down
8 changes: 8 additions & 0 deletions lib/Doctrine/ORM/Query/TreeWalker.php
Expand Up @@ -290,6 +290,14 @@ function walkNullComparisonExpression($nullCompExpr);
*/
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.
*
Expand Down
8 changes: 8 additions & 0 deletions lib/Doctrine/ORM/Query/TreeWalkerAdapter.php
Expand Up @@ -324,6 +324,14 @@ public function walkNullComparisonExpression($nullCompExpr) {}
*/
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.
*
Expand Down
13 changes: 13 additions & 0 deletions lib/Doctrine/ORM/Query/TreeWalkerChain.php
Expand Up @@ -472,6 +472,19 @@ public function walkInExpression($inExpr)
}
}

/**
* 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.
*
Expand Down
15 changes: 15 additions & 0 deletions tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
Expand Up @@ -134,6 +134,21 @@ public function testNotInExpressionSupportedInWherePart()
$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)');
Expand Down

1 comment on commit c1fec32

@romanb
Copy link
Contributor

@romanb romanb commented on c1fec32 Jul 30, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in ClassMetadataFactory are not needed.

In SqlWalker, just use: $discrClass = $this->_em->getClassMetadata($class->rootEntityName);

The discriminator column is always on the table of the root entity.

Please sign in to comment.