示例#1
1
 /**
  * Gather columns and fk constraints that are required for one part of relationship.
  *
  * @param array $joinColumns
  * @param \Doctrine\DBAL\Schema\Table $theJoinTable
  * @param ClassMetadata $class
  * @param array $mapping
  * @param array $primaryKeyColumns
  * @param array $uniqueConstraints
  */
 private function _gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$uniqueConstraints)
 {
     $localColumns = array();
     $foreignColumns = array();
     $fkOptions = array();
     $foreignTableName = $this->quoteStrategy->getTableName($class, $this->platform);
     foreach ($joinColumns as $joinColumn) {
         list($definingClass, $referencedFieldName) = $this->getDefiningClass($class, $joinColumn['referencedColumnName']);
         if (!$definingClass) {
             throw new \Doctrine\ORM\ORMException("Column name `" . $joinColumn['referencedColumnName'] . "` referenced for relation from " . $mapping['sourceEntity'] . " towards " . $mapping['targetEntity'] . " does not exist.");
         }
         $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
         $quotedRefColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $class, $this->platform);
         $primaryKeyColumns[] = $quotedColumnName;
         $localColumns[] = $quotedColumnName;
         $foreignColumns[] = $quotedRefColumnName;
         if (!$theJoinTable->hasColumn($quotedColumnName)) {
             // Only add the column to the table if it does not exist already.
             // It might exist already if the foreign key is mapped into a regular
             // property as well.
             $fieldMapping = $definingClass->getFieldMapping($referencedFieldName);
             $columnDef = null;
             if (isset($joinColumn['columnDefinition'])) {
                 $columnDef = $joinColumn['columnDefinition'];
             } else {
                 if (isset($fieldMapping['columnDefinition'])) {
                     $columnDef = $fieldMapping['columnDefinition'];
                 }
             }
             $columnOptions = array('notnull' => false, 'columnDefinition' => $columnDef);
             if (isset($joinColumn['nullable'])) {
                 $columnOptions['notnull'] = !$joinColumn['nullable'];
             }
             if (isset($fieldMapping['options'])) {
                 $columnOptions['options'] = $fieldMapping['options'];
             }
             if ($fieldMapping['type'] == "string" && isset($fieldMapping['length'])) {
                 $columnOptions['length'] = $fieldMapping['length'];
             } else {
                 if ($fieldMapping['type'] == "decimal") {
                     $columnOptions['scale'] = $fieldMapping['scale'];
                     $columnOptions['precision'] = $fieldMapping['precision'];
                 }
             }
             $theJoinTable->addColumn($quotedColumnName, $fieldMapping['type'], $columnOptions);
         }
         if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) {
             $uniqueConstraints[] = array('columns' => array($quotedColumnName));
         }
         if (isset($joinColumn['onDelete'])) {
             $fkOptions['onDelete'] = $joinColumn['onDelete'];
         }
     }
     $theJoinTable->addUnnamedForeignKeyConstraint($foreignTableName, $localColumns, $foreignColumns, $fkOptions);
 }
