1
0
mirror of synced 2024-12-05 03:06:05 +03:00

Improved composite primary key support

This commit is contained in:
Asmir Mustafic 2015-01-18 23:20:47 +01:00 committed by Marco Pivetta
parent 2eb7dedf4f
commit 5e29bbd41f
2 changed files with 170 additions and 71 deletions

View File

@ -317,4 +317,12 @@ class ORMException extends Exception
{
return new self("It is not allowed to overwrite internal function '$functionName' in the DQL parser through user-defined functions.");
}
/**
* @return ORMException
*/
public static function cantUseInOperatorOnCompositeKeys()
{
return new self("Can't use IN operator on entities that have composite keys.");
}
}

View File

@ -335,7 +335,11 @@ class BasicEntityPersister implements EntityPersister
. ' FROM ' . $tableName
. ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?';
$flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, (array) $id);
if (!is_array($id)) {
$id = array($this->class->identifier[0] => $id);
}
$flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id);
$value = $this->conn->fetchColumn($sql, array_values($flatId));
@ -852,12 +856,12 @@ class BasicEntityPersister implements EntityPersister
list($params, $types) = $valueVisitor->getParamsAndTypes();
foreach ($params as $param) {
$sqlParams[] = PersisterHelper::getIdentifierValues($param, $this->em);
$sqlParams = array_merge($sqlParams, $this->getValues($param));
}
foreach ($types as $type) {
list ($field, $value) = $type;
$sqlTypes[] = $this->getType($field, $value, $this->class);
$sqlTypes = array_merge($sqlTypes, $this->getTypes($field, $value, $this->class));
}
return array($sqlParams, $sqlTypes);
@ -1565,40 +1569,61 @@ class BasicEntityPersister implements EntityPersister
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
$selectedColumns = array();
$columns = $this->getSelectConditionStatementColumnSQL($field, $assoc);
if (count($columns) > 1 && $comparison === Comparison::IN) {
/*
* @todo try to support multi-column IN expressions.
* Example: (col1, col2) IN (('val1A', 'val2A'), ('val1B', 'val2B'))
*/
throw ORMException::cantUseInOperatorOnCompositeKeys();
}
foreach ($columns as $column) {
$placeholder = '?';
$condition = $this->getSelectConditionStatementColumnSQL($field, $assoc);
if (isset($this->class->fieldMappings[$field]['requireSQLConversion'])) {
$placeholder = Type::getType($this->class->getTypeOfField($field))->convertToDatabaseValueSQL($placeholder, $this->platform);
}
if ($comparison !== null) {
if (null !== $comparison) {
// special case null value handling
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) {
return $condition . ' IS NULL';
} else if ($comparison === Comparison::NEQ && $value === null) {
return $condition . ' IS NOT NULL';
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && null ===$value) {
$selectedColumns[] = $column . ' IS NULL';
continue;
}
return $condition . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
if ($comparison === Comparison::NEQ && null === $value) {
$selectedColumns[] = $column . ' IS NOT NULL';
continue;
}
$selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
continue;
}
if (is_array($value)) {
$in = sprintf('%s IN (%s)' , $condition, $placeholder);
$in = sprintf('%s IN (%s)', $column, $placeholder);
if (false !== array_search(null, $value, true)) {
return sprintf('(%s OR %s IS NULL)' , $in, $condition);
$selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column);
continue;
}
return $in;
$selectedColumns[] = $in;
continue;
}
if ($value === null) {
return sprintf('%s IS NULL' , $condition);
if (null === $value) {
$selectedColumns[] = sprintf('%s IS NULL', $column);
continue;
}
return sprintf('%s = %s' , $condition, $placeholder);
$selectedColumns[] = sprintf('%s = %s', $column, $placeholder);
}
return implode(' AND ', $selectedColumns);
}
/**
@ -1607,7 +1632,7 @@ class BasicEntityPersister implements EntityPersister
* @param string $field
* @param array|null $assoc
*
* @return string
* @return array
*
* @throws \Doctrine\ORM\ORMException
*/
@ -1618,36 +1643,38 @@ class BasicEntityPersister implements EntityPersister
? $this->class->fieldMappings[$field]['inherited']
: $this->class->name;
return $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform);
return array($this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform));
}
if (isset($this->class->associationMappings[$field])) {
$association = $this->class->associationMappings[$field];
// Many-To-Many requires join table check for joinColumn
$columns = array();
if ($association['type'] === ClassMetadata::MANY_TO_MANY) {
if ( ! $association['isOwningSide']) {
$association = $assoc;
}
$joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform);
$joinColumn = $assoc['isOwningSide']
? $association['joinTable']['joinColumns'][0]
: $association['joinTable']['inverseJoinColumns'][0];
return $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
foreach ($association['joinTable']['joinColumns'] as $joinColumn) {
$columns[] = $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
}
} else {
if ( ! $association['isOwningSide']) {
throw ORMException::invalidFindByInverseAssociation($this->class->name, $field);
}
$joinColumn = $association['joinColumns'][0];
$className = (isset($association['inherited']))
? $association['inherited']
: $this->class->name;
return $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
foreach ($association['joinColumns'] as $joinColumn) {
$columns[] = $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
}
}
return $columns;
}
if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) {
@ -1655,12 +1682,13 @@ class BasicEntityPersister implements EntityPersister
// therefore checking for spaces and function calls which are not allowed.
// found a join column condition, not really a "field"
return $field;
return array($field);
}
throw ORMException::unrecognizedField($field);
}
/**
* Gets the conditional SQL fragment used in the WHERE clause when selecting
* entities in this persister.
@ -1778,8 +1806,8 @@ class BasicEntityPersister implements EntityPersister
continue; // skip null values.
}
$types[] = $this->getType($field, $value, $this->class);
$params[] = $this->getValue($value);
$types = array_merge($types, $this->getTypes($field, $value, $this->class));
$params = array_merge($params, $this->getValues($value));
}
return array($params, $types);
@ -1807,13 +1835,98 @@ class BasicEntityPersister implements EntityPersister
continue; // skip null values.
}
$types[] = $this->getType($criterion['field'], $criterion['value'], $criterion['class']);
$params[] = PersisterHelper::getIdentifierValues($criterion['value'], $this->em);
$types = array_merge($types, $this->getTypes($criterion['field'], $criterion['value'], $criterion['class']));
$params = array_merge($params, $this->getValues($criterion['value']));
}
return array($params, $types);
}
/**
* Infers field types to be used by parameter type casting.
*
* @param string $field
* @param mixed $value
*
* @return array
*
* @throws \Doctrine\ORM\Query\QueryException
*/
private function getTypes($field, $value, ClassMetadata $class)
{
$types = array();
switch (true) {
case (isset($this->class->fieldMappings[$field])):
$types[] = $this->class->fieldMappings[$field]['type'];
break;
case (isset($this->class->associationMappings[$field])):
$assoc = $this->class->associationMappings[$field];
$targetPersister = $this->em->getUnitOfWork()->getEntityPersister($assoc['targetEntity']);
if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) {
if(!$assoc['isOwningSide']){
$class = $this->em->getClassMetadata($assoc['targetEntity']);
$assoc = $class->associationMappings[$assoc['mappedBy']];
}
$parameters = $targetPersister->expandParameters($assoc['relationToSourceKeyColumns']);
} else {
$parameters = $targetPersister->expandParameters($assoc['isOwningSide']?$assoc['targetToSourceKeyColumns']:$assoc['sourceToTargetKeyColumns']);
}
$types = array_merge($types, $parameters[1]);
break;
default:
$types[] = null;
break;
}
if (is_array($value)) {
$types = array_map(function ($type) {
$type = Type::getType($type)->getBindingType();
return $type + Connection::ARRAY_PARAM_OFFSET;
}, $types);
}
return $types;
}
/**
* Retrieves the parameters that identifies a value.
*
* @param mixed $value
*
* @return array
*/
private function getValues($value)
{
if (is_array($value)) {
$newValue = array();
foreach ($value as $itemValue) {
$newValue = array_merge($newValue, $this->getValues($itemValue));
}
return array($newValue);
}
if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
$class = $this->em->getClassMetadata(get_class($value));
if ($class->isIdentifierComposite) {
$newValue = array();
foreach ($class->getIdentifierValues($value) as $innerValue) {
$newValue = array_merge($newValue, $this->getValues($innerValue));
}
return $newValue;
}
}
return array($this->getIndividualValue($value));
}
/**
* Infers field type to be used by parameter type casting.
*
@ -1837,28 +1950,6 @@ class BasicEntityPersister implements EntityPersister
return $type;
}
/**
* Retrieves parameter value.
*
* @param mixed $value
*
* @return mixed
*/
private function getValue($value)
{
if ( ! is_array($value)) {
return $this->getIndividualValue($value);
}
$newValue = array();
foreach ($value as $itemValue) {
$newValue[] = $this->getIndividualValue($itemValue);
}
return $newValue;
}
/**
* Retrieves an individual parameter value.
*