diff --git a/src/Db/Adapter/PostgresAdapter.php b/src/Db/Adapter/PostgresAdapter.php index 02bcc6f0..99a26d73 100644 --- a/src/Db/Adapter/PostgresAdapter.php +++ b/src/Db/Adapter/PostgresAdapter.php @@ -882,26 +882,27 @@ protected function getForeignKeys(string $tableName): array { $parts = $this->getSchemaName($tableName); + /* // This should work but is blocked on a bug in cakephp/database // The field ordering after reflection is lost - /* $dialect = $this->getSchemaDialect(); [$query, $params] = $dialect->describeForeignKeySql($parts['table'], [ 'schema' => $parts['schema'], 'database' => $this->getOption('database'), ]); $rows = $this->query($query, $params)->fetchAll('assoc'); + $foreignKeys = []; foreach ($rows as $row) { $name = $row['name']; $foreignKeys[$name]['table'] = $parts['table']; $foreignKeys[$name]['columns'][] = $row['column_name']; $foreignKeys[$name]['referenced_table'] = $row['references_table']; - $foreignKeys[$name]['references_columns'][] = $row['references_field']; + $foreignKeys[$name]['referenced_columns'][] = $row['references_field']; } */ - $parts = $this->getSchemaName($tableName); $foreignKeys = []; + $parts = $this->getSchemaName($tableName); $params = [ $parts['schema'], $parts['table'], diff --git a/src/Db/Adapter/SqlserverAdapter.php b/src/Db/Adapter/SqlserverAdapter.php index 67e1893e..e28fbb9b 100644 --- a/src/Db/Adapter/SqlserverAdapter.php +++ b/src/Db/Adapter/SqlserverAdapter.php @@ -9,6 +9,7 @@ namespace Migrations\Db\Adapter; use BadMethodCallException; +use Cake\Database\Schema\SchemaDialect; use InvalidArgumentException; use Migrations\Db\AlterInstructions; use Migrations\Db\Literal; @@ -99,6 +100,18 @@ public function rollbackTransaction(): void $this->getConnection()->rollback(); } + /** + * Get the schema dialect for this adapter. + * + * @return \Cake\Database\Schema\SchemaDialect + */ + protected function getSchemaDialect(): SchemaDialect + { + $driver = $this->getConnection()->getDriver(); + + return $driver->schemaDialect(); + } + /** * Quotes a schema name for use in a query. * @@ -125,7 +138,9 @@ public function quoteTableName(string $tableName): string */ public function quoteColumnName(string $columnName): string { - return '[' . str_replace(']', '\]', $columnName) . ']'; + $driver = $this->getConnection()->getDriver(); + + return $driver->quoteIdentifier($columnName); } /** @@ -136,15 +151,15 @@ public function hasTable(string $tableName): bool if ($this->hasCreatedTable($tableName)) { return true; } + $dialect = $this->getSchemaDialect(); $parts = $this->getSchemaName($tableName); - /** @var array $result */ - $result = $this->query( - 'SELECT count(*) as [count] FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?', - [$parts['schema'], $parts['table']] - )->fetch('assoc'); + [$query, $params] = $dialect->listTablesSql(['schema' => $parts['schema']]); - return $result['count'] > 0; + $rows = $this->query($query, $params)->fetchAll(); + $tables = array_column($rows, 0); + + return in_array($parts['table'], $tables, true); } /** @@ -367,14 +382,22 @@ public function getColumnComment(string $tableName, ?string $columnName): ?strin */ public function getColumns(string $tableName): array { + // TODO we can't use cakephp/database for reflection here + // as we'd be missing some attributes. $parts = $this->getSchemaName($tableName); $columns = []; - $sql = "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type], + $sql = "SELECT + DISTINCT TABLE_SCHEMA AS [schema], + TABLE_NAME as [table_name], + COLUMN_NAME AS [name], + DATA_TYPE AS [type], IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default], CHARACTER_MAXIMUM_LENGTH AS [char_length], NUMERIC_PRECISION AS [precision], - NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position], - COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity] + NUMERIC_SCALE AS [scale], + ORDINAL_POSITION AS [ordinal_position], + COLUMNPROPERTY(object_id(TABLE_NAME), + COLUMN_NAME, 'IsIdentity') as [identity] FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ordinal_position"; @@ -653,19 +676,21 @@ protected function getIndexColumns(string $tableId, string $indexId): array public function getIndexes(string $tableName): array { $parts = $this->getSchemaName($tableName); + $dialect = $this->getSchemaDialect(); + [$query, $params] = $dialect->describeIndexSql($parts['table'], ['schema' => $parts['schema']]); + $rows = $this->query($query, $params)->fetchAll('assoc'); $indexes = []; - $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id] -FROM sys.[tables] AS T - INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] - INNER JOIN sys.[schemas] S ON s.schema_id = T.schema_id -WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND S.[name] = ? AND T.[name] = ? -ORDER BY T.[name], I.[index_id]"; - - $rows = $this->query($sql, [$parts['schema'], $parts['table']])->fetchAll('assoc'); foreach ($rows as $row) { - $columns = $this->getIndexColumns($row['table_id'], $row['index_id']); - $indexes[$row['index_name']] = ['columns' => $columns]; + $name = $row['index_name']; + if (!isset($indexes[$name])) { + $indexes[$name] = [ + 'columns' => [], + 'isPrimary' => false, + ]; + } + $indexes[$name]['columns'][] = $row['column_name']; + $indexes[$name]['isPrimary'] = $row['is_primary_key']; } return $indexes; @@ -808,27 +833,15 @@ public function hasPrimaryKey(string $tableName, $columns, ?string $constraint = */ public function getPrimaryKey(string $tableName): array { - $parts = $this->getSchemaName($tableName); - $rows = $this->query( - "SELECT - tc.CONSTRAINT_NAME, - kcu.COLUMN_NAME - FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc - JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu - ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME - WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' - AND tc.CONSTRAINT_SCHEMA = ? - AND tc.TABLE_NAME = ? - ORDER BY kcu.ORDINAL_POSITION", - [$parts['schema'], $parts['table']] - )->fetchAll('assoc'); - + $indexes = $this->getIndexes($tableName); $primaryKey = [ 'columns' => [], ]; - foreach ($rows as $row) { - $primaryKey['constraint'] = $row['CONSTRAINT_NAME']; - $primaryKey['columns'][] = $row['COLUMN_NAME']; + foreach ($indexes as $name => $row) { + if ($row['isPrimary']) { + $primaryKey['constraint'] = $name; + $primaryKey['columns'] = $row['columns']; + } } return $primaryKey; @@ -870,26 +883,26 @@ public function hasForeignKey(string $tableName, $columns, ?string $constraint = protected function getForeignKeys(string $tableName): array { $parts = $this->getSchemaName($tableName); + $dialect = $this->getSchemaDialect(); $foreignKeys = []; - $rows = $this->query( - "SELECT - tc.CONSTRAINT_NAME, - tc.TABLE_NAME, kcu.COLUMN_NAME, - ccu.TABLE_NAME AS REFERENCED_TABLE_NAME, - ccu.COLUMN_NAME AS REFERENCED_COLUMN_NAME - FROM - INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc - JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME - JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu ON ccu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME - WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' AND tc.TABLE_SCHEMA = ? AND tc.TABLE_NAME = ? - ORDER BY kcu.ORDINAL_POSITION", - [$parts['schema'], $parts['table']] - )->fetchAll('assoc'); + + [$query, $params] = $dialect->describeForeignKeySql($parts['table'], ['schema' => $parts['schema']]); + $rows = $this->query($query, $params)->fetchAll('assoc'); + foreach ($rows as $row) { - $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME']; - $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME']; - $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME']; - $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME']; + $name = $row['foreign_key_name']; + if (!isset($foreignKeys[$name])) { + $foreignKeys[$name] = [ + 'table' => '', + 'columns' => [], + 'referenced_table' => '', + 'referenced_columns' => [], + ]; + } + $foreignKeys[$name]['table'] = $parts['table']; + $foreignKeys[$name]['columns'][] = $row['column']; + $foreignKeys[$name]['referenced_table'] = $row['reference_table']; + $foreignKeys[$name]['referenced_columns'][] = $row['reference_column']; } foreach ($foreignKeys as $name => $key) { $foreignKeys[$name]['columns'] = array_values(array_unique($key['columns'])); diff --git a/tests/TestCase/Db/Adapter/SqlserverAdapterTest.php b/tests/TestCase/Db/Adapter/SqlserverAdapterTest.php index 0198132d..f0357ed1 100644 --- a/tests/TestCase/Db/Adapter/SqlserverAdapterTest.php +++ b/tests/TestCase/Db/Adapter/SqlserverAdapterTest.php @@ -44,7 +44,6 @@ protected function setUp(): void 'connection' => ConnectionManager::get('test'), 'database' => $config['database'], ]; - $this->adapter = new SqlserverAdapter($this->config, $this->getConsoleIo()); // ensure the database is empty for each test @@ -1136,7 +1135,8 @@ public function testDropAllSchemas() public function testQuoteSchemaName() { $this->assertEquals('[schema]', $this->adapter->quoteSchemaName('schema')); - $this->assertEquals('[schema.schema]', $this->adapter->quoteSchemaName('schema.schema')); + // Dotted schema names are not supported + $this->assertEquals('[schema].[schema]', $this->adapter->quoteSchemaName('schema.schema')); } public function testInvalidSqlType()