示例#2
0
 /**
  * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
  *
  * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
  * @param int $joinType
  *
  * @return string
  *
  * @throws QueryException
  */
 public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER)
 {
     $sql = '';
     $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
     $joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable;
     $indexBy = $joinAssociationDeclaration->indexBy;
     $relation = $this->queryComponents[$joinedDqlAlias]['relation'];
     $targetClass = $this->em->getClassMetadata($relation['targetEntity']);
     $sourceClass = $this->em->getClassMetadata($relation['sourceEntity']);
     $targetTableName = $targetClass->getQuotedTableName($this->platform);
     $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
     $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
     // Ensure we got the owning side, since it has all mapping info
     $assoc = !$relation['isOwningSide'] ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
     if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
         if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
             throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
         }
     }
     // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
     // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
     // The owning side is necessary at this point because only it contains the JoinColumn information.
     switch (true) {
         case $assoc['type'] & ClassMetadata::TO_ONE:
             $conditions = array();
             foreach ($assoc['joinColumns'] as $joinColumn) {
                 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
                 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
                 if ($relation['isOwningSide']) {
                     $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
                     continue;
                 }
                 $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
             }
             // Apply remaining inheritance restrictions
             $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
             if ($discrSql) {
                 $conditions[] = $discrSql;
             }
             // Apply the filters
             $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
             if ($filterExpr) {
                 $conditions[] = $filterExpr;
             }
             $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ' . implode(' AND ', $conditions);
             break;
         case $assoc['type'] == ClassMetadata::MANY_TO_MANY:
             // Join relation table
             $joinTable = $assoc['joinTable'];
             $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
             $joinTableName = $sourceClass->getQuotedJoinTableName($assoc, $this->platform);
             $conditions = array();
             $relationColumns = $relation['isOwningSide'] ? $assoc['joinTable']['joinColumns'] : $assoc['joinTable']['inverseJoinColumns'];
             foreach ($relationColumns as $joinColumn) {
                 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
                 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
                 $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
             }
             $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
             // Join target table
             $sql .= $joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN ';
             $conditions = array();
             $relationColumns = $relation['isOwningSide'] ? $assoc['joinTable']['inverseJoinColumns'] : $assoc['joinTable']['joinColumns'];
             foreach ($relationColumns as $joinColumn) {
                 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
                 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
                 $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
             }
             // Apply remaining inheritance restrictions
             $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
             if ($discrSql) {
                 $conditions[] = $discrSql;
             }
             // Apply the filters
             $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
             if ($filterExpr) {
                 $conditions[] = $filterExpr;
             }
             $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ' . implode(' AND ', $conditions);
             break;
     }
     // FIXME: these should either be nested or all forced to be left joins (DDC-XXX)
     if ($targetClass->isInheritanceTypeJoined()) {
         $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
     }
     // Apply the indexes
     if ($indexBy) {
         // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
         $this->rsm->addIndexBy($indexBy->simpleStateFieldPathExpression->identificationVariable, $indexBy->simpleStateFieldPathExpression->field);
     } else {
         if (isset($relation['indexBy'])) {
             $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
         }
     }
     return $sql;
 }
