From f148912a28601d9c6234d59661f0e4ad1ae0c138 Mon Sep 17 00:00:00 2001
From: Guilherme Blanco <guilhermeblanco@gmail.com>
Date: Mon, 15 Aug 2011 01:53:56 -0300
Subject: [PATCH] [DDC-934][DDC-1100][DDC-1331] Implemented support to
 multi-values in INSTANCE OF expression.

---
 lib/Doctrine/ORM/AbstractQuery.php            |  2 +
 .../ORM/Query/AST/InstanceOfExpression.php    |  3 +-
 lib/Doctrine/ORM/Query/Parser.php             | 48 +++++++++++++----
 lib/Doctrine/ORM/Query/SqlWalker.php          | 51 +++++++++++--------
 lib/Doctrine/ORM/QueryBuilder.php             |  2 +
 .../ORM/Query/SelectSqlGenerationTest.php     | 18 +++++--
 6 files changed, 86 insertions(+), 38 deletions(-)

diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php
index 26eee1fb8..88bbfc2c1 100644
--- a/lib/Doctrine/ORM/AbstractQuery.php
+++ b/lib/Doctrine/ORM/AbstractQuery.php
@@ -195,6 +195,8 @@ abstract class AbstractQuery
      */
     public function setParameter($key, $value, $type = null)
     {
+        $key = trim($key, ':');
+        
         if ($type === null) {
             $type = Query\ParameterTypeInferer::inferType($value);
         }
diff --git a/lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php b/lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php
index 3aefd61d9..b586f4812 100644
--- a/lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php
+++ b/lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php
@@ -20,7 +20,8 @@
 namespace Doctrine\ORM\Query\AST;
 
 /**
- * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
+ * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
+ * InstanceOfParameter  ::= AbstractSchemaName | InputParameter
  *
  * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  * @link    www.doctrine-project.org
diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php
index 0f7c88538..699537456 100644
--- a/lib/Doctrine/ORM/Query/Parser.php
+++ b/lib/Doctrine/ORM/Query/Parser.php
@@ -2676,7 +2676,7 @@ class Parser
     }
 
     /**
-     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
+     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
      *
      * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
      */
@@ -2690,22 +2690,50 @@ class Parser
         }
 
         $this->match(Lexer::T_INSTANCE);
+        $this->match(Lexer::T_OF);
+        
+        $exprValues = array();
+            
+        if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
+            $this->match(Lexer::T_OPEN_PARENTHESIS);
+            
+            $exprValues[] = $this->InstanceOfParameter();
 
-        if ($this->_lexer->isNextToken(Lexer::T_OF)) {
-            $this->match(Lexer::T_OF);
+            while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
+                $this->match(Lexer::T_COMMA);
+                
+                $exprValues[] = $this->InstanceOfParameter();
+            }
+            
+            $this->match(Lexer::T_CLOSE_PARENTHESIS);
+            
+            $instanceOfExpression->value = $exprValues;
+        
+            return $instanceOfExpression;
         }
 
-        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();
-        }
+        $exprValues[] = $this->InstanceOfParameter();
 
-        $instanceOfExpression->value = $exprValue;
+        $instanceOfExpression->value = $exprValues;
         
         return $instanceOfExpression;
     }
+    
+    /**
+     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
+     * 
+     * @return mixed
+     */
+    public function InstanceOfParameter()
+    {
+        if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
+            $this->match(Lexer::T_INPUT_PARAMETER);
+            
+            return new AST\InputParameter($this->_lexer->token['value']);
+        }
+        
+        return $this->AliasIdentificationVariable();
+    }
 
     /**
      * LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char]
diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php
index cc387543c..dc97d6bb5 100644
--- a/lib/Doctrine/ORM/Query/SqlWalker.php
+++ b/lib/Doctrine/ORM/Query/SqlWalker.php
@@ -1756,34 +1756,41 @@ class SqlWalker implements TreeWalker
         if ($this->_useSqlTableAliases) {
             $sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.';
         }
+        
+        $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
+        
+        $sqlParameterList = array();
+        
+        foreach ($instanceOfExpr->value as $parameter) {
+            if ($parameter instanceof AST\InputParameter) {
+                // We need to modify the parameter value to be its correspondent mapped value
+                $dqlParamKey = $parameter->name;
+                $paramValue  = $this->_query->getParameter($dqlParamKey);
 
-        $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' <> ' : ' = ');
+                if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) {
+                    throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue));
+                }
 
-        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 {
+                // Get name from ClassMetadata to resolve aliases.
+                $entityClassName = $this->_em->getClassMetadata($parameter)->name;
             }
-            
-            $entityClassName = $paramValue->name;
-        } else {
-            // Get name from ClassMetadata to resolve aliases.
-            $entityClassName = $this->_em->getClassMetadata($instanceOfExpr->value)->name;
-        }
 
-        if ($entityClassName == $class->name) {
-            $sql .= $this->_conn->quote($class->discriminatorValue);
-        } else {
-            $discrMap = array_flip($class->discriminatorMap);
-            if (!isset($discrMap[$entityClassName])) {
-                throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
+            if ($entityClassName == $class->name) {
+                $sqlParameterList[] = $this->_conn->quote($class->discriminatorValue);
+            } else {
+                $discrMap = array_flip($class->discriminatorMap);
+                
+                if (!isset($discrMap[$entityClassName])) {
+                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
+                }
+
+                $sqlParameterList[] = $this->_conn->quote($discrMap[$entityClassName]);
             }
-            
-            $sql .= $this->_conn->quote($discrMap[$entityClassName]);
         }
+        
+        $sql .= '(' . implode(', ', $sqlParameterList) . ')';
 
         return $sql;
     }
diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php
index 4facce77f..9b2829408 100644
--- a/lib/Doctrine/ORM/QueryBuilder.php
+++ b/lib/Doctrine/ORM/QueryBuilder.php
@@ -323,6 +323,8 @@ class QueryBuilder
      */
     public function setParameter($key, $value, $type = null)
     {
+        $key = trim($key, ':');
+        
         if ($type === null) {
             $type = Query\ParameterTypeInferer::inferType($value);
         }
diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
index 503807594..4c081402d 100644
--- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php
@@ -380,7 +380,15 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
     {
         $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'"
+            "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')"
+        );
+    }
+    
+    public function testSupportsInstanceOfExpressionInWherePartWithMultipleValues()
+    {
+        $this->assertSqlGeneration(
+            "SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF (Doctrine\Tests\Models\Company\CompanyEmployee, \Doctrine\Tests\Models\Company\CompanyManager)",
+            "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee', 'manager')"
         );
     }
     
@@ -391,7 +399,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
     {
         $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'"
+            "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')"
         );
     }
     
@@ -410,7 +418,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
     {
         $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'"
+            "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 IN ('manager')"
         );
     }
 
@@ -418,7 +426,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
     {
         $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'"
+            "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 IN ('manager')"
         );
     }
 
@@ -426,7 +434,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
     {
         $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'",
+            "SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_ WHERE c0_.discr IN ('employee')",
             array(), array(1 => $this->_em->getClassMetadata('Doctrine\Tests\Models\Company\CompanyEmployee'))
         );
     }