octicon-rss(16/)
You've already forked yii2
mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-10 02:13:17 +08:00
Fixes #17057: Fixed issues with table names that contain special characters or keywords in MSSQL
This commit is contained in:
octicon-git-branch(16/)
octicon-tag(16/)
committed by
Alexander Makarov
gitea-unlock(16/)
parent
fc29f5f843
commit
eb65dba796
octicon-diff(16/tw-mr-1) 6 changed files with 122 additions and 10 deletions
@@ -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