示例#3
0
 /**
  * Gathers columns and fk constraints that are required for one part of relationship.
  *
  * @param array         $joinColumns
  * @param Table         $theJoinTable
  * @param ClassMetadata $class
  * @param array         $mapping
  * @param array         $primaryKeyColumns
  * @param array         $addedFks
  * @param array         $blacklistedFks
  *
  * @return void
  *
  * @throws \Doctrine\ORM\ORMException
  */
 private function gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$addedFks, &$blacklistedFks)
 {
     $localColumns = array();
     $foreignColumns = array();
     $fkOptions = array();
     $foreignTableName = $this->quoteStrategy->getTableName($class, $this->platform);
     $uniqueConstraints = array();
     foreach ($joinColumns as $joinColumn) {
         list($definingClass, $referencedFieldName) = $this->getDefiningClass($class, $joinColumn['referencedColumnName']);
         if (!$definingClass) {
             throw new \Doctrine\ORM\ORMException("Column name `" . $joinColumn['referencedColumnName'] . "` referenced for relation from " . $mapping['sourceEntity'] . " towards " . $mapping['targetEntity'] . " does not exist.");
         }
         $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
         $quotedRefColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $class, $this->platform);
         $primaryKeyColumns[] = $quotedColumnName;
         $localColumns[] = $quotedColumnName;
         $foreignColumns[] = $quotedRefColumnName;
         if (!$theJoinTable->hasColumn($quotedColumnName)) {
             // Only add the column to the table if it does not exist already.
             // It might exist already if the foreign key is mapped into a regular
             // property as well.
             $fieldMapping = $definingClass->getFieldMapping($referencedFieldName);
             $columnDef = null;
             if (isset($joinColumn['columnDefinition'])) {
                 $columnDef = $joinColumn['columnDefinition'];
             } elseif (isset($fieldMapping['columnDefinition'])) {
                 $columnDef = $fieldMapping['columnDefinition'];
             }
             $columnOptions = array('notnull' => false, 'columnDefinition' => $columnDef);
             if (isset($joinColumn['nullable'])) {
                 $columnOptions['notnull'] = !$joinColumn['nullable'];
             }
             if (isset($fieldMapping['options'])) {
                 $columnOptions['options'] = $fieldMapping['options'];
             }
             if ($fieldMapping['type'] == "string" && isset($fieldMapping['length'])) {
                 $columnOptions['length'] = $fieldMapping['length'];
             } elseif ($fieldMapping['type'] == "decimal") {
                 $columnOptions['scale'] = $fieldMapping['scale'];
                 $columnOptions['precision'] = $fieldMapping['precision'];
             }
             $theJoinTable->addColumn($quotedColumnName, $fieldMapping['type'], $columnOptions);
         }
         if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) {
             $uniqueConstraints[] = array('columns' => array($quotedColumnName));
         }
         if (isset($joinColumn['onDelete'])) {
             $fkOptions['onDelete'] = $joinColumn['onDelete'];
         }
     }
     // Prefer unique constraints over implicit simple indexes created for foreign keys.
     // Also avoids index duplication.
     foreach ($uniqueConstraints as $indexName => $unique) {
         $theJoinTable->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName);
     }
     $compositeName = $theJoinTable->getName() . '.' . implode('', $localColumns);
     if (isset($addedFks[$compositeName]) && ($foreignTableName != $addedFks[$compositeName]['foreignTableName'] || 0 < count(array_diff($foreignColumns, $addedFks[$compositeName]['foreignColumns'])))) {
         foreach ($theJoinTable->getForeignKeys() as $fkName => $key) {
             if (0 === count(array_diff($key->getLocalColumns(), $localColumns)) && ($key->getForeignTableName() != $foreignTableName || 0 < count(array_diff($key->getForeignColumns(), $foreignColumns)))) {
                 $theJoinTable->removeForeignKey($fkName);
                 break;
             }
         }
         $blacklistedFks[$compositeName] = true;
     } elseif (!isset($blacklistedFks[$compositeName])) {
         $addedFks[$compositeName] = array('foreignTableName' => $foreignTableName, 'foreignColumns' => $foreignColumns);
         $theJoinTable->addUnnamedForeignKeyConstraint($foreignTableName, $localColumns, $foreignColumns, $fkOptions);
     }
 }
