diff --git a/UPGRADE.md b/UPGRADE.md index a5f943b3fe9..0ee930b0bf5 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -168,6 +168,11 @@ all drivers and middleware. # Upgrade to 4.3 +## Deprecated `AbstractSchemaManager::_getPortableTableDefinition()` + +The `AbstractSchemaManager::_getPortableTableDefinition()` method has been deprecated. Use the schema name and the +unqualified table name separately instead. + ## Deprecated `PostgreSQLSchemaManager` methods related to the current schema The following `PostgreSQLSchemaManager` methods have been deprecated: diff --git a/docs/en/reference/types.rst b/docs/en/reference/types.rst index aa84193b885..f02ee836dcb 100644 --- a/docs/en/reference/types.rst +++ b/docs/en/reference/types.rst @@ -478,7 +478,6 @@ The following table shows an overview of Doctrine's type abstraction. The matrix contains the mapping information for how a specific Doctrine type is mapped to the database and back to PHP. Please also notice the mapping specific footnotes for additional information. -:: +-------------------+--------------------+-----------------------------------------------------------------------------------------------+ | Doctrine | PHP | Database vendor | @@ -602,10 +601,10 @@ Please also notice the mapping specific footnotes for additional information. | | +--------------------------+ | | | | | **PostgreSQL** | *all* | ``UUID`` | +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ - | **binary** | ``resource`` | **MySQL** | *all* | ``VARBINARY(n)`` [3] | + | **binary** | ``string`` | **MySQL** | *all* | ``VARBINARY(n)`` [3] | | [2] [6] | +--------------------------+ | | - | | | **SQL Server** | +----------------------------------------------------------+ - | | +--------------------------+ | ``BINARY(n)`` [4] | + | | | **SQL Server** | | ``BINARY(n)`` [4] | + | | +--------------------------+---------+----------------------------------------------------------+ | | | **Oracle** | *all* | ``RAW(n)`` | | | +--------------------------+---------+----------------------------------------------------------+ | | | **PostgreSQL** | *all* | ``BYTEA`` [15] | diff --git a/src/Driver/IBMDB2/Result.php b/src/Driver/IBMDB2/Result.php index b2cf62c8154..3acf6358e8c 100644 --- a/src/Driver/IBMDB2/Result.php +++ b/src/Driver/IBMDB2/Result.php @@ -11,6 +11,7 @@ use function db2_fetch_array; use function db2_fetch_assoc; +use function db2_field_name; use function db2_free_result; use function db2_num_fields; use function db2_num_rows; diff --git a/src/Platforms/SQLServerPlatform.php b/src/Platforms/SQLServerPlatform.php index 80409bcffb3..8a8a23e0050 100644 --- a/src/Platforms/SQLServerPlatform.php +++ b/src/Platforms/SQLServerPlatform.php @@ -1191,11 +1191,17 @@ protected function getLikeWildcardCharacters(): string protected function getCommentOnTableSQL(string $tableName, string $comment): string { + if (str_contains($tableName, '.')) { + [$schemaName, $tableName] = explode('.', $tableName); + } else { + $schemaName = 'dbo'; + } + return $this->getAddExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', - $this->quoteStringLiteral('dbo'), + $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), ); diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index a890762ce68..03e7ebdd189 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -837,7 +837,11 @@ protected function _getPortableTableIndexesList(array $rows): array return $indexes; } - /** @param array $table */ + /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * + * @param array $table + */ abstract protected function _getPortableTableDefinition(array $table): string; /** @param array $view */ diff --git a/src/Schema/DB2SchemaManager.php b/src/Schema/DB2SchemaManager.php index 0a2dba95f0a..bdc261ba511 100644 --- a/src/Schema/DB2SchemaManager.php +++ b/src/Schema/DB2SchemaManager.php @@ -12,6 +12,7 @@ use function array_change_key_case; use function implode; use function preg_match; +use function sprintf; use function str_replace; use function strpos; use function strtolower; @@ -23,6 +24,8 @@ /** * IBM Db2 Schema Manager. * + * @link https://www.ibm.com/docs/en/db2/11.5?topic=sql-catalog-views + * * @extends AbstractSchemaManager */ class DB2SchemaManager extends AbstractSchemaManager @@ -101,6 +104,8 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string @@ -178,10 +183,10 @@ protected function normalizeName(string $name): string protected function selectTableNames(string $databaseName): Result { $sql = <<<'SQL' -SELECT NAME -FROM SYSIBM.SYSTABLES +SELECT TABNAME AS NAME +FROM SYSCAT.TABLES WHERE TYPE = 'T' - AND CREATOR = ? + AND TABSCHEMA = ? SQL; return $this->connection->executeQuery($sql, [$databaseName]); @@ -189,13 +194,18 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['C.TABSCHEMA = ?']; + $params = [$databaseName]; - if ($tableName === null) { - $sql .= ' C.TABNAME AS NAME,'; + if ($tableName !== null) { + $conditions[] = 'C.TABNAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' +SELECT + C.TABNAME AS NAME, C.COLNAME, C.TYPENAME, C.CODEPAGE, @@ -212,30 +222,30 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = JOIN SYSCAT.TABLES AS T ON T.TABSCHEMA = C.TABSCHEMA AND T.TABNAME = C.TABNAME -SQL; - - $conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"]; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'C.TABNAME = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO'; + WHERE %s + AND T.TYPE = 'T' +ORDER BY C.TABNAME, C.COLNO +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['IDX.TABSCHEMA = ?']; + $params = [$databaseName]; - if ($tableName === null) { - $sql .= ' IDX.TABNAME AS NAME,'; + if ($tableName !== null) { + $conditions[] = 'IDX.TABNAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + IDX.TABNAME AS NAME, IDX.INDNAME AS KEY_NAME, IDXCOL.COLNAME AS COLUMN_NAME, CASE @@ -251,30 +261,32 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = ON IDX.TABSCHEMA = T.TABSCHEMA AND IDX.TABNAME = T.TABNAME JOIN SYSCAT.INDEXCOLUSE AS IDXCOL ON IDX.INDSCHEMA = IDXCOL.INDSCHEMA AND IDX.INDNAME = IDXCOL.INDNAME -SQL; - - $conditions = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"]; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'IDX.TABNAME = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ'; + WHERE %s + AND T.TYPE = 'T' + ORDER BY IDX.TABNAME, + IDX.INDNAME, + IDXCOL.COLSEQ +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['R.TABSCHEMA = ?']; + $params = [$databaseName]; - if ($tableName === null) { - $sql .= ' R.TABNAME AS NAME,'; + if ($tableName !== null) { + $conditions[] = 'R.TABNAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + R.TABNAME AS NAME, FKCOL.COLNAME AS LOCAL_COLUMN, R.REFTABNAME AS FOREIGN_TABLE, PKCOL.COLNAME AS FOREIGN_COLUMN, @@ -300,17 +312,14 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN AND PKCOL.TABSCHEMA = R.REFTABSCHEMA AND PKCOL.TABNAME = R.REFTABNAME AND PKCOL.COLSEQ = FKCOL.COLSEQ -SQL; - - $conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"]; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'R.TABNAME = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ'; + WHERE %s + AND T.TYPE = 'T' + ORDER BY R.TABNAME, + R.CONSTNAME, + FKCOL.COLSEQ +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } @@ -320,31 +329,29 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = 'SELECT NAME, REMARKS'; - - $conditions = []; - $params = []; + $conditions = ['TABSCHEMA = ?']; + $params = [$databaseName]; if ($tableName !== null) { - $conditions[] = 'NAME = ?'; + $conditions[] = 'TABNAME = ?'; $params[] = $tableName; } - $sql .= ' FROM SYSIBM.SYSTABLES'; - - if ($conditions !== []) { - $sql .= ' WHERE ' . implode(' AND ', $conditions); - } - - /** @var array> $metadata */ - $metadata = $this->connection->executeQuery($sql, $params) - ->fetchAllAssociativeIndexed(); + $sql = sprintf( + <<<'SQL' + SELECT TABNAME, + REMARKS + FROM SYSCAT.TABLES + WHERE %s + AND TYPE = 'T' + ORDER BY TABNAME +SQL, + implode(' AND ', $conditions), + ); $tableOptions = []; - foreach ($metadata as $table => $data) { - $data = array_change_key_case($data, CASE_LOWER); - - $tableOptions[$table] = ['comment' => $data['remarks']]; + foreach ($this->connection->iterateKeyValue($sql, $params) as $table => $remarks) { + $tableOptions[$table] = ['comment' => $remarks]; } return $tableOptions; diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php index 9ee8d94b41d..b0f5628f365 100644 --- a/src/Schema/MySQLSchemaManager.php +++ b/src/Schema/MySQLSchemaManager.php @@ -26,6 +26,7 @@ use function is_string; use function preg_match; use function preg_match_all; +use function sprintf; use function str_contains; use function strtok; use function strtolower; @@ -61,6 +62,8 @@ class MySQLSchemaManager extends AbstractSchemaManager private ?DefaultTableOptions $defaultTableOptions = null; /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string @@ -342,17 +345,23 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $columnTypeSQL = $this->platform->getColumnTypeSQLSnippet('c', $databaseName); - - $sql = 'SELECT'; + // The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition + // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions + // caused by https://bugs.mysql.com/bug.php?id=81347 + $conditions = ['c.TABLE_SCHEMA = ?', 't.TABLE_SCHEMA = ?']; + $params = [$databaseName, $databaseName]; - if ($tableName === null) { - $sql .= ' c.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 't.TABLE_NAME = ?'; + $params[] = $tableName; } - $sql .= <<platform->getColumnTypeSQLSnippet('c', $databaseName), + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['TABLE_SCHEMA = ?']; + $params = [$databaseName]; - if ($tableName === null) { - $sql .= ' TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'TABLE_NAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' +SELECT + TABLE_NAME, NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name, SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type FROM information_schema.STATISTICS -SQL; - - $conditions = ['TABLE_SCHEMA = ?']; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'TABLE_NAME = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX'; +WHERE %s +ORDER BY TABLE_NAME, + SEQ_IN_INDEX +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT DISTINCT'; + // The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition + // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions + // caused by https://bugs.mysql.com/bug.php?id=81347 + $conditions = ['k.TABLE_SCHEMA = ?', 'c.CONSTRAINT_SCHEMA = ?']; + $params = [$databaseName, $databaseName]; - if ($tableName === null) { - $sql .= ' k.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'k.TABLE_NAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' +SELECT + k.TABLE_NAME, k.CONSTRAINT_NAME, k.COLUMN_NAME, k.REFERENCED_TABLE_NAME, @@ -431,25 +442,14 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN INNER JOIN information_schema.referential_constraints c ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.TABLE_NAME = k.TABLE_NAME -SQL; - - $conditions = ['k.TABLE_SCHEMA = ?']; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'k.TABLE_NAME = ?'; - $params[] = $tableName; - } - - // The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition - // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions - // caused by https://bugs.mysql.com/bug.php?id=81347 - $conditions[] = 'c.CONSTRAINT_SCHEMA = ?'; - $params[] = $databaseName; - - $conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL'; - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY k.CONSTRAINT_NAME, k.ORDINAL_POSITION'; +WHERE %s +AND k.REFERENCED_COLUMN_NAME IS NOT NULL +ORDER BY k.TABLE_NAME, + k.CONSTRAINT_NAME, + k.ORDINAL_POSITION +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 3d4ef111ea8..edf0b6fd7ea 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -16,6 +16,7 @@ use function implode; use function is_string; use function preg_match; +use function sprintf; use function str_contains; use function str_replace; use function str_starts_with; @@ -43,6 +44,8 @@ protected function _getPortableViewDefinition(array $view): View } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string @@ -312,13 +315,18 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['C.OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; - if ($tableName === null) { - $sql .= ' C.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'C.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + C.TABLE_NAME, C.COLUMN_NAME, C.DATA_TYPE, C.DATA_DEFAULT, @@ -336,30 +344,29 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND D.COLUMN_NAME = C.COLUMN_NAME -SQL; - - $conditions = ['C.OWNER = :OWNER']; - $params = ['OWNER' => $databaseName]; - - if ($tableName !== null) { - $conditions[] = 'C.TABLE_NAME = :TABLE_NAME'; - $params['TABLE_NAME'] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID'; + WHERE %s + ORDER BY C.TABLE_NAME, C.COLUMN_ID +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['IND_COL.INDEX_OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; - if ($tableName === null) { - $sql .= ' IND_COL.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + IND_COL.TABLE_NAME, IND_COL.INDEX_NAME AS NAME, IND.INDEX_TYPE AS TYPE, DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE, @@ -373,31 +380,31 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME -SQL; - - $conditions = ['IND_COL.INDEX_OWNER = :OWNER']; - $params = ['OWNER' => $databaseName]; - - if ($tableName !== null) { - $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME'; - $params['TABLE_NAME'] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME' - . ', IND_COL.COLUMN_POSITION'; + WHERE %s + ORDER BY IND_COL.TABLE_NAME, + IND_COL.INDEX_NAME, + IND_COL.COLUMN_POSITION +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; - if ($tableName === null) { - $sql .= ' COLS.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + COLS.TABLE_NAME, ALC.CONSTRAINT_NAME, ALC.DELETE_RULE, ALC.DEFERRABLE, @@ -410,18 +417,13 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND R_COLS.POSITION = COLS.POSITION -SQL; - - $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER']; - $params = ['OWNER' => $databaseName]; - - if ($tableName !== null) { - $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; - $params['TABLE_NAME'] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME' - . ', COLS.POSITION'; + WHERE %s + ORDER BY COLS.TABLE_NAME, + COLS.CONSTRAINT_NAME, + COLS.POSITION +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } @@ -431,8 +433,6 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = 'SELECT TABLE_NAME, COMMENTS'; - $conditions = ['OWNER = :OWNER']; $params = ['OWNER' => $databaseName]; @@ -441,19 +441,20 @@ protected function fetchTableOptionsByTable(string $databaseName, ?string $table $params['TABLE_NAME'] = $tableName; } - $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions); - - /** @var array> $metadata */ - $metadata = $this->connection->executeQuery($sql, $params) - ->fetchAllAssociativeIndexed(); + $sql = sprintf( + <<<'SQL' + SELECT TABLE_NAME, + COMMENTS + FROM ALL_TAB_COMMENTS + WHERE %s + ORDER BY TABLE_NAME +SQL, + implode(' AND ', $conditions), + ); $tableOptions = []; - foreach ($metadata as $table => $data) { - $data = array_change_key_case($data, CASE_LOWER); - - $tableOptions[$table] = [ - 'comment' => $data['comments'], - ]; + foreach ($this->connection->iterateKeyValue($sql, $params) as $table => $comments) { + $tableOptions[$table] = ['comment' => $comments]; } return $tableOptions; diff --git a/src/Schema/PostgreSQLSchemaManager.php b/src/Schema/PostgreSQLSchemaManager.php index b5a2215d078..ba529002c57 100644 --- a/src/Schema/PostgreSQLSchemaManager.php +++ b/src/Schema/PostgreSQLSchemaManager.php @@ -14,7 +14,6 @@ use function array_change_key_case; use function array_key_exists; use function array_map; -use function array_merge; use function assert; use function explode; use function implode; @@ -136,6 +135,8 @@ protected function _getPortableTableForeignKeysList(array $tableForeignKeys): ar } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string @@ -158,9 +159,16 @@ protected function _getPortableTableIndexesList(array $rows): array foreach ($rows as $row) { $colNumbers = array_map('intval', explode(' ', $row['indkey'])); $columnNameSql = sprintf( - 'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC', + <<<'SQL' + SELECT attnum, + quote_ident(attname) AS attname + FROM pg_attribute + WHERE attrelid = %d + AND attnum IN (%s) + ORDER BY attnum + SQL, $row['indrelid'], - implode(' ,', $colNumbers), + implode(', ', $colNumbers), ); $indexColumns = $this->connection->fetchAllAssociative($columnNameSql); @@ -406,13 +414,13 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT '; - - if ($tableName === null) { - $sql .= 'c.relname AS table_name, n.nspname AS schema_name,'; - } + $params = []; - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + quote_ident(n.nspname) AS schema_name, + quote_ident(c.relname) AS table_name, a.attnum, quote_ident(a.attname) AS field, t.typname AS type, @@ -451,41 +459,36 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = ON d.objid = c.oid AND d.deptype = 'e' AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') - SQL; - - $conditions = array_merge([ - 'a.attnum > 0', - 'd.refobjid IS NULL', - - // 'r' for regular tables - 'p' for partitioned tables - "c.relkind IN('r', 'p')", - - // exclude partitions (tables that inherit from partitioned tables) - <<<'SQL' - NOT EXISTS ( - SELECT 1 - FROM pg_inherits - INNER JOIN pg_class parent on pg_inherits.inhparent = parent.oid - AND parent.relkind = 'p' - WHERE inhrelid = c.oid - ) + WHERE a.attnum > 0 + AND d.refobjid IS NULL + -- 'r' for regular tables - 'p' for partitioned tables + AND c.relkind IN('r', 'p') + -- exclude partitions (tables that inherit from partitioned tables) + AND NOT EXISTS ( + SELECT 1 + FROM pg_inherits + INNER JOIN pg_class parent on pg_inherits.inhparent = parent.oid + AND parent.relkind = 'p' + WHERE inhrelid = c.oid + ) + AND %s + ORDER BY a.attnum SQL, - ], $this->buildQueryConditions($tableName)); - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum'; + implode(' AND ', $this->buildQueryConditions($tableName, $params)), + ); - return $this->connection->executeQuery($sql); + return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; - - if ($tableName === null) { - $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; - } + $params = []; - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + quote_ident(tn.nspname) AS schema_name, + quote_ident(tc.relname) AS table_name, quote_ident(ic.relname) AS relname, i.indisunique, i.indisprimary, @@ -498,29 +501,26 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = JOIN pg_class AS ic ON ic.oid = i.indexrelid WHERE ic.oid IN ( SELECT indexrelid - FROM pg_index i, pg_class c, pg_namespace n -SQL; - - $conditions = array_merge([ - 'c.oid = i.indrelid', - 'c.relnamespace = n.oid', - ], $this->buildQueryConditions($tableName)); - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ')'; + FROM pg_index i + JOIN pg_class AS c ON c.oid = i.indrelid + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE %s) + SQL, + implode(' AND ', $this->buildQueryConditions($tableName, $params)), + ); - return $this->connection->executeQuery($sql); + return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT r.conname'; - - if ($tableName === null) { - $sql .= ', pkn.nspname AS schema_name, pkc.relname AS table_name'; - } + $params = [$this->getMaxIndexKeys()]; - $sql .= <<<'SQL' - , + $sql = sprintf( + <<<'SQL' + SELECT quote_ident(pkn.nspname) AS schema_name, + quote_ident(pkc.relname) AS table_name, + r.conname, pka.attname AS pk_attname, fkn.nspname AS fk_nspname, fkc.relname AS fk_relname, @@ -549,14 +549,16 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN WHERE r.conrelid IN ( SELECT c.oid - FROM pg_class c, pg_namespace n - SQL; - - $conditions = array_merge(['n.oid = c.relnamespace'], $this->buildQueryConditions($tableName)); - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ") AND r.contype = 'f'"; + FROM pg_class c + JOIN pg_namespace n + ON n.oid = c.relnamespace + WHERE %s + ) AND r.contype = 'f' + SQL, + implode(' AND ', $this->buildQueryConditions($tableName, $params)), + ); - return $this->connection->executeQuery($sql, [$this->getMaxIndexKeys()]); + return $this->connection->executeQuery($sql, $params); } /** @@ -564,37 +566,53 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = <<<'SQL' -SELECT c.relname, - CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged, - obj_description(c.oid, 'pg_class') AS comment -FROM pg_class c - INNER JOIN pg_namespace n - ON n.oid = c.relnamespace -SQL; + $params = []; - $conditions = array_merge(["c.relkind = 'r'"], $this->buildQueryConditions($tableName)); + $sql = sprintf( + <<<'SQL' + SELECT quote_ident(n.nspname) AS schema_name, + quote_ident(c.relname) AS table_name, + CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged, + obj_description(c.oid, 'pg_class') AS comment + FROM pg_class c + INNER JOIN pg_namespace n + ON n.oid = c.relnamespace + WHERE + c.relkind = 'r' + AND %s + SQL, + implode(' AND ', $this->buildQueryConditions($tableName, $params)), + ); - $sql .= ' WHERE ' . implode(' AND ', $conditions); + $tableOptions = []; + foreach ($this->connection->iterateAssociative($sql, $params) as $row) { + $tableOptions[$this->_getPortableTableDefinition($row)] = $row; + } - return $this->connection->fetchAllAssociativeIndexed($sql); + return $tableOptions; } - /** @return list */ - private function buildQueryConditions(?string $tableName): array + /** + * @param list $params + * + * @return non-empty-list + */ + private function buildQueryConditions(?string $tableName, array &$params): array { $conditions = []; if ($tableName !== null) { if (str_contains($tableName, '.')) { [$schemaName, $tableName] = explode('.', $tableName); - $conditions[] = 'n.nspname = ' . $this->platform->quoteStringLiteral($schemaName); + + $conditions[] = 'n.nspname = ?'; + $params[] = $schemaName; } else { $conditions[] = 'n.nspname = ANY(current_schemas(false))'; } - $identifier = new Identifier($tableName); - $conditions[] = 'c.relname = ' . $this->platform->quoteStringLiteral($identifier->getName()); + $conditions[] = 'c.relname = ?'; + $params[] = $tableName; } $conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')"; diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php index 06d48342e62..285b5254ec0 100644 --- a/src/Schema/SQLServerSchemaManager.php +++ b/src/Schema/SQLServerSchemaManager.php @@ -188,9 +188,14 @@ protected function _getPortableTableForeignKeysList(array $rows): array $name = $row['ForeignKey']; if (! isset($foreignKeys[$name])) { + if ($row['ReferenceSchemaName'] === $this->getCurrentSchemaName()) { + $row['ReferenceSchemaName'] = null; + } + $foreignKeys[$name] = [ 'local' => [$row['ColumnName']], 'foreignTable' => $row['ReferenceTableName'], + 'foreignSchema' => $row['ReferenceSchemaName'], 'foreign' => [$row['ReferenceColumnName']], 'name' => $name, 'onUpdate' => str_replace('_', ' ', $row['update_referential_action_desc']), @@ -220,6 +225,8 @@ protected function _getPortableTableIndexesList(array $rows): array } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string @@ -287,11 +294,10 @@ protected function selectTableNames(string $databaseName): Result { // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams $sql = <<<'SQL' -SELECT name AS table_name, - SCHEMA_NAME(schema_id) AS schema_name -FROM sys.objects -WHERE type = 'U' - AND name != 'sysdiagrams' +SELECT SCHEMA_NAME(schema_id) AS schema_name, + name AS table_name +FROM sys.tables +WHERE name != 'sysdiagrams' ORDER BY name SQL; @@ -300,13 +306,13 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; - - if ($tableName === null) { - $sql .= ' obj.name AS table_name, scm.name AS schema_name,'; - } + $params = []; - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + scm.name AS schema_name, + tbl.name AS table_name, col.name, type.name AS type, col.max_length AS length, @@ -322,41 +328,37 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = FROM sys.columns AS col JOIN sys.types AS type ON col.user_type_id = type.user_type_id - JOIN sys.objects AS obj - ON col.object_id = obj.object_id + JOIN sys.tables AS tbl + ON col.object_id = tbl.object_id JOIN sys.schemas AS scm - ON obj.schema_id = scm.schema_id + ON tbl.schema_id = scm.schema_id LEFT JOIN sys.default_constraints def ON col.default_object_id = def.object_id AND col.object_id = def.parent_object_id LEFT JOIN sys.extended_properties AS prop - ON obj.object_id = prop.major_id + ON tbl.object_id = prop.major_id AND col.column_id = prop.minor_id AND prop.name = 'MS_Description' -SQL; - - // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams - $conditions = ["obj.type = 'U'", "obj.name != 'sysdiagrams'"]; - $params = []; - - if ($tableName !== null) { - $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name'); - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions); + WHERE %s + ORDER BY scm.name, + tbl.name, + col.column_id +SQL, + $this->getWhereClause($tableName, 'scm.name', 'tbl.name', $params), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; - - if ($tableName === null) { - $sql .= ' tbl.name AS table_name, scm.name AS schema_name,'; - } + $params = []; - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + scm.name AS schema_name, + tbl.name AS table_name, idx.name AS key_name, col.name AS column_name, ~idx.is_unique AS non_unique, @@ -377,59 +379,52 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id -SQL; - - $conditions = []; - $params = []; - - if ($tableName !== null) { - $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); - $sql .= ' WHERE ' . implode(' AND ', $conditions); - } - - $sql .= ' ORDER BY idx.index_id, idxcol.key_ordinal'; + WHERE %s + ORDER BY scm.name, + tbl.name, + idx.index_id, + idxcol.key_ordinal +SQL, + $this->getWhereClause($tableName, 'scm.name', 'tbl.name', $params), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; - - if ($tableName === null) { - $sql .= ' OBJECT_NAME (f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,'; - } + $params = []; - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + SCHEMA_NAME(f.schema_id) AS schema_name, + OBJECT_NAME(f.parent_object_id) AS table_name, f.name AS ForeignKey, - SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, - OBJECT_NAME (f.parent_object_id) AS TableName, - COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, - SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, - OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, - COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, + COL_NAME(fc.parent_object_id, fc.parent_column_id) AS ColumnName, + SCHEMA_NAME(t.schema_id) ReferenceSchemaName, + OBJECT_NAME(f.referenced_object_id) AS ReferenceTableName, + COL_NAME(fc.referenced_object_id, fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc - INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id - ON f.OBJECT_ID = fc.constraint_object_id -SQL; - - $conditions = []; - $params = []; - - if ($tableName !== null) { - $conditions[] = $this->getTableWhereClause( + ON f.object_id = fc.constraint_object_id + INNER JOIN sys.tables AS t + ON t.object_id = fc.referenced_object_id + WHERE %s + ORDER BY 1, + 2, + 3, + fc.constraint_column_id +SQL, + $this->getWhereClause( $tableName, 'SCHEMA_NAME(f.schema_id)', 'OBJECT_NAME(f.parent_object_id)', - ); - - $sql .= ' WHERE ' . implode(' AND ', $conditions); - } - - $sql .= ' ORDER BY fc.constraint_column_id'; + $params, + ), + ); return $this->connection->executeQuery($sql, $params); } @@ -439,34 +434,32 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT - tbl.name, - p.value AS [table_comment] + scm.name AS schema_name, + tbl.name AS table_name, + p.value FROM sys.tables AS tbl + JOIN sys.schemas AS scm + ON tbl.schema_id = scm.schema_id INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 -SQL; - - $conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"]; - $params = []; - - if ($tableName !== null) { - $conditions[] = "tbl.name = N'" . $tableName . "'"; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions); - - /** @var array> $metadata */ - $metadata = $this->connection->executeQuery($sql, $params) - ->fetchAllAssociativeIndexed(); + WHERE + p.name = N'MS_Description' + AND %s +SQL, + $this->getWhereClause($tableName, 'scm.name', 'tbl.name', $params), + ); $tableOptions = []; - foreach ($metadata as $table => $data) { + foreach ($this->connection->iterateAssociative($sql, $params) as $data) { $data = array_change_key_case($data, CASE_LOWER); - $tableOptions[$table] = [ - 'comment' => $data['table_comment'], + $tableOptions[$this->_getPortableTableDefinition($data)] = [ + 'comment' => $data['value'], ]; } @@ -476,21 +469,36 @@ protected function fetchTableOptionsByTable(string $databaseName, ?string $table /** * Returns the where clause to filter schema and table name in a query. * - * @param string $table The full qualified name of the table. - * @param string $schemaColumn The name of the column to compare the schema to in the where clause. - * @param string $tableColumn The name of the column to compare the table to in the where clause. + * @param ?string $tableName The full qualified name of the table. + * @param string $schemaColumn The name of the column to compare the schema to in the where clause. + * @param string $tableColumn The name of the column to compare the table to in the where clause. + * @param list $params */ - private function getTableWhereClause(string $table, string $schemaColumn, string $tableColumn): string - { - if (str_contains($table, '.')) { - [$schema, $table] = explode('.', $table); - $schema = $this->platform->quoteStringLiteral($schema); - $table = $this->platform->quoteStringLiteral($table); - } else { - $schema = 'SCHEMA_NAME()'; - $table = $this->platform->quoteStringLiteral($table); + private function getWhereClause( + ?string $tableName, + string $schemaColumn, + string $tableColumn, + array &$params, + ): string { + $conditions = []; + + if ($tableName !== null) { + if (str_contains($tableName, '.')) { + [$schemaName, $tableName] = explode('.', $tableName); + + $conditions = [sprintf('%s = ?', $schemaColumn)]; + $params[] = $schemaName; + } else { + $conditions = [sprintf('%s = SCHEMA_NAME()', $schemaColumn)]; + } + + $conditions[] = sprintf('%s = ?', $tableColumn); + $params[] = $tableName; } - return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); + // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams + $conditions[] = sprintf("%s != 'sysdiagrams'", $tableColumn); + + return implode(' AND ', $conditions); } } diff --git a/src/Schema/SQLiteSchemaManager.php b/src/Schema/SQLiteSchemaManager.php index c8a5c80285f..aa361f57613 100644 --- a/src/Schema/SQLiteSchemaManager.php +++ b/src/Schema/SQLiteSchemaManager.php @@ -27,6 +27,7 @@ use function preg_quote; use function preg_replace; use function rtrim; +use function sprintf; use function str_contains; use function str_ends_with; use function str_replace; @@ -94,6 +95,8 @@ public function listTableForeignKeys(string $table): array } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string @@ -404,9 +407,7 @@ protected function selectTableNames(string $databaseName): Result SELECT name AS table_name FROM sqlite_master WHERE type = 'table' - AND name != 'sqlite_sequence' - AND name != 'geometry_columns' - AND name != 'spatial_ref_sys' + AND name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence') UNION ALL SELECT name FROM sqlite_temp_master @@ -419,77 +420,62 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT t.name AS table_name, c.* FROM sqlite_master t JOIN pragma_table_info(t.name) c -SQL; - - $conditions = [ - "t.type = 'table'", - "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", - ]; - $params = []; - - if ($tableName !== null) { - $conditions[] = 't.name = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid'; + WHERE %s + ORDER BY t.name, + c.cid +SQL, + $this->getWhereClause($tableName, $params), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT t.name AS table_name, i.name, i."unique" FROM sqlite_master t JOIN pragma_index_list(t.name) i -SQL; - - $conditions = [ - "t.type = 'table'", - "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", - ]; - $params = []; - - if ($tableName !== null) { - $conditions[] = 't.name = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq'; + WHERE %s + ORDER BY t.name, i.seq +SQL, + $this->getWhereClause($tableName, $params), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT t.name AS table_name, p.* FROM sqlite_master t JOIN pragma_foreign_key_list(t.name) p - ON p."seq" != '-1' -SQL; - - $conditions = [ - "t.type = 'table'", - "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", - ]; - $params = []; - - if ($tableName !== null) { - $conditions[] = 't.name = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq'; + ON p.seq != '-1' + WHERE %s + ORDER BY t.name, + p.id DESC, + p.seq +SQL, + $this->getWhereClause($tableName, $params), + ); return $this->connection->executeQuery($sql, $params); } @@ -595,27 +581,21 @@ protected function fetchIndexColumns(string $databaseName, ?string $tableName = */ private function fetchPrimaryKeyColumns(?string $tableName = null): array { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT t.name AS table_name, p.name FROM sqlite_master t JOIN pragma_table_info(t.name) p - SQL; - - $conditions = [ - "t.type = 'table'", - "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", - ]; - $params = []; - - if ($tableName !== null) { - $conditions[] = 't.name = ?'; - $params[] = $tableName; - } - - $conditions[] = 'p.pk > 0'; - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.pk'; + WHERE %s + AND p.pk > 0 + ORDER BY t.name, + p.pk + SQL, + $this->getWhereClause($tableName, $params), + ); return $this->connection->fetchAllAssociative($sql, $params); } @@ -644,4 +624,20 @@ protected function fetchTableOptionsByTable(string $databaseName, ?string $table return $tableOptions; } + + /** @param list $params */ + private function getWhereClause(?string $tableName, array &$params): string + { + $conditions = [ + "t.type = 'table'", + "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", + ]; + + if ($tableName !== null) { + $conditions[] = 't.name = ?'; + $params[] = $tableName; + } + + return implode(' AND ', $conditions); + } } diff --git a/tests/Functional/Schema/OracleSchemaManagerTest.php b/tests/Functional/Schema/OracleSchemaManagerTest.php index 19498a27037..e497d443c9e 100644 --- a/tests/Functional/Schema/OracleSchemaManagerTest.php +++ b/tests/Functional/Schema/OracleSchemaManagerTest.php @@ -69,87 +69,6 @@ public function testAlterTableColumnNotNull(): void self::assertTrue($columns['bar']->getNotnull()); } - public function testListTableDetailsWithDifferentIdentifierQuotingRequirements(): void - { - $primaryTableName = '"Primary_Table"'; - $offlinePrimaryTable = new Table($primaryTableName); - $offlinePrimaryTable->addColumn( - '"Id"', - Types::INTEGER, - ['autoincrement' => true], - ); - $offlinePrimaryTable->addColumn('select', Types::INTEGER); - $offlinePrimaryTable->addColumn('foo', Types::INTEGER); - $offlinePrimaryTable->addColumn('BAR', Types::INTEGER); - $offlinePrimaryTable->addColumn('"BAZ"', Types::INTEGER); - $offlinePrimaryTable->addIndex(['select'], 'from'); - $offlinePrimaryTable->addIndex(['foo'], 'foo_index'); - $offlinePrimaryTable->addIndex(['BAR'], 'BAR_INDEX'); - $offlinePrimaryTable->addIndex(['"BAZ"'], 'BAZ_INDEX'); - $offlinePrimaryTable->setPrimaryKey(['"Id"']); - - $foreignTableName = 'foreign'; - $offlineForeignTable = new Table($foreignTableName); - $offlineForeignTable->addColumn('id', Types::INTEGER, ['autoincrement' => true]); - $offlineForeignTable->addColumn('"Fk"', Types::INTEGER); - $offlineForeignTable->addIndex(['"Fk"'], '"Fk_index"'); - $offlineForeignTable->addForeignKeyConstraint( - $primaryTableName, - ['"Fk"'], - ['"Id"'], - [], - '"Primary_Table_Fk"', - ); - $offlineForeignTable->setPrimaryKey(['id']); - - $this->dropTableIfExists($foreignTableName); - $this->dropTableIfExists($primaryTableName); - - $this->schemaManager->createTable($offlinePrimaryTable); - $this->schemaManager->createTable($offlineForeignTable); - - $onlinePrimaryTable = $this->schemaManager->introspectTable($primaryTableName); - $onlineForeignTable = $this->schemaManager->introspectTable($foreignTableName); - - $platform = $this->connection->getDatabasePlatform(); - - // Primary table assertions - self::assertSame($primaryTableName, $onlinePrimaryTable->getObjectName()->toSQL($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('"Id"')); - self::assertSame('"Id"', $onlinePrimaryTable->getColumn('"Id"')->getObjectName()->toSQL($platform)); - - $onlinePrimaryTablePrimaryKey = $onlinePrimaryTable->getPrimaryKey(); - self::assertNotNull($onlinePrimaryTablePrimaryKey); - self::assertSame(['"Id"'], $onlinePrimaryTablePrimaryKey->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('select')); - self::assertTrue($onlinePrimaryTable->hasColumn('foo')); - self::assertTrue($onlinePrimaryTable->hasColumn('BAR')); - self::assertTrue($onlinePrimaryTable->hasColumn('"BAZ"')); - self::assertTrue($onlinePrimaryTable->hasIndex('from')); - self::assertTrue($onlinePrimaryTable->getIndex('from')->hasColumnAtPosition('"select"')); - - self::assertTrue($onlinePrimaryTable->hasIndex('foo_index')); - self::assertTrue($onlinePrimaryTable->getIndex('foo_index')->hasColumnAtPosition('foo')); - self::assertTrue($onlinePrimaryTable->hasIndex('BAR_INDEX')); - self::assertTrue($onlinePrimaryTable->getIndex('BAR_INDEX')->hasColumnAtPosition('BAR')); - self::assertTrue($onlinePrimaryTable->hasIndex('BAZ_INDEX')); - self::assertTrue($onlinePrimaryTable->getIndex('BAZ_INDEX')->hasColumnAtPosition('"BAZ"')); - - // Foreign table assertions - self::assertTrue($onlineForeignTable->hasColumn('id')); - - $onlineForeignTablePrimaryKey = $onlineForeignTable->getPrimaryKey(); - self::assertNotNull($onlineForeignTablePrimaryKey); - - self::assertTrue($onlineForeignTable->hasColumn('"Fk"')); - self::assertTrue($onlineForeignTable->hasIndex('"Fk_index"')); - self::assertTrue($onlineForeignTable->getIndex('"Fk_index"')->hasColumnAtPosition('"Fk"')); - - self::assertTrue($onlineForeignTable->hasForeignKey('"Primary_Table_Fk"')); - } - public function testListTableColumnsSameTableNamesInDifferentSchemas(): void { $table = $this->createListTableColumns(); diff --git a/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php b/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php index 5b017a581f0..43e92150df7 100644 --- a/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php +++ b/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php @@ -7,7 +7,6 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; -use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\View; @@ -20,7 +19,6 @@ use Doctrine\DBAL\Types\Types; use PHPUnit\Framework\Attributes\DataProvider; -use function array_pop; use function sprintf; use function strtolower; use function version_compare; @@ -110,48 +108,6 @@ public function testAlterTableAutoIncrementDrop(): void self::assertFalse($tableFinal->getColumn('id')->getAutoincrement()); } - public function testTableWithSchema(): void - { - $this->connection->executeStatement('CREATE SCHEMA nested'); - - $referencedName = OptionallyQualifiedName::unquoted('referenced', 'nested'); - $referencingName = OptionallyQualifiedName::unquoted('referencing', 'nested'); - - $referencedTable = new Table($referencedName->toString()); - $column = $referencedTable->addColumn('id', Types::INTEGER); - $column->setAutoincrement(true); - $referencedTable->setPrimaryKey(['id']); - - $referencingTable = new Table($referencingName->toString()); - $column = $referencingTable->addColumn('id', Types::INTEGER); - $column->setAutoincrement(true); - $referencingTable->setPrimaryKey(['id']); - $referencingTable->addForeignKeyConstraint($referencedTable->getName(), ['id'], ['id']); - - $this->schemaManager->createTable($referencedTable); - $this->schemaManager->createTable($referencingTable); - - $tableNames = $this->schemaManager->listTableNames(); - self::assertContains($referencingName->toString(), $tableNames); - - $tables = $this->schemaManager->listTables(); - self::assertNotNull($this->findTableByName($tables, $referencingName->toString())); - - $referencingTable = $this->schemaManager->introspectTable($referencingName->toString()); - self::assertTrue($referencingTable->hasColumn('id')); - - $primaryKey = $referencingTable->getPrimaryKey(); - self::assertNotNull($primaryKey); - self::assertEquals(['id'], $primaryKey->getColumns()); - - $foreignKeys = $referencingTable->getForeignKeys(); - self::assertCount(1, $foreignKeys); - - $foreignKey = array_pop($foreignKeys); - self::assertNotNull($foreignKey); - $this->assertOptionallyQualifiedNameEquals($referencedName, $foreignKey->getReferencedTableName()); - } - public function testListSameTableNameColumnsWithDifferentSchema(): void { $this->connection->executeStatement('CREATE SCHEMA another'); diff --git a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php index 7c17c70cd2e..684e3ee20e9 100644 --- a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -7,7 +7,9 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Schema\AbstractAsset; use Doctrine\DBAL\Schema\AbstractSchemaManager; @@ -1256,6 +1258,110 @@ private function createReservedKeywordTables(): void $schemaManager->createSchemaObjects($schema); } + /** @throws Exception */ + public function testQuotedIdentifiers(): void + { + $platform = $this->connection->getDatabasePlatform(); + + if ($platform instanceof DB2Platform) { + self::markTestIncomplete( + 'Introspection of lower-case identifiers as quoted is currently not implemented on IBM DB2.', + ); + } + + if (! $platform instanceof OraclePlatform && ! $platform instanceof PostgreSQLPlatform) { + self::markTestSkipped('The current platform does not auto-quote introspected identifiers.'); + } + + $artists = new Table('"Artists"'); + $artists->addColumn('"Id"', Types::INTEGER); + $artists->addColumn('"Name"', Types::INTEGER); + $artists->addIndex(['"Name"'], '"Idx_Name"'); + $artists->setPrimaryKey(['"Id"']); + + $tracks = new Table('"Tracks"'); + $tracks->addColumn('"Id"', Types::INTEGER); + $tracks->addColumn('"Artist_Id"', Types::INTEGER); + $tracks->addIndex(['"Artist_Id"'], '"Idx_Artist_Id"'); + $tracks->addForeignKeyConstraint( + '"Artists"', + ['"Artist_Id"'], + ['"Id"'], + [], + '"Artists_Fk"', + ); + $tracks->setPrimaryKey(['"Id"']); + + $this->dropTableIfExists('"Tracks"'); + $this->dropTableIfExists('"Artists"'); + + $this->schemaManager->createTable($artists); + $this->schemaManager->createTable($tracks); + + $artists = $this->schemaManager->introspectTable('"Artists"'); + $tracks = $this->schemaManager->introspectTable('"Tracks"'); + + $platform = $this->connection->getDatabasePlatform(); + + // Primary table assertions + self::assertOptionallyQualifiedNameEquals( + OptionallyQualifiedName::quoted('Artists'), + $artists->getObjectName(), + ); + + self::assertUnqualifiedNameEquals( + UnqualifiedName::quoted('Id'), + $artists->getColumn('"Id"')->getObjectName(), + ); + + self::assertUnqualifiedNameEquals( + UnqualifiedName::quoted('Name'), + $artists->getColumn('"Name"')->getObjectName(), + ); + + self::assertSame(['"Name"'], $artists->getIndex('"Idx_Name"')->getQuotedColumns($platform)); + + $primaryKey = $artists->getPrimaryKey(); + self::assertNotNull($primaryKey); + self::assertSame(['"Id"'], $primaryKey->getQuotedColumns($platform)); + + // Foreign table assertions + self::assertUnqualifiedNameEquals( + UnqualifiedName::quoted('Id'), + $tracks->getColumn('"Id"')->getObjectName(), + ); + + $primaryKey = $tracks->getPrimaryKey(); + self::assertNotNull($primaryKey); + self::assertSame(['"Id"'], $primaryKey->getQuotedColumns($platform)); + + self::assertUnqualifiedNameEquals( + UnqualifiedName::quoted('Artist_Id'), + $tracks->getColumn('"Artist_Id"')->getObjectName(), + ); + + self::assertTrue($tracks->hasIndex('"Idx_Artist_Id"')); + self::assertSame( + ['"Artist_Id"'], + $tracks->getIndex('"Idx_Artist_Id"')->getQuotedColumns($platform), + ); + + $constraint = $tracks->getForeignKey('"Artists_Fk"'); + + self::assertUnqualifiedNameListEquals([ + UnqualifiedName::quoted('Artist_Id'), + ], $constraint->getReferencingColumnNames()); + + self::assertOptionallyQualifiedNameEquals( + OptionallyQualifiedName::quoted('Artists'), + $constraint->getReferencedTableName(), + ); + + self::assertUnqualifiedNameListEquals([ + UnqualifiedName::quoted('Id'), + ], $constraint->getReferencedColumnNames()); + } + public function testChangeIndexWithForeignKeys(): void { $this->dropTableIfExists('child'); @@ -1362,4 +1468,52 @@ public function testDefaultSchemaName(): void } abstract public function getExpectedDefaultSchemaName(): ?string; + + public function testTableWithSchema(): void + { + if (! $this->connection->getDatabasePlatform()->supportsSchemas()) { + self::markTestSkipped('The currently used database platform does not support schemas.'); + } + + $this->connection->executeStatement('CREATE SCHEMA nested'); + + $nestedRelatedTable = new Table('nested.schemarelated'); + $column = $nestedRelatedTable->addColumn('id', Types::INTEGER); + $column->setAutoincrement(true); + $nestedRelatedTable->setPrimaryKey(['id']); + + $nestedSchemaTable = new Table('nested.schematable'); + $column = $nestedSchemaTable->addColumn('id', Types::INTEGER); + $column->setAutoincrement(true); + $nestedSchemaTable->setPrimaryKey(['id']); + $nestedSchemaTable->addForeignKeyConstraint($nestedRelatedTable->getName(), ['id'], ['id']); + $nestedSchemaTable->setComment('This is a comment'); + + $this->schemaManager->createTable($nestedRelatedTable); + $this->schemaManager->createTable($nestedSchemaTable); + + $tableNames = $this->schemaManager->listTableNames(); + self::assertContains('nested.schematable', $tableNames); + + $tables = $this->schemaManager->listTables(); + self::assertNotNull($this->findTableByName($tables, 'nested.schematable')); + + $nestedSchemaTable = $this->schemaManager->introspectTable('nested.schematable'); + self::assertTrue($nestedSchemaTable->hasColumn('id')); + + $primaryKey = $nestedSchemaTable->getPrimaryKey(); + self::assertNotNull($primaryKey); + self::assertEquals(['id'], $primaryKey->getColumns()); + + $relatedFks = array_values($nestedSchemaTable->getForeignKeys()); + self::assertCount(1, $relatedFks); + $relatedFk = $relatedFks[0]; + + self::assertOptionallyQualifiedNameEquals( + OptionallyQualifiedName::unquoted('schemarelated', 'nested'), + $relatedFk->getReferencedTableName(), + ); + + self::assertEquals('This is a comment', $nestedSchemaTable->getComment()); + } }