mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 06:37:55 +08:00 
			
		
		
		
	Fixes #17057: Fixed issues with table names that contain special characters or keywords in MSSQL
This commit is contained in:
		
				
					committed by
					
						
						Alexander Makarov
					
				
			
			
				
	
			
			
			
						parent
						
							fc29f5f843
						
					
				
				
					commit
					eb65dba796
				
			@ -10,6 +10,7 @@ Yii Framework 2 Change Log
 | 
				
			|||||||
- Enh #17396: Added 'invoked by controller' to the debug log message when `\yii\base\Action` is used (alexkart)
 | 
					- Enh #17396: Added 'invoked by controller' to the debug log message when `\yii\base\Action` is used (alexkart)
 | 
				
			||||||
- Bug #17325: Fixed "Cannot drop view" for MySQL while `migrate/fresh` (alexkart)
 | 
					- Bug #17325: Fixed "Cannot drop view" for MySQL while `migrate/fresh` (alexkart)
 | 
				
			||||||
- Bug #17384: Fixed SQL error when passing `DISTINCT ON` queries (brandonkelly)
 | 
					- Bug #17384: Fixed SQL error when passing `DISTINCT ON` queries (brandonkelly)
 | 
				
			||||||
 | 
					- Bug #17057: Fixed issues with table names that contain special characters or keywords in MSSQL (alexkart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2.0.21 June 18, 2019
 | 
					2.0.21 June 18, 2019
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@ use yii\base\NotSupportedException;
 | 
				
			|||||||
use yii\caching\Cache;
 | 
					use yii\caching\Cache;
 | 
				
			||||||
use yii\caching\CacheInterface;
 | 
					use yii\caching\CacheInterface;
 | 
				
			||||||
use yii\caching\TagDependency;
 | 
					use yii\caching\TagDependency;
 | 
				
			||||||
use yii\helpers\StringHelper;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Schema is the base class for concrete DBMS-specific schema classes.
 | 
					 * Schema is the base class for concrete DBMS-specific schema classes.
 | 
				
			||||||