示例#4
0
    /**
     * Gets the SQL join fragment used when selecting entities from a
     * many-to-many association.
     *
     * @param array $manyToMany
     *
     * @return string
     */
    protected function getSelectManyToManyJoinSQL(array $manyToMany)
    {
        $conditions         = array();
        $association        = $manyToMany;
        $sourceTableAlias   = $this->getSQLTableAlias($this->class->name);

        if ( ! $manyToMany['isOwningSide']) {
            $targetEntity   = $this->em->getClassMetadata($manyToMany['targetEntity']);
            $association    = $targetEntity->associationMappings[$manyToMany['mappedBy']];
        }

        $joinTableName  = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform);
        $joinColumns    = ($manyToMany['isOwningSide'])
            ? $association['joinTable']['inverseJoinColumns']
            : $association['joinTable']['joinColumns'];

        foreach ($joinColumns as $joinColumn) {
            $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
            $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform);
            $conditions[]       = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableName . '.' . $quotedSourceColumn;
        }

        return ' INNER JOIN ' . $joinTableName . ' ON ' . implode(' AND ', $conditions);
    }
 /**
  * Gets the SQL fragment with the list of columns to select when querying for
  * an entity in this persister.
  *
  * Subclasses should override this method to alter or change the select column
  * list SQL fragment. Note that in the implementation of BasicEntityPersister
  * the resulting SQL fragment is generated only once and cached in {@link selectColumnListSql}.
  * Subclasses may or may not do the same.
  *
  * @return string The SQL fragment.
  */
 protected function getSelectColumnsSQL()
 {
     if ($this->currentPersisterContext->selectColumnListSql !== null) {
         return $this->currentPersisterContext->selectColumnListSql;
     }
     $columnList = array();
     $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r');
     // r for root
     // Add regular columns to select list
     foreach ($this->class->fieldNames as $field) {
         $columnList[] = $this->getSelectColumnSQL($field, $this->class);
     }
     $this->currentPersisterContext->selectJoinSql = '';
     $eagerAliasCounter = 0;
     foreach ($this->class->associationMappings as $assocField => $assoc) {
         $assocColumnSQL = $this->getSelectColumnAssociationSQL($assocField, $assoc, $this->class);
         if ($assocColumnSQL) {
             $columnList[] = $assocColumnSQL;
         }
         $isAssocToOneInverseSide = $assoc['type'] & ClassMetadata::TO_ONE && !$assoc['isOwningSide'];
         $isAssocFromOneEager = $assoc['type'] !== ClassMetadata::MANY_TO_MANY && $assoc['fetch'] === ClassMetadata::FETCH_EAGER;
         if (!($isAssocFromOneEager || $isAssocToOneInverseSide)) {
             continue;
         }
         if (($assoc['type'] & ClassMetadata::TO_MANY) > 0 && $this->currentPersisterContext->handlesLimits) {
             continue;
         }
         $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']);
         if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) {
             continue;
             // now this is why you shouldn't use inheritance
         }
         $assocAlias = 'e' . $eagerAliasCounter++;
         $this->currentPersisterContext->rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField);
         foreach ($eagerEntity->fieldNames as $field) {
             $columnList[] = $this->getSelectColumnSQL($field, $eagerEntity, $assocAlias);
         }
         foreach ($eagerEntity->associationMappings as $eagerAssocField => $eagerAssoc) {
             $eagerAssocColumnSQL = $this->getSelectColumnAssociationSQL($eagerAssocField, $eagerAssoc, $eagerEntity, $assocAlias);
             if ($eagerAssocColumnSQL) {
                 $columnList[] = $eagerAssocColumnSQL;
             }
         }
         $association = $assoc;
         $joinCondition = array();
         if (isset($assoc['indexBy'])) {
             $this->currentPersisterContext->rsm->addIndexBy($assocAlias, $assoc['indexBy']);
         }
         if (!$assoc['isOwningSide']) {
             $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']);
             $association = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
         }
         $joinTableAlias = $this->getSQLTableAlias($eagerEntity->name, $assocAlias);
         $joinTableName = $this->quoteStrategy->getTableName($eagerEntity, $this->platform);
         if ($assoc['isOwningSide']) {
             $tableAlias = $this->getSQLTableAlias($association['targetEntity'], $assocAlias);
             $this->currentPersisterContext->selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($association['joinColumns']);
             foreach ($association['joinColumns'] as $joinColumn) {
                 $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
                 $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform);
                 $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity']) . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol;
             }
             // Add filter SQL
             if ($filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias)) {
                 $joinCondition[] = $filterSql;
             }
         } else {
             $this->currentPersisterContext->selectJoinSql .= ' LEFT JOIN';
             foreach ($association['joinColumns'] as $joinColumn) {
                 $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
                 $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform);
                 $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' . $this->getSQLTableAlias($association['targetEntity']) . '.' . $targetCol;
             }
         }
         $this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON ';
         $this->currentPersisterContext->selectJoinSql .= implode(' AND ', $joinCondition);
     }
     $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
     return $this->currentPersisterContext->selectColumnListSql;
 }