/** * Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. * * @param JoinVariableDeclaration $joinVarDecl * @return string The SQL. */ public function walkJoinVariableDeclaration($joinVarDecl) { $join = $joinVarDecl->join; $joinType = $join->joinType; if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) { $sql = ' LEFT JOIN '; } else { $sql = ' INNER JOIN '; } $joinAssocPathExpr = $join->joinAssociationPathExpression; $joinedDqlAlias = $join->aliasIdentificationVariable; $targetQComp = $this->_queryComponents[$joinedDqlAlias]; $targetClass = $targetQComp['metadata']; $relation = $targetQComp['relation']; $sourceClass = $this->_queryComponents[$joinAssocPathExpr->identificationVariable]['metadata']; $targetTableName = $targetClass->getQuotedTableName($this->_platform); $targetTableAlias = $this->getSqlTableAlias($targetClass->getTableName(), $joinedDqlAlias); $sourceTableAlias = $this->getSqlTableAlias($sourceClass->getTableName(), $joinAssocPathExpr->identificationVariable); // Ensure we got the owning side, since it has all mapping info if (!$relation->isOwningSide) { $assoc = $targetClass->associationMappings[$relation->mappedBy]; } else { $assoc = $relation; } if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true) { if ($relation->isOneToMany() || $relation->isManyToMany()) { throw QueryException::iterateWithFetchJoinNotAllowed($assoc); } } if ($assoc->isOneToOne()) { $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; $first = true; foreach ($assoc->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) { if (!$first) { $sql .= ' AND '; } else { $first = false; } if ($relation->isOwningSide) { $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); $sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; } else { $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } } } else { if ($assoc->isManyToMany()) { // Join relation table $joinTable = $assoc->joinTable; $joinTableAlias = $this->getSqlTableAlias($joinTable['name'], $joinedDqlAlias); $sql .= $assoc->getQuotedJoinTableName($this->_platform) . ' ' . $joinTableAlias . ' ON '; if ($relation->isOwningSide) { foreach ($assoc->relationToSourceKeyColumns as $relationColumn => $sourceColumn) { $sql .= $sourceTableAlias . '.' . $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform) . ' = ' . $joinTableAlias . '.' . $relationColumn; } } else { foreach ($assoc->relationToTargetKeyColumns as $relationColumn => $targetColumn) { $sql .= $sourceTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform) . ' = ' . $joinTableAlias . '.' . $relationColumn; } } // Join target table $sql .= $joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN '; $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; if ($relation->isOwningSide) { foreach ($assoc->relationToTargetKeyColumns as $relationColumn => $targetColumn) { $sql .= $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform) . ' = ' . $joinTableAlias . '.' . $relationColumn; } } else { foreach ($assoc->relationToSourceKeyColumns as $relationColumn => $sourceColumn) { $sql .= $targetTableAlias . '.' . $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform) . ' = ' . $joinTableAlias . '.' . $relationColumn; } } } } // Handle WITH clause if ($join->conditionalExpression !== null) { $sql .= ' AND (' . implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $join->conditionalExpression->conditionalTerms)) . ')'; } $discrSql = $this->_generateDiscriminatorColumnConditionSql($joinedDqlAlias); if ($discrSql) { $sql .= ' AND ' . $discrSql; } if ($targetClass->isInheritanceTypeJoined()) { $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); } return $sql; }
/** * 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; }
/** * Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. * * @param JoinVariableDeclaration $joinVarDecl * @return string The SQL. */ public function walkJoinVariableDeclaration($joinVarDecl) { $join = $joinVarDecl->join; $joinType = $join->joinType; if ($joinVarDecl->indexBy) { // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. $this->_rsm->addIndexBy($joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, $joinVarDecl->indexBy->simpleStateFieldPathExpression->field); } if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) { $sql = ' LEFT JOIN '; } else { $sql = ' INNER JOIN '; } $joinAssocPathExpr = $join->joinAssociationPathExpression; $joinedDqlAlias = $join->aliasIdentificationVariable; $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->table['name'], $joinedDqlAlias); $sourceTableAlias = $this->getSqlTableAlias($sourceClass->table['name'], $joinAssocPathExpr->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) { if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) { throw QueryException::iterateWithFetchJoinNotAllowed($assoc); } } if ($assoc['type'] & ClassMetadata::TO_ONE) { $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; $first = true; foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) { if (!$first) { $sql .= ' AND '; } else { $first = false; } if ($relation['isOwningSide']) { $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); $sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; } else { $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } } } else { if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) { // Join relation table $joinTable = $assoc['joinTable']; $joinTableAlias = $this->getSqlTableAlias($joinTable['name'], $joinedDqlAlias); $sql .= $sourceClass->getQuotedJoinTableName($assoc, $this->_platform) . ' ' . $joinTableAlias . ' ON '; $first = true; if ($relation['isOwningSide']) { foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) { if (!$first) { $sql .= ' AND '; } else { $first = false; } $sql .= $sourceTableAlias . '.' . $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform) . ' = ' . $joinTableAlias . '.' . $relationColumn; } } else { foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) { if (!$first) { $sql .= ' AND '; } else { $first = false; } $sql .= $sourceTableAlias . '.' . $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform) . ' = ' . $joinTableAlias . '.' . $relationColumn; } } // Join target table $sql .= $joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN '; $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; $first = true; if ($relation['isOwningSide']) { foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) { if (!$first) { $sql .= ' AND '; } else { $first = false; } $sql .= $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform) . ' = ' . $joinTableAlias . '.' . $relationColumn; } } else { foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) { if (!$first) { $sql .= ' AND '; } else { $first = false; } $sql .= $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform) . ' = ' . $joinTableAlias . '.' . $relationColumn; } } } } // Handle WITH clause if (($condExpr = $join->conditionalExpression) !== null) { // Phase 2 AST optimization: Skip processment of ConditionalExpression // if only one ConditionalTerm is defined $sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')'; } $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias)); if ($discrSql) { $sql .= ' AND ' . $discrSql; } // FIXME: these should either be nested or all forced to be left joins (DDC-XXX) if ($targetClass->isInheritanceTypeJoined()) { $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); } return $sql; }