diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b00b525edf..49da794e7c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -5,6 +5,7 @@ Yii Framework 2 Change Log -------------------------- - Bug #13694: `yii\widgets\Pjax` now sends `X-Pjax-Url` header with response to fix redirect (wleona3, Faryshta) +- Bug #13842: Fixed ambiguous table SQL error while using unique validator (vladis84, samdark) - Bug #14012: `yii\db\pgsql\Schema::findViewNames()` was skipping materialized views (insolita) - Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) - Enh #13963: Added tests for yii\behaviors\TimestampBehavior (vladis84) diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index 7ec4900e77..aee0066236 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -148,10 +148,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface } if (empty($this->from)) { - /* @var $modelClass ActiveRecord */ - $modelClass = $this->modelClass; - $tableName = $modelClass::tableName(); - $this->from = [$tableName]; + $this->from = [$this->getPrimaryTableName()]; } if (empty($this->select) && !empty($this->join)) { @@ -559,9 +556,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface private function getTableNameAndAlias() { if (empty($this->from)) { - /* @var $modelClass ActiveRecord */ - $modelClass = $this->modelClass; - $tableName = $modelClass::tableName(); + $tableName = $this->getPrimaryTableName(); } else { $tableName = ''; foreach ($this->from as $alias => $tableName) { @@ -783,9 +778,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface list($tableName, ) = $this->getTableNameAndAlias(); $this->from = [$alias => $tableName]; } else { - /* @var $modelClass ActiveRecord */ - $modelClass = $this->modelClass; - $tableName = $modelClass::tableName(); + $tableName = $this->getPrimaryTableName(); foreach ($this->from as $key => $table) { if ($table === $tableName) { @@ -796,4 +789,89 @@ class ActiveQuery extends Query implements ActiveQueryInterface } return $this; } + + /** + * Table names based on from + * @return string[] table names + * @throws \yii\base\InvalidConfigException + * @since 2.0.12 + */ + public function getFromTableNames() + { + $tableNames = []; + + if (empty($this->from)) { + $tableNames[] = $this->getPrimaryTableName(); + } elseif (is_array($this->from)) { + $tableNames = array_values($this->from); + } elseif (is_string($this->from)) { + $tableNames = preg_split('/\s*,\s*/', trim($this->from), -1, PREG_SPLIT_NO_EMPTY); + } else { + throw new InvalidConfigException(gettype($this->from). ' in $from is not supported.'); + } + + // Clean up table names + foreach ($tableNames as &$tableName) { + $tableName = preg_replace('/^(\w+)\s*.*$/', '$1', $tableName); + } + + return $tableNames; + } + + /** + * @return string[] table aliases based on from + * @throws \yii\base\InvalidConfigException + * @since 2.0.12 + */ + public function getFromAliases() + { + $tablesAlias = []; + + if (empty($this->from)) { + return [$this->getPrimaryTableName()]; + } + + if (is_string($this->from)) { + $tableNames = preg_split('/\s*,\s*/', trim($this->from), -1, PREG_SPLIT_NO_EMPTY); + } elseif (is_array($this->from)) { + $tableNames = $this->from; + } else { + throw new InvalidConfigException(gettype($this->from). ' in $from is not supported.'); + } + + foreach ($tableNames as $alias => $tableName) { + if (is_string($alias)) { + $tablesAlias[] = $alias; + } else { + $tablesAlias[] = $this->getAliasFromTableName($tableName); + } + } + + return $tablesAlias; + } + + /** + * Getting alias from table name + * Input string may contain alias already specified + * + * @param string $tableName + * @return string + * @since 2.0.12 + */ + private function getAliasFromTableName($tableName) + { + $cleanedUpTableName = preg_replace('/\'|\"|`|as/u', '', trim($tableName)); + return preg_replace('/^.+\s+(\w+)$/', '$1', $cleanedUpTableName); + } + + /** + * @return string primary table name + * @since 2.0.12 + */ + protected function getPrimaryTableName() + { + /* @var $modelClass ActiveRecord */ + $modelClass = $this->modelClass; + return $modelClass::tableName(); + } } diff --git a/framework/helpers/ActiveQueryHelper.php b/framework/helpers/ActiveQueryHelper.php deleted file mode 100644 index f136c56ac0..0000000000 --- a/framework/helpers/ActiveQueryHelper.php +++ /dev/null @@ -1,107 +0,0 @@ -from; - - if (empty($from)) { - $tableNames[] = self::getTableNameForModel($query); - } elseif (is_array($from)) { - $tableNames = array_values($from); - } elseif (is_string($from)) { - $tableNames = preg_split('/\s*,\s*/', trim($from), -1, PREG_SPLIT_NO_EMPTY); - } else { - self::generateNotSupportedTypeFrom($from); - } - - // Clear table alias. - foreach ($tableNames as &$tableName) { - $tableName = preg_replace('/^(\w+)\s*.*$/', '$1', $tableName); - } - - return $tableNames; - } - - /** - * Tables alias calculate on from. - * @param ActiveQuery $query - * @return string[] table alias - */ - public static function getTablesAlias(ActiveQuery $query) - { - $tablesAlias = []; - $from = $query->from; - - if (empty($from)) { - return [self::getTableNameForModel($query)]; - } - - if (is_string($from)) { - $tableNames = preg_split('/\s*,\s*/', trim($from), -1, PREG_SPLIT_NO_EMPTY); - } elseif (is_array($from)) { - $tableNames = $from; - } else { - self::generateNotSupportedTypeFrom($from); - } - - foreach ($tableNames as $alias => $tableName) { - if (is_string($alias)) { - $tablesAlias[] = $alias; - } else { - $tablesAlias[] = self::getAliasForTableName($tableName); - } - } - - return $tablesAlias; - } - - /** - * @param string $tableName - * @return string - */ - private static function getAliasForTableName($tableName) - { - $cleanedTableName = preg_replace('/\'|\"|`|as/u', '', trim($tableName)); - - $alias = preg_replace('/^.+\s+(\w+)$/', '$1', $cleanedTableName); - - return $alias; - } - - /** - * Table name get Model. - * @param ActiveQuery $query - * @return type - */ - private static function getTableNameForModel(ActiveQuery $query) - { - /* @var $modelClass ActiveRecord */ - $modelClass = $query->modelClass; - - return $modelClass::tableName(); - } - - /** - * @param mixed $from - * @throws InvalidConfigException - */ - private static function generateNotSupportedTypeFrom($from) - { - $error = sprintf('Not supported type "$s"', gettype($from)); - - throw new InvalidConfigException($error); - } -} diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index d17b5bee29..d9f17b5d38 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -250,11 +250,14 @@ class UniqueValidator extends Validator // Add table prefix for column $targetClass = $this->getTargetClass($model); + + /** @var ActiveRecord $targetClass */ $query = $targetClass::find(); - $tableAlias = ActiveQueryHelper::getTablesAlias($query)[0]; + $tableAliases = $query->getFromAliases(); + $primaryTableAlias = $tableAliases[0]; $prefixedConditions = []; foreach ($conditions as $columnName => $columnValue) { - $prefixedColumn = "{$tableAlias}.{$columnName}"; + $prefixedColumn = "{$primaryTableAlias}.{$columnName}"; $prefixedConditions[$prefixedColumn] = $columnValue; } diff --git a/tests/framework/db/ActiveQueryTest.php b/tests/framework/db/ActiveQueryTest.php index 4ab1502c05..d0c113a68a 100644 --- a/tests/framework/db/ActiveQueryTest.php +++ b/tests/framework/db/ActiveQueryTest.php @@ -231,4 +231,82 @@ abstract class ActiveQueryTest extends DatabaseTestCase $this->assertInstanceOf('yii\db\ActiveQuery', $result); $this->assertEquals(['alias' => 'old'], $result->from); } + + public function testGetTableNames_notFilledFrom() + { + $query = new ActiveQuery(Profile::className()); + + $tableNames = $query->getFromTableNames(); + + $this->assertEquals([Profile::tableName()], $tableNames); + } + + public function testGetTableNames_isFromArray() + { + $query = new ActiveQuery(null); + $query->from = ['prf' => 'profile', 'usr' => 'user']; + + $tableNames = $query->getFromTableNames(); + + $this->assertEquals(['profile', 'user'], $tableNames); + } + + public function testGetTableNames_isFromString() + { + $query = new ActiveQuery(null); + $query->from = 'profile AS \'prf\', user "usr", `order`, "customer"'; + + $tableNames = $query->getFromTableNames(); + + $this->assertEquals(['profile', 'user', '`order`', '"customer"'], $tableNames); + } + + public function testGetTableNames_isFromObject_generateException() + { + $query = new ActiveQuery(null); + $query->from = new \stdClass; + + $this->setExpectedException('\yii\base\InvalidConfigException'); + + $query->getFromTableNames(); + } + + public function testGetTablesAlias_notFilledFrom() + { + $query = new ActiveQuery(Profile::className()); + + $tablesAlias = $query->getFromAliases(); + + $this->assertEquals([Profile::tableName()], $tablesAlias); + } + + public function testGetTablesAlias_isFromArray() + { + $query = new ActiveQuery(null); + $query->from = ['prf' => 'profile', 'usr' => 'user']; + + $tablesAlias = $query->getFromAliases(); + + $this->assertEquals(['prf', 'usr'], $tablesAlias); + } + + public function testGetTablesAlias_isFromString() + { + $query = new ActiveQuery(null); + $query->from = 'profile AS \'prf\', user "usr", service srv, order'; + + $tablesAlias = $query->getFromAliases(); + + $this->assertEquals(['prf', 'usr', 'srv', 'order'], $tablesAlias); + } + + public function testGetTablesAlias_isFromObject_generateException() + { + $query = new ActiveQuery(null); + $query->from = new \stdClass; + + $this->setExpectedException('\yii\base\InvalidConfigException'); + + $query->getFromAliases(); + } } diff --git a/tests/framework/helpers/ActiveQueryHelperTest.php b/tests/framework/helpers/ActiveQueryHelperTest.php deleted file mode 100644 index f78e592f99..0000000000 --- a/tests/framework/helpers/ActiveQueryHelperTest.php +++ /dev/null @@ -1,92 +0,0 @@ -assertEquals([Profile::tableName()], $tableNames); - } - - public function testGetTableNames_isFromArray() - { - $query = new ActiveQuery(null); - $query->from = ['prf' => 'profile', 'usr' => 'user']; - - $tableNames = ActiveQueryHelper::getTableNames($query); - - $this->assertEquals(['profile', 'user'], $tableNames); - } - - public function testGetTableNames_isFromString() - { - $query = new ActiveQuery(null); - $query->from = 'profile AS \'prf\', user "usr", `order`, "customer"'; - - $tableNames = ActiveQueryHelper::getTableNames($query); - - $this->assertEquals(['profile', 'user', '`order`', '"customer"'], $tableNames); - } - - public function testGetTableNames_isFromObject_generateException() - { - $query = new ActiveQuery(null); - $query->from = new \stdClass; - - $this->setExpectedException('\yii\base\InvalidConfigException'); - - ActiveQueryHelper::getTableNames($query); - } - - public function testGetTablesAlias_notFilledFrom() - { - $query = new ActiveQuery(Profile::className()); - - $tablesAlias = ActiveQueryHelper::getTablesAlias($query); - - $this->assertEquals([Profile::tableName()], $tablesAlias); - } - - public function testGetTablesAlias_isFromArray() - { - $query = new ActiveQuery(null); - $query->from = ['prf' => 'profile', 'usr' => 'user']; - - $tablesAlias = ActiveQueryHelper::getTablesAlias($query); - - $this->assertEquals(['prf', 'usr'], $tablesAlias); - } - - public function testGetTablesAlias_isFromString() - { - $query = new ActiveQuery(null); - $query->from = 'profile AS \'prf\', user "usr", service srv, order'; - - $tablesAlias = ActiveQueryHelper::getTablesAlias($query); - - $this->assertEquals(['prf', 'usr', 'srv', 'order'], $tablesAlias); - } - - public function testGetTablesAlias_isFromObject_generateException() - { - $query = new ActiveQuery(null); - $query->from = new \stdClass; - - $this->setExpectedException('\yii\base\InvalidConfigException'); - - ActiveQueryHelper::getTablesAlias($query); - } -}