vendor/doctrine/lib/Doctrine/ORM/Query/SqlWalker.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 /*
       
     3  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       
     4  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
       
     5  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
       
     6  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
       
     7  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
       
     8  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
       
     9  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
       
    10  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
       
    11  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    12  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
       
    13  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    14  *
       
    15  * This software consists of voluntary contributions made by many individuals
       
    16  * and is licensed under the LGPL. For more information, see
       
    17  * <http://www.doctrine-project.org>.
       
    18  */
       
    19 
       
    20 namespace Doctrine\ORM\Query;
       
    21 
       
    22 use Doctrine\DBAL\LockMode,
       
    23     Doctrine\ORM\Mapping\ClassMetadata,
       
    24     Doctrine\ORM\Query,
       
    25     Doctrine\ORM\Query\QueryException;
       
    26 
       
    27 /**
       
    28  * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
       
    29  * the corresponding SQL.
       
    30  *
       
    31  * @author Roman Borschel <roman@code-factory.org>
       
    32  * @author Benjamin Eberlei <kontakt@beberlei.de>
       
    33  * @since 2.0
       
    34  * @todo Rename: SQLWalker
       
    35  */
       
    36 class SqlWalker implements TreeWalker
       
    37 {
       
    38     /**
       
    39      * @var ResultSetMapping
       
    40      */
       
    41     private $_rsm;
       
    42 
       
    43     /** Counters for generating unique column aliases, table aliases and parameter indexes. */
       
    44     private $_aliasCounter = 0;
       
    45     private $_tableAliasCounter = 0;
       
    46     private $_scalarResultCounter = 1;
       
    47     private $_sqlParamIndex = 0;
       
    48 
       
    49     /**
       
    50      * @var ParserResult
       
    51      */
       
    52     private $_parserResult;
       
    53 
       
    54     /**
       
    55      * @var EntityManager
       
    56      */
       
    57     private $_em;
       
    58 
       
    59     /**
       
    60      * @var Doctrine\DBAL\Connection
       
    61      */
       
    62     private $_conn;
       
    63 
       
    64     /**
       
    65      * @var AbstractQuery
       
    66      */
       
    67     private $_query;
       
    68 
       
    69     private $_tableAliasMap = array();
       
    70 
       
    71     /** Map from result variable names to their SQL column alias names. */
       
    72     private $_scalarResultAliasMap = array();
       
    73 
       
    74     /** Map of all components/classes that appear in the DQL query. */
       
    75     private $_queryComponents;
       
    76 
       
    77     /** A list of classes that appear in non-scalar SelectExpressions. */
       
    78     private $_selectedClasses = array();
       
    79 
       
    80     /**
       
    81      * The DQL alias of the root class of the currently traversed query.
       
    82      */
       
    83     private $_rootAliases = array();
       
    84 
       
    85     /**
       
    86      * Flag that indicates whether to generate SQL table aliases in the SQL.
       
    87      * These should only be generated for SELECT queries, not for UPDATE/DELETE.
       
    88      */
       
    89     private $_useSqlTableAliases = true;
       
    90 
       
    91     /**
       
    92      * The database platform abstraction.
       
    93      *
       
    94      * @var AbstractPlatform
       
    95      */
       
    96     private $_platform;
       
    97 
       
    98     /**
       
    99      * {@inheritDoc}
       
   100      */
       
   101     public function __construct($query, $parserResult, array $queryComponents)
       
   102     {
       
   103         $this->_query = $query;
       
   104         $this->_parserResult = $parserResult;
       
   105         $this->_queryComponents = $queryComponents;
       
   106         $this->_rsm = $parserResult->getResultSetMapping();
       
   107         $this->_em = $query->getEntityManager();
       
   108         $this->_conn = $this->_em->getConnection();
       
   109         $this->_platform = $this->_conn->getDatabasePlatform();
       
   110     }
       
   111 
       
   112     /**
       
   113      * Gets the Query instance used by the walker.
       
   114      *
       
   115      * @return Query.
       
   116      */
       
   117     public function getQuery()
       
   118     {
       
   119         return $this->_query;
       
   120     }
       
   121 
       
   122     /**
       
   123      * Gets the Connection used by the walker.
       
   124      *
       
   125      * @return Connection
       
   126      */
       
   127     public function getConnection()
       
   128     {
       
   129         return $this->_conn;
       
   130     }
       
   131 
       
   132     /**
       
   133      * Gets the EntityManager used by the walker.
       
   134      *
       
   135      * @return EntityManager
       
   136      */
       
   137     public function getEntityManager()
       
   138     {
       
   139         return $this->_em;
       
   140     }
       
   141 
       
   142     /**
       
   143      * Gets the information about a single query component.
       
   144      *
       
   145      * @param string $dqlAlias The DQL alias.
       
   146      * @return array
       
   147      */
       
   148     public function getQueryComponent($dqlAlias)
       
   149     {
       
   150         return $this->_queryComponents[$dqlAlias];
       
   151     }
       
   152 
       
   153     /**
       
   154      * Gets an executor that can be used to execute the result of this walker.
       
   155      *
       
   156      * @return AbstractExecutor
       
   157      */
       
   158     public function getExecutor($AST)
       
   159     {
       
   160         $isDeleteStatement = $AST instanceof AST\DeleteStatement;
       
   161         $isUpdateStatement = $AST instanceof AST\UpdateStatement;
       
   162 
       
   163         if ($isDeleteStatement) {
       
   164             $primaryClass = $this->_em->getClassMetadata(
       
   165                 $AST->deleteClause->abstractSchemaName
       
   166             );
       
   167 
       
   168             if ($primaryClass->isInheritanceTypeJoined()) {
       
   169                 return new Exec\MultiTableDeleteExecutor($AST, $this);
       
   170             } else {
       
   171                 return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
       
   172             }
       
   173         } else if ($isUpdateStatement) {
       
   174             $primaryClass = $this->_em->getClassMetadata(
       
   175                 $AST->updateClause->abstractSchemaName
       
   176             );
       
   177 
       
   178             if ($primaryClass->isInheritanceTypeJoined()) {
       
   179                 return new Exec\MultiTableUpdateExecutor($AST, $this);
       
   180             } else {
       
   181                 return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
       
   182             }
       
   183         }
       
   184 
       
   185         return new Exec\SingleSelectExecutor($AST, $this);
       
   186     }
       
   187 
       
   188     /**
       
   189      * Generates a unique, short SQL table alias.
       
   190      *
       
   191      * @param string $tableName Table name
       
   192      * @param string $dqlAlias The DQL alias.
       
   193      * @return string Generated table alias.
       
   194      */
       
   195     public function getSQLTableAlias($tableName, $dqlAlias = '')
       
   196     {
       
   197         $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
       
   198 
       
   199         if ( ! isset($this->_tableAliasMap[$tableName])) {
       
   200             $this->_tableAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_';
       
   201         }
       
   202 
       
   203         return $this->_tableAliasMap[$tableName];
       
   204     }
       
   205 
       
   206     /**
       
   207      * Forces the SqlWalker to use a specific alias for a table name, rather than
       
   208      * generating an alias on its own.
       
   209      *
       
   210      * @param string $tableName
       
   211      * @param string $alias
       
   212      * @param string $dqlAlias
       
   213      * @return string
       
   214      */
       
   215     public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
       
   216     {
       
   217         $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
       
   218 
       
   219         $this->_tableAliasMap[$tableName] = $alias;
       
   220 
       
   221         return $alias;
       
   222     }
       
   223 
       
   224     /**
       
   225      * Gets an SQL column alias for a column name.
       
   226      *
       
   227      * @param string $columnName
       
   228      * @return string
       
   229      */
       
   230     public function getSQLColumnAlias($columnName)
       
   231     {
       
   232         return $columnName . $this->_aliasCounter++;
       
   233     }
       
   234 
       
   235     /**
       
   236      * Generates the SQL JOINs that are necessary for Class Table Inheritance
       
   237      * for the given class.
       
   238      *
       
   239      * @param ClassMetadata $class The class for which to generate the joins.
       
   240      * @param string $dqlAlias The DQL alias of the class.
       
   241      * @return string The SQL.
       
   242      */
       
   243     private function _generateClassTableInheritanceJoins($class, $dqlAlias)
       
   244     {
       
   245         $sql = '';
       
   246 
       
   247         $baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
       
   248 
       
   249         // INNER JOIN parent class tables
       
   250         foreach ($class->parentClasses as $parentClassName) {
       
   251             $parentClass = $this->_em->getClassMetadata($parentClassName);
       
   252             $tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias);
       
   253             // If this is a joined association we must use left joins to preserve the correct result.
       
   254             $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
       
   255             $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform)
       
   256                   . ' ' . $tableAlias . ' ON ';
       
   257             $first = true;
       
   258             foreach ($class->identifier as $idField) {
       
   259                 if ($first) $first = false; else $sql .= ' AND ';
       
   260 
       
   261                 $columnName = $class->getQuotedColumnName($idField, $this->_platform);
       
   262                 $sql .= $baseTableAlias . '.' . $columnName
       
   263                       . ' = '
       
   264                       . $tableAlias . '.' . $columnName;
       
   265             }
       
   266         }
       
   267 
       
   268         // LEFT JOIN subclass tables, if partial objects disallowed.
       
   269         if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
       
   270             foreach ($class->subClasses as $subClassName) {
       
   271                 $subClass = $this->_em->getClassMetadata($subClassName);
       
   272                 $tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias);
       
   273                 $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform)
       
   274                         . ' ' . $tableAlias . ' ON ';
       
   275                 $first = true;
       
   276                 foreach ($class->identifier as $idField) {
       
   277                     if ($first) $first = false; else $sql .= ' AND ';
       
   278 
       
   279                     $columnName = $class->getQuotedColumnName($idField, $this->_platform);
       
   280                     $sql .= $baseTableAlias . '.' . $columnName
       
   281                             . ' = '
       
   282                             . $tableAlias . '.' . $columnName;
       
   283                 }
       
   284             }
       
   285         }
       
   286 
       
   287         return $sql;
       
   288     }
       
   289 
       
   290     private function _generateOrderedCollectionOrderByItems()
       
   291     {
       
   292         $sql = '';
       
   293         foreach ($this->_selectedClasses AS $dqlAlias => $class) {
       
   294             $qComp = $this->_queryComponents[$dqlAlias];
       
   295             if (isset($qComp['relation']['orderBy'])) {
       
   296                 foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) {
       
   297                     if ($qComp['metadata']->isInheritanceTypeJoined()) {
       
   298                         $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
       
   299                     } else {
       
   300                         $tableName = $qComp['metadata']->table['name'];
       
   301                     }
       
   302 
       
   303                     if ($sql != '') {
       
   304                         $sql .= ', ';
       
   305                     }
       
   306                     $sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.' .
       
   307                             $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation";
       
   308                 }
       
   309             }
       
   310         }
       
   311         return $sql;
       
   312     }
       
   313 
       
   314     /**
       
   315      * Generates a discriminator column SQL condition for the class with the given DQL alias.
       
   316      *
       
   317      * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
       
   318      * @return string
       
   319      */
       
   320     private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
       
   321     {
       
   322         $encapsulate = false;
       
   323         $sql = '';
       
   324 
       
   325         foreach ($dqlAliases as $dqlAlias) {
       
   326             $class = $this->_queryComponents[$dqlAlias]['metadata'];
       
   327 
       
   328             if ($class->isInheritanceTypeSingleTable()) {
       
   329                 $conn = $this->_em->getConnection();
       
   330                 $values = array();
       
   331                 if ($class->discriminatorValue !== null) { // discrimnators can be 0
       
   332                     $values[] = $conn->quote($class->discriminatorValue);
       
   333                 }
       
   334 
       
   335                 foreach ($class->subClasses as $subclassName) {
       
   336                     $values[] = $conn->quote($this->_em->getClassMetadata($subclassName)->discriminatorValue);
       
   337                 }
       
   338 
       
   339                 if ($sql != '') {
       
   340                     $sql .= ' AND ';
       
   341                     $encapsulate = true;
       
   342                 }
       
   343 
       
   344                 $sql .= ($sql != '' ? ' AND ' : '')
       
   345                       . (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.' : '')
       
   346                       . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
       
   347             }
       
   348         }
       
   349 
       
   350         return ($encapsulate) ? '(' . $sql . ')' : $sql;
       
   351     }
       
   352 
       
   353     /**
       
   354      * Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
       
   355      *
       
   356      * @return string The SQL.
       
   357      */
       
   358     public function walkSelectStatement(AST\SelectStatement $AST)
       
   359     {
       
   360         $sql = $this->walkSelectClause($AST->selectClause);
       
   361         $sql .= $this->walkFromClause($AST->fromClause);
       
   362 
       
   363         if (($whereClause = $AST->whereClause) !== null) {
       
   364             $sql .= $this->walkWhereClause($whereClause);
       
   365         } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
       
   366             $sql .= ' WHERE ' . $discSql;
       
   367         }
       
   368 
       
   369         $sql .= $AST->groupByClause ? $this->walkGroupByClause($AST->groupByClause) : '';
       
   370         $sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : '';
       
   371 
       
   372         if (($orderByClause = $AST->orderByClause) !== null) {
       
   373             $sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : '';
       
   374         } else if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') {
       
   375             $sql .= ' ORDER BY '.$orderBySql;
       
   376         }
       
   377 
       
   378 
       
   379         $sql = $this->_platform->modifyLimitQuery(
       
   380             $sql, $this->_query->getMaxResults(), $this->_query->getFirstResult()
       
   381         );
       
   382 
       
   383         if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) {
       
   384             if ($lockMode == LockMode::PESSIMISTIC_READ) {
       
   385                 $sql .= " " . $this->_platform->getReadLockSQL();
       
   386             } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
       
   387                 $sql .= " " . $this->_platform->getWriteLockSQL();
       
   388             } else if ($lockMode == LockMode::OPTIMISTIC) {
       
   389                 foreach ($this->_selectedClasses AS $class) {
       
   390                     if ( ! $class->isVersioned) {
       
   391                         throw \Doctrine\ORM\OptimisticLockException::lockFailed();
       
   392                     }
       
   393                 }
       
   394             }
       
   395         }
       
   396 
       
   397         return $sql;
       
   398     }
       
   399 
       
   400     /**
       
   401      * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
       
   402      *
       
   403      * @param UpdateStatement
       
   404      * @return string The SQL.
       
   405      */
       
   406     public function walkUpdateStatement(AST\UpdateStatement $AST)
       
   407     {
       
   408         $this->_useSqlTableAliases = false;
       
   409         $sql = $this->walkUpdateClause($AST->updateClause);
       
   410 
       
   411         if (($whereClause = $AST->whereClause) !== null) {
       
   412             $sql .= $this->walkWhereClause($whereClause);
       
   413         } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
       
   414             $sql .= ' WHERE ' . $discSql;
       
   415         }
       
   416 
       
   417         return $sql;
       
   418     }
       
   419 
       
   420     /**
       
   421      * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL.
       
   422      *
       
   423      * @param DeleteStatement
       
   424      * @return string The SQL.
       
   425      */
       
   426     public function walkDeleteStatement(AST\DeleteStatement $AST)
       
   427     {
       
   428         $this->_useSqlTableAliases = false;
       
   429         $sql = $this->walkDeleteClause($AST->deleteClause);
       
   430 
       
   431         if (($whereClause = $AST->whereClause) !== null) {
       
   432             $sql .= $this->walkWhereClause($whereClause);
       
   433         } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
       
   434             $sql .= ' WHERE ' . $discSql;
       
   435         }
       
   436 
       
   437         return $sql;
       
   438     }
       
   439 
       
   440 
       
   441     /**
       
   442      * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
       
   443      *
       
   444      * @param string $identificationVariable
       
   445      * @param string $fieldName
       
   446      * @return string The SQL.
       
   447      */
       
   448     public function walkIdentificationVariable($identificationVariable, $fieldName = null)
       
   449     {
       
   450         $class = $this->_queryComponents[$identificationVariable]['metadata'];
       
   451 
       
   452         if (
       
   453             $fieldName !== null && $class->isInheritanceTypeJoined() &&
       
   454             isset($class->fieldMappings[$fieldName]['inherited'])
       
   455         ) {
       
   456             $class = $this->_em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
       
   457         }
       
   458 
       
   459         return $this->getSQLTableAlias($class->table['name'], $identificationVariable);
       
   460     }
       
   461 
       
   462     /**
       
   463      * Walks down a PathExpression AST node, thereby generating the appropriate SQL.
       
   464      *
       
   465      * @param mixed
       
   466      * @return string The SQL.
       
   467      */
       
   468     public function walkPathExpression($pathExpr)
       
   469     {
       
   470         $sql = '';
       
   471 
       
   472         switch ($pathExpr->type) {
       
   473             case AST\PathExpression::TYPE_STATE_FIELD:
       
   474                 $fieldName = $pathExpr->field;
       
   475                 $dqlAlias = $pathExpr->identificationVariable;
       
   476                 $class = $this->_queryComponents[$dqlAlias]['metadata'];
       
   477 
       
   478                 if ($this->_useSqlTableAliases) {
       
   479                     $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
       
   480                 }
       
   481 
       
   482                 $sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
       
   483                 break;
       
   484 
       
   485             case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
       
   486                 // 1- the owning side:
       
   487                 //    Just use the foreign key, i.e. u.group_id
       
   488                 $fieldName = $pathExpr->field;
       
   489                 $dqlAlias = $pathExpr->identificationVariable;
       
   490                 $class = $this->_queryComponents[$dqlAlias]['metadata'];
       
   491 
       
   492                 if (isset($class->associationMappings[$fieldName]['inherited'])) {
       
   493                     $class = $this->_em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
       
   494                 }
       
   495 
       
   496                 $assoc = $class->associationMappings[$fieldName];
       
   497 
       
   498                 if ($assoc['isOwningSide']) {
       
   499                     // COMPOSITE KEYS NOT (YET?) SUPPORTED
       
   500                     if (count($assoc['sourceToTargetKeyColumns']) > 1) {
       
   501                         throw QueryException::associationPathCompositeKeyNotSupported();
       
   502                     }
       
   503 
       
   504                     if ($this->_useSqlTableAliases) {
       
   505                         $sql .= $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.';
       
   506                     }
       
   507 
       
   508                     $sql .= reset($assoc['targetToSourceKeyColumns']);
       
   509                 } else {
       
   510                     throw QueryException::associationPathInverseSideNotSupported();
       
   511                 }
       
   512                 break;
       
   513                 
       
   514             default:
       
   515                 throw QueryException::invalidPathExpression($pathExpr);
       
   516         }
       
   517 
       
   518         return $sql;
       
   519     }
       
   520 
       
   521     /**
       
   522      * Walks down a SelectClause AST node, thereby generating the appropriate SQL.
       
   523      *
       
   524      * @param $selectClause
       
   525      * @return string The SQL.
       
   526      */
       
   527     public function walkSelectClause($selectClause)
       
   528     {
       
   529         $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '') . implode(
       
   530             ', ', array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions))
       
   531         );
       
   532 
       
   533         $addMetaColumns = ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
       
   534                 $this->_query->getHydrationMode() == Query::HYDRATE_OBJECT
       
   535                 ||
       
   536                 $this->_query->getHydrationMode() != Query::HYDRATE_OBJECT &&
       
   537                 $this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
       
   538 
       
   539         foreach ($this->_selectedClasses as $dqlAlias => $class) {
       
   540             // Register as entity or joined entity result
       
   541             if ($this->_queryComponents[$dqlAlias]['relation'] === null) {
       
   542                 $this->_rsm->addEntityResult($class->name, $dqlAlias);
       
   543             } else {
       
   544                 $this->_rsm->addJoinedEntityResult(
       
   545                     $class->name, $dqlAlias,
       
   546                     $this->_queryComponents[$dqlAlias]['parent'],
       
   547                     $this->_queryComponents[$dqlAlias]['relation']['fieldName']
       
   548                 );
       
   549             }
       
   550 
       
   551             if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
       
   552                 // Add discriminator columns to SQL
       
   553                 $rootClass = $this->_em->getClassMetadata($class->rootEntityName);
       
   554                 $tblAlias = $this->getSQLTableAlias($rootClass->table['name'], $dqlAlias);
       
   555                 $discrColumn = $rootClass->discriminatorColumn;
       
   556                 $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
       
   557                 $sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias;
       
   558 
       
   559                 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
   560                 $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
       
   561                 $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']);
       
   562 
       
   563                 // Add foreign key columns to SQL, if necessary
       
   564                 if ($addMetaColumns) {
       
   565                     //FIXME: Include foreign key columns of child classes also!!??
       
   566                     foreach ($class->associationMappings as $assoc) {
       
   567                         if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
       
   568                             if (isset($assoc['inherited'])) {
       
   569                                 $owningClass = $this->_em->getClassMetadata($assoc['inherited']);
       
   570                                 $sqlTableAlias = $this->getSQLTableAlias($owningClass->table['name'], $dqlAlias);
       
   571                             } else {
       
   572                                 $sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
       
   573                             }
       
   574                             
       
   575                             foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
       
   576                                 $columnAlias = $this->getSQLColumnAlias($srcColumn);
       
   577                                 $sql .= ", $sqlTableAlias." . $srcColumn . ' AS ' . $columnAlias;
       
   578                                 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
   579                                 $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true));
       
   580                             }
       
   581                         }
       
   582                     }
       
   583                 }
       
   584             } else {
       
   585                 // Add foreign key columns to SQL, if necessary
       
   586                 if ($addMetaColumns) {
       
   587                     $sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
       
   588                     foreach ($class->associationMappings as $assoc) {
       
   589                         if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
       
   590                             foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
       
   591                                 $columnAlias = $this->getSQLColumnAlias($srcColumn);
       
   592                                 $sql .= ', ' . $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
       
   593                                 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
   594                                 $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true));
       
   595                             }
       
   596                         }
       
   597                     }
       
   598                 }
       
   599             }
       
   600         }
       
   601 
       
   602         return $sql;
       
   603     }
       
   604 
       
   605     /**
       
   606      * Walks down a FromClause AST node, thereby generating the appropriate SQL.
       
   607      *
       
   608      * @return string The SQL.
       
   609      */
       
   610     public function walkFromClause($fromClause)
       
   611     {
       
   612         $identificationVarDecls = $fromClause->identificationVariableDeclarations;
       
   613         $sqlParts = array();
       
   614 
       
   615         foreach ($identificationVarDecls as $identificationVariableDecl) {
       
   616             $sql = '';
       
   617 
       
   618             $rangeDecl = $identificationVariableDecl->rangeVariableDeclaration;
       
   619             $dqlAlias = $rangeDecl->aliasIdentificationVariable;
       
   620         
       
   621             $this->_rootAliases[] = $dqlAlias;
       
   622 
       
   623             $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
       
   624             $sql .= $class->getQuotedTableName($this->_platform) . ' '
       
   625                   . $this->getSQLTableAlias($class->table['name'], $dqlAlias);
       
   626 
       
   627             if ($class->isInheritanceTypeJoined()) {
       
   628                 $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
       
   629             }
       
   630 
       
   631             foreach ($identificationVariableDecl->joinVariableDeclarations as $joinVarDecl) {
       
   632                 $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
       
   633             }
       
   634 
       
   635             if ($identificationVariableDecl->indexBy) {
       
   636                 $this->_rsm->addIndexBy(
       
   637                     $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
       
   638                     $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field
       
   639                 );
       
   640             }
       
   641 
       
   642             $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));
       
   643         }
       
   644 
       
   645         return ' FROM ' . implode(', ', $sqlParts);
       
   646     }
       
   647 
       
   648     /**
       
   649      * Walks down a FunctionNode AST node, thereby generating the appropriate SQL.
       
   650      *
       
   651      * @return string The SQL.
       
   652      */
       
   653     public function walkFunction($function)
       
   654     {
       
   655         return $function->getSql($this);
       
   656     }
       
   657 
       
   658     /**
       
   659      * Walks down an OrderByClause AST node, thereby generating the appropriate SQL.
       
   660      *
       
   661      * @param OrderByClause
       
   662      * @return string The SQL.
       
   663      */
       
   664     public function walkOrderByClause($orderByClause)
       
   665     {
       
   666         $colSql = $this->_generateOrderedCollectionOrderByItems();
       
   667         if ($colSql != '') {
       
   668             $colSql = ", ".$colSql;
       
   669         }
       
   670 
       
   671         // OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
       
   672         return ' ORDER BY ' . implode(
       
   673             ', ', array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems)
       
   674         )  . $colSql;
       
   675     }
       
   676 
       
   677     /**
       
   678      * Walks down an OrderByItem AST node, thereby generating the appropriate SQL.
       
   679      *
       
   680      * @param OrderByItem
       
   681      * @return string The SQL.
       
   682      */
       
   683     public function walkOrderByItem($orderByItem)
       
   684     {
       
   685         $sql = '';
       
   686         $expr = $orderByItem->expression;
       
   687 
       
   688         if ($expr instanceof AST\PathExpression) {
       
   689             $sql = $this->walkPathExpression($expr);
       
   690         } else {
       
   691             $columnName = $this->_queryComponents[$expr]['token']['value'];
       
   692 
       
   693             $sql = $this->_scalarResultAliasMap[$columnName];
       
   694         }
       
   695 
       
   696         return $sql . ' ' . strtoupper($orderByItem->type);
       
   697     }
       
   698 
       
   699     /**
       
   700      * Walks down a HavingClause AST node, thereby generating the appropriate SQL.
       
   701      *
       
   702      * @param HavingClause
       
   703      * @return string The SQL.
       
   704      */
       
   705     public function walkHavingClause($havingClause)
       
   706     {
       
   707         return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
       
   708     }
       
   709 
       
   710     /**
       
   711      * Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL.
       
   712      *
       
   713      * @param JoinVariableDeclaration $joinVarDecl
       
   714      * @return string The SQL.
       
   715      */
       
   716     public function walkJoinVariableDeclaration($joinVarDecl)
       
   717     {
       
   718         $join = $joinVarDecl->join;
       
   719         $joinType = $join->joinType;
       
   720 
       
   721         if ($joinVarDecl->indexBy) {
       
   722             // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
       
   723             $this->_rsm->addIndexBy(
       
   724                 $joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
       
   725                 $joinVarDecl->indexBy->simpleStateFieldPathExpression->field
       
   726             );
       
   727         }
       
   728 
       
   729         if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) {
       
   730             $sql = ' LEFT JOIN ';
       
   731         } else {
       
   732             $sql = ' INNER JOIN ';
       
   733         }
       
   734 
       
   735         $joinAssocPathExpr = $join->joinAssociationPathExpression;
       
   736         $joinedDqlAlias = $join->aliasIdentificationVariable;
       
   737         $relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
       
   738         $targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
       
   739         $sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
       
   740         $targetTableName = $targetClass->getQuotedTableName($this->_platform);
       
   741         $targetTableAlias = $this->getSQLTableAlias($targetClass->table['name'], $joinedDqlAlias);
       
   742         $sourceTableAlias = $this->getSQLTableAlias($sourceClass->table['name'], $joinAssocPathExpr->identificationVariable);
       
   743 
       
   744         // Ensure we got the owning side, since it has all mapping info
       
   745         $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
       
   746 
       
   747         if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true) {
       
   748             if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
       
   749                 throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
       
   750             }
       
   751         }
       
   752 
       
   753         if ($joinVarDecl->indexBy) {
       
   754             // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
       
   755             $this->_rsm->addIndexBy(
       
   756                 $joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
       
   757                 $joinVarDecl->indexBy->simpleStateFieldPathExpression->field
       
   758             );
       
   759         } else if (isset($relation['indexBy'])) {
       
   760             $this->_rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
       
   761         }
       
   762 
       
   763         // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
       
   764         // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
       
   765         // The owning side is necessary at this point because only it contains the JoinColumn information.
       
   766         if ($assoc['type'] & ClassMetadata::TO_ONE) {
       
   767 
       
   768             $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
       
   769             $first = true;
       
   770 
       
   771             foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) {
       
   772                 if ( ! $first) $sql .= ' AND '; else $first = false;
       
   773 
       
   774                 if ($relation['isOwningSide']) {
       
   775                     if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) {
       
   776                         $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
       
   777                     } else {
       
   778                         $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
       
   779                     }
       
   780                     $sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
       
   781                 } else {
       
   782                     if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) {
       
   783                         $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
       
   784                     } else {
       
   785                         $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
       
   786                     }
       
   787                     $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
       
   788                 }
       
   789             }
       
   790         } else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
       
   791             // Join relation table
       
   792             $joinTable = $assoc['joinTable'];
       
   793             $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
       
   794             $sql .= $sourceClass->getQuotedJoinTableName($assoc, $this->_platform) . ' ' . $joinTableAlias . ' ON ';
       
   795 
       
   796             $first = true;
       
   797             if ($relation['isOwningSide']) {
       
   798                 foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
       
   799                     if ( ! $first) $sql .= ' AND '; else $first = false;
       
   800 
       
   801                     if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$sourceColumn])) {
       
   802                         $quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted.
       
   803                     } else {
       
   804                         $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform);
       
   805                     }
       
   806 
       
   807                     $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
       
   808                 }
       
   809             } else {
       
   810                 foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
       
   811                     if ( ! $first) $sql .= ' AND '; else $first = false;
       
   812 
       
   813                     if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) {
       
   814                         $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
       
   815                     } else {
       
   816                         $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
       
   817                     }
       
   818 
       
   819                     $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
       
   820                 }
       
   821             }
       
   822 
       
   823             // Join target table
       
   824             $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
       
   825             	? ' LEFT JOIN ' : ' INNER JOIN ';
       
   826             $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
       
   827 
       
   828             $first = true;
       
   829             if ($relation['isOwningSide']) {
       
   830                 foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
       
   831                     if ( ! $first) $sql .= ' AND '; else $first = false;
       
   832 
       
   833                     if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) {
       
   834                         $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
       
   835                     } else {
       
   836                         $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
       
   837                     }
       
   838 
       
   839                     $sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
       
   840                 }
       
   841             } else {
       
   842                 foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
       
   843                     if ( ! $first) $sql .= ' AND '; else $first = false;
       
   844 
       
   845                     if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$sourceColumn])) {
       
   846                         $quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted.
       
   847                     } else {
       
   848                         $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform);
       
   849                     }
       
   850 
       
   851                     $sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
       
   852                 }
       
   853             }
       
   854         }
       
   855 
       
   856         // Handle WITH clause
       
   857         if (($condExpr = $join->conditionalExpression) !== null) {
       
   858             // Phase 2 AST optimization: Skip processment of ConditionalExpression
       
   859             // if only one ConditionalTerm is defined
       
   860             $sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')';
       
   861         }
       
   862 
       
   863         $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
       
   864 
       
   865         if ($discrSql) {
       
   866             $sql .= ' AND ' . $discrSql;
       
   867         }
       
   868 
       
   869         // FIXME: these should either be nested or all forced to be left joins (DDC-XXX)
       
   870         if ($targetClass->isInheritanceTypeJoined()) {
       
   871             $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
       
   872         }
       
   873 
       
   874         return $sql;
       
   875     }
       
   876     
       
   877     /**
       
   878      * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
       
   879      *
       
   880      * @param CoalesceExpression $coalesceExpression
       
   881      * @return string The SQL.
       
   882      */
       
   883     public function walkCoalesceExpression($coalesceExpression)
       
   884     {
       
   885         $sql = 'COALESCE(';
       
   886         
       
   887         $scalarExpressions = array();
       
   888         
       
   889         foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
       
   890             $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
       
   891         }
       
   892         
       
   893         $sql .= implode(', ', $scalarExpressions) . ')';
       
   894         
       
   895         return $sql;
       
   896     }
       
   897     
       
   898     public function walkCaseExpression($expression)
       
   899     {
       
   900         switch (true) {
       
   901             case ($expression instanceof AST\CoalesceExpression):
       
   902                 return $this->walkCoalesceExpression($expression);
       
   903                 
       
   904             case ($expression instanceof AST\NullIfExpression):
       
   905                 return $this->walkNullIfExpression($expression);
       
   906                 
       
   907             default:
       
   908                 return '';
       
   909         }
       
   910     }
       
   911     
       
   912     /**
       
   913      * Walks down a NullIfExpression AST node and generates the corresponding SQL.
       
   914      *
       
   915      * @param NullIfExpression $nullIfExpression
       
   916      * @return string The SQL.
       
   917      */
       
   918     public function walkNullIfExpression($nullIfExpression)
       
   919     {
       
   920         $firstExpression = is_string($nullIfExpression->firstExpression) 
       
   921             ? $this->_conn->quote($nullIfExpression->firstExpression)
       
   922             : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
       
   923         
       
   924         $secondExpression = is_string($nullIfExpression->secondExpression) 
       
   925             ? $this->_conn->quote($nullIfExpression->secondExpression)
       
   926             : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
       
   927         
       
   928         return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
       
   929     }
       
   930 
       
   931     /**
       
   932      * Walks down a SelectExpression AST node and generates the corresponding SQL.
       
   933      *
       
   934      * @param SelectExpression $selectExpression
       
   935      * @return string The SQL.
       
   936      */
       
   937     public function walkSelectExpression($selectExpression)
       
   938     {
       
   939         $sql = '';
       
   940         $expr = $selectExpression->expression;
       
   941 
       
   942         if ($expr instanceof AST\PathExpression) {
       
   943             if ($expr->type == AST\PathExpression::TYPE_STATE_FIELD) {
       
   944                 $fieldName = $expr->field;
       
   945                 $dqlAlias = $expr->identificationVariable;
       
   946                 $qComp = $this->_queryComponents[$dqlAlias];
       
   947                 $class = $qComp['metadata'];
       
   948 
       
   949                 if ( ! $selectExpression->fieldIdentificationVariable) {
       
   950                     $resultAlias = $fieldName;
       
   951                 } else {
       
   952                     $resultAlias = $selectExpression->fieldIdentificationVariable;
       
   953                 }
       
   954 
       
   955                 if ($class->isInheritanceTypeJoined()) {
       
   956                     $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
       
   957                 } else {
       
   958                     $tableName = $class->getTableName();
       
   959                 }
       
   960 
       
   961                 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
       
   962                 $columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
       
   963 
       
   964                 $columnAlias = $this->getSQLColumnAlias($columnName);
       
   965                 $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
       
   966                 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
   967                 $this->_rsm->addScalarResult($columnAlias, $resultAlias);
       
   968             } else {
       
   969                 throw QueryException::invalidPathExpression($expr->type);
       
   970             }
       
   971         }
       
   972         else if ($expr instanceof AST\AggregateExpression) {
       
   973             if ( ! $selectExpression->fieldIdentificationVariable) {
       
   974                 $resultAlias = $this->_scalarResultCounter++;
       
   975             } else {
       
   976                 $resultAlias = $selectExpression->fieldIdentificationVariable;
       
   977             }
       
   978 
       
   979             $columnAlias = 'sclr' . $this->_aliasCounter++;
       
   980             $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias;
       
   981             $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
       
   982 
       
   983             $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
   984             $this->_rsm->addScalarResult($columnAlias, $resultAlias);
       
   985         }
       
   986         else if ($expr instanceof AST\Subselect) {
       
   987             if ( ! $selectExpression->fieldIdentificationVariable) {
       
   988                 $resultAlias = $this->_scalarResultCounter++;
       
   989             } else {
       
   990                 $resultAlias = $selectExpression->fieldIdentificationVariable;
       
   991             }
       
   992 
       
   993             $columnAlias = 'sclr' . $this->_aliasCounter++;
       
   994             $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias;
       
   995             $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
       
   996 
       
   997             $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
   998             $this->_rsm->addScalarResult($columnAlias, $resultAlias);
       
   999         }
       
  1000         else if ($expr instanceof AST\Functions\FunctionNode) {
       
  1001             if ( ! $selectExpression->fieldIdentificationVariable) {
       
  1002                 $resultAlias = $this->_scalarResultCounter++;
       
  1003             } else {
       
  1004                 $resultAlias = $selectExpression->fieldIdentificationVariable;
       
  1005             }
       
  1006 
       
  1007             $columnAlias = 'sclr' . $this->_aliasCounter++;
       
  1008             $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
       
  1009             $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
       
  1010 
       
  1011             $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
  1012             $this->_rsm->addScalarResult($columnAlias, $resultAlias);
       
  1013         } else if (
       
  1014             $expr instanceof AST\SimpleArithmeticExpression ||
       
  1015             $expr instanceof AST\ArithmeticTerm ||
       
  1016             $expr instanceof AST\ArithmeticFactor ||
       
  1017             $expr instanceof AST\ArithmeticPrimary ||
       
  1018             $expr instanceof AST\Literal
       
  1019         ) {
       
  1020             if ( ! $selectExpression->fieldIdentificationVariable) {
       
  1021                 $resultAlias = $this->_scalarResultCounter++;
       
  1022             } else {
       
  1023                 $resultAlias = $selectExpression->fieldIdentificationVariable;
       
  1024             }
       
  1025 
       
  1026             $columnAlias = 'sclr' . $this->_aliasCounter++;
       
  1027             
       
  1028             if ($expr instanceof AST\Literal) {
       
  1029                 $sql .= $this->walkLiteral($expr) . ' AS ' .$columnAlias;
       
  1030             } else {
       
  1031                 $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
       
  1032             }
       
  1033             
       
  1034             $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
       
  1035 
       
  1036             $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
  1037             $this->_rsm->addScalarResult($columnAlias, $resultAlias);
       
  1038         } else if (
       
  1039             $expr instanceof AST\NullIfExpression ||
       
  1040             $expr instanceof AST\CoalesceExpression ||
       
  1041             $expr instanceof AST\CaseExpression
       
  1042         ) {
       
  1043             if ( ! $selectExpression->fieldIdentificationVariable) {
       
  1044                 $resultAlias = $this->_scalarResultCounter++;
       
  1045             } else {
       
  1046                 $resultAlias = $selectExpression->fieldIdentificationVariable;
       
  1047             }
       
  1048 
       
  1049             $columnAlias = 'sclr' . $this->_aliasCounter++;
       
  1050             
       
  1051             $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias;
       
  1052             
       
  1053             $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
       
  1054 
       
  1055             $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
  1056             $this->_rsm->addScalarResult($columnAlias, $resultAlias);
       
  1057         } else {
       
  1058             // IdentificationVariable or PartialObjectExpression
       
  1059             if ($expr instanceof AST\PartialObjectExpression) {
       
  1060                 $dqlAlias = $expr->identificationVariable;
       
  1061                 $partialFieldSet = $expr->partialFieldSet;
       
  1062             } else {
       
  1063                 $dqlAlias = $expr;
       
  1064                 $partialFieldSet = array();
       
  1065             }
       
  1066 
       
  1067             $queryComp = $this->_queryComponents[$dqlAlias];
       
  1068             $class = $queryComp['metadata'];
       
  1069 
       
  1070             if ( ! isset($this->_selectedClasses[$dqlAlias])) {
       
  1071                 $this->_selectedClasses[$dqlAlias] = $class;
       
  1072             }
       
  1073 
       
  1074             $beginning = true;
       
  1075             // Select all fields from the queried class
       
  1076             foreach ($class->fieldMappings as $fieldName => $mapping) {
       
  1077                 if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
       
  1078                     continue;
       
  1079                 }
       
  1080 
       
  1081                 if (isset($mapping['inherited'])) {
       
  1082                     $tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name'];
       
  1083                 } else {
       
  1084                     $tableName = $class->table['name'];
       
  1085                 }
       
  1086 
       
  1087                 if ($beginning) $beginning = false; else $sql .= ', ';
       
  1088 
       
  1089                 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
       
  1090                 $columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
       
  1091                 $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform)
       
  1092                       . ' AS ' . $columnAlias;
       
  1093 
       
  1094                 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
  1095                 $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
       
  1096             }
       
  1097 
       
  1098             // Add any additional fields of subclasses (excluding inherited fields)
       
  1099             // 1) on Single Table Inheritance: always, since its marginal overhead
       
  1100             // 2) on Class Table Inheritance only if partial objects are disallowed,
       
  1101             //    since it requires outer joining subtables.
       
  1102             if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
       
  1103                 foreach ($class->subClasses as $subClassName) {
       
  1104                     $subClass = $this->_em->getClassMetadata($subClassName);
       
  1105                     $sqlTableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias);
       
  1106                     foreach ($subClass->fieldMappings as $fieldName => $mapping) {
       
  1107                         if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
       
  1108                             continue;
       
  1109                         }
       
  1110 
       
  1111                         if ($beginning) $beginning = false; else $sql .= ', ';
       
  1112 
       
  1113                         $columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
       
  1114                         $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform)
       
  1115                                 . ' AS ' . $columnAlias;
       
  1116 
       
  1117                         $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
       
  1118                         $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
       
  1119                     }
       
  1120 
       
  1121                     // Add join columns (foreign keys) of the subclass
       
  1122                     //TODO: Probably better do this in walkSelectClause to honor the INCLUDE_META_COLUMNS hint
       
  1123                     foreach ($subClass->associationMappings as $fieldName => $assoc) {
       
  1124                         if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
       
  1125                             foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
       
  1126                                 if ($beginning) $beginning = false; else $sql .= ', ';
       
  1127                                 $columnAlias = $this->getSQLColumnAlias($srcColumn);
       
  1128                                 $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
       
  1129                                 $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
       
  1130                             }
       
  1131                         }
       
  1132                     }
       
  1133                 }
       
  1134             }
       
  1135         }
       
  1136 
       
  1137         return $sql;
       
  1138     }
       
  1139 
       
  1140     /**
       
  1141      * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL.
       
  1142      *
       
  1143      * @param QuantifiedExpression
       
  1144      * @return string The SQL.
       
  1145      */
       
  1146     public function walkQuantifiedExpression($qExpr)
       
  1147     {
       
  1148         return ' ' . strtoupper($qExpr->type)
       
  1149              . '(' . $this->walkSubselect($qExpr->subselect) . ')';
       
  1150     }
       
  1151 
       
  1152     /**
       
  1153      * Walks down a Subselect AST node, thereby generating the appropriate SQL.
       
  1154      *
       
  1155      * @param Subselect
       
  1156      * @return string The SQL.
       
  1157      */
       
  1158     public function walkSubselect($subselect)
       
  1159     {
       
  1160         $useAliasesBefore = $this->_useSqlTableAliases;
       
  1161         $this->_useSqlTableAliases = true;
       
  1162 
       
  1163         $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
       
  1164         $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
       
  1165         $sql .= $subselect->whereClause ? $this->walkWhereClause($subselect->whereClause) : '';
       
  1166         $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
       
  1167         $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
       
  1168         $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
       
  1169 
       
  1170         $this->_useSqlTableAliases = $useAliasesBefore;
       
  1171 
       
  1172         return $sql;
       
  1173     }
       
  1174 
       
  1175     /**
       
  1176      * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL.
       
  1177      *
       
  1178      * @param SubselectFromClause
       
  1179      * @return string The SQL.
       
  1180      */
       
  1181     public function walkSubselectFromClause($subselectFromClause)
       
  1182     {
       
  1183         $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
       
  1184         $sqlParts = array ();
       
  1185 
       
  1186         foreach ($identificationVarDecls as $subselectIdVarDecl) {
       
  1187             $sql = '';
       
  1188 
       
  1189             $rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration;
       
  1190             $dqlAlias = $rangeDecl->aliasIdentificationVariable;
       
  1191 
       
  1192             $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
       
  1193             $sql .= $class->getQuotedTableName($this->_platform) . ' '
       
  1194                   . $this->getSQLTableAlias($class->table['name'], $dqlAlias);
       
  1195 
       
  1196             if ($class->isInheritanceTypeJoined()) {
       
  1197                 $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
       
  1198             }
       
  1199 
       
  1200             foreach ($subselectIdVarDecl->joinVariableDeclarations as $joinVarDecl) {
       
  1201                 $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
       
  1202             }
       
  1203 
       
  1204             $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));
       
  1205         }
       
  1206 
       
  1207         return ' FROM ' . implode(', ', $sqlParts);
       
  1208     }
       
  1209 
       
  1210     /**
       
  1211      * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL.
       
  1212      *
       
  1213      * @param SimpleSelectClause
       
  1214      * @return string The SQL.
       
  1215      */
       
  1216     public function walkSimpleSelectClause($simpleSelectClause)
       
  1217     {
       
  1218         return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
       
  1219              . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
       
  1220     }
       
  1221 
       
  1222     /**
       
  1223      * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL.
       
  1224      *
       
  1225      * @param SimpleSelectExpression
       
  1226      * @return string The SQL.
       
  1227      */
       
  1228     public function walkSimpleSelectExpression($simpleSelectExpression)
       
  1229     {
       
  1230         $sql = '';
       
  1231         $expr = $simpleSelectExpression->expression;
       
  1232 
       
  1233         if ($expr instanceof AST\PathExpression) {
       
  1234             $sql .= $this->walkPathExpression($expr);
       
  1235         } else if ($expr instanceof AST\AggregateExpression) {
       
  1236             if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
       
  1237                 $alias = $this->_scalarResultCounter++;
       
  1238             } else {
       
  1239                 $alias = $simpleSelectExpression->fieldIdentificationVariable;
       
  1240             }
       
  1241 
       
  1242             $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
       
  1243         } else if ($expr instanceof AST\Subselect) {
       
  1244             if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
       
  1245                 $alias = $this->_scalarResultCounter++;
       
  1246             } else {
       
  1247                 $alias = $simpleSelectExpression->fieldIdentificationVariable;
       
  1248             }
       
  1249 
       
  1250             $columnAlias = 'sclr' . $this->_aliasCounter++;
       
  1251             $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
       
  1252             $this->_scalarResultAliasMap[$alias] = $columnAlias;
       
  1253         } else if ($expr instanceof AST\Functions\FunctionNode) {
       
  1254             if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
       
  1255                 $alias = $this->_scalarResultCounter++;
       
  1256             } else {
       
  1257                 $alias = $simpleSelectExpression->fieldIdentificationVariable;
       
  1258             }
       
  1259 
       
  1260             $columnAlias = 'sclr' . $this->_aliasCounter++;
       
  1261             $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
       
  1262             $this->_scalarResultAliasMap[$alias] = $columnAlias;
       
  1263         } else if (
       
  1264             $expr instanceof AST\SimpleArithmeticExpression ||
       
  1265             $expr instanceof AST\ArithmeticTerm ||
       
  1266             $expr instanceof AST\ArithmeticFactor ||
       
  1267             $expr instanceof AST\ArithmeticPrimary
       
  1268         ) {
       
  1269             if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
       
  1270                 $alias = $this->_scalarResultCounter++;
       
  1271             } else {
       
  1272                 $alias = $simpleSelectExpression->fieldIdentificationVariable;
       
  1273             }
       
  1274 
       
  1275             $columnAlias = 'sclr' . $this->_aliasCounter++;
       
  1276             $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
       
  1277             $this->_scalarResultAliasMap[$alias] = $columnAlias;
       
  1278         } else {
       
  1279             // IdentificationVariable
       
  1280             $class = $this->_queryComponents[$expr]['metadata'];
       
  1281             $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr);
       
  1282             $first = true;
       
  1283 
       
  1284             foreach ($class->identifier as $identifier) {
       
  1285                 if ($first) $first = false; else $sql .= ', ';
       
  1286                 $sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform);
       
  1287             }
       
  1288         }
       
  1289 
       
  1290         return ' ' . $sql;
       
  1291     }
       
  1292 
       
  1293     /**
       
  1294      * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL.
       
  1295      *
       
  1296      * @param AggregateExpression
       
  1297      * @return string The SQL.
       
  1298      */
       
  1299     public function walkAggregateExpression($aggExpression)
       
  1300     {
       
  1301         return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
       
  1302              . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
       
  1303     }
       
  1304 
       
  1305     /**
       
  1306      * Walks down a GroupByClause AST node, thereby generating the appropriate SQL.
       
  1307      *
       
  1308      * @param GroupByClause
       
  1309      * @return string The SQL.
       
  1310      */
       
  1311     public function walkGroupByClause($groupByClause)
       
  1312     {
       
  1313         $sql = '';
       
  1314         foreach ($groupByClause->groupByItems AS $groupByItem) {
       
  1315             if (is_string($groupByItem)) {
       
  1316                 foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) {
       
  1317                     if ($sql != '') {
       
  1318                         $sql .= ', ';
       
  1319                     }
       
  1320                     $groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField);
       
  1321                     $groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD;
       
  1322                     $sql .= $this->walkGroupByItem($groupByItem);
       
  1323                 }
       
  1324             } else {
       
  1325                 if ($sql != '') {
       
  1326                     $sql .= ', ';
       
  1327                 }
       
  1328                 $sql .= $this->walkGroupByItem($groupByItem);
       
  1329             }
       
  1330         }
       
  1331         return ' GROUP BY ' . $sql;
       
  1332     }
       
  1333 
       
  1334     /**
       
  1335      * Walks down a GroupByItem AST node, thereby generating the appropriate SQL.
       
  1336      *
       
  1337      * @param GroupByItem
       
  1338      * @return string The SQL.
       
  1339      */
       
  1340     public function walkGroupByItem(AST\PathExpression $pathExpr)
       
  1341     {
       
  1342         return $this->walkPathExpression($pathExpr);
       
  1343     }
       
  1344 
       
  1345     /**
       
  1346      * Walks down a DeleteClause AST node, thereby generating the appropriate SQL.
       
  1347      *
       
  1348      * @param DeleteClause
       
  1349      * @return string The SQL.
       
  1350      */
       
  1351     public function walkDeleteClause(AST\DeleteClause $deleteClause)
       
  1352     {
       
  1353         $sql = 'DELETE FROM ';
       
  1354         $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
       
  1355         $sql .= $class->getQuotedTableName($this->_platform);
       
  1356 
       
  1357         $this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $deleteClause->aliasIdentificationVariable);
       
  1358 
       
  1359         $this->_rootAliases[] = $deleteClause->aliasIdentificationVariable;
       
  1360 
       
  1361         return $sql;
       
  1362     }
       
  1363 
       
  1364     /**
       
  1365      * Walks down an UpdateClause AST node, thereby generating the appropriate SQL.
       
  1366      *
       
  1367      * @param UpdateClause
       
  1368      * @return string The SQL.
       
  1369      */
       
  1370     public function walkUpdateClause($updateClause)
       
  1371     {
       
  1372         $sql = 'UPDATE ';
       
  1373         $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName);
       
  1374         $sql .= $class->getQuotedTableName($this->_platform);
       
  1375 
       
  1376         $this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $updateClause->aliasIdentificationVariable);
       
  1377 
       
  1378         $this->_rootAliases[] = $updateClause->aliasIdentificationVariable;
       
  1379 
       
  1380         $sql .= ' SET ' . implode(
       
  1381             ', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems)
       
  1382         );
       
  1383 
       
  1384         return $sql;
       
  1385     }
       
  1386 
       
  1387     /**
       
  1388      * Walks down an UpdateItem AST node, thereby generating the appropriate SQL.
       
  1389      *
       
  1390      * @param UpdateItem
       
  1391      * @return string The SQL.
       
  1392      */
       
  1393     public function walkUpdateItem($updateItem)
       
  1394     {
       
  1395         $useTableAliasesBefore = $this->_useSqlTableAliases;
       
  1396         $this->_useSqlTableAliases = false;
       
  1397 
       
  1398         $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
       
  1399 
       
  1400         $newValue = $updateItem->newValue;
       
  1401 
       
  1402         if ($newValue === null) {
       
  1403             $sql .= 'NULL';
       
  1404         } else if ($newValue instanceof AST\Node) {
       
  1405             $sql .= $newValue->dispatch($this);
       
  1406         } else {
       
  1407             $sql .= $this->_conn->quote($newValue);
       
  1408         }
       
  1409 
       
  1410         $this->_useSqlTableAliases = $useTableAliasesBefore;
       
  1411 
       
  1412         return $sql;
       
  1413     }
       
  1414 
       
  1415     /**
       
  1416      * Walks down a WhereClause AST node, thereby generating the appropriate SQL.
       
  1417      *
       
  1418      * @param WhereClause
       
  1419      * @return string The SQL.
       
  1420      */
       
  1421     public function walkWhereClause($whereClause)
       
  1422     {
       
  1423         $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases);
       
  1424         $condSql = $this->walkConditionalExpression($whereClause->conditionalExpression);
       
  1425 
       
  1426         return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
       
  1427     }
       
  1428 
       
  1429     /**
       
  1430      * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL.
       
  1431      *
       
  1432      * @param ConditionalExpression
       
  1433      * @return string The SQL.
       
  1434      */
       
  1435     public function walkConditionalExpression($condExpr)
       
  1436     {
       
  1437         // Phase 2 AST optimization: Skip processment of ConditionalExpression
       
  1438         // if only one ConditionalTerm is defined
       
  1439         return ( ! ($condExpr instanceof AST\ConditionalExpression))
       
  1440             ? $this->walkConditionalTerm($condExpr)
       
  1441             : implode(
       
  1442                 ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)
       
  1443             );
       
  1444     }
       
  1445 
       
  1446     /**
       
  1447      * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL.
       
  1448      *
       
  1449      * @param ConditionalTerm
       
  1450      * @return string The SQL.
       
  1451      */
       
  1452     public function walkConditionalTerm($condTerm)
       
  1453     {
       
  1454         // Phase 2 AST optimization: Skip processment of ConditionalTerm
       
  1455         // if only one ConditionalFactor is defined
       
  1456         return ( ! ($condTerm instanceof AST\ConditionalTerm))
       
  1457             ? $this->walkConditionalFactor($condTerm)
       
  1458             : implode(
       
  1459                 ' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors)
       
  1460             );
       
  1461     }
       
  1462 
       
  1463     /**
       
  1464      * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL.
       
  1465      *
       
  1466      * @param ConditionalFactor
       
  1467      * @return string The SQL.
       
  1468      */
       
  1469     public function walkConditionalFactor($factor)
       
  1470     {
       
  1471         // Phase 2 AST optimization: Skip processment of ConditionalFactor
       
  1472         // if only one ConditionalPrimary is defined
       
  1473         return ( ! ($factor instanceof AST\ConditionalFactor))
       
  1474             ? $this->walkConditionalPrimary($factor)
       
  1475             : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
       
  1476     }
       
  1477 
       
  1478     /**
       
  1479      * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL.
       
  1480      *
       
  1481      * @param ConditionalPrimary
       
  1482      * @return string The SQL.
       
  1483      */
       
  1484     public function walkConditionalPrimary($primary)
       
  1485     {
       
  1486         if ($primary->isSimpleConditionalExpression()) {
       
  1487             return $primary->simpleConditionalExpression->dispatch($this);
       
  1488         } else if ($primary->isConditionalExpression()) {
       
  1489             $condExpr = $primary->conditionalExpression;
       
  1490 
       
  1491             return '(' . $this->walkConditionalExpression($condExpr) . ')';
       
  1492         }
       
  1493     }
       
  1494 
       
  1495     /**
       
  1496      * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL.
       
  1497      *
       
  1498      * @param ExistsExpression
       
  1499      * @return string The SQL.
       
  1500      */
       
  1501     public function walkExistsExpression($existsExpr)
       
  1502     {
       
  1503         $sql = ($existsExpr->not) ? 'NOT ' : '';
       
  1504 
       
  1505         $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
       
  1506 
       
  1507         return $sql;
       
  1508     }
       
  1509 
       
  1510     /**
       
  1511      * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL.
       
  1512      *
       
  1513      * @param CollectionMemberExpression
       
  1514      * @return string The SQL.
       
  1515      */
       
  1516     public function walkCollectionMemberExpression($collMemberExpr)
       
  1517     {
       
  1518         $sql = $collMemberExpr->not ? 'NOT ' : '';
       
  1519         $sql .= 'EXISTS (SELECT 1 FROM ';
       
  1520         $entityExpr = $collMemberExpr->entityExpression;
       
  1521         $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
       
  1522         
       
  1523         $fieldName = $collPathExpr->field;
       
  1524         $dqlAlias = $collPathExpr->identificationVariable;
       
  1525         
       
  1526         $class = $this->_queryComponents[$dqlAlias]['metadata'];
       
  1527         
       
  1528         if ($entityExpr instanceof AST\InputParameter) {
       
  1529             $dqlParamKey = $entityExpr->name;
       
  1530             $entity = $this->_query->getParameter($dqlParamKey);
       
  1531         } else {
       
  1532             //TODO
       
  1533             throw new \BadMethodCallException("Not implemented");
       
  1534         }
       
  1535         
       
  1536         $assoc = $class->associationMappings[$fieldName];
       
  1537         
       
  1538         if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
       
  1539             $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
       
  1540             $targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']);
       
  1541             $sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
       
  1542             
       
  1543             $sql .= $targetClass->getQuotedTableName($this->_platform)
       
  1544                   . ' ' . $targetTableAlias . ' WHERE ';
       
  1545                     
       
  1546             $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
       
  1547             
       
  1548             $first = true;
       
  1549             
       
  1550             foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
       
  1551                 if ($first) $first = false; else $sql .= ' AND ';
       
  1552                 
       
  1553                 $sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform) 
       
  1554                       . ' = ' 
       
  1555                       . $targetTableAlias . '.' . $sourceColumn;
       
  1556             }
       
  1557             
       
  1558             $sql .= ' AND ';
       
  1559             $first = true;
       
  1560             
       
  1561             foreach ($targetClass->identifier as $idField) {
       
  1562                 if ($first) $first = false; else $sql .= ' AND ';
       
  1563                 
       
  1564                 $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
       
  1565                 $sql .= $targetTableAlias . '.' 
       
  1566                       . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?';
       
  1567             }
       
  1568         } else { // many-to-many
       
  1569             $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
       
  1570             
       
  1571             $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
       
  1572             $joinTable = $owningAssoc['joinTable'];
       
  1573 
       
  1574             // SQL table aliases
       
  1575             $joinTableAlias = $this->getSQLTableAlias($joinTable['name']);
       
  1576             $targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']);
       
  1577             $sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
       
  1578             
       
  1579             // join to target table
       
  1580             $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform)
       
  1581                   . ' ' . $joinTableAlias . ' INNER JOIN '
       
  1582                   . $targetClass->getQuotedTableName($this->_platform)
       
  1583                   . ' ' . $targetTableAlias . ' ON ';
       
  1584             
       
  1585             // join conditions
       
  1586             $joinColumns = $assoc['isOwningSide']
       
  1587                 ? $joinTable['inverseJoinColumns']
       
  1588                 : $joinTable['joinColumns'];
       
  1589 
       
  1590             $first = true;
       
  1591             foreach ($joinColumns as $joinColumn) {
       
  1592                 if ($first) $first = false; else $sql .= ' AND ';
       
  1593 
       
  1594                 $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
       
  1595                         . $targetTableAlias . '.' . $targetClass->getQuotedColumnName(
       
  1596                                 $targetClass->fieldNames[$joinColumn['referencedColumnName']],
       
  1597                                 $this->_platform);
       
  1598             }
       
  1599 
       
  1600             $sql .= ' WHERE ';
       
  1601 
       
  1602             $joinColumns = $assoc['isOwningSide']
       
  1603                 ? $joinTable['joinColumns']
       
  1604                 : $joinTable['inverseJoinColumns'];
       
  1605 
       
  1606             $first = true;
       
  1607             foreach ($joinColumns as $joinColumn) {
       
  1608                 if ($first) $first = false; else $sql .= ' AND ';
       
  1609 
       
  1610                 $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' 
       
  1611                       . $sourceTableAlias . '.' . $class->getQuotedColumnName(
       
  1612                               $class->fieldNames[$joinColumn['referencedColumnName']],
       
  1613                               $this->_platform);
       
  1614             }
       
  1615             
       
  1616             $sql .= ' AND ';
       
  1617             $first = true;
       
  1618             
       
  1619             foreach ($targetClass->identifier as $idField) {
       
  1620                 if ($first) $first = false; else $sql .= ' AND ';
       
  1621                 
       
  1622                 $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
       
  1623                 $sql .= $targetTableAlias . '.' 
       
  1624                       . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?';
       
  1625             }
       
  1626         }
       
  1627 
       
  1628         return $sql . ')';
       
  1629     }
       
  1630     
       
  1631     /**
       
  1632      * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL.
       
  1633      *
       
  1634      * @param EmptyCollectionComparisonExpression
       
  1635      * @return string The SQL.
       
  1636      */
       
  1637     public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
       
  1638     {
       
  1639         $sizeFunc = new AST\Functions\SizeFunction('size');
       
  1640         $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
       
  1641 
       
  1642         return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
       
  1643     }
       
  1644 
       
  1645     /**
       
  1646      * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL.
       
  1647      *
       
  1648      * @param NullComparisonExpression
       
  1649      * @return string The SQL.
       
  1650      */
       
  1651     public function walkNullComparisonExpression($nullCompExpr)
       
  1652     {
       
  1653         $sql = '';
       
  1654         $innerExpr = $nullCompExpr->expression;
       
  1655 
       
  1656         if ($innerExpr instanceof AST\InputParameter) {
       
  1657             $dqlParamKey = $innerExpr->name;
       
  1658             $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
       
  1659             $sql .= ' ?';
       
  1660         } else {
       
  1661             $sql .= $this->walkPathExpression($innerExpr);
       
  1662         }
       
  1663 
       
  1664         $sql .= ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
       
  1665 
       
  1666         return $sql;
       
  1667     }
       
  1668 
       
  1669     /**
       
  1670      * Walks down an InExpression AST node, thereby generating the appropriate SQL.
       
  1671      *
       
  1672      * @param InExpression
       
  1673      * @return string The SQL.
       
  1674      */
       
  1675     public function walkInExpression($inExpr)
       
  1676     {
       
  1677         $sql = $this->walkPathExpression($inExpr->pathExpression)
       
  1678              . ($inExpr->not ? ' NOT' : '') . ' IN (';
       
  1679 
       
  1680         if ($inExpr->subselect) {
       
  1681             $sql .= $this->walkSubselect($inExpr->subselect);
       
  1682         } else {
       
  1683             $sql .= implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals));
       
  1684         }
       
  1685 
       
  1686         $sql .= ')';
       
  1687 
       
  1688         return $sql;
       
  1689     }
       
  1690 
       
  1691     /**
       
  1692      * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
       
  1693      *
       
  1694      * @param InstanceOfExpression
       
  1695      * @return string The SQL.
       
  1696      */
       
  1697     public function walkInstanceOfExpression($instanceOfExpr)
       
  1698     {
       
  1699         $sql = '';
       
  1700 
       
  1701         $dqlAlias = $instanceOfExpr->identificationVariable;
       
  1702         $discrClass = $class = $this->_queryComponents[$dqlAlias]['metadata'];
       
  1703         $fieldName = null;
       
  1704 
       
  1705         if ($class->discriminatorColumn) {
       
  1706             $discrClass = $this->_em->getClassMetadata($class->rootEntityName);
       
  1707         }
       
  1708 
       
  1709         if ($this->_useSqlTableAliases) {
       
  1710             $sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.';
       
  1711         }
       
  1712 
       
  1713         $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' <> ' : ' = ');
       
  1714 
       
  1715         if ($instanceOfExpr->value instanceof AST\InputParameter) {
       
  1716             // We need to modify the parameter value to be its correspondent mapped value
       
  1717             $dqlParamKey = $instanceOfExpr->value->name;
       
  1718             $paramValue  = $this->_query->getParameter($dqlParamKey);
       
  1719             
       
  1720             if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) {
       
  1721                 throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue));
       
  1722             }
       
  1723             
       
  1724             $entityClassName = $paramValue->name;
       
  1725         } else {
       
  1726             // Get name from ClassMetadata to resolve aliases.
       
  1727             $entityClassName = $this->_em->getClassMetadata($instanceOfExpr->value)->name;
       
  1728         }
       
  1729 
       
  1730         if ($entityClassName == $class->name) {
       
  1731             $sql .= $this->_conn->quote($class->discriminatorValue);
       
  1732         } else {
       
  1733             $discrMap = array_flip($class->discriminatorMap);
       
  1734             if (!isset($discrMap[$entityClassName])) {
       
  1735                 throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
       
  1736             }
       
  1737             
       
  1738             $sql .= $this->_conn->quote($discrMap[$entityClassName]);
       
  1739         }
       
  1740 
       
  1741         return $sql;
       
  1742     }
       
  1743 
       
  1744     /**
       
  1745      * Walks down an InParameter AST node, thereby generating the appropriate SQL.
       
  1746      *
       
  1747      * @param InParameter
       
  1748      * @return string The SQL.
       
  1749      */
       
  1750     public function walkInParameter($inParam)
       
  1751     {
       
  1752         return $inParam instanceof AST\InputParameter ?
       
  1753                 $this->walkInputParameter($inParam) :
       
  1754                 $this->walkLiteral($inParam);
       
  1755     }
       
  1756 
       
  1757     /**
       
  1758      * Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
       
  1759      *
       
  1760      * @param mixed
       
  1761      * @return string The SQL.
       
  1762      */
       
  1763     public function walkLiteral($literal)
       
  1764     {
       
  1765         switch ($literal->type) {
       
  1766             case AST\Literal::STRING:
       
  1767                 return $this->_conn->quote($literal->value);
       
  1768             case AST\Literal::BOOLEAN:
       
  1769                 $bool = strtolower($literal->value) == 'true' ? true : false;
       
  1770                 $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool);
       
  1771                 return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal;
       
  1772             case AST\Literal::NUMERIC:
       
  1773                 return $literal->value;
       
  1774             default:
       
  1775                 throw QueryException::invalidLiteral($literal);
       
  1776         }
       
  1777     }
       
  1778 
       
  1779     /**
       
  1780      * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL.
       
  1781      *
       
  1782      * @param BetweenExpression
       
  1783      * @return string The SQL.
       
  1784      */
       
  1785     public function walkBetweenExpression($betweenExpr)
       
  1786     {
       
  1787         $sql = $this->walkArithmeticExpression($betweenExpr->expression);
       
  1788 
       
  1789         if ($betweenExpr->not) $sql .= ' NOT';
       
  1790 
       
  1791         $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
       
  1792               . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
       
  1793 
       
  1794         return $sql;
       
  1795     }
       
  1796 
       
  1797     /**
       
  1798      * Walks down a LikeExpression AST node, thereby generating the appropriate SQL.
       
  1799      *
       
  1800      * @param LikeExpression
       
  1801      * @return string The SQL.
       
  1802      */
       
  1803     public function walkLikeExpression($likeExpr)
       
  1804     {
       
  1805         $stringExpr = $likeExpr->stringExpression;
       
  1806         $sql = $stringExpr->dispatch($this) . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
       
  1807 
       
  1808         if ($likeExpr->stringPattern instanceof AST\InputParameter) {
       
  1809             $inputParam = $likeExpr->stringPattern;
       
  1810             $dqlParamKey = $inputParam->name;
       
  1811             $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
       
  1812             $sql .= '?';
       
  1813         } else {
       
  1814             $sql .= $this->_conn->quote($likeExpr->stringPattern);
       
  1815         }
       
  1816 
       
  1817         if ($likeExpr->escapeChar) {
       
  1818             $sql .= ' ESCAPE ' . $this->_conn->quote($likeExpr->escapeChar);
       
  1819         }
       
  1820 
       
  1821         return $sql;
       
  1822     }
       
  1823 
       
  1824     /**
       
  1825      * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL.
       
  1826      *
       
  1827      * @param StateFieldPathExpression
       
  1828      * @return string The SQL.
       
  1829      */
       
  1830     public function walkStateFieldPathExpression($stateFieldPathExpression)
       
  1831     {
       
  1832         return $this->walkPathExpression($stateFieldPathExpression);
       
  1833     }
       
  1834 
       
  1835     /**
       
  1836      * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL.
       
  1837      *
       
  1838      * @param ComparisonExpression
       
  1839      * @return string The SQL.
       
  1840      */
       
  1841     public function walkComparisonExpression($compExpr)
       
  1842     {
       
  1843         $sql = '';
       
  1844         $leftExpr = $compExpr->leftExpression;
       
  1845         $rightExpr = $compExpr->rightExpression;
       
  1846 
       
  1847         if ($leftExpr instanceof AST\Node) {
       
  1848             $sql .= $leftExpr->dispatch($this);
       
  1849         } else {
       
  1850             $sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr);
       
  1851         }
       
  1852 
       
  1853         $sql .= ' ' . $compExpr->operator . ' ';
       
  1854 
       
  1855         if ($rightExpr instanceof AST\Node) {
       
  1856             $sql .= $rightExpr->dispatch($this);
       
  1857         } else {
       
  1858             $sql .= is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr);
       
  1859         }
       
  1860 
       
  1861         return $sql;
       
  1862     }
       
  1863 
       
  1864     /**
       
  1865      * Walks down an InputParameter AST node, thereby generating the appropriate SQL.
       
  1866      *
       
  1867      * @param InputParameter
       
  1868      * @return string The SQL.
       
  1869      */
       
  1870     public function walkInputParameter($inputParam)
       
  1871     {
       
  1872         $this->_parserResult->addParameterMapping($inputParam->name, $this->_sqlParamIndex++);
       
  1873 
       
  1874         return '?';
       
  1875     }
       
  1876 
       
  1877     /**
       
  1878      * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL.
       
  1879      *
       
  1880      * @param ArithmeticExpression
       
  1881      * @return string The SQL.
       
  1882      */
       
  1883     public function walkArithmeticExpression($arithmeticExpr)
       
  1884     {
       
  1885         return ($arithmeticExpr->isSimpleArithmeticExpression())
       
  1886         	? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
       
  1887         	: '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
       
  1888     }
       
  1889 
       
  1890     /**
       
  1891      * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL.
       
  1892      *
       
  1893      * @param SimpleArithmeticExpression
       
  1894      * @return string The SQL.
       
  1895      */
       
  1896     public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
       
  1897     {
       
  1898         return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression))
       
  1899             ? $this->walkArithmeticTerm($simpleArithmeticExpr)
       
  1900             : implode(
       
  1901                 ' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms)
       
  1902             );
       
  1903     }
       
  1904 
       
  1905     /**
       
  1906      * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL.
       
  1907      *
       
  1908      * @param mixed
       
  1909      * @return string The SQL.
       
  1910      */
       
  1911     public function walkArithmeticTerm($term)
       
  1912     {
       
  1913         if (is_string($term)) {
       
  1914             return $term;
       
  1915         }
       
  1916 
       
  1917         // Phase 2 AST optimization: Skip processment of ArithmeticTerm
       
  1918         // if only one ArithmeticFactor is defined
       
  1919         return ( ! ($term instanceof AST\ArithmeticTerm))
       
  1920             ? $this->walkArithmeticFactor($term)
       
  1921             : implode(
       
  1922                 ' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors)
       
  1923             );
       
  1924     }
       
  1925 
       
  1926     /**
       
  1927      * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL.
       
  1928      *
       
  1929      * @param mixed
       
  1930      * @return string The SQL.
       
  1931      */
       
  1932     public function walkArithmeticFactor($factor)
       
  1933     {
       
  1934         if (is_string($factor)) {
       
  1935             return $factor;
       
  1936         }
       
  1937         
       
  1938         // Phase 2 AST optimization: Skip processment of ArithmeticFactor
       
  1939         // if only one ArithmeticPrimary is defined
       
  1940         return ( ! ($factor instanceof AST\ArithmeticFactor))
       
  1941             ? $this->walkArithmeticPrimary($factor)
       
  1942             : ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '')) 
       
  1943                 . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
       
  1944     }
       
  1945 
       
  1946     /**
       
  1947      * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
       
  1948      *
       
  1949      * @param mixed
       
  1950      * @return string The SQL.
       
  1951      */
       
  1952     public function walkArithmeticPrimary($primary)
       
  1953     {
       
  1954         if ($primary instanceof AST\SimpleArithmeticExpression) {
       
  1955             return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
       
  1956         } else if ($primary instanceof AST\Node) {
       
  1957             return $primary->dispatch($this);
       
  1958         }
       
  1959 
       
  1960         // TODO: We need to deal with IdentificationVariable here
       
  1961         return '';
       
  1962     }
       
  1963 
       
  1964     /**
       
  1965      * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL.
       
  1966      *
       
  1967      * @param mixed
       
  1968      * @return string The SQL.
       
  1969      */
       
  1970     public function walkStringPrimary($stringPrimary)
       
  1971     {
       
  1972         return (is_string($stringPrimary))
       
  1973             ? $this->_conn->quote($stringPrimary)
       
  1974             : $stringPrimary->dispatch($this);
       
  1975     }
       
  1976 }