mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 06:37:55 +08:00 
			
		
		
		
	Fixes #15047: yii\db\Query::select() and yii\db\Query::addSelect() now check for duplicate column names
				
					
				
			This commit is contained in:
		
				
					committed by
					
						
						Alexander Makarov
					
				
			
			
				
	
			
			
			
						parent
						
							8b74e29663
						
					
				
				
					commit
					5afe0a0d36
				
			@ -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)
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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()
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user