@ -485,7 +484,7 @@ abstract class Schema extends BaseObject
 | 
				
			|||||||
        if (strpos($name, '.') === false) {
 | 
					        if (strpos($name, '.') === false) {
 | 
				
			||||||
            return $this->quoteSimpleTableName($name);
 | 
					            return $this->quoteSimpleTableName($name);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        $parts = explode('.', $name);
 | 
					        $parts = $this->getTableNameParts($name);
 | 
				
			||||||
        foreach ($parts as $i => $part) {
 | 
					        foreach ($parts as $i => $part) {
 | 
				
			||||||
            $parts[$i] = $this->quoteSimpleTableName($part);
 | 
					            $parts[$i] = $this->quoteSimpleTableName($part);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -493,6 +492,17 @@ abstract class Schema extends BaseObject
 | 
				
			|||||||
        return implode('.', $parts);
 | 
					        return implode('.', $parts);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Splits full table name into parts
 | 
				
			||||||
 | 
					     * @param string $name
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     * @since 2.0.22
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function getTableNameParts($name)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return explode('.', $name);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Quotes a column name for use in a query.
 | 
					     * Quotes a column name for use in a query.
 | 
				
			||||||
     * If the column name contains prefix, the prefix will also be properly quoted.
 | 
					     * If the column name contains prefix, the prefix will also be properly quoted.
 | 
				
			||||||
@ -661,7 +671,7 @@ abstract class Schema extends BaseObject
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
 | 
					        $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
 | 
				
			||||||
        $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
 | 
					        $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
 | 
				
			||||||
        return new $exceptionClass($message, $errorInfo, (int) $e->getCode(), $e);
 | 
					        return new $exceptionClass($message, $errorInfo, (int)$e->getCode(), $e);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -98,7 +98,7 @@ class Schema extends \yii\db\Schema implements ConstraintFinderInterface
 | 
				
			|||||||
    protected function resolveTableName($name)
 | 
					    protected function resolveTableName($name)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $resolvedName = new TableSchema();
 | 
					        $resolvedName = new TableSchema();
 | 
				
			||||||
        $parts = explode('.', str_replace(['[', ']'], '', $name));
 | 
					        $parts = $this->getTableNameParts($name);
 | 
				
			||||||
        $partCount = count($parts);
 | 
					        $partCount = count($parts);
 | 
				
			||||||
        if ($partCount === 4) {
 | 
					        if ($partCount === 4) {
 | 
				
			||||||
            // server name, catalog name, schema name and table name passed
 | 
					            // server name, catalog name, schema name and table name passed
 | 
				
			||||||
@ -126,6 +126,25 @@ class Schema extends \yii\db\Schema implements ConstraintFinderInterface
 | 
				
			|||||||
        return $resolvedName;
 | 
					        return $resolvedName;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * {@inheritDoc}
 | 
				
			||||||
 | 
					     * @param string $name
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     * @since 2.0.22
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function getTableNameParts($name)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $parts = [$name];
 | 
				
			||||||
 | 
					        preg_match_all('/([^.\[\]]+)|\[([^\[\]]+)\]/', $name, $matches);
 | 
				
			||||||
 | 
					        if (isset($matches[0]) && is_array($matches[0]) && !empty($matches[0])) {
 | 
				
			||||||
 | 
					            $parts = $matches[0];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $parts = str_replace(['[', ']'], '', $parts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $parts;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * {@inheritdoc}
 | 
					     * {@inheritdoc}
 | 
				
			||||||
     * @see https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-database-principals-transact-sql
 | 
					     * @see https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-database-principals-transact-sql
 | 
				
			||||||
@ -223,8 +242,8 @@ SQL;
 | 
				
			|||||||
        $result = [];
 | 
					        $result = [];
 | 
				
			||||||
        foreach ($indexes as $name => $index) {
 | 
					        foreach ($indexes as $name => $index) {
 | 
				
			||||||
            $result[] = new IndexConstraint([
 | 
					            $result[] = new IndexConstraint([
 | 
				
			||||||
                'isPrimary' => (bool) $index[0]['index_is_primary'],
 | 
					                'isPrimary' => (bool)$index[0]['index_is_primary'],
 | 
				
			||||||
                'isUnique' => (bool) $index[0]['index_is_unique'],
 | 
					                'isUnique' => (bool)$index[0]['index_is_unique'],
 | 
				
			||||||
                'name' => $name,
 | 
					                'name' => $name,
 | 
				
			||||||
                'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
 | 
					                'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
 | 
				
			||||||
            ]);
 | 
					            ]);
 | 
				
			||||||
@ -297,7 +316,7 @@ SQL;
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function resolveTableNames($table, $name)
 | 
					    protected function resolveTableNames($table, $name)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $parts = explode('.', str_replace(['[', ']'], '', $name));
 | 
					        $parts = $this->getTableNameParts($name);
 | 
				
			||||||
        $partCount = count($parts);
 | 
					        $partCount = count($parts);
 | 
				
			||||||
        if ($partCount === 4) {
 | 
					        if ($partCount === 4) {
 | 
				
			||||||
            // server name, catalog name, schema name and table name passed
 | 
					            // server name, catalog name, schema name and table name passed
 | 
				
			||||||
@ -349,9 +368,9 @@ SQL;
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            if (!empty($matches[2])) {
 | 
					            if (!empty($matches[2])) {
 | 
				
			||||||
                $values = explode(',', $matches[2]);
 | 
					                $values = explode(',', $matches[2]);
 | 
				
			||||||
                $column->size = $column->precision = (int) $values[0];
 | 
					                $column->size = $column->precision = (int)$values[0];
 | 
				
			||||||
                if (isset($values[1])) {
 | 
					                if (isset($values[1])) {
 | 
				
			||||||
                    $column->scale = (int) $values[1];
 | 
					                    $column->scale = (int)$values[1];
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
 | 
					                if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
 | 
				
			||||||
                    $column->type = 'boolean';
 | 
					                    $column->type = 'boolean';
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@ IF OBJECT_ID('[T_constraints_3]', 'U') IS NOT NULL DROP TABLE [T_constraints_3];
 | 
				
			|||||||
IF OBJECT_ID('[T_constraints_2]', 'U') IS NOT NULL DROP TABLE [T_constraints_2];
 | 
					IF OBJECT_ID('[T_constraints_2]', 'U') IS NOT NULL DROP TABLE [T_constraints_2];
 | 
				
			||||||
IF OBJECT_ID('[T_constraints_1]', 'U') IS NOT NULL DROP TABLE [T_constraints_1];
 | 
					IF OBJECT_ID('[T_constraints_1]', 'U') IS NOT NULL DROP TABLE [T_constraints_1];
 | 
				
			||||||
IF OBJECT_ID('[T_upsert]', 'U') IS NOT NULL DROP TABLE [T_upsert];
 | 
					IF OBJECT_ID('[T_upsert]', 'U') IS NOT NULL DROP TABLE [T_upsert];
 | 
				
			||||||
 | 
					IF OBJECT_ID('[table.with.special.characters]', 'U') IS NOT NULL DROP TABLE [table.with.special.characters];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE TABLE [dbo].[profile] (
 | 
					CREATE TABLE [dbo].[profile] (
 | 
				
			||||||
    [id] [int] IDENTITY NOT NULL,
 | 
					    [id] [int] IDENTITY NOT NULL,
 | 
				
			||||||
@ -89,7 +90,9 @@ CREATE TABLE [dbo].[order_item] (
 | 
				
			|||||||
        [item_id] ASC
 | 
					        [item_id] ASC
 | 
				
			||||||
    ) ON [PRIMARY]
 | 
					    ) ON [PRIMARY]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
);CREATE TABLE [dbo].[order_item_with_null_fk] (
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE [dbo].[order_item_with_null_fk] (
 | 
				
			||||||
    [order_id] [int],
 | 
					    [order_id] [int],
 | 
				
			||||||
    [item_id] [int],
 | 
					    [item_id] [int],
 | 
				
			||||||
    [quantity] [int] NOT NULL,
 | 
					    [quantity] [int] NOT NULL,
 | 
				
			||||||
@ -311,3 +314,7 @@ CREATE TABLE [T_upsert]
 | 
				
			|||||||
    [profile_id] INT NULL,
 | 
					    [profile_id] INT NULL,
 | 
				
			||||||
    UNIQUE ([email], [recovery_email])
 | 
					    UNIQUE ([email], [recovery_email])
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE [dbo].[table.with.special.characters] (
 | 
				
			||||||
 | 
					    [id] [int]
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
				
			|||||||
@ -83,4 +83,54 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @dataProvider quoteTableNameDataProvider
 | 
				
			||||||
 | 
					     * @param $name
 | 
				
			||||||
 | 
					     * @param $expectedName
 | 
				
			||||||
 | 
					     * @throws \yii\base\NotSupportedException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function testQuoteTableName($name, $expectedName)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $schema = $this->getConnection()->getSchema();
 | 
				
			||||||
 | 
					        $quotedName = $schema->quoteTableName($name);
 | 
				
			||||||
 | 
					        $this->assertEquals($expectedName, $quotedName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function quoteTableNameDataProvider()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            ['test', '[test]'],
 | 
				
			||||||
 | 
					            ['test.test', '[test].[test]'],
 | 
				
			||||||
 | 
					            ['test.test.test', '[test].[test].[test]'],
 | 
				
			||||||
 | 
					            ['[test]', '[test]'],
 | 
				
			||||||
 | 
					            ['[test].[test]', '[test].[test]'],
 | 
				
			||||||
 | 
					            ['test.[test.test]', '[test].[test.test]'],
 | 
				
			||||||
 | 
					            ['test.test.[test.test]', '[test].[test].[test.test]'],
 | 
				
			||||||
 | 
					            ['[test].[test.test]', '[test].[test.test]'],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @dataProvider getTableSchemaDataProvider
 | 
				
			||||||
 | 
					     * @param $name
 | 
				
			||||||
 | 
					     * @param $expectedName
 | 
				
			||||||
 | 
					     * @throws \yii\base\NotSupportedException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function testGetTableSchema($name, $expectedName)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $schema = $this->getConnection()->getSchema();
 | 
				
			||||||
 | 
					        $tableSchema = $schema->getTableSchema($name);
 | 
				
			||||||
 | 
					        $this->assertEquals($expectedName, $tableSchema->name);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getTableSchemaDataProvider()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            ['[dbo].[profile]', 'profile'],
 | 
				
			||||||
 | 
					            ['dbo.profile', 'profile'],
 | 
				
			||||||
 | 
					            ['profile', 'profile'],
 | 
				
			||||||
 | 
					            ['dbo.[table.with.special.characters]', 'table.with.special.characters'],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -82,4 +82,29 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return $result;
 | 
					        return $result;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @dataProvider quoteTableNameDataProvider
 | 
				
			||||||
 | 
					     * @param $name
 | 
				
			||||||
 | 
					     * @param $expectedName
 | 
				
			||||||
 | 
					     * @throws \yii\base\NotSupportedException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function testQuoteTableName($name, $expectedName)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $schema = $this->getConnection()->getSchema();
 | 
				
			||||||
 | 
					        $quotedName = $schema->quoteTableName($name);
 | 
				
			||||||
 | 
					        $this->assertEquals($expectedName, $quotedName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function quoteTableNameDataProvider()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            ['test', '`test`'],
 | 
				
			||||||
 | 
					            ['test.test', '`test`.`test`'],
 | 
				
			||||||
 | 
					            ['test.test.test', '`test`.`test`.`test`'],
 | 
				
			||||||
 | 
					            ['`test`', '`test`'],
 | 
				
			||||||
 | 
					            ['`test`.`test`', '`test`.`test`'],
 | 
				
			||||||
 | 
					            ['test.`test`.test', '`test`.`test`.`test`'],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user