Fixes #15047: yii\db\Query::select() and yii\db\Query::addSelect() now check for duplicate column names

This commit is contained in:
wapmorgan
2018-02-02 10:59:31 +03:00
committed by Alexander Makarov
parent 8b74e29663
commit 5afe0a0d36
3 changed files with 81 additions and 1 deletions

View File

@ -4,6 +4,7 @@ Yii Framework 2 Change Log
2.0.14 under development 2.0.14 under development
------------------------ ------------------------
- Enh #15047: `yii\db\Query::select()` and `yii\db\Query::addSelect()` now check for duplicate column names (wapmorgan)
- Enh #14643: Added `yii\web\ErrorAction::$layout` property to conveniently set layout from error action config (swods, cebe, samdark) - Enh #14643: Added `yii\web\ErrorAction::$layout` property to conveniently set layout from error action config (swods, cebe, samdark)
- Enh #13465: Added `yii\helpers\FileHelper::findDirectory()` method (ArsSirek, developeruz) - Enh #13465: Added `yii\helpers\FileHelper::findDirectory()` method (ArsSirek, developeruz)
- Enh #8527: Added `yii\i18n\Locale` component having `getCurrencySymbol()` method (amarox, samdark) - Enh #8527: Added `yii\i18n\Locale` component having `getCurrencySymbol()` method (amarox, samdark)

View File

@ -594,7 +594,7 @@ PATTERN;
} elseif (!is_array($columns)) { } elseif (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
} }
$this->select = $columns; $this->select = $this->getUniqueColumns($columns);
$this->selectOption = $option; $this->selectOption = $option;
return $this; return $this;
} }
@ -621,6 +621,7 @@ PATTERN;
} elseif (!is_array($columns)) { } elseif (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
} }
$columns = $this->getUniqueColumns($columns);
if ($this->select === null) { if ($this->select === null) {
$this->select = $columns; $this->select = $columns;
} else { } else {
@ -630,6 +631,51 @@ PATTERN;
return $this; return $this;
} }
/**
* Returns unique column names excluding duplicates.
* Columns to be removed:
* - if column definition already present in SELECT part with same alias
* - if column definition without alias already present in SELECT part without alias too
* @param array $columns the columns to be merged to the select.
* @since 2.0.14
*/
protected function getUniqueColumns($columns)
{
$columns = array_unique($columns);
$unaliasedColumns = $this->getUnaliasedColumnsFromSelect();
foreach ($columns as $columnAlias => $columnDefinition) {
if ($columnDefinition instanceof Query) {
continue;
}
if (
(is_string($columnAlias) && isset($this->select[$columnAlias]) && $this->select[$columnAlias] === $columnDefinition)
|| (is_integer($columnAlias) && in_array($columnDefinition, $unaliasedColumns))
) {
unset($columns[$columnAlias]);
}
}
return $columns;
}
/**
* @return array List of columns without aliases from SELECT statement.
* @since 2.0.14
*/
protected function getUnaliasedColumnsFromSelect()
{
$result = [];
if (is_array($this->select)) {
foreach ($this->select as $name => $value) {
if (is_integer($name)) {
$result[] = $value;
}
}
}
return array_unique($result);
}
/** /**
* Sets the value indicating whether to SELECT DISTINCT or not. * Sets the value indicating whether to SELECT DISTINCT or not.
* @param bool $value whether to SELECT DISTINCT or not. * @param bool $value whether to SELECT DISTINCT or not.
@ -1180,4 +1226,13 @@ PATTERN;
'params' => $from->params, 'params' => $from->params,
]); ]);
} }
/**
* Returns the SQL representation of Query
* @return string
*/
public function __toString()
{
return serialize($this);
}
} }

View File

@ -37,6 +37,30 @@ abstract class QueryTest extends DatabaseTestCase
$query->select('id, name'); $query->select('id, name');
$query->addSelect('email'); $query->addSelect('email');
$this->assertEquals(['id', 'name', 'email'], $query->select); $this->assertEquals(['id', 'name', 'email'], $query->select);
$query = new Query();
$query->select('name, lastname');
$query->addSelect('name');
$this->assertEquals(['name', 'lastname'], $query->select);
$query = new Query();
$query->addSelect(['*', 'abc']);
$query->addSelect(['*', 'bca']);
$this->assertEquals(['*', 'abc', 'bca'], $query->select);
$query = new Query();
$query->addSelect(['field1 as a', 'field 1 as b']);
$this->assertEquals(['field1 as a', 'field 1 as b'], $query->select);
$query = new Query();
$query->select(['name' => 'firstname', 'lastname']);
$query->addSelect(['firstname', 'surname' => 'lastname']);
$query->addSelect(['firstname', 'lastname']);
$this->assertEquals(['name' => 'firstname', 'lastname', 'firstname', 'surname' => 'lastname'], $query->select);
$query = new Query();
$query->select('name, name, name as X, name as X');
$this->assertEquals(['name', 'name as X'], array_values($query->select));
} }
public function testFrom() public function testFrom()