From 23cc4bf4fe7328721dfed58824ac1b0dbc1949ab Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 10 May 2017 00:34:45 +0300 Subject: [PATCH] Further changes for unique and exist validators - Combined methods for getting names and aliases of from tables - Normalized names and aliases - Added MSSQL syntax - Added support for spaces in aliases and table names --- framework/db/ActiveQuery.php | 78 ++++++------------- framework/validators/ExistValidator.php | 4 +- framework/validators/UniqueValidator.php | 4 +- tests/framework/db/ActiveQueryTest.php | 68 +++++++++++----- .../validators/UniqueValidatorTest.php | 2 +- 5 files changed, 78 insertions(+), 78 deletions(-) diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index 634bdb9d3a..371eb24e07 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -791,77 +791,45 @@ class ActiveQuery extends Query implements ActiveQueryInterface } /** - * Table names based on [[from]] - * @return string[] table names + * Returns table names used in [[from]] indexed by aliases. + * @return string[] table names indexed by aliases * @throws \yii\base\InvalidConfigException * @since 2.0.12 */ - public function getTableNamesUsedInFrom() + public function getTablesUsedInFrom() { - $tableNames = []; - if (empty($this->from)) { - $tableNames[] = $this->getPrimaryTableName(); + $tableNames = [$this->getPrimaryTableName()]; } elseif (is_array($this->from)) { - $tableNames = array_values($this->from); + $tableNames = $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 getAliasesUsedInFrom() - { - $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.'); - } - + // Clean up table names and aliases + $cleanedUpTableNames = []; foreach ($tableNames as $alias => $tableName) { - if (is_string($alias)) { - $tablesAlias[] = $alias; - } else { - $tablesAlias[] = $this->getAliasFromTableName($tableName); + if (!is_string($alias)) { + if (preg_match('~^\s*([\'"`\[].*?[\'"`\]]|\w+)(?:(?:\s+(?:as)?\s*)([\'"`\[].*?[\'"`\]]|\w+))?\s*$~iu', $tableName, $matches)) { + if (isset($matches[1])) { + if (isset($matches[2])) { + list(, $tableName, $alias) = $matches; + } else { + $tableName = $alias = $matches[1]; + } + } + } } + + $tableName = str_replace(["'", '"', '`', '[', ']'], '', $tableName); + $alias = str_replace(["'", '"', '`', '[', ']'], '', $alias); + + $cleanedUpTableNames[$alias] = $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 $cleanedUpTableNames; } /** diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php index 6fb69748da..a286efbab7 100644 --- a/framework/validators/ExistValidator.php +++ b/framework/validators/ExistValidator.php @@ -156,11 +156,11 @@ class ExistValidator extends Validator /** @var ActiveRecord $targetClass */ $query = $targetClass::find(); - $tableAliases = $query->getAliasesUsedInFrom(); + $tableAliases = array_keys($query->getTablesUsedInFrom()); $primaryTableAlias = $tableAliases[0]; $prefixedConditions = []; foreach ($conditions as $columnName => $columnValue) { - $prefixedColumn = "{$primaryTableAlias}.{$columnName}"; + $prefixedColumn = "{{{$primaryTableAlias}}}.[[{$columnName}]]"; $prefixedConditions[$prefixedColumn] = $columnValue; } diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 4da5e615c7..a091772024 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -252,11 +252,11 @@ class UniqueValidator extends Validator /** @var ActiveRecord $targetClass */ $query = $targetClass::find(); - $tableAliases = $query->getAliasesUsedInFrom(); + $tableAliases = array_keys($query->getTablesUsedInFrom()); $primaryTableAlias = $tableAliases[0]; $prefixedConditions = []; foreach ($conditions as $columnName => $columnValue) { - $prefixedColumn = "{$primaryTableAlias}.{$columnName}"; + $prefixedColumn = "{{{$primaryTableAlias}}}.[[{$columnName}]]"; $prefixedConditions[$prefixedColumn] = $columnValue; } diff --git a/tests/framework/db/ActiveQueryTest.php b/tests/framework/db/ActiveQueryTest.php index 16f55416a6..777121be42 100644 --- a/tests/framework/db/ActiveQueryTest.php +++ b/tests/framework/db/ActiveQueryTest.php @@ -236,29 +236,45 @@ abstract class ActiveQueryTest extends DatabaseTestCase { $query = new ActiveQuery(Profile::className()); - $tableNames = $query->getTableNamesUsedInFrom(); + $tables = $query->getTablesUsedInFrom(); - $this->assertEquals([Profile::tableName()], $tableNames); + $this->assertEquals([ + Profile::tableName() => Profile::tableName(), + ], $tables); } public function testGetTableNames_isFromArray() { $query = new ActiveQuery(null); - $query->from = ['prf' => 'profile', 'usr' => 'user']; + $query->from = [ + 'prf' => 'profile', + 'usr' => 'user', + 'a b' => 'c d', + ]; - $tableNames = $query->getTableNamesUsedInFrom(); + $tables = $query->getTablesUsedInFrom(); - $this->assertEquals(['profile', 'user'], $tableNames); + $this->assertEquals([ + 'prf' => 'profile', + 'usr' => 'user', + 'a b' => 'c d', + ], $tables); } public function testGetTableNames_isFromString() { $query = new ActiveQuery(null); - $query->from = 'profile AS \'prf\', user "usr", `order`, "customer"'; + $query->from = 'profile AS \'prf\', user "usr", `order`, "customer", "a b" as "c d"'; - $tableNames = $query->getTableNamesUsedInFrom(); + $tables = $query->getTablesUsedInFrom(); - $this->assertEquals(['profile', 'user', '`order`', '"customer"'], $tableNames); + $this->assertEquals([ + 'prf' => 'profile', + 'usr' => 'user', + 'order' => 'order', + 'customer' => 'customer', + 'c d' => 'a b', + ], $tables); } public function testGetTableNames_isFromObject_generateException() @@ -268,36 +284,52 @@ abstract class ActiveQueryTest extends DatabaseTestCase $this->setExpectedException('\yii\base\InvalidConfigException'); - $query->getTableNamesUsedInFrom(); + $query->getTablesUsedInFrom(); } public function testGetTablesAlias_notFilledFrom() { $query = new ActiveQuery(Profile::className()); - $tablesAlias = $query->getAliasesUsedInFrom(); + $tables = $query->getTablesUsedInFrom(); - $this->assertEquals([Profile::tableName()], $tablesAlias); + $this->assertEquals([ + Profile::tableName() => Profile::tableName(), + ], $tables); } public function testGetTablesAlias_isFromArray() { $query = new ActiveQuery(null); - $query->from = ['prf' => 'profile', 'usr' => 'user']; + $query->from = [ + 'prf' => 'profile', + 'usr' => 'user', + 'a b' => 'c d', + ]; - $tablesAlias = $query->getAliasesUsedInFrom(); + $tables = $query->getTablesUsedInFrom(); - $this->assertEquals(['prf', 'usr'], $tablesAlias); + $this->assertEquals([ + 'prf' => 'profile', + 'usr' => 'user', + 'a b' => 'c d', + ], $tables); } public function testGetTablesAlias_isFromString() { $query = new ActiveQuery(null); - $query->from = 'profile AS \'prf\', user "usr", service srv, order'; + $query->from = 'profile AS \'prf\', user "usr", service srv, order, [a b] [c d]'; - $tablesAlias = $query->getAliasesUsedInFrom(); + $tables = $query->getTablesUsedInFrom(); - $this->assertEquals(['prf', 'usr', 'srv', 'order'], $tablesAlias); + $this->assertEquals([ + 'prf' => 'profile', + 'usr' => 'user', + 'srv' => 'service', + 'order' => 'order', + 'c d' => 'a b', + ], $tables); } public function testGetTablesAlias_isFromObject_generateException() @@ -307,6 +339,6 @@ abstract class ActiveQueryTest extends DatabaseTestCase $this->setExpectedException('\yii\base\InvalidConfigException'); - $query->getAliasesUsedInFrom(); + $query->getTablesUsedInFrom(); } } diff --git a/tests/framework/validators/UniqueValidatorTest.php b/tests/framework/validators/UniqueValidatorTest.php index afdb0c44b4..e567ad5588 100644 --- a/tests/framework/validators/UniqueValidatorTest.php +++ b/tests/framework/validators/UniqueValidatorTest.php @@ -337,7 +337,7 @@ abstract class UniqueValidatorTest extends DatabaseTestCase $attribute = 'id'; $targetAttribute = 'id'; $result = $this->invokeMethod(new UniqueValidator(), 'prepareConditions', [$targetAttribute, $model, $attribute]); - $expected = [Profile::tableName() . '.' . $attribute => $model->id]; + $expected = ['{{' . Profile::tableName() . '}}.[[' . $attribute . ']]' => $model->id]; $this->assertEquals($expected, $result); }