From 90b3daa719c0d6c51d0d5d3318eb739a1806e136 Mon Sep 17 00:00:00 2001 From: mondrake Date: Sat, 16 Oct 2021 12:40:03 +0200 Subject: [PATCH 01/38] Adjust OraclePlatform and OracleSchemaManager (#1) --- src/Platforms/AbstractPlatform.php | 24 +++++++ src/Platforms/OraclePlatform.php | 60 ++++++++++++++++++ src/Schema/OracleSchemaManager.php | 62 +++++++++++++++++++ .../Schema/OracleSchemaManagerTest.php | 42 +++++++++++++ 4 files changed, 188 insertions(+) diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index e7f0819b585..4360cb17bd8 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -2794,6 +2794,14 @@ public function getListTableConstraintsSQL($table) throw Exception::notSupported(__METHOD__); } + /** + * Returns the SQL to list all the columns of all the tables in the database. + */ + public function getListAllColumnsSQL(string $database): string + { + throw Exception::notSupported(__METHOD__); + } + /** * @param string $table * @param string $database @@ -2841,6 +2849,14 @@ public function getListViewsSQL($database) throw Exception::notSupported(__METHOD__); } + /** + * Returns the SQL to list all the indexes in the database. + */ + public function getListAllIndexesSQL(string $database): string + { + throw Exception::notSupported(__METHOD__); + } + /** * Returns the list of indexes for the current database. * @@ -2863,6 +2879,14 @@ public function getListTableIndexesSQL($table, $database = null) throw Exception::notSupported(__METHOD__); } + /** + * Returns the SQL to list all the foreign keys in the database. + */ + public function getListAllForeignKeysSQL(string $database): string + { + throw Exception::notSupported(__METHOD__); + } + /** * @param string $table * diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 7be7621d3e4..bfd250abff9 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -410,6 +410,27 @@ protected function _getCreateTableSQL($name, array $columns, array $options = [] return $sql; } + public function getListAllIndexesSQL(string $database): string + { + $databaseIdentifier = $this->normalizeIdentifier($database); + $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + + return <<normalizeIdentifier($database); + $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + + return <<listTableNames(); + + // Get all column definitions in one database call. + $columnsByTable = $this->getAssetRecordsByTable($this->_platform->getListAllColumnsSQL($currentDatabase)); + + // Get all foreign keys definitions in one database call. + $foreignKeysByTable = $this->getAssetRecordsByTable( + $this->_platform->getListAllForeignKeysSQL($currentDatabase) + ); + + // Get all indexes definitions in one database call. + $indexesByTable = $this->getAssetRecordsByTable($this->_platform->getListAllIndexesSQL($currentDatabase)); + + $tables = []; + foreach ($tableNames as $tableName) { + $unquotedTableName = trim($tableName, '"'); + + $columns = $this->_getPortableTableColumnList($tableName, '', $columnsByTable[$unquotedTableName]); + + $foreignKeys = []; + if (isset($foreignKeysByTable[$unquotedTableName])) { + $foreignKeys = $this->_getPortableTableForeignKeysList($foreignKeysByTable[$unquotedTableName]); + } + + $indexes = []; + if (isset($indexesByTable[$unquotedTableName])) { + $indexes = $this->_getPortableTableIndexesList($indexesByTable[$unquotedTableName], $tableName); + } + + $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); + } + + return $tables; + } + + /** + * Helper method to group a set of asset records by the table name. + * + * @param string $sql An SQL statement to be executed, that contains a TABLE_NAME field for grouping. + * + * @return array>> An associative array with key being the table name, + * and value a simple array of records associated with + * the table. + */ + private function getAssetRecordsByTable(string $sql): array + { + $input = $this->_conn->fetchAllAssociative($sql); + $output = []; + foreach ($input as $record) { + $output[$record['TABLE_NAME']][] = $record; + } + + return $output; + } + /** * {@inheritdoc} */ diff --git a/tests/Functional/Schema/OracleSchemaManagerTest.php b/tests/Functional/Schema/OracleSchemaManagerTest.php index 1e7e575352c..5e117117254 100644 --- a/tests/Functional/Schema/OracleSchemaManagerTest.php +++ b/tests/Functional/Schema/OracleSchemaManagerTest.php @@ -2,6 +2,7 @@ namespace Doctrine\DBAL\Tests\Functional\Schema; +use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema; @@ -276,4 +277,45 @@ public function testCreateAndListSequences(): void "Skipped for uppercase letters are contained in sequences' names. Fix the schema manager in 3.0." ); } + + public function testCreateSchemaNumberOfQueriesInvariable(): void + { + // Create a table. + $this->connection->executeUpdate(<<connection->getConfiguration()->setSQLLogger($sqlLoggerStack); + $schema = $this->schemaManager->createSchema(); + $firstQueryCount = $sqlLoggerStack->currentQuery; + + // Create another table. + $this->connection->executeUpdate(<<connection->executeUpdate('CREATE UNIQUE INDEX tbl_test_2766_uix_1 ON tbl_test_2766_1 (x_number)'); + + // Introspect the db schema again. + $preCount = $sqlLoggerStack->currentQuery; + $schema = $this->schemaManager->createSchema(); + $secondQueryCount = $sqlLoggerStack->currentQuery - $preCount; + + // The number of queries needed to execute createSchema should be the same + // regardless of additional tables added. + self::assertSame($firstQueryCount, $secondQueryCount); + } } From 5cb5de7a84db6516351fa8297d7d5132a2be72d2 Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 17 Oct 2021 11:22:06 +0200 Subject: [PATCH 02/38] Rename methods --- src/Platforms/AbstractPlatform.php | 6 +++--- src/Platforms/OraclePlatform.php | 6 +++--- src/Schema/OracleSchemaManager.php | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index 4360cb17bd8..3217ed61edc 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -2797,7 +2797,7 @@ public function getListTableConstraintsSQL($table) /** * Returns the SQL to list all the columns of all the tables in the database. */ - public function getListAllColumnsSQL(string $database): string + public function getListDatabaseColumnsSQL(string $database): string { throw Exception::notSupported(__METHOD__); } @@ -2852,7 +2852,7 @@ public function getListViewsSQL($database) /** * Returns the SQL to list all the indexes in the database. */ - public function getListAllIndexesSQL(string $database): string + public function getListDatabaseIndexesSQL(string $database): string { throw Exception::notSupported(__METHOD__); } @@ -2882,7 +2882,7 @@ public function getListTableIndexesSQL($table, $database = null) /** * Returns the SQL to list all the foreign keys in the database. */ - public function getListAllForeignKeysSQL(string $database): string + public function getListDatabaseForeignKeysSQL(string $database): string { throw Exception::notSupported(__METHOD__); } diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index bfd250abff9..c844dcc1513 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -410,7 +410,7 @@ protected function _getCreateTableSQL($name, array $columns, array $options = [] return $sql; } - public function getListAllIndexesSQL(string $database): string + public function getListDatabaseIndexesSQL(string $database): string { $databaseIdentifier = $this->normalizeIdentifier($database); $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); @@ -644,7 +644,7 @@ private function getAutoincrementIdentifierName(Identifier $table) : $identifierName; } - public function getListAllForeignKeysSQL(string $database): string + public function getListDatabaseForeignKeysSQL(string $database): string { $databaseIdentifier = $this->normalizeIdentifier($database); $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); @@ -710,7 +710,7 @@ public function getListTableConstraintsSQL($table) return 'SELECT * FROM user_constraints WHERE table_name = ' . $table; } - public function getListAllColumnsSQL(string $database): string + public function getListDatabaseColumnsSQL(string $database): string { $databaseIdentifier = $this->normalizeIdentifier($database); $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index fecd04b6913..30040f25a57 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -32,15 +32,15 @@ public function listTables() $tableNames = $this->listTableNames(); // Get all column definitions in one database call. - $columnsByTable = $this->getAssetRecordsByTable($this->_platform->getListAllColumnsSQL($currentDatabase)); + $columnsByTable = $this->getAssetRecordsByTable($this->_platform->getListDatabaseColumnsSQL($currentDatabase)); // Get all foreign keys definitions in one database call. $foreignKeysByTable = $this->getAssetRecordsByTable( - $this->_platform->getListAllForeignKeysSQL($currentDatabase) + $this->_platform->getListDatabaseForeignKeysSQL($currentDatabase) ); // Get all indexes definitions in one database call. - $indexesByTable = $this->getAssetRecordsByTable($this->_platform->getListAllIndexesSQL($currentDatabase)); + $indexesByTable = $this->getAssetRecordsByTable($this->_platform->getListDatabaseIndexesSQL($currentDatabase)); $tables = []; foreach ($tableNames as $tableName) { From 3670c996ae985ad6e109272b1b39ee1c44a666fe Mon Sep 17 00:00:00 2001 From: mondrake Date: Tue, 19 Oct 2021 22:39:59 +0200 Subject: [PATCH 03/38] Requested changes - first step --- src/Platforms/AbstractPlatform.php | 24 ------- src/Platforms/DatabaseAsset.php | 28 ++++++++ src/Platforms/OraclePlatform.php | 2 +- src/Schema/AbstractSchemaManager.php | 65 ++++++++++++++++++- src/Schema/OracleSchemaManager.php | 62 ------------------ .../Schema/OracleSchemaManagerTest.php | 6 +- 6 files changed, 96 insertions(+), 91 deletions(-) create mode 100644 src/Platforms/DatabaseAsset.php diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index e8caeb87fc0..a820e4aa01c 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -3072,14 +3072,6 @@ public function getListTableConstraintsSQL($table) throw Exception::notSupported(__METHOD__); } - /** - * Returns the SQL to list all the columns of all the tables in the database. - */ - public function getListDatabaseColumnsSQL(string $database): string - { - throw Exception::notSupported(__METHOD__); - } - /** * @param string $table * @param string $database @@ -3135,14 +3127,6 @@ public function getListViewsSQL($database) throw Exception::notSupported(__METHOD__); } - /** - * Returns the SQL to list all the indexes in the database. - */ - public function getListDatabaseIndexesSQL(string $database): string - { - throw Exception::notSupported(__METHOD__); - } - /** * Returns the list of indexes for the current database. * @@ -3165,14 +3149,6 @@ public function getListTableIndexesSQL($table, $database = null) throw Exception::notSupported(__METHOD__); } - /** - * Returns the SQL to list all the foreign keys in the database. - */ - public function getListDatabaseForeignKeysSQL(string $database): string - { - throw Exception::notSupported(__METHOD__); - } - /** * @param string $table * diff --git a/src/Platforms/DatabaseAsset.php b/src/Platforms/DatabaseAsset.php new file mode 100644 index 00000000000..ebe5cf08317 --- /dev/null +++ b/src/Platforms/DatabaseAsset.php @@ -0,0 +1,28 @@ +listTableNames(); + $columnsByTable = []; + $foreignKeysByTable = []; + $indexesByTable = []; + + if ($this->_platform instanceof DatabaseAsset) { + $currentDatabase = $this->_conn->getDatabase() ?? ''; + + // Get all column definitions in one database call. + $columnsByTable = $this->getAssetRecordsByTable( + $this->_platform->getListDatabaseColumnsSQL($currentDatabase) + ); + + // Get all foreign keys definitions in one database call. + $foreignKeysByTable = $this->getAssetRecordsByTable( + $this->_platform->getListDatabaseForeignKeysSQL($currentDatabase) + ); + + // Get all indexes definitions in one database call. + $indexesByTable = $this->getAssetRecordsByTable( + $this->_platform->getListDatabaseIndexesSQL($currentDatabase) + ); + } + $tables = []; foreach ($tableNames as $tableName) { - $tables[] = $this->listTableDetails($tableName); + if ($this->_platform instanceof DatabaseAsset) { + $unquotedTableName = trim($tableName, '"'); + + $columns = $this->_getPortableTableColumnList($tableName, '', $columnsByTable[$unquotedTableName]); + + $foreignKeys = []; + if (isset($foreignKeysByTable[$unquotedTableName])) { + $foreignKeys = $this->_getPortableTableForeignKeysList($foreignKeysByTable[$unquotedTableName]); + } + + $indexes = []; + if (isset($indexesByTable[$unquotedTableName])) { + $indexes = $this->_getPortableTableIndexesList($indexesByTable[$unquotedTableName], $tableName); + } + + $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); + } else { + $tables[] = $this->listTableDetails($tableName); + } } return $tables; } + /** + * Helper method to group a set of asset records by the table name. + * + * @param string $sql An SQL statement to be executed, that contains a TABLE_NAME field for grouping. + * + * @return array>> An associative array with key being the table name, + * and value a simple array of records associated with + * the table. + */ + protected function getAssetRecordsByTable(string $sql): array + { + $input = $this->_conn->fetchAllAssociative($sql); + $output = []; + foreach ($input as $record) { + $output[$record['TABLE_NAME']][] = $record; + } + + return $output; + } + /** * @param string $name * diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index c5df54dcac2..63209c93622 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -24,68 +24,6 @@ */ class OracleSchemaManager extends AbstractSchemaManager { - /** - * {@inheritdoc} - */ - public function listTables() - { - $currentDatabase = $this->_conn->getDatabase() ?? ''; - - $tableNames = $this->listTableNames(); - - // Get all column definitions in one database call. - $columnsByTable = $this->getAssetRecordsByTable($this->_platform->getListDatabaseColumnsSQL($currentDatabase)); - - // Get all foreign keys definitions in one database call. - $foreignKeysByTable = $this->getAssetRecordsByTable( - $this->_platform->getListDatabaseForeignKeysSQL($currentDatabase) - ); - - // Get all indexes definitions in one database call. - $indexesByTable = $this->getAssetRecordsByTable($this->_platform->getListDatabaseIndexesSQL($currentDatabase)); - - $tables = []; - foreach ($tableNames as $tableName) { - $unquotedTableName = trim($tableName, '"'); - - $columns = $this->_getPortableTableColumnList($tableName, '', $columnsByTable[$unquotedTableName]); - - $foreignKeys = []; - if (isset($foreignKeysByTable[$unquotedTableName])) { - $foreignKeys = $this->_getPortableTableForeignKeysList($foreignKeysByTable[$unquotedTableName]); - } - - $indexes = []; - if (isset($indexesByTable[$unquotedTableName])) { - $indexes = $this->_getPortableTableIndexesList($indexesByTable[$unquotedTableName], $tableName); - } - - $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); - } - - return $tables; - } - - /** - * Helper method to group a set of asset records by the table name. - * - * @param string $sql An SQL statement to be executed, that contains a TABLE_NAME field for grouping. - * - * @return array>> An associative array with key being the table name, - * and value a simple array of records associated with - * the table. - */ - private function getAssetRecordsByTable(string $sql): array - { - $input = $this->_conn->fetchAllAssociative($sql); - $output = []; - foreach ($input as $record) { - $output[$record['TABLE_NAME']][] = $record; - } - - return $output; - } - /** * {@inheritdoc} */ diff --git a/tests/Functional/Schema/OracleSchemaManagerTest.php b/tests/Functional/Schema/OracleSchemaManagerTest.php index 92ab7020784..9975cecd584 100644 --- a/tests/Functional/Schema/OracleSchemaManagerTest.php +++ b/tests/Functional/Schema/OracleSchemaManagerTest.php @@ -273,7 +273,7 @@ public function testCreateAndListSequences(): void public function testCreateSchemaNumberOfQueriesInvariable(): void { // Create a table. - $this->connection->executeUpdate(<<connection->executeStatement(<<currentQuery; // Create another table. - $this->connection->executeUpdate(<<connection->executeStatement(<<connection->executeUpdate('CREATE UNIQUE INDEX tbl_test_2766_uix_1 ON tbl_test_2766_1 (x_number)'); + $this->connection->executeStatement('CREATE UNIQUE INDEX tbl_test_2766_uix_1 ON tbl_test_2766_1 (x_number)'); // Introspect the db schema again. $preCount = $sqlLoggerStack->currentQuery; From 94a54d6d175fd3c49d4e05eb1d9e8f1a38876489 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 20 Oct 2021 18:00:18 +0200 Subject: [PATCH 04/38] Requested changes - part 2 --- psalm.xml.dist | 4 + src/Platforms/OraclePlatform.php | 125 ++++++++++++++++----------- src/Schema/AbstractSchemaManager.php | 4 +- 3 files changed, 82 insertions(+), 51 deletions(-) diff --git a/psalm.xml.dist b/psalm.xml.dist index a056b84d447..53c16f723d0 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -85,6 +85,10 @@ TODO: remove in 4.0.0 --> + + diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 669e4228248..48d04176cb2 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -433,23 +433,7 @@ protected function _getCreateTableSQL($name, array $columns, array $options = [] public function getListDatabaseIndexesSQL(string $database): string { - $databaseIdentifier = $this->normalizeIdentifier($database); - $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); - - return <<getListIndexesSQL($database); } /** @@ -459,6 +443,31 @@ public function getListDatabaseIndexesSQL(string $database): string */ public function getListTableIndexesSQL($table, $database = null) { + return $this->getListIndexesSQL($database ?? '', $table); + } + + private function getListIndexesSQL(string $database, ?string $table = null): string + { + if ($table === null) { + $databaseIdentifier = $this->normalizeIdentifier($database); + $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + + return <<normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); @@ -655,25 +664,7 @@ private function getAutoincrementIdentifierName(Identifier $table) public function getListDatabaseForeignKeysSQL(string $database): string { - $databaseIdentifier = $this->normalizeIdentifier($database); - $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); - - return <<getListForeignKeysSQL($database); } /** @@ -681,6 +672,33 @@ public function getListDatabaseForeignKeysSQL(string $database): string */ public function getListTableForeignKeysSQL($table) { + return $this->getListForeignKeysSQL('', $table); + } + + private function getListForeignKeysSQL(string $database, ?string $table = null): string + { + if ($table === null) { + $databaseIdentifier = $this->normalizeIdentifier($database); + $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + + return <<normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); @@ -721,18 +739,7 @@ public function getListTableConstraintsSQL($table) public function getListDatabaseColumnsSQL(string $database): string { - $databaseIdentifier = $this->normalizeIdentifier($database); - $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); - - return <<getListColumnsSQL($database); } /** @@ -740,6 +747,26 @@ public function getListDatabaseColumnsSQL(string $database): string */ public function getListTableColumnsSQL($table, $database = null) { + return $this->getListColumnsSQL($database ?? '', $table); + } + + private function getListColumnsSQL(string $database, ?string $table = null): string + { + if ($table === null) { + $databaseIdentifier = $this->normalizeIdentifier($database); + $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + + return <<normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); @@ -748,7 +775,7 @@ public function getListTableColumnsSQL($table, $database = null) $tabColumnsOwnerCondition = ''; $colCommentsOwnerCondition = ''; - if ($database !== null && $database !== '/') { + if ($database !== '' && $database !== '/') { $database = $this->normalizeIdentifier($database); $database = $this->quoteStringLiteral($database->getName()); $tabColumnsTableName = 'all_tab_columns'; diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index 3fa0a7bb0f5..a7f3f90ee30 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -295,9 +295,9 @@ public function listTables() { $tableNames = $this->listTableNames(); - $columnsByTable = []; + $columnsByTable = []; $foreignKeysByTable = []; - $indexesByTable = []; + $indexesByTable = []; if ($this->_platform instanceof DatabaseAsset) { $currentDatabase = $this->_conn->getDatabase() ?? ''; From 15cf5ba6d47b29461cc214ae7ee6b8bd48a45704 Mon Sep 17 00:00:00 2001 From: mondrake Date: Fri, 22 Oct 2021 20:47:19 +0200 Subject: [PATCH 05/38] Requested changes - step 3 --- psalm.xml.dist | 2 +- ...hp => DatabaseIntrospectionSQLBuilder.php} | 2 +- src/Platforms/OraclePlatform.php | 184 ++++++------------ src/Schema/AbstractSchemaManager.php | 6 +- .../Schema/OracleSchemaManagerTest.php | 42 ---- tests/Platforms/OraclePlatformTest.php | 68 ------- 6 files changed, 66 insertions(+), 238 deletions(-) rename src/Platforms/{DatabaseAsset.php => DatabaseIntrospectionSQLBuilder.php} (95%) diff --git a/psalm.xml.dist b/psalm.xml.dist index 53c16f723d0..e7af7750f3c 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -88,7 +88,7 @@ - + diff --git a/src/Platforms/DatabaseAsset.php b/src/Platforms/DatabaseIntrospectionSQLBuilder.php similarity index 95% rename from src/Platforms/DatabaseAsset.php rename to src/Platforms/DatabaseIntrospectionSQLBuilder.php index ebe5cf08317..deff302b803 100644 --- a/src/Platforms/DatabaseAsset.php +++ b/src/Platforms/DatabaseIntrospectionSQLBuilder.php @@ -9,7 +9,7 @@ * @deprecated The methods defined in this interface will be made part of the {@link AbstractPlatform} base class in * the next major release. */ -interface DatabaseAsset +interface DatabaseIntrospectionSQLBuilder { /** * Returns the SQL to list all the columns of all the tables in the database. diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 48d04176cb2..9bf575dacf2 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -30,7 +30,7 @@ /** * OraclePlatform. */ -class OraclePlatform extends AbstractPlatform implements DatabaseAsset +class OraclePlatform extends AbstractPlatform implements DatabaseIntrospectionSQLBuilder { /** * Assertion for Oracle identifiers. @@ -448,11 +448,17 @@ public function getListTableIndexesSQL($table, $database = null) private function getListIndexesSQL(string $database, ?string $table = null): string { - if ($table === null) { - $databaseIdentifier = $this->normalizeIdentifier($database); - $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + $databaseIdentifier = $this->normalizeIdentifier($database); + $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); - return <<normalizeIdentifier($table); + $quotedTableIdentifier = $this->quoteStringLiteral($tableIdentifier->getName()); + $tableCondition = 'AND ind_col.table_name = ' . $quotedTableIdentifier; + } + + return <<normalizeIdentifier($table); - $table = $this->quoteStringLiteral($table->getName()); - - return "SELECT uind_col.index_name AS name, - ( - SELECT uind.index_type - FROM user_indexes uind - WHERE uind.index_name = uind_col.index_name - ) AS type, - decode( - ( - SELECT uind.uniqueness - FROM user_indexes uind - WHERE uind.index_name = uind_col.index_name - ), - 'NONUNIQUE', - 0, - 'UNIQUE', - 1 - ) AS is_unique, - uind_col.column_name AS column_name, - uind_col.column_position AS column_pos, - ( - SELECT ucon.constraint_type - FROM user_constraints ucon - WHERE ucon.index_name = uind_col.index_name - ) AS is_primary - FROM user_ind_columns uind_col - WHERE uind_col.table_name = " . $table . ' - ORDER BY uind_col.column_position ASC'; } /** @@ -672,16 +646,31 @@ public function getListDatabaseForeignKeysSQL(string $database): string */ public function getListTableForeignKeysSQL($table) { - return $this->getListForeignKeysSQL('', $table); + return $this->getListForeignKeysSQL(null, $table); } - private function getListForeignKeysSQL(string $database, ?string $table = null): string + private function getListForeignKeysSQL(?string $database, ?string $table = null): string { - if ($table === null) { - $databaseIdentifier = $this->normalizeIdentifier($database); - $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + $databaseCondition = ''; + $constraintColumnsTable = 'all_cons_columns'; + $constraintTable = 'all_constraints'; + if ($database !== null) { + $databaseIdentifier = $this->normalizeIdentifier($database); + $databaseCondition = 'cols.owner = ' . $this->quoteStringLiteral($databaseIdentifier->getName()); + } - return <<normalizeIdentifier($table); + $tableCondition = ($database === null ? '' : 'AND ') . + 'cols.table_name = ' . $this->quoteStringLiteral($tableIdentifier->getName()); + if ($database === null) { + $constraintColumnsTable = 'user_cons_columns'; + $constraintTable = 'user_constraints'; + } + } + + return <<normalizeIdentifier($table); - $table = $this->quoteStringLiteral($table->getName()); - - return "SELECT alc.constraint_name, - alc.DELETE_RULE, - cols.column_name \"local_column\", - cols.position, - ( - SELECT r_cols.table_name - FROM user_cons_columns r_cols - WHERE alc.r_constraint_name = r_cols.constraint_name - AND r_cols.position = cols.position - ) AS \"references_table\", - ( - SELECT r_cols.column_name - FROM user_cons_columns r_cols - WHERE alc.r_constraint_name = r_cols.constraint_name - AND r_cols.position = cols.position - ) AS \"foreign_column\" - FROM user_cons_columns cols - JOIN user_constraints alc - ON alc.constraint_name = cols.constraint_name - AND alc.constraint_type = 'R' - AND alc.table_name = " . $table . ' - ORDER BY cols.constraint_name ASC, cols.position ASC'; } /** @@ -747,63 +709,39 @@ public function getListDatabaseColumnsSQL(string $database): string */ public function getListTableColumnsSQL($table, $database = null) { - return $this->getListColumnsSQL($database ?? '', $table); + return $this->getListColumnsSQL($database, $table); } - private function getListColumnsSQL(string $database, ?string $table = null): string + private function getListColumnsSQL(?string $database, ?string $table = null): string { - if ($table === null) { - $databaseIdentifier = $this->normalizeIdentifier($database); - $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + $databaseCondition = ''; + $tableColumnsTable = 'all_tab_columns'; + $columnCommentsTable = 'all_col_comments'; + if ($database !== null) { + $databaseIdentifier = $this->normalizeIdentifier($database); + $databaseCondition = 'c.owner = ' . $this->quoteStringLiteral($databaseIdentifier->getName()); + } + + $tableCondition = ''; + if ($table !== null) { + $tableIdentifier = $this->normalizeIdentifier($table); + $tableCondition = ($database === null ? '' : 'AND ') . + 'c.table_name = ' . $this->quoteStringLiteral($tableIdentifier->getName()); + if ($database === null) { + $constraintColumnsTable = 'user_tab_columns'; + $constraintTable = 'user_col_comments'; + } + } - return <<normalizeIdentifier($table); - $table = $this->quoteStringLiteral($table->getName()); - - $tabColumnsTableName = 'user_tab_columns'; - $colCommentsTableName = 'user_col_comments'; - $tabColumnsOwnerCondition = ''; - $colCommentsOwnerCondition = ''; - - if ($database !== '' && $database !== '/') { - $database = $this->normalizeIdentifier($database); - $database = $this->quoteStringLiteral($database->getName()); - $tabColumnsTableName = 'all_tab_columns'; - $colCommentsTableName = 'all_col_comments'; - $tabColumnsOwnerCondition = ' AND c.owner = ' . $database; - $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER'; - } - - return sprintf( - <<<'SQL' -SELECT c.*, - ( - SELECT d.comments - FROM %s d - WHERE d.TABLE_NAME = c.TABLE_NAME%s - AND d.COLUMN_NAME = c.COLUMN_NAME - ) AS comments -FROM %s c -WHERE c.table_name = %s%s -ORDER BY c.column_id -SQL - , - $colCommentsTableName, - $colCommentsOwnerCondition, - $tabColumnsTableName, - $table, - $tabColumnsOwnerCondition - ); } /** diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index a7f3f90ee30..36113f96374 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -8,7 +8,7 @@ use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\DatabaseAsset; +use Doctrine\DBAL\Platforms\DatabaseIntrospectionSQLBuilder; use Doctrine\Deprecations\Deprecation; use Throwable; @@ -299,7 +299,7 @@ public function listTables() $foreignKeysByTable = []; $indexesByTable = []; - if ($this->_platform instanceof DatabaseAsset) { + if ($this->_platform instanceof DatabaseIntrospectionSQLBuilder) { $currentDatabase = $this->_conn->getDatabase() ?? ''; // Get all column definitions in one database call. @@ -320,7 +320,7 @@ public function listTables() $tables = []; foreach ($tableNames as $tableName) { - if ($this->_platform instanceof DatabaseAsset) { + if ($this->_platform instanceof DatabaseIntrospectionSQLBuilder) { $unquotedTableName = trim($tableName, '"'); $columns = $this->_getPortableTableColumnList($tableName, '', $columnsByTable[$unquotedTableName]); diff --git a/tests/Functional/Schema/OracleSchemaManagerTest.php b/tests/Functional/Schema/OracleSchemaManagerTest.php index 9975cecd584..c45b4118706 100644 --- a/tests/Functional/Schema/OracleSchemaManagerTest.php +++ b/tests/Functional/Schema/OracleSchemaManagerTest.php @@ -2,7 +2,6 @@ namespace Doctrine\DBAL\Tests\Functional\Schema; -use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; @@ -269,45 +268,4 @@ public function testCreateAndListSequences(): void "Skipped for uppercase letters are contained in sequences' names. Fix the schema manager in 3.0." ); } - - public function testCreateSchemaNumberOfQueriesInvariable(): void - { - // Create a table. - $this->connection->executeStatement(<<connection->getConfiguration()->setSQLLogger($sqlLoggerStack); - $schema = $this->schemaManager->createSchema(); - $firstQueryCount = $sqlLoggerStack->currentQuery; - - // Create another table. - $this->connection->executeStatement(<<connection->executeStatement('CREATE UNIQUE INDEX tbl_test_2766_uix_1 ON tbl_test_2766_1 (x_number)'); - - // Introspect the db schema again. - $preCount = $sqlLoggerStack->currentQuery; - $schema = $this->schemaManager->createSchema(); - $secondQueryCount = $sqlLoggerStack->currentQuery - $preCount; - - // The number of queries needed to execute createSchema should be the same - // regardless of additional tables added. - self::assertSame($firstQueryCount, $secondQueryCount); - } } diff --git a/tests/Platforms/OraclePlatformTest.php b/tests/Platforms/OraclePlatformTest.php index 3ea844df881..c18cf67feef 100644 --- a/tests/Platforms/OraclePlatformTest.php +++ b/tests/Platforms/OraclePlatformTest.php @@ -823,74 +823,6 @@ public function testQuotedTableNames(): void self::assertEquals($createTriggerStatement, $sql[3]); } - /** - * @dataProvider getReturnsGetListTableColumnsSQL - */ - public function testReturnsGetListTableColumnsSQL(?string $database, string $expectedSql): void - { - // note: this assertion is a bit strict, as it compares a full SQL string. - // Should this break in future, then please try to reduce the matching to substring matching while reworking - // the tests - self::assertEquals($expectedSql, $this->platform->getListTableColumnsSQL('"test"', $database)); - } - - /** - * @return mixed[][] - */ - public static function getReturnsGetListTableColumnsSQL(): iterable - { - return [ - [ - null, - <<<'SQL' -SELECT c.*, - ( - SELECT d.comments - FROM user_col_comments d - WHERE d.TABLE_NAME = c.TABLE_NAME - AND d.COLUMN_NAME = c.COLUMN_NAME - ) AS comments -FROM user_tab_columns c -WHERE c.table_name = 'test' -ORDER BY c.column_id -SQL -, - ], - [ - '/', - <<<'SQL' -SELECT c.*, - ( - SELECT d.comments - FROM user_col_comments d - WHERE d.TABLE_NAME = c.TABLE_NAME - AND d.COLUMN_NAME = c.COLUMN_NAME - ) AS comments -FROM user_tab_columns c -WHERE c.table_name = 'test' -ORDER BY c.column_id -SQL -, - ], - [ - 'scott', - <<<'SQL' -SELECT c.*, - ( - SELECT d.comments - FROM all_col_comments d - WHERE d.TABLE_NAME = c.TABLE_NAME AND d.OWNER = c.OWNER - AND d.COLUMN_NAME = c.COLUMN_NAME - ) AS comments -FROM all_tab_columns c -WHERE c.table_name = 'test' AND c.owner = 'SCOTT' -ORDER BY c.column_id -SQL -, - ], - ]; - } - protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(): string { return 'CONSTRAINT "select" UNIQUE (foo)'; From b002f73ade1fe76850ba02925c89dea1414e6515 Mon Sep 17 00:00:00 2001 From: mondrake Date: Sat, 23 Oct 2021 22:53:27 +0200 Subject: [PATCH 06/38] Requested changes - step 4 --- .../DatabaseIntrospectionSQLBuilder.php | 4 +- src/Platforms/OraclePlatform.php | 41 +++++---- src/Platforms/SqlitePlatform.php | 83 +++++++++++++++++-- src/Schema/AbstractSchemaManager.php | 18 ++-- 4 files changed, 114 insertions(+), 32 deletions(-) diff --git a/src/Platforms/DatabaseIntrospectionSQLBuilder.php b/src/Platforms/DatabaseIntrospectionSQLBuilder.php index deff302b803..6a353951ed9 100644 --- a/src/Platforms/DatabaseIntrospectionSQLBuilder.php +++ b/src/Platforms/DatabaseIntrospectionSQLBuilder.php @@ -3,8 +3,8 @@ namespace Doctrine\DBAL\Platforms; /** - * Database asset interface. - * Interface that DBAL platforms can implement when all database assets can be retrieved with a single metadata query. + * Database introspection SQL builder interface. + * Interface that DBAL platforms can implement when all database objects can be retrieved with a single metadata query. * * @deprecated The methods defined in this interface will be made part of the {@link AbstractPlatform} base class in * the next major release. diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 9bf575dacf2..b7718fbbb31 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -443,23 +443,32 @@ public function getListDatabaseIndexesSQL(string $database): string */ public function getListTableIndexesSQL($table, $database = null) { - return $this->getListIndexesSQL($database ?? '', $table); + return $this->getListIndexesSQL($database, $table); } - private function getListIndexesSQL(string $database, ?string $table = null): string + private function getListIndexesSQL(?string $database, ?string $table = null): string { - $databaseIdentifier = $this->normalizeIdentifier($database); - $quotedDatabaseIdentifier = $this->quoteStringLiteral($databaseIdentifier->getName()); + $databaseCondition = ''; + $indexesTable = 'all_indexes'; + $constraintsTable = 'all_constraints'; + if ($database !== null) { + $databaseIdentifier = $this->normalizeIdentifier($database); + $databaseCondition = 'ind_col.index_owner = ' . $this->quoteStringLiteral($databaseIdentifier->getName()); + } $tableCondition = ''; if ($table !== null) { - $tableIdentifier = $this->normalizeIdentifier($table); - $quotedTableIdentifier = $this->quoteStringLiteral($tableIdentifier->getName()); - $tableCondition = 'AND ind_col.table_name = ' . $quotedTableIdentifier; + $tableIdentifier = $this->normalizeIdentifier($table); + $tableCondition = ($database === null ? '' : 'AND ') . + 'ind_col.table_name = ' . $this->quoteStringLiteral($tableIdentifier->getName()); + if ($database === null) { + $indexesTable = 'user_indexes'; + $constraintsTable = 'user_constraints'; + } } return <<normalizeIdentifier($database); $databaseCondition = 'cols.owner = ' . $this->quoteStringLiteral($databaseIdentifier->getName()); @@ -666,12 +675,12 @@ private function getListForeignKeysSQL(?string $database, ?string $table = null) 'cols.table_name = ' . $this->quoteStringLiteral($tableIdentifier->getName()); if ($database === null) { $constraintColumnsTable = 'user_cons_columns'; - $constraintTable = 'user_constraints'; + $constraintsTable = 'user_constraints'; } } return <<getListColumnsSQL($database); + } + /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { - $table = str_replace('.', '__', $table); + return $this->getListColumnsSQL($database, $table); + } + + private function getListColumnsSQL(?string $database, ?string $table = null): string + { + $tableCondition = ''; + if ($table !== null) { + $tableIdentifier = str_replace('.', '__', $table); + $tableCondition = 'AND m.name = ' . $this->quoteStringLiteral($tableIdentifier); + } + + return <<quoteStringLiteral($table)); + public function getListDatabaseIndexesSQL(string $database): string + { + return $this->getListIndexesSQL($database); } /** @@ -476,9 +504,27 @@ public function getListTableColumnsSQL($table, $database = null) */ public function getListTableIndexesSQL($table, $database = null) { - $table = str_replace('.', '__', $table); + return $this->getListIndexesSQL($database, $table); + } + + private function getListIndexesSQL(?string $database, ?string $table = null): string + { + $tableCondition = ''; + if ($table !== null) { + $tableIdentifier = str_replace('.', '__', $table); + $tableCondition = 'AND m.name = ' . $this->quoteStringLiteral($tableIdentifier); + } - return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table)); + return <<getListForeignKeysSQL($database); + } + /** * @param string $table * @param string|null $database @@ -865,9 +916,27 @@ public function getCreateTableSQL(Table $table, $createFlags = null) */ public function getListTableForeignKeysSQL($table, $database = null) { - $table = str_replace('.', '__', $table); + return $this->getListForeignKeysSQL(null, $table); + } + + private function getListForeignKeysSQL(?string $database, ?string $table = null): string + { + $tableCondition = ''; + if ($table !== null) { + $tableIdentifier = str_replace('.', '__', $table); + $tableCondition = 'AND m.name = ' . $this->quoteStringLiteral($tableIdentifier); + } - return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table)); + return <<_conn->getDatabase() ?? ''; // Get all column definitions in one database call. - $columnsByTable = $this->getAssetRecordsByTable( + $columnsByTable = $this->getObjectRecordsByTable( $this->_platform->getListDatabaseColumnsSQL($currentDatabase) ); // Get all foreign keys definitions in one database call. - $foreignKeysByTable = $this->getAssetRecordsByTable( + $foreignKeysByTable = $this->getObjectRecordsByTable( $this->_platform->getListDatabaseForeignKeysSQL($currentDatabase) ); // Get all indexes definitions in one database call. - $indexesByTable = $this->getAssetRecordsByTable( + $indexesByTable = $this->getObjectRecordsByTable( $this->_platform->getListDatabaseIndexesSQL($currentDatabase) ); } @@ -345,20 +346,23 @@ public function listTables() } /** - * Helper method to group a set of asset records by the table name. + * Helper method to group a set of object records by the table name. * - * @param string $sql An SQL statement to be executed, that contains a TABLE_NAME field for grouping. + * @param string $sql An SQL statement to be executed, whose first field is used for grouping. It is up to the + * platform to ensure the first field contains the table name. * * @return array>> An associative array with key being the table name, * and value a simple array of records associated with * the table. */ - protected function getAssetRecordsByTable(string $sql): array + private function getObjectRecordsByTable(string $sql): array { $input = $this->_conn->fetchAllAssociative($sql); $output = []; foreach ($input as $record) { - $output[$record['TABLE_NAME']][] = $record; + $tableName = array_key_first($record); + assert(is_string($tableName)); + $output[$record[$tableName]][] = $record; } return $output; From 1bd2b6f3d51e82db8dc84590160bd4a9dcccf2b0 Mon Sep 17 00:00:00 2001 From: mondrake Date: Mon, 25 Oct 2021 16:30:39 +0200 Subject: [PATCH 07/38] Requested changes - step 5 --- src/Platforms/OraclePlatform.php | 175 ++++++++++++++++----------- src/Platforms/SqlitePlatform.php | 83 ++----------- src/Schema/AbstractSchemaManager.php | 85 ++++++++----- 3 files changed, 164 insertions(+), 179 deletions(-) diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index b7718fbbb31..9fadaeaf95f 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -15,6 +15,7 @@ use InvalidArgumentException; use function array_merge; +use function assert; use function count; use function explode; use function func_get_arg; @@ -448,39 +449,48 @@ public function getListTableIndexesSQL($table, $database = null) private function getListIndexesSQL(?string $database, ?string $table = null): string { - $databaseCondition = ''; - $indexesTable = 'all_indexes'; - $constraintsTable = 'all_constraints'; - if ($database !== null) { - $databaseIdentifier = $this->normalizeIdentifier($database); - $databaseCondition = 'ind_col.index_owner = ' . $this->quoteStringLiteral($databaseIdentifier->getName()); - } - - $tableCondition = ''; - if ($table !== null) { - $tableIdentifier = $this->normalizeIdentifier($table); - $tableCondition = ($database === null ? '' : 'AND ') . - 'ind_col.table_name = ' . $this->quoteStringLiteral($tableIdentifier->getName()); - if ($database === null) { - $indexesTable = 'user_indexes'; - $constraintsTable = 'user_constraints'; - } - } + $conditions = []; - return <<quoteStringLiteral( + $this->normalizeIdentifier($database)->getName() + ); + if (isset($table)) { + $conditions[] = 'ind_col.table_name = ' . $this->quoteStringLiteral( + $this->normalizeIdentifier($table)->getName() + ); + } + } else { + $sql .= <<quoteStringLiteral( + $this->normalizeIdentifier($table)->getName() + ); + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + $sql .= ' ORDER BY ind_col.table_name, ind_col.index_name, ind_col.column_position'; + + return $sql; } /** @@ -660,41 +670,54 @@ public function getListTableForeignKeysSQL($table) private function getListForeignKeysSQL(?string $database, ?string $table = null): string { - $databaseCondition = ''; - $constraintColumnsTable = 'all_cons_columns'; - $constraintsTable = 'all_constraints'; - if ($database !== null) { - $databaseIdentifier = $this->normalizeIdentifier($database); - $databaseCondition = 'cols.owner = ' . $this->quoteStringLiteral($databaseIdentifier->getName()); - } + $conditions = []; - $tableCondition = ''; - if ($table !== null) { - $tableIdentifier = $this->normalizeIdentifier($table); - $tableCondition = ($database === null ? '' : 'AND ') . - 'cols.table_name = ' . $this->quoteStringLiteral($tableIdentifier->getName()); - if ($database === null) { - $constraintColumnsTable = 'user_cons_columns'; - $constraintsTable = 'user_constraints'; - } - } - - return <<quoteStringLiteral( + $this->normalizeIdentifier($database)->getName() + ); + if (isset($table)) { + $conditions[] = 'cols.table_name = ' . $this->quoteStringLiteral( + $this->normalizeIdentifier($table)->getName() + ); + } + } else { + assert(isset($table)); + $sql .= <<quoteStringLiteral( + $this->normalizeIdentifier($table)->getName() + ); + } + + $conditions[] = "alc.constraint_type = 'R'"; + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + $sql .= ' ORDER BY cols.table_name, cols.constraint_name, cols.position'; + + return $sql; } /** @@ -723,34 +746,40 @@ public function getListTableColumnsSQL($table, $database = null) private function getListColumnsSQL(?string $database, ?string $table = null): string { - $databaseCondition = ''; - $tableColumnsTable = 'all_tab_columns'; - $columnCommentsTable = 'all_col_comments'; - if ($database !== null) { - $databaseIdentifier = $this->normalizeIdentifier($database); - $databaseCondition = 'c.owner = ' . $this->quoteStringLiteral($databaseIdentifier->getName()); - } + $conditions = []; - $tableCondition = ''; - if ($table !== null) { - $tableIdentifier = $this->normalizeIdentifier($table); - $tableCondition = ($database === null ? '' : 'AND ') . - 'c.table_name = ' . $this->quoteStringLiteral($tableIdentifier->getName()); - if ($database === null) { - $constraintColumnsTable = 'user_tab_columns'; - $constraintTable = 'user_col_comments'; - } - } + $sql = 'SELECT c.table_name, c.*, d.comments AS comments '; - return <<quoteStringLiteral( + $this->normalizeIdentifier($database)->getName() + ); + if (isset($table)) { + $conditions[] = 'c.table_name = ' . $this->quoteStringLiteral( + $this->normalizeIdentifier($table)->getName() + ); + } + } else { + assert(isset($table)); + $sql .= <<quoteStringLiteral( + $this->normalizeIdentifier($table)->getName() + ); + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + $sql .= ' ORDER BY c.table_name, c.column_id'; + + return $sql; } /** diff --git a/src/Platforms/SqlitePlatform.php b/src/Platforms/SqlitePlatform.php index ebece853b4e..c197f4f33fe 100644 --- a/src/Platforms/SqlitePlatform.php +++ b/src/Platforms/SqlitePlatform.php @@ -37,7 +37,7 @@ * * @todo Rename: SQLitePlatform */ -class SqlitePlatform extends AbstractPlatform implements DatabaseIntrospectionSQLBuilder +class SqlitePlatform extends AbstractPlatform { /** * {@inheritDoc} @@ -461,42 +461,14 @@ public function getListTableConstraintsSQL($table) ); } - public function getListDatabaseColumnsSQL(string $database): string - { - return $this->getListColumnsSQL($database); - } - /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { - return $this->getListColumnsSQL($database, $table); - } - - private function getListColumnsSQL(?string $database, ?string $table = null): string - { - $tableCondition = ''; - if ($table !== null) { - $tableIdentifier = str_replace('.', '__', $table); - $tableCondition = 'AND m.name = ' . $this->quoteStringLiteral($tableIdentifier); - } - - return <<getListIndexesSQL($database); + return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table)); } /** @@ -504,27 +476,9 @@ public function getListDatabaseIndexesSQL(string $database): string */ public function getListTableIndexesSQL($table, $database = null) { - return $this->getListIndexesSQL($database, $table); - } - - private function getListIndexesSQL(?string $database, ?string $table = null): string - { - $tableCondition = ''; - if ($table !== null) { - $tableIdentifier = str_replace('.', '__', $table); - $tableCondition = 'AND m.name = ' . $this->quoteStringLiteral($tableIdentifier); - } + $table = str_replace('.', '__', $table); - return <<quoteStringLiteral($table)); } /** @@ -903,11 +857,6 @@ public function getCreateTableSQL(Table $table, $createFlags = null) return parent::getCreateTableSQL($table, $createFlags); } - public function getListDatabaseForeignKeysSQL(string $database): string - { - return $this->getListForeignKeysSQL($database); - } - /** * @param string $table * @param string|null $database @@ -916,27 +865,9 @@ public function getListDatabaseForeignKeysSQL(string $database): string */ public function getListTableForeignKeysSQL($table, $database = null) { - return $this->getListForeignKeysSQL(null, $table); - } - - private function getListForeignKeysSQL(?string $database, ?string $table = null): string - { - $tableCondition = ''; - if ($table !== null) { - $tableIdentifier = str_replace('.', '__', $table); - $tableCondition = 'AND m.name = ' . $this->quoteStringLiteral($tableIdentifier); - } + $table = str_replace('.', '__', $table); - return <<quoteStringLiteral($table)); } /** diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index 015367409e0..b5295b9e0c4 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -296,55 +296,80 @@ public function listTables() { $tableNames = $this->listTableNames(); - $columnsByTable = []; - $foreignKeysByTable = []; - $indexesByTable = []; + $tables = []; if ($this->_platform instanceof DatabaseIntrospectionSQLBuilder) { - $currentDatabase = $this->_conn->getDatabase() ?? ''; - - // Get all column definitions in one database call. - $columnsByTable = $this->getObjectRecordsByTable( - $this->_platform->getListDatabaseColumnsSQL($currentDatabase) - ); - - // Get all foreign keys definitions in one database call. - $foreignKeysByTable = $this->getObjectRecordsByTable( - $this->_platform->getListDatabaseForeignKeysSQL($currentDatabase) - ); - - // Get all indexes definitions in one database call. - $indexesByTable = $this->getObjectRecordsByTable( - $this->_platform->getListDatabaseIndexesSQL($currentDatabase) - ); - } + $objectsByTable = $this->getDatabaseObjectsRecordsByTable(); - $tables = []; - foreach ($tableNames as $tableName) { - if ($this->_platform instanceof DatabaseIntrospectionSQLBuilder) { + foreach ($tableNames as $tableName) { $unquotedTableName = trim($tableName, '"'); - $columns = $this->_getPortableTableColumnList($tableName, '', $columnsByTable[$unquotedTableName]); + $columns = $this->_getPortableTableColumnList( + $tableName, + '', + $objectsByTable['columns'][$unquotedTableName] + ); $foreignKeys = []; - if (isset($foreignKeysByTable[$unquotedTableName])) { - $foreignKeys = $this->_getPortableTableForeignKeysList($foreignKeysByTable[$unquotedTableName]); + if (isset($objectsByTable['foreignKeys'][$unquotedTableName])) { + $foreignKeys = $this->_getPortableTableForeignKeysList( + $objectsByTable['foreignKeys'][$unquotedTableName] + ); } $indexes = []; - if (isset($indexesByTable[$unquotedTableName])) { - $indexes = $this->_getPortableTableIndexesList($indexesByTable[$unquotedTableName], $tableName); + if (isset($objectsByTable['indexes'][$unquotedTableName])) { + $indexes = $this->_getPortableTableIndexesList( + $objectsByTable['indexes'][$unquotedTableName], + $tableName + ); } $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); - } else { - $tables[] = $this->listTableDetails($tableName); } + + return $tables; + } + + foreach ($tableNames as $tableName) { + $tables[] = $this->listTableDetails($tableName); } return $tables; } + /** + * Helper method to group a set of object records by the table name. + * + * @return array>>> An associative array with key being + * the object type, and value a simple array of records associated with the object type. + */ + private function getDatabaseObjectsRecordsByTable(): array + { + assert($this->_platform instanceof DatabaseIntrospectionSQLBuilder); + + $currentDatabase = $this->_conn->getDatabase() ?? ''; + + $objectsByTable = []; + + // Get all column definitions in one database call. + $objectsByTable['columns'] = $this->getObjectRecordsByTable( + $this->_platform->getListDatabaseColumnsSQL($currentDatabase) + ); + + // Get all foreign keys definitions in one database call. + $objectsByTable['foreignKeys'] = $this->getObjectRecordsByTable( + $this->_platform->getListDatabaseForeignKeysSQL($currentDatabase) + ); + + // Get all indexes definitions in one database call. + $objectsByTable['indexes'] = $this->getObjectRecordsByTable( + $this->_platform->getListDatabaseIndexesSQL($currentDatabase) + ); + + return $objectsByTable; + } + /** * Helper method to group a set of object records by the table name. * From dccb03192f9dcb6d008b430b573278ff1c05b9fc Mon Sep 17 00:00:00 2001 From: mondrake Date: Mon, 25 Oct 2021 20:04:04 +0200 Subject: [PATCH 08/38] Requested changes - step 6 --- src/Platforms/OraclePlatform.php | 42 ++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 9fadaeaf95f..3ed6ea5543a 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -444,6 +444,13 @@ public function getListDatabaseIndexesSQL(string $database): string */ public function getListTableIndexesSQL($table, $database = null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4882', + '%s is deprecated, call getListDatabaseIndexesSQL() instead.', + __METHOD__ + ); + return $this->getListIndexesSQL($database, $table); } @@ -451,7 +458,7 @@ private function getListIndexesSQL(?string $database, ?string $table = null): st { $conditions = []; - $sql = <<quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); @@ -476,7 +484,7 @@ private function getListIndexesSQL(?string $database, ?string $table = null): st ); } } else { - $sql .= <<getListForeignKeysSQL(null, $table); } @@ -672,7 +687,7 @@ private function getListForeignKeysSQL(?string $database, ?string $table = null) { $conditions = []; - $sql = <<quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); @@ -700,13 +716,14 @@ private function getListForeignKeysSQL(?string $database, ?string $table = null) } } else { assert(isset($table)); - $sql .= <<quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); @@ -741,6 +758,13 @@ public function getListDatabaseColumnsSQL(string $database): string */ public function getListTableColumnsSQL($table, $database = null) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/4882', + '%s is deprecated, call getListDatabaseColumnsSQL() instead.', + __METHOD__ + ); + return $this->getListColumnsSQL($database, $table); } @@ -751,11 +775,12 @@ private function getListColumnsSQL(?string $database, ?string $table = null): st $sql = 'SELECT c.table_name, c.*, d.comments AS comments '; if (isset($database)) { - $sql .= <<quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); @@ -766,11 +791,12 @@ private function getListColumnsSQL(?string $database, ?string $table = null): st } } else { assert(isset($table)); - $sql .= <<quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); From 29e11040ca55ff5ea124062be66c9acd19751617 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 27 Oct 2021 09:06:28 +0200 Subject: [PATCH 09/38] Requested changes - step 7 --- src/Platforms/OraclePlatform.php | 17 +++++++---------- src/Schema/AbstractSchemaManager.php | 18 ++++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 3ed6ea5543a..3357fea7df6 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -15,7 +15,6 @@ use InvalidArgumentException; use function array_merge; -use function assert; use function count; use function explode; use function func_get_arg; @@ -468,7 +467,7 @@ private function getListIndexesSQL(?string $database, ?string $table = null): st con.constraint_type AS is_primary SQL; - if (isset($database)) { + if ($database !== null) { $sql .= <<<'SQL' FROM all_ind_columns ind_col LEFT JOIN all_indexes ind ON ind.owner = ind_col.index_owner AND ind.index_name = ind_col.index_name @@ -478,7 +477,7 @@ private function getListIndexesSQL(?string $database, ?string $table = null): st $conditions[] = 'ind_col.index_owner = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); - if (isset($table)) { + if ($table !== null) { $conditions[] = 'ind_col.table_name = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); @@ -489,7 +488,7 @@ private function getListIndexesSQL(?string $database, ?string $table = null): st LEFT JOIN user_indexes ind ON ind.owner = ind_col.index_owner AND ind.index_name = ind_col.index_name LEFT JOIN user_constraints con ON con.owner = ind_col.index_owner AND con.index_name = ind_col.index_name SQL; - assert(isset($table)); + $conditions[] = 'ind_col.table_name = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); @@ -697,7 +696,7 @@ private function getListForeignKeysSQL(?string $database, ?string $table = null) r_cols.column_name "foreign_column" SQL; - if (isset($database)) { + if ($database !== null) { $sql .= <<<'SQL' FROM all_cons_columns cols LEFT JOIN all_constraints alc ON alc.owner = cols.owner AND alc.constraint_name = cols.constraint_name @@ -709,13 +708,12 @@ private function getListForeignKeysSQL(?string $database, ?string $table = null) $conditions[] = 'cols.owner = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); - if (isset($table)) { + if ($table !== null) { $conditions[] = 'cols.table_name = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); } } else { - assert(isset($table)); $sql .= <<<'SQL' FROM user_cons_columns cols LEFT JOIN user_constraints alc ON alc.owner = cols.owner AND alc.constraint_name = cols.constraint_name @@ -774,7 +772,7 @@ private function getListColumnsSQL(?string $database, ?string $table = null): st $sql = 'SELECT c.table_name, c.*, d.comments AS comments '; - if (isset($database)) { + if ($database !== null) { $sql .= <<<'SQL' FROM all_tab_columns c LEFT JOIN all_col_comments d ON d.OWNER = c.OWNER AND d.TABLE_NAME = c.TABLE_NAME AND @@ -784,13 +782,12 @@ private function getListColumnsSQL(?string $database, ?string $table = null): st $conditions[] = 'c.owner = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); - if (isset($table)) { + if ($table !== null) { $conditions[] = 'c.table_name = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); } } else { - assert(isset($table)); $sql .= <<<'SQL' FROM user_tab_columns c LEFT JOIN user_col_comments d ON d.OWNER = c.OWNER AND d.TABLE_NAME = c.TABLE_NAME AND diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index b5295b9e0c4..d01a479072c 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -299,7 +299,7 @@ public function listTables() $tables = []; if ($this->_platform instanceof DatabaseIntrospectionSQLBuilder) { - $objectsByTable = $this->getDatabaseObjectsRecordsByTable(); + $objectsByTable = $this->getDatabaseObjectRecordsByTable($this->_platform); foreach ($tableNames as $tableName) { $unquotedTableName = trim($tableName, '"'); @@ -341,30 +341,28 @@ public function listTables() /** * Helper method to group a set of object records by the table name. * - * @return array>>> An associative array with key being + * @return array>>> An associative array with key being * the object type, and value a simple array of records associated with the object type. */ - private function getDatabaseObjectsRecordsByTable(): array + private function getDatabaseObjectRecordsByTable(DatabaseIntrospectionSQLBuilder $platform): array { - assert($this->_platform instanceof DatabaseIntrospectionSQLBuilder); - - $currentDatabase = $this->_conn->getDatabase() ?? ''; + $currentDatabase = $this->_conn->getDatabase(); $objectsByTable = []; // Get all column definitions in one database call. $objectsByTable['columns'] = $this->getObjectRecordsByTable( - $this->_platform->getListDatabaseColumnsSQL($currentDatabase) + $platform->getListDatabaseColumnsSQL($currentDatabase) ); // Get all foreign keys definitions in one database call. $objectsByTable['foreignKeys'] = $this->getObjectRecordsByTable( - $this->_platform->getListDatabaseForeignKeysSQL($currentDatabase) + $platform->getListDatabaseForeignKeysSQL($currentDatabase) ); // Get all indexes definitions in one database call. $objectsByTable['indexes'] = $this->getObjectRecordsByTable( - $this->_platform->getListDatabaseIndexesSQL($currentDatabase) + $platform->getListDatabaseIndexesSQL($currentDatabase) ); return $objectsByTable; @@ -376,7 +374,7 @@ private function getDatabaseObjectsRecordsByTable(): array * @param string $sql An SQL statement to be executed, whose first field is used for grouping. It is up to the * platform to ensure the first field contains the table name. * - * @return array>> An associative array with key being the table name, + * @return array>> An associative array with key being the table name, * and value a simple array of records associated with * the table. */ From 8907aad5311c3abab091991b00e891124d95aaa9 Mon Sep 17 00:00:00 2001 From: mondrake Date: Thu, 28 Oct 2021 10:14:56 +0200 Subject: [PATCH 10/38] Requested changes - step 8 --- src/Platforms/OraclePlatform.php | 100 +++++++++++++++---------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 3357fea7df6..a345b48542a 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -458,44 +458,44 @@ private function getListIndexesSQL(?string $database, ?string $table = null): st $conditions = []; $sql = <<<'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, - ind_col.column_name AS column_name, - ind_col.column_position AS column_pos, - con.constraint_type AS is_primary + 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, + IND_COL.COLUMN_NAME AS COLUMN_NAME, + IND_COL.COLUMN_POSITION AS COLUMN_POS, + CON.CONSTRAINT_TYPE AS IS_PRIMARY SQL; if ($database !== null) { $sql .= <<<'SQL' - FROM all_ind_columns ind_col - LEFT JOIN all_indexes ind ON ind.owner = ind_col.index_owner AND ind.index_name = ind_col.index_name - LEFT JOIN all_constraints con ON con.owner = ind_col.index_owner AND con.index_name = ind_col.index_name + FROM ALL_IND_COLUMNS IND_COL + LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME + 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 = ' . $this->quoteStringLiteral( + $conditions[] = 'IND_COL.INDEX_OWNER = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); if ($table !== null) { - $conditions[] = 'ind_col.table_name = ' . $this->quoteStringLiteral( + $conditions[] = 'IND_COL.TABLE_NAME = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); } } else { $sql .= <<<'SQL' - FROM user_ind_columns ind_col - LEFT JOIN user_indexes ind ON ind.owner = ind_col.index_owner AND ind.index_name = ind_col.index_name - LEFT JOIN user_constraints con ON con.owner = ind_col.index_owner AND con.index_name = ind_col.index_name + FROM USER_IND_COLUMNS IND_COL + LEFT JOIN USER_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME + LEFT JOIN USER_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME SQL; - $conditions[] = 'ind_col.table_name = ' . $this->quoteStringLiteral( + $conditions[] = 'IND_COL.TABLE_NAME = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); } $sql .= ' WHERE ' . implode(' AND ', $conditions); - $sql .= ' ORDER BY ind_col.table_name, ind_col.index_name, ind_col.column_position'; + $sql .= ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION'; return $sql; } @@ -687,50 +687,50 @@ private function getListForeignKeysSQL(?string $database, ?string $table = null) $conditions = []; $sql = <<<'SQL' - SELECT cols.table_name, - alc.constraint_name, - alc.DELETE_RULE, - cols.column_name "local_column", - cols.position, - r_cols.table_name "references_table", - r_cols.column_name "foreign_column" + SELECT COLS.TABLE_NAME, + ALC.CONSTRAINT_NAME, + ALC.DELETE_RULE, + COLS.COLUMN_NAME "local_column", + COLS.POSITION, + R_COLS.TABLE_NAME "references_table", + R_COLS.COLUMN_NAME "foreign_column" SQL; if ($database !== null) { $sql .= <<<'SQL' - FROM all_cons_columns cols - LEFT JOIN all_constraints alc ON alc.owner = cols.owner AND alc.constraint_name = cols.constraint_name - 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 + FROM ALL_CONS_COLUMNS COLS + LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME + 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[] = 'cols.owner = ' . $this->quoteStringLiteral( + $conditions[] = 'COLS.OWNER = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); if ($table !== null) { - $conditions[] = 'cols.table_name = ' . $this->quoteStringLiteral( + $conditions[] = 'COLS.TABLE_NAME = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); } } else { $sql .= <<<'SQL' - FROM user_cons_columns cols - LEFT JOIN user_constraints alc ON alc.owner = cols.owner AND alc.constraint_name = cols.constraint_name - LEFT JOIN user_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 + FROM USER_CONS_COLUMNS COLS + LEFT JOIN USER_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME + LEFT JOIN USER_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[] = 'cols.table_name = ' . $this->quoteStringLiteral( + $conditions[] = 'COLS.TABLE_NAME = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); } - $conditions[] = "alc.constraint_type = 'R'"; + $conditions[] = "ALC.CONSTRAINT_TYPE = 'R'"; $sql .= ' WHERE ' . implode(' AND ', $conditions); - $sql .= ' ORDER BY cols.table_name, cols.constraint_name, cols.position'; + $sql .= ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION'; return $sql; } @@ -770,37 +770,37 @@ private function getListColumnsSQL(?string $database, ?string $table = null): st { $conditions = []; - $sql = 'SELECT c.table_name, c.*, d.comments AS comments '; + $sql = 'SELECT C.TABLE_NAME, C.*, D.COMMENTS AS COMMENTS '; if ($database !== null) { $sql .= <<<'SQL' - FROM all_tab_columns c - LEFT JOIN all_col_comments d ON d.OWNER = c.OWNER AND d.TABLE_NAME = c.TABLE_NAME AND - d.COLUMN_NAME = c.COLUMN_NAME + FROM ALL_TAB_COLUMNS C + LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND + D.COLUMN_NAME = C.COLUMN_NAME SQL; - $conditions[] = 'c.owner = ' . $this->quoteStringLiteral( + $conditions[] = 'C.OWNER = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); if ($table !== null) { - $conditions[] = 'c.table_name = ' . $this->quoteStringLiteral( + $conditions[] = 'C.TABLE_NAME = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); } } else { $sql .= <<<'SQL' - FROM user_tab_columns c - LEFT JOIN user_col_comments d ON d.OWNER = c.OWNER AND d.TABLE_NAME = c.TABLE_NAME AND - d.COLUMN_NAME = c.COLUMN_NAME + FROM USER_TAB_COLUMNS C + LEFT JOIN USER_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND + D.COLUMN_NAME = C.COLUMN_NAME SQL; - $conditions[] = 'c.table_name = ' . $this->quoteStringLiteral( + $conditions[] = 'C.TABLE_NAME = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($table)->getName() ); } $sql .= ' WHERE ' . implode(' AND ', $conditions); - $sql .= ' ORDER BY c.table_name, c.column_id'; + $sql .= ' ORDER BY C.TABLE_NAME, C.COLUMN_ID'; return $sql; } From 7b1266fb03d2399dfe7d3dce93f0c48db9292997 Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 31 Oct 2021 14:37:46 +0100 Subject: [PATCH 11/38] 1 --- src/Schema/AbstractSchemaManager.php | 21 ++++++ src/Schema/OracleSchemaManager.php | 98 ++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index fe56e4a56bb..e8c8089372f 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -309,6 +309,27 @@ public function listTables() return $tables; } + /** + * Helper method to group a set of object records by the table name. + * + * @param string $sql An SQL statement to be executed. + * @param string $groupingField The name of the resultset field to use for grouping. + * + * @return array>> An associative array with key being the table name, + * and value a simple array of records associated with + * the table. + */ + protected function getObjectRecordsByTable(string $sql, string $groupingField): array + { + $input = $this->_conn->fetchAllAssociative($sql); + $output = []; + foreach ($input as $record) { + $output[$record[$groupingField]][] = $record; + } + + return $output; + } + /** * @param string $name * diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 63209c93622..fe8d4557b12 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -320,6 +320,104 @@ private function getQuotedIdentifierName($identifier) return $identifier; } + /** + * {@inheritdoc} + */ + public function listTables() + { + $currentDatabase = $this->_conn->getDatabase(); + $tableNames = $this->listTableNames(); + + $objectsByTable = []; + + // Get all column definitions in one database call. + $sql = <<<'SQL' + SELECT C.*, D.COMMENTS AS COMMENTS + FROM ALL_TAB_COLUMNS C + LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND + D.COLUMN_NAME = C.COLUMN_NAME + WHERE C.OWNER = +SQL; + + $sql .= $this->quoteStringLiteral($this->normalizeIdentifier($currentDatabase)->getName()); + $sql .= ' ORDER BY C.TABLE_NAME, C.COLUMN_ID'; + + $objectsByTable['columns'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + + // Get all foreign keys definitions in one database call. + $sql = <<<'SQL' + SELECT COLS.TABLE_NAME, + ALC.CONSTRAINT_NAME, + ALC.DELETE_RULE, + COLS.COLUMN_NAME "local_column", + COLS.POSITION, + R_COLS.TABLE_NAME "references_table", + R_COLS.COLUMN_NAME "foreign_column" + FROM ALL_CONS_COLUMNS COLS + LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME + 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 + WHERE ALC.CONSTRAINT_TYPE = 'R' AND COLS.OWNER = +SQL; + + $sql .= $this->quoteStringLiteral($this->normalizeIdentifier($currentDatabase)->getName()); + $sql .= 'ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION'; + + $objectsByTable['foreignKeys'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + + // Get all indexes definitions in one database call. + $sql = <<<'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, + IND_COL.COLUMN_NAME AS COLUMN_NAME, + IND_COL.COLUMN_POSITION AS COLUMN_POS, + CON.CONSTRAINT_TYPE AS IS_PRIMARY + FROM ALL_IND_COLUMNS IND_COL + LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME + LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME + WHERE IND_COL.INDEX_OWNER = +SQL; + + $sql .= $this->quoteStringLiteral($this->normalizeIdentifier($currentDatabase)->getName()); + $sql .= ' ORDER BY iND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION'; + + $objectsByTable['indexes'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + + $tables = []; + + foreach ($tableNames as $tableName) { + $unquotedTableName = trim($tableName, '"'); + + $columns = $this->_getPortableTableColumnList( + $tableName, + '', + $objectsByTable['columns'][$unquotedTableName] + ); + + $foreignKeys = []; + if (isset($objectsByTable['foreignKeys'][$unquotedTableName])) { + $foreignKeys = $this->_getPortableTableForeignKeysList( + $objectsByTable['foreignKeys'][$unquotedTableName] + ); + } + + $indexes = []; + if (isset($objectsByTable['indexes'][$unquotedTableName])) { + $indexes = $this->_getPortableTableIndexesList( + $objectsByTable['indexes'][$unquotedTableName], + $tableName + ); + } + + $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); + } + + return $tables; + } + /** * {@inheritdoc} */ From f4f62873599e896a14422830eed1e8854f7ba31a Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 31 Oct 2021 14:38:46 +0100 Subject: [PATCH 12/38] 2 --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 2ac9c8639cd..c1ded7a17ab 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -4,7 +4,7 @@ name: "Continuous Integration" on: pull_request: branches: - - "*.x" + - "fix*" push: branches: - "*.x" From dc69211492afa24ae6fb535b088bdedba80c3d6d Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 31 Oct 2021 14:46:59 +0100 Subject: [PATCH 13/38] 3 --- src/Schema/OracleSchemaManager.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index fe8d4557b12..309da8ddc77 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -339,7 +339,7 @@ public function listTables() WHERE C.OWNER = SQL; - $sql .= $this->quoteStringLiteral($this->normalizeIdentifier($currentDatabase)->getName()); + $sql .= $this->_platform->quoteStringLiteral($currentDatabase); $sql .= ' ORDER BY C.TABLE_NAME, C.COLUMN_ID'; $objectsByTable['columns'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); @@ -361,7 +361,7 @@ public function listTables() WHERE ALC.CONSTRAINT_TYPE = 'R' AND COLS.OWNER = SQL; - $sql .= $this->quoteStringLiteral($this->normalizeIdentifier($currentDatabase)->getName()); + $sql .= $this->_platform->quoteStringLiteral($currentDatabase); $sql .= 'ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION'; $objectsByTable['foreignKeys'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); @@ -381,7 +381,7 @@ public function listTables() WHERE IND_COL.INDEX_OWNER = SQL; - $sql .= $this->quoteStringLiteral($this->normalizeIdentifier($currentDatabase)->getName()); + $sql .= $this->_platform->quoteStringLiteral($currentDatabase); $sql .= ' ORDER BY iND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION'; $objectsByTable['indexes'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); From a7be87ebfc6214b242a23a09173fcad846d5751f Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 31 Oct 2021 14:56:06 +0100 Subject: [PATCH 14/38] 4 --- .github/workflows/continuous-integration.yml | 2 +- src/Schema/AbstractSchemaManager.php | 2 +- src/Schema/OracleSchemaManager.php | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c1ded7a17ab..2ac9c8639cd 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -4,7 +4,7 @@ name: "Continuous Integration" on: pull_request: branches: - - "fix*" + - "*.x" push: branches: - "*.x" diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index e8c8089372f..7ff7f26f72b 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -312,7 +312,7 @@ public function listTables() /** * Helper method to group a set of object records by the table name. * - * @param string $sql An SQL statement to be executed. + * @param string $sql An SQL statement to be executed. * @param string $groupingField The name of the resultset field to use for grouping. * * @return array>> An associative array with key being the table name, diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 309da8ddc77..bf63de4e22f 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -3,6 +3,7 @@ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Types\Type; @@ -326,6 +327,11 @@ private function getQuotedIdentifierName($identifier) public function listTables() { $currentDatabase = $this->_conn->getDatabase(); + + if ($currentDatabase === null) { + throw new DatabaseObjectNotFoundException(); + } + $tableNames = $this->listTableNames(); $objectsByTable = []; From a9b08f3b6d8fb8078af8eb73240aaa1550ca8c9f Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 31 Oct 2021 15:00:51 +0100 Subject: [PATCH 15/38] 5 --- src/Schema/OracleSchemaManager.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index bf63de4e22f..b665b8ff751 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -3,7 +3,6 @@ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; -use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Types\Type; @@ -329,7 +328,7 @@ public function listTables() $currentDatabase = $this->_conn->getDatabase(); if ($currentDatabase === null) { - throw new DatabaseObjectNotFoundException(); + throw new Exception('Invalid database name.'); } $tableNames = $this->listTableNames(); From 01fc7211693328f153d7badcde6b7477e8d119db Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 31 Oct 2021 15:09:49 +0100 Subject: [PATCH 16/38] Change approach --- src/Schema/AbstractSchemaManager.php | 42 ++--------- src/Schema/OracleSchemaManager.php | 103 +++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 37 deletions(-) diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index ffcad5bbfc4..2d161718eb9 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -349,51 +349,19 @@ public function listTables() /** * Helper method to group a set of object records by the table name. * - * @return array>>> An associative array with key being - * the object type, and value a simple array of records associated with the object type. - */ - private function getDatabaseObjectRecordsByTable(DatabaseIntrospectionSQLBuilder $platform): array - { - $currentDatabase = $this->_conn->getDatabase(); - - $objectsByTable = []; - - // Get all column definitions in one database call. - $objectsByTable['columns'] = $this->getObjectRecordsByTable( - $platform->getListDatabaseColumnsSQL($currentDatabase) - ); - - // Get all foreign keys definitions in one database call. - $objectsByTable['foreignKeys'] = $this->getObjectRecordsByTable( - $platform->getListDatabaseForeignKeysSQL($currentDatabase) - ); - - // Get all indexes definitions in one database call. - $objectsByTable['indexes'] = $this->getObjectRecordsByTable( - $platform->getListDatabaseIndexesSQL($currentDatabase) - ); - - return $objectsByTable; - } - - /** - * Helper method to group a set of object records by the table name. - * - * @param string $sql An SQL statement to be executed, whose first field is used for grouping. It is up to the - * platform to ensure the first field contains the table name. + * @param string $sql An SQL statement to be executed. + * @param string $groupingField The name of the resultset field to use for grouping. * - * @return array>> An associative array with key being the table name, + * @return array>> An associative array with key being the table name, * and value a simple array of records associated with * the table. */ - private function getObjectRecordsByTable(string $sql): array + protected function getObjectRecordsByTable(string $sql, string $groupingField): array { $input = $this->_conn->fetchAllAssociative($sql); $output = []; foreach ($input as $record) { - $tableName = array_key_first($record); - assert(is_string($tableName)); - $output[$record[$tableName]][] = $record; + $output[$record[$groupingField]][] = $record; } return $output; diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 63209c93622..b665b8ff751 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -320,6 +320,109 @@ private function getQuotedIdentifierName($identifier) return $identifier; } + /** + * {@inheritdoc} + */ + public function listTables() + { + $currentDatabase = $this->_conn->getDatabase(); + + if ($currentDatabase === null) { + throw new Exception('Invalid database name.'); + } + + $tableNames = $this->listTableNames(); + + $objectsByTable = []; + + // Get all column definitions in one database call. + $sql = <<<'SQL' + SELECT C.*, D.COMMENTS AS COMMENTS + FROM ALL_TAB_COLUMNS C + LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND + D.COLUMN_NAME = C.COLUMN_NAME + WHERE C.OWNER = +SQL; + + $sql .= $this->_platform->quoteStringLiteral($currentDatabase); + $sql .= ' ORDER BY C.TABLE_NAME, C.COLUMN_ID'; + + $objectsByTable['columns'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + + // Get all foreign keys definitions in one database call. + $sql = <<<'SQL' + SELECT COLS.TABLE_NAME, + ALC.CONSTRAINT_NAME, + ALC.DELETE_RULE, + COLS.COLUMN_NAME "local_column", + COLS.POSITION, + R_COLS.TABLE_NAME "references_table", + R_COLS.COLUMN_NAME "foreign_column" + FROM ALL_CONS_COLUMNS COLS + LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME + 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 + WHERE ALC.CONSTRAINT_TYPE = 'R' AND COLS.OWNER = +SQL; + + $sql .= $this->_platform->quoteStringLiteral($currentDatabase); + $sql .= 'ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION'; + + $objectsByTable['foreignKeys'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + + // Get all indexes definitions in one database call. + $sql = <<<'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, + IND_COL.COLUMN_NAME AS COLUMN_NAME, + IND_COL.COLUMN_POSITION AS COLUMN_POS, + CON.CONSTRAINT_TYPE AS IS_PRIMARY + FROM ALL_IND_COLUMNS IND_COL + LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME + LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME + WHERE IND_COL.INDEX_OWNER = +SQL; + + $sql .= $this->_platform->quoteStringLiteral($currentDatabase); + $sql .= ' ORDER BY iND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION'; + + $objectsByTable['indexes'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + + $tables = []; + + foreach ($tableNames as $tableName) { + $unquotedTableName = trim($tableName, '"'); + + $columns = $this->_getPortableTableColumnList( + $tableName, + '', + $objectsByTable['columns'][$unquotedTableName] + ); + + $foreignKeys = []; + if (isset($objectsByTable['foreignKeys'][$unquotedTableName])) { + $foreignKeys = $this->_getPortableTableForeignKeysList( + $objectsByTable['foreignKeys'][$unquotedTableName] + ); + } + + $indexes = []; + if (isset($objectsByTable['indexes'][$unquotedTableName])) { + $indexes = $this->_getPortableTableIndexesList( + $objectsByTable['indexes'][$unquotedTableName], + $tableName + ); + } + + $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); + } + + return $tables; + } + /** * {@inheritdoc} */ From af06f19af881106d5d710d743aedc75a9baaf4a9 Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 31 Oct 2021 17:02:11 +0100 Subject: [PATCH 17/38] Cleanup --- psalm.xml.dist | 4 - .../DatabaseIntrospectionSQLBuilder.php | 28 -- src/Platforms/OraclePlatform.php | 260 ++++++------------ src/Schema/AbstractSchemaManager.php | 37 --- tests/Platforms/OraclePlatformTest.php | 68 +++++ 5 files changed, 155 insertions(+), 242 deletions(-) delete mode 100644 src/Platforms/DatabaseIntrospectionSQLBuilder.php diff --git a/psalm.xml.dist b/psalm.xml.dist index d08c8c1b22e..57e27061c50 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -85,10 +85,6 @@ TODO: remove in 4.0.0 --> - - diff --git a/src/Platforms/DatabaseIntrospectionSQLBuilder.php b/src/Platforms/DatabaseIntrospectionSQLBuilder.php deleted file mode 100644 index 6a353951ed9..00000000000 --- a/src/Platforms/DatabaseIntrospectionSQLBuilder.php +++ /dev/null @@ -1,28 +0,0 @@ -getListIndexesSQL($database); - } - /** * {@inheritDoc} * @@ -443,61 +438,36 @@ public function getListDatabaseIndexesSQL(string $database): string */ public function getListTableIndexesSQL($table, $database = null) { - Deprecation::trigger( - 'doctrine/dbal', - 'https://github.com/doctrine/dbal/pull/4882', - '%s is deprecated, call getListDatabaseIndexesSQL() instead.', - __METHOD__ - ); - - return $this->getListIndexesSQL($database, $table); - } - - private function getListIndexesSQL(?string $database, ?string $table = null): string - { - $conditions = []; - - $sql = <<<'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, - IND_COL.COLUMN_NAME AS COLUMN_NAME, - IND_COL.COLUMN_POSITION AS COLUMN_POS, - CON.CONSTRAINT_TYPE AS IS_PRIMARY -SQL; - - if ($database !== null) { - $sql .= <<<'SQL' - FROM ALL_IND_COLUMNS IND_COL - LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME - 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 = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($database)->getName() - ); - if ($table !== null) { - $conditions[] = 'IND_COL.TABLE_NAME = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($table)->getName() - ); - } - } else { - $sql .= <<<'SQL' - FROM USER_IND_COLUMNS IND_COL - LEFT JOIN USER_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME - LEFT JOIN USER_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME -SQL; - - $conditions[] = 'IND_COL.TABLE_NAME = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($table)->getName() - ); - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions); - $sql .= ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION'; + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); - return $sql; + return "SELECT uind_col.index_name AS name, + ( + SELECT uind.index_type + FROM user_indexes uind + WHERE uind.index_name = uind_col.index_name + ) AS type, + decode( + ( + SELECT uind.uniqueness + FROM user_indexes uind + WHERE uind.index_name = uind_col.index_name + ), + 'NONUNIQUE', + 0, + 'UNIQUE', + 1 + ) AS is_unique, + uind_col.column_name AS column_name, + uind_col.column_position AS column_pos, + ( + SELECT ucon.constraint_type + FROM user_constraints ucon + WHERE ucon.index_name = uind_col.index_name + ) AS is_primary + FROM user_ind_columns uind_col + WHERE uind_col.table_name = " . $table . ' + ORDER BY uind_col.column_position ASC'; } /** @@ -662,77 +632,36 @@ private function getAutoincrementIdentifierName(Identifier $table) : $identifierName; } - public function getListDatabaseForeignKeysSQL(string $database): string - { - return $this->getListForeignKeysSQL($database); - } - /** * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) { - Deprecation::trigger( - 'doctrine/dbal', - 'https://github.com/doctrine/dbal/pull/4882', - '%s is deprecated, call getListDatabaseForeignKeysSQL() instead.', - __METHOD__ - ); - - return $this->getListForeignKeysSQL(null, $table); - } - - private function getListForeignKeysSQL(?string $database, ?string $table = null): string - { - $conditions = []; - - $sql = <<<'SQL' - SELECT COLS.TABLE_NAME, - ALC.CONSTRAINT_NAME, - ALC.DELETE_RULE, - COLS.COLUMN_NAME "local_column", - COLS.POSITION, - R_COLS.TABLE_NAME "references_table", - R_COLS.COLUMN_NAME "foreign_column" -SQL; - - if ($database !== null) { - $sql .= <<<'SQL' - FROM ALL_CONS_COLUMNS COLS - LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME - 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[] = 'COLS.OWNER = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($database)->getName() - ); - if ($table !== null) { - $conditions[] = 'COLS.TABLE_NAME = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($table)->getName() - ); - } - } else { - $sql .= <<<'SQL' - FROM USER_CONS_COLUMNS COLS - LEFT JOIN USER_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME - LEFT JOIN USER_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[] = 'COLS.TABLE_NAME = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($table)->getName() - ); - } - - $conditions[] = "ALC.CONSTRAINT_TYPE = 'R'"; - - $sql .= ' WHERE ' . implode(' AND ', $conditions); - $sql .= ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION'; + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); - return $sql; + return "SELECT alc.constraint_name, + alc.DELETE_RULE, + cols.column_name \"local_column\", + cols.position, + ( + SELECT r_cols.table_name + FROM user_cons_columns r_cols + WHERE alc.r_constraint_name = r_cols.constraint_name + AND r_cols.position = cols.position + ) AS \"references_table\", + ( + SELECT r_cols.column_name + FROM user_cons_columns r_cols + WHERE alc.r_constraint_name = r_cols.constraint_name + AND r_cols.position = cols.position + ) AS \"foreign_column\" + FROM user_cons_columns cols + JOIN user_constraints alc + ON alc.constraint_name = cols.constraint_name + AND alc.constraint_type = 'R' + AND alc.table_name = " . $table . ' + ORDER BY cols.constraint_name ASC, cols.position ASC'; } /** @@ -746,63 +675,48 @@ public function getListTableConstraintsSQL($table) return 'SELECT * FROM user_constraints WHERE table_name = ' . $table; } - public function getListDatabaseColumnsSQL(string $database): string - { - return $this->getListColumnsSQL($database); - } - /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { - Deprecation::trigger( - 'doctrine/dbal', - 'https://github.com/doctrine/dbal/pull/4882', - '%s is deprecated, call getListDatabaseColumnsSQL() instead.', - __METHOD__ - ); - - return $this->getListColumnsSQL($database, $table); - } - - private function getListColumnsSQL(?string $database, ?string $table = null): string - { - $conditions = []; - - $sql = 'SELECT C.TABLE_NAME, C.*, D.COMMENTS AS COMMENTS '; + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); - if ($database !== null) { - $sql .= <<<'SQL' - FROM ALL_TAB_COLUMNS C - LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND - D.COLUMN_NAME = C.COLUMN_NAME -SQL; + $tabColumnsTableName = 'user_tab_columns'; + $colCommentsTableName = 'user_col_comments'; + $tabColumnsOwnerCondition = ''; + $colCommentsOwnerCondition = ''; - $conditions[] = 'C.OWNER = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($database)->getName() - ); - if ($table !== null) { - $conditions[] = 'C.TABLE_NAME = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($table)->getName() - ); - } - } else { - $sql .= <<<'SQL' - FROM USER_TAB_COLUMNS C - LEFT JOIN USER_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND - D.COLUMN_NAME = C.COLUMN_NAME -SQL; - - $conditions[] = 'C.TABLE_NAME = ' . $this->quoteStringLiteral( - $this->normalizeIdentifier($table)->getName() - ); + if ($database !== null && $database !== '/') { + $database = $this->normalizeIdentifier($database); + $database = $this->quoteStringLiteral($database->getName()); + $tabColumnsTableName = 'all_tab_columns'; + $colCommentsTableName = 'all_col_comments'; + $tabColumnsOwnerCondition = ' AND c.owner = ' . $database; + $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER'; } - $sql .= ' WHERE ' . implode(' AND ', $conditions); - $sql .= ' ORDER BY C.TABLE_NAME, C.COLUMN_ID'; - - return $sql; + return sprintf( + <<<'SQL' +SELECT c.*, + ( + SELECT d.comments + FROM %s d + WHERE d.TABLE_NAME = c.TABLE_NAME%s + AND d.COLUMN_NAME = c.COLUMN_NAME + ) AS comments +FROM %s c +WHERE c.table_name = %s%s +ORDER BY c.column_id +SQL + , + $colCommentsTableName, + $colCommentsOwnerCondition, + $tabColumnsTableName, + $table, + $tabColumnsOwnerCondition + ); } /** diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index 2d161718eb9..7ff7f26f72b 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -8,13 +8,11 @@ use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\DatabaseIntrospectionSQLBuilder; use Doctrine\Deprecations\Deprecation; use Throwable; use function array_filter; use function array_intersect; -use function array_key_first; use function array_map; use function array_values; use function assert; @@ -26,7 +24,6 @@ use function preg_match; use function str_replace; use function strtolower; -use function trim; /** * Base class for schema managers. Schema managers are used to inspect and/or @@ -305,40 +302,6 @@ public function listTables() $tableNames = $this->listTableNames(); $tables = []; - - if ($this->_platform instanceof DatabaseIntrospectionSQLBuilder) { - $objectsByTable = $this->getDatabaseObjectRecordsByTable($this->_platform); - - foreach ($tableNames as $tableName) { - $unquotedTableName = trim($tableName, '"'); - - $columns = $this->_getPortableTableColumnList( - $tableName, - '', - $objectsByTable['columns'][$unquotedTableName] - ); - - $foreignKeys = []; - if (isset($objectsByTable['foreignKeys'][$unquotedTableName])) { - $foreignKeys = $this->_getPortableTableForeignKeysList( - $objectsByTable['foreignKeys'][$unquotedTableName] - ); - } - - $indexes = []; - if (isset($objectsByTable['indexes'][$unquotedTableName])) { - $indexes = $this->_getPortableTableIndexesList( - $objectsByTable['indexes'][$unquotedTableName], - $tableName - ); - } - - $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); - } - - return $tables; - } - foreach ($tableNames as $tableName) { $tables[] = $this->listTableDetails($tableName); } diff --git a/tests/Platforms/OraclePlatformTest.php b/tests/Platforms/OraclePlatformTest.php index c18cf67feef..3ea844df881 100644 --- a/tests/Platforms/OraclePlatformTest.php +++ b/tests/Platforms/OraclePlatformTest.php @@ -823,6 +823,74 @@ public function testQuotedTableNames(): void self::assertEquals($createTriggerStatement, $sql[3]); } + /** + * @dataProvider getReturnsGetListTableColumnsSQL + */ + public function testReturnsGetListTableColumnsSQL(?string $database, string $expectedSql): void + { + // note: this assertion is a bit strict, as it compares a full SQL string. + // Should this break in future, then please try to reduce the matching to substring matching while reworking + // the tests + self::assertEquals($expectedSql, $this->platform->getListTableColumnsSQL('"test"', $database)); + } + + /** + * @return mixed[][] + */ + public static function getReturnsGetListTableColumnsSQL(): iterable + { + return [ + [ + null, + <<<'SQL' +SELECT c.*, + ( + SELECT d.comments + FROM user_col_comments d + WHERE d.TABLE_NAME = c.TABLE_NAME + AND d.COLUMN_NAME = c.COLUMN_NAME + ) AS comments +FROM user_tab_columns c +WHERE c.table_name = 'test' +ORDER BY c.column_id +SQL +, + ], + [ + '/', + <<<'SQL' +SELECT c.*, + ( + SELECT d.comments + FROM user_col_comments d + WHERE d.TABLE_NAME = c.TABLE_NAME + AND d.COLUMN_NAME = c.COLUMN_NAME + ) AS comments +FROM user_tab_columns c +WHERE c.table_name = 'test' +ORDER BY c.column_id +SQL +, + ], + [ + 'scott', + <<<'SQL' +SELECT c.*, + ( + SELECT d.comments + FROM all_col_comments d + WHERE d.TABLE_NAME = c.TABLE_NAME AND d.OWNER = c.OWNER + AND d.COLUMN_NAME = c.COLUMN_NAME + ) AS comments +FROM all_tab_columns c +WHERE c.table_name = 'test' AND c.owner = 'SCOTT' +ORDER BY c.column_id +SQL +, + ], + ]; + } + protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(): string { return 'CONSTRAINT "select" UNIQUE (foo)'; From 9f0f02c3d76d143de36968084d79ea73f3479d79 Mon Sep 17 00:00:00 2001 From: mondrake Date: Sat, 6 Nov 2021 22:40:09 +0100 Subject: [PATCH 18/38] Requested changes --- src/Schema/AbstractSchemaManager.php | 17 ++++---- src/Schema/OracleSchemaManager.php | 58 ++++++++++++++-------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index 7ff7f26f72b..4fe43c0891e 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -312,19 +312,20 @@ public function listTables() /** * Helper method to group a set of object records by the table name. * - * @param string $sql An SQL statement to be executed. - * @param string $groupingField The name of the resultset field to use for grouping. + * @param string $sql An SQL statement to be executed. + * @param list|array $params Query parameters + * @param string $groupingField The name of the resultset field to use for grouping. * - * @return array>> An associative array with key being the table name, - * and value a simple array of records associated with - * the table. + * @return array>> An associative array with key being the table name, + * and value a simple array of records associated with + * the table. */ - protected function getObjectRecordsByTable(string $sql, string $groupingField): array + protected function getObjectRecordsByTable(string $sql, array $params, string $groupingField): array { - $input = $this->_conn->fetchAllAssociative($sql); + $input = $this->_conn->fetchAllAssociative($sql, $params); $output = []; foreach ($input as $record) { - $output[$record[$groupingField]][] = $record; + $output[(string) $record[$groupingField]][] = $record; } return $output; diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index b665b8ff751..b50a34ff8cd 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -7,7 +7,9 @@ use Doctrine\DBAL\Types\Type; use function array_change_key_case; +use function array_keys; use function array_values; +use function assert; use function is_string; use function preg_match; use function str_replace; @@ -327,13 +329,7 @@ public function listTables() { $currentDatabase = $this->_conn->getDatabase(); - if ($currentDatabase === null) { - throw new Exception('Invalid database name.'); - } - - $tableNames = $this->listTableNames(); - - $objectsByTable = []; + assert($currentDatabase !== null); // Get all column definitions in one database call. $sql = <<<'SQL' @@ -341,13 +337,15 @@ public function listTables() FROM ALL_TAB_COLUMNS C LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND D.COLUMN_NAME = C.COLUMN_NAME - WHERE C.OWNER = + WHERE C.OWNER = :OWNER + ORDER BY C.TABLE_NAME, C.COLUMN_ID SQL; - $sql .= $this->_platform->quoteStringLiteral($currentDatabase); - $sql .= ' ORDER BY C.TABLE_NAME, C.COLUMN_ID'; - - $objectsByTable['columns'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + $columnsData = $this->getObjectRecordsByTable( + $sql, + ['OWNER' => $currentDatabase], + 'TABLE_NAME' + ); // Get all foreign keys definitions in one database call. $sql = <<<'SQL' @@ -363,13 +361,15 @@ public function listTables() 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 - WHERE ALC.CONSTRAINT_TYPE = 'R' AND COLS.OWNER = + WHERE ALC.CONSTRAINT_TYPE = 'R' AND COLS.OWNER = :OWNER + ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION SQL; - $sql .= $this->_platform->quoteStringLiteral($currentDatabase); - $sql .= 'ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION'; - - $objectsByTable['foreignKeys'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + $foreignKeysData = $this->getObjectRecordsByTable( + $sql, + ['OWNER' => $currentDatabase], + 'TABLE_NAME' + ); // Get all indexes definitions in one database call. $sql = <<<'SQL' @@ -383,36 +383,38 @@ public function listTables() FROM ALL_IND_COLUMNS IND_COL LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME - WHERE IND_COL.INDEX_OWNER = + WHERE IND_COL.INDEX_OWNER = :OWNER + ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION SQL; - $sql .= $this->_platform->quoteStringLiteral($currentDatabase); - $sql .= ' ORDER BY iND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION'; - - $objectsByTable['indexes'] = $this->getObjectRecordsByTable($sql, 'TABLE_NAME'); + $indexesData = $this->getObjectRecordsByTable( + $sql, + ['OWNER' => $currentDatabase], + 'TABLE_NAME' + ); $tables = []; - foreach ($tableNames as $tableName) { + foreach (array_keys($columnsData) as $tableName) { $unquotedTableName = trim($tableName, '"'); $columns = $this->_getPortableTableColumnList( $tableName, '', - $objectsByTable['columns'][$unquotedTableName] + $columnsData[$unquotedTableName] ); $foreignKeys = []; - if (isset($objectsByTable['foreignKeys'][$unquotedTableName])) { + if (isset($foreignKeysData[$unquotedTableName])) { $foreignKeys = $this->_getPortableTableForeignKeysList( - $objectsByTable['foreignKeys'][$unquotedTableName] + $foreignKeysData[$unquotedTableName] ); } $indexes = []; - if (isset($objectsByTable['indexes'][$unquotedTableName])) { + if (isset($indexesData[$unquotedTableName])) { $indexes = $this->_getPortableTableIndexesList( - $objectsByTable['indexes'][$unquotedTableName], + $indexesData[$unquotedTableName], $tableName ); } From 1fdbacdb35805432b887e82472294bbe34278228 Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 7 Nov 2021 19:26:00 +0100 Subject: [PATCH 19/38] Reimplemented listTableDetails() --- src/Schema/AbstractSchemaManager.php | 8 +- src/Schema/OracleSchemaManager.php | 243 +++++++++++++++++++-------- 2 files changed, 177 insertions(+), 74 deletions(-) diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index 4fe43c0891e..beed6d5a708 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -312,19 +312,17 @@ public function listTables() /** * Helper method to group a set of object records by the table name. * - * @param string $sql An SQL statement to be executed. - * @param list|array $params Query parameters + * @param array> $data The resultset to be grouped. * @param string $groupingField The name of the resultset field to use for grouping. * * @return array>> An associative array with key being the table name, * and value a simple array of records associated with * the table. */ - protected function getObjectRecordsByTable(string $sql, array $params, string $groupingField): array + protected function groupObjectRecordsByTable(array $data, string $groupingField): array { - $input = $this->_conn->fetchAllAssociative($sql, $params); $output = []; - foreach ($input as $record) { + foreach ($data as $record) { $output[(string) $record[$groupingField]][] = $record; } diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index b50a34ff8cd..307a00a1dda 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -9,12 +9,13 @@ use function array_change_key_case; use function array_keys; use function array_values; -use function assert; +use function implode; use function is_string; use function preg_match; use function str_replace; use function strpos; use function strtolower; +use function strtoupper; use function trim; use const CASE_LOWER; @@ -327,51 +328,108 @@ private function getQuotedIdentifierName($identifier) */ public function listTables() { - $currentDatabase = $this->_conn->getDatabase(); + $currentDatabase = (string) $this->_conn->getDatabase(); - assert($currentDatabase !== null); + $columnsData = $this->groupObjectRecordsByTable( + $this->getDatabaseColumns($currentDatabase), + 'TABLE_NAME' + ); + $foreignKeysData = $this->groupObjectRecordsByTable( + $this->getDatabaseForeignKeys($currentDatabase), + 'TABLE_NAME' + ); + $indexesData = $this->groupObjectRecordsByTable( + $this->getDatabaseIndexes($currentDatabase), + 'TABLE_NAME' + ); + + $tables = []; + + foreach (array_keys($columnsData) as $tableName) { + $columns = $this->_getPortableTableColumnList( + $tableName, + '', + $columnsData[$tableName] + ); + + $foreignKeys = []; + if (! empty($foreignKeysData[$tableName])) { + $foreignKeys = $this->_getPortableTableForeignKeysList( + $foreignKeysData[$tableName] + ); + } + + $indexes = []; + if (! empty($indexesData[$tableName])) { + $indexes = $this->_getPortableTableIndexesList( + $indexesData[$tableName], + $tableName + ); + } + + $table = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); + if (! empty($columnsData[$tableName][0]['TABLE_COMMENTS'])) { + $table->addOption('comment', $columnsData[$tableName][0]['TABLE_COMMENTS']); + } + + $tables[] = $table; + } - // Get all column definitions in one database call. + return $tables; + } + + /** + * Gets all column definitions in one database call. + * + * @return array> + */ + private function getDatabaseColumns(string $databaseName, ?string $tableName = null): array + { $sql = <<<'SQL' - SELECT C.*, D.COMMENTS AS COMMENTS + SELECT C.OWNER, + C.TABLE_NAME, + C.COLUMN_NAME, + C.DATA_TYPE, + C.DATA_DEFAULT, + C.DATA_PRECISION, + C.DATA_SCALE, + C.CHAR_LENGTH, + C.DATA_LENGTH, + C.NULLABLE, + D.COMMENTS, + E.COMMENTS TABLE_COMMENTS FROM ALL_TAB_COLUMNS C + LEFT JOIN ALL_TAB_COMMENTS E ON E.OWNER = C.OWNER AND E.TABLE_NAME = C.TABLE_NAME LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND D.COLUMN_NAME = C.COLUMN_NAME - WHERE C.OWNER = :OWNER - ORDER BY C.TABLE_NAME, C.COLUMN_ID SQL; - $columnsData = $this->getObjectRecordsByTable( - $sql, - ['OWNER' => $currentDatabase], - 'TABLE_NAME' - ); + $conditions = []; + $params = []; - // Get all foreign keys definitions in one database call. - $sql = <<<'SQL' - SELECT COLS.TABLE_NAME, - ALC.CONSTRAINT_NAME, - ALC.DELETE_RULE, - COLS.COLUMN_NAME "local_column", - COLS.POSITION, - R_COLS.TABLE_NAME "references_table", - R_COLS.COLUMN_NAME "foreign_column" - FROM ALL_CONS_COLUMNS COLS - LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME - 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 - WHERE ALC.CONSTRAINT_TYPE = 'R' AND COLS.OWNER = :OWNER - ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION + $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); + $sql .= <<<'SQL' + ORDER BY C.TABLE_NAME, C.COLUMN_ID SQL; - $foreignKeysData = $this->getObjectRecordsByTable( - $sql, - ['OWNER' => $currentDatabase], - 'TABLE_NAME' - ); + return $this->_conn->fetchAllAssociative($sql, $params); + } - // Get all indexes definitions in one database call. + /** + * Gets all indexes definitions in one database call. + * + * @return array> + */ + private function getDatabaseIndexes(string $databaseName, ?string $tableName = null): array + { $sql = <<<'SQL' SELECT IND_COL.TABLE_NAME, IND_COL.INDEX_NAME AS NAME, @@ -383,46 +441,80 @@ public function listTables() FROM ALL_IND_COLUMNS IND_COL LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME - WHERE IND_COL.INDEX_OWNER = :OWNER - ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION SQL; - $indexesData = $this->getObjectRecordsByTable( - $sql, - ['OWNER' => $currentDatabase], - 'TABLE_NAME' - ); + $conditions = []; + $params = []; - $tables = []; + $conditions[] = 'IND_COL.INDEX_OWNER = :OWNER'; + $params['OWNER'] = $databaseName; - foreach (array_keys($columnsData) as $tableName) { - $unquotedTableName = trim($tableName, '"'); + if ($tableName !== null) { + $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; + } - $columns = $this->_getPortableTableColumnList( - $tableName, - '', - $columnsData[$unquotedTableName] - ); + $sql .= ' WHERE ' . implode(' AND ', $conditions); + $sql .= <<<'SQL' + ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION +SQL; - $foreignKeys = []; - if (isset($foreignKeysData[$unquotedTableName])) { - $foreignKeys = $this->_getPortableTableForeignKeysList( - $foreignKeysData[$unquotedTableName] - ); - } + return $this->_conn->fetchAllAssociative($sql, $params); + } - $indexes = []; - if (isset($indexesData[$unquotedTableName])) { - $indexes = $this->_getPortableTableIndexesList( - $indexesData[$unquotedTableName], - $tableName - ); - } + /** + * Gets all foreign keys definitions in one database call. + * + * @return array> + */ + private function getDatabaseForeignKeys(string $databaseName, ?string $tableName = null): array + { + $sql = <<<'SQL' + SELECT COLS.TABLE_NAME, + ALC.CONSTRAINT_NAME, + ALC.DELETE_RULE, + COLS.COLUMN_NAME LOCAL_COLUMN, + COLS.POSITION, + R_COLS.TABLE_NAME REFERENCES_TABLE, + R_COLS.COLUMN_NAME FOREIGN_COLUMN + FROM ALL_CONS_COLUMNS COLS + LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME + 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 = []; + $params = []; + + $conditions[] = 'ALC.CONSTRAINT_TYPE = \'R\''; + $conditions[] = 'COLS.OWNER = :OWNER'; + $params['OWNER'] = $databaseName; - $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); + if ($tableName !== null) { + $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; } - return $tables; + $sql .= ' WHERE ' . implode(' AND ', $conditions); + $sql .= <<<'SQL' + ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION +SQL; + + return $this->_conn->fetchAllAssociative($sql, $params); + } + + /** + * Remove quotes from the given identifier, if needed. + * + * Uppercases the given identifier if it is not quoted by intention to reflect Oracle's internal auto uppercasing + * strategy of unquoted identifiers. + */ + private function unquoteIdentifier(string $identifier): string + { + $temp = new Identifier($identifier); + + return $temp->isQuoted() ? $temp->getName() : strtoupper($identifier); } /** @@ -430,14 +522,27 @@ public function listTables() */ public function listTableDetails($name): Table { - $table = parent::listTableDetails($name); + $currentDatabase = (string) $this->_conn->getDatabase(); + $tableName = $this->unquoteIdentifier($name); - $sql = $this->_platform->getListTableCommentsSQL($name); + $columnsData = $this->getDatabaseColumns($currentDatabase, $tableName); + $columns = $this->_getPortableTableColumnList($name, '', $columnsData); - $tableOptions = $this->_conn->fetchAssociative($sql); + $indexes = []; + $indexesData = $this->getDatabaseIndexes($currentDatabase, $tableName); + if (! empty($indexesData)) { + $indexes = $this->_getPortableTableIndexesList($indexesData, $name); + } + + $foreignKeys = []; + $foreignKeysData = $this->getDatabaseForeignKeys($currentDatabase, $tableName); + if (! empty($foreignKeysData)) { + $foreignKeys = $this->_getPortableTableForeignKeysList($foreignKeysData); + } - if ($tableOptions !== false) { - $table->addOption('comment', $tableOptions['COMMENTS']); + $table = new Table($name, $columns, $indexes, [], $foreignKeys, []); + if (! empty($columnsData[0]['TABLE_COMMENTS'])) { + $table->addOption('comment', $columnsData[0]['TABLE_COMMENTS']); } return $table; From aa18e2141ee41cdf7eb4315bbd693b952a87a384 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Tue, 23 Nov 2021 19:05:24 -0800 Subject: [PATCH 20/38] Introduce Result::fetchAllAssociativeGrouped() --- src/Result.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Result.php b/src/Result.php index 53dd7983da2..5957fc5f9ac 100644 --- a/src/Result.php +++ b/src/Result.php @@ -149,6 +149,22 @@ public function fetchAllAssociativeIndexed(): array return $data; } + /** + * @return array>> + * + * @throws Exception + */ + public function fetchAllAssociativeGrouped(): array + { + $data = []; + + foreach ($this->fetchAllAssociative() as $row) { + $data[array_shift($row)][] = $row; + } + + return $data; + } + /** * @return list * From e6a48cd49598899ea3b9e314617eb8331d5f99a8 Mon Sep 17 00:00:00 2001 From: mondrake Date: Tue, 23 Nov 2021 12:44:54 -0800 Subject: [PATCH 21/38] Optimize OracleSchemaManager::listTables() Co-authored-by: Sergei Morozov --- src/Schema/OracleSchemaManager.php | 253 ++++++++++++++++++++++++++++- 1 file changed, 247 insertions(+), 6 deletions(-) diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index da2265198ce..3ba1381e70b 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -4,15 +4,19 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use function array_change_key_case; use function array_values; +use function assert; +use function implode; use function is_string; use function preg_match; use function str_replace; use function strpos; use function strtolower; +use function strtoupper; use function trim; use const CASE_LOWER; @@ -323,16 +327,253 @@ private function getQuotedIdentifierName($identifier): string */ public function listTableDetails($name): Table { - $table = parent::listTableDetails($name); + $currentDatabase = $this->_conn->getDatabase(); - $sql = $this->_platform->getListTableCommentsSQL($name); + assert($currentDatabase !== null); - $tableOptions = $this->_conn->fetchAssociative($sql); + $tableName = $this->normalizeIdentifier($name); - if ($tableOptions !== false) { - $table->addOption('comment', $tableOptions['COMMENTS']); + $options = []; + $comment = $this->selectDatabaseTableComments($currentDatabase, $tableName) + ->fetchOne(); + + if ($comment !== false) { + $options['comment'] = $comment; + } + + return new Table( + $name, + $this->_getPortableTableColumnList( + $name, + $currentDatabase, + $this->selectDatabaseColumns($currentDatabase, $tableName) + ->fetchAllAssociative() + ), + $this->_getPortableTableIndexesList( + $this->selectDatabaseIndexes($currentDatabase, $tableName) + ->fetchAllAssociative(), + $name + ), + [], + $this->_getPortableTableForeignKeysList( + $this->selectDatabaseForeignKeys($currentDatabase, $tableName) + ->fetchAllAssociative() + ), + $options + ); + } + + /** + * {@inheritdoc} + */ + public function listTables() + { + $currentDatabase = $this->_conn->getDatabase(); + + assert($currentDatabase !== null); + + $columns = $this->selectDatabaseColumns($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $indexes = $this->selectDatabaseIndexes($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $foreignKeys = $this->selectDatabaseForeignKeys($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $comments = $this->selectDatabaseTableComments($currentDatabase) + ->fetchAllKeyValue(); + + $tables = []; + + foreach ($columns as $tableName => $tableColumns) { + $options = []; + + if (isset($comments[$tableName])) { + $options['comment'] = $comments[$tableName]; + } + + $tables[] = new Table( + $tableName, + $this->_getPortableTableColumnList($tableName, $currentDatabase, $tableColumns), + $this->_getPortableTableIndexesList($indexes[$tableName] ?? [], $tableName), + [], + $this->_getPortableTableForeignKeysList($foreignKeys[$tableName] ?? []), + $options + ); } - return $table; + return $tables; + } + + /** + * Selects column definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' C.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + C.COLUMN_NAME, + C.DATA_TYPE, + C.DATA_DEFAULT, + C.DATA_PRECISION, + C.DATA_SCALE, + C.CHAR_LENGTH, + C.DATA_LENGTH, + C.NULLABLE, + D.COMMENTS + FROM ALL_TAB_COLUMNS C + LEFT JOIN ALL_COL_COMMENTS D + 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'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects index definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseIndexes(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' IND_COL.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + IND_COL.INDEX_NAME AS NAME, + IND.INDEX_TYPE AS TYPE, + DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE, + IND_COL.COLUMN_NAME AS COLUMN_NAME, + IND_COL.COLUMN_POSITION AS COLUMN_POS, + CON.CONSTRAINT_TYPE AS IS_PRIMARY + FROM ALL_IND_COLUMNS IND_COL + LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME + 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'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects foreign key definitions of the tables in the specified database. If the table name is specified, + * narrows down the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' COLS.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + ALC.CONSTRAINT_NAME, + ALC.DELETE_RULE, + COLS.COLUMN_NAME LOCAL_COLUMN, + COLS.POSITION, + R_COLS.TABLE_NAME REFERENCES_TABLE, + R_COLS.COLUMN_NAME FOREIGN_COLUMN + FROM ALL_CONS_COLUMNS COLS + LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME + 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'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects comments of the tables in the specified database. If the table name is specified, narrows down the + * selection to this table. + * + * @throws Exception + */ + private function selectDatabaseTableComments(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' TABLE_NAME,'; + } + + $sql .= ' COMMENTS'; + + $conditions = ['OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; + + if ($tableName !== null) { + $conditions[] = 'TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; + } + + $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions); + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Normalizes the given identifier. + * + * Upper-cases the given identifier if it is not quoted by intention to reflect Oracle's internal auto upper-casing + * strategy of unquoted identifiers. + * + * @param string $name + */ + private function normalizeIdentifier($name): string + { + $identifier = new Identifier($name); + + return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name); } } From 7f2bef6daa732a302010796045c996f914f36c0c Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Tue, 23 Nov 2021 13:36:05 -0800 Subject: [PATCH 22/38] Optimize MySQLSchemaManager::listTables() --- src/Schema/MySQLSchemaManager.php | 263 ++++++++++++++++++++++++++++-- 1 file changed, 246 insertions(+), 17 deletions(-) diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php index 3cbdc0cb4b6..abe9a59a7fb 100644 --- a/src/Schema/MySQLSchemaManager.php +++ b/src/Schema/MySQLSchemaManager.php @@ -2,9 +2,11 @@ namespace Doctrine\DBAL\Schema; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\MariaDb1027Platform; use Doctrine\DBAL\Platforms\MySQL; use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use function array_change_key_case; @@ -12,6 +14,7 @@ use function array_values; use function assert; use function explode; +use function implode; use function is_string; use function preg_match; use function strpos; @@ -334,32 +337,77 @@ protected function _getPortableTableForeignKeysList($tableForeignKeys) */ public function listTableDetails($name) { - $table = parent::listTableDetails($name); + $currentDatabase = $this->_conn->getDatabase() ?? ''; - $sql = $this->_platform->getListTableMetadataSQL($name); + $options = []; + $metadata = $this->selectDatabaseTableMetadata($currentDatabase, $name) + ->fetchAssociative(); - $tableOptions = $this->_conn->fetchAssociative($sql); - - if ($tableOptions === false) { - return $table; + if ($metadata !== false) { + $options = $this->buildTableOptions($metadata); } - $table->addOption('engine', $tableOptions['ENGINE']); + return new Table( + $name, + $this->_getPortableTableColumnList( + $name, + $currentDatabase, + $this->selectDatabaseColumns($currentDatabase, $name) + ->fetchAllAssociative() + ), + $this->_getPortableTableIndexesList( + $this->selectDatabaseIndexes($currentDatabase, $name) + ->fetchAllAssociative(), + $name + ), + [], + $this->_getPortableTableForeignKeysList( + $this->selectDatabaseForeignKeys($currentDatabase, $name) + ->fetchAllAssociative() + ), + $options, + ); + } - if ($tableOptions['TABLE_COLLATION'] !== null) { - $table->addOption('collation', $tableOptions['TABLE_COLLATION']); - } + /** + * {@inheritdoc} + */ + public function listTables() + { + $currentDatabase = $this->_conn->getDatabase() ?? ''; - $table->addOption('charset', $tableOptions['CHARACTER_SET_NAME']); + $columns = $this->selectDatabaseColumns($currentDatabase) + ->fetchAllAssociativeGrouped(); - if ($tableOptions['AUTO_INCREMENT'] !== null) { - $table->addOption('autoincrement', $tableOptions['AUTO_INCREMENT']); - } + $indexes = $this->selectDatabaseIndexes($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $foreignKeys = $this->selectDatabaseForeignKeys($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $metadata = $this->selectDatabaseTableMetadata($currentDatabase) + ->fetchAllAssociativeIndexed(); - $table->addOption('comment', $tableOptions['TABLE_COMMENT']); - $table->addOption('create_options', $this->parseCreateOptions($tableOptions['CREATE_OPTIONS'])); + $tables = []; - return $table; + foreach ($columns as $tableName => $tableColumns) { + $options = []; + + if (isset($metadata[$tableName])) { + $options = $this->buildTableOptions($metadata[$tableName]); + } + + $tables[] = new Table( + $tableName, + $this->_getPortableTableColumnList($tableName, $currentDatabase, $tableColumns), + $this->_getPortableTableIndexesList($indexes[$tableName] ?? [], $tableName), + [], + $this->_getPortableTableForeignKeysList($foreignKeys[$tableName] ?? []), + $options + ); + } + + return $tables; } public function createComparator(): Comparator @@ -367,6 +415,23 @@ public function createComparator(): Comparator return new MySQL\Comparator($this->getDatabasePlatform()); } + /** + * @param array $row + * + * @return array + */ + private function buildTableOptions(array $row): array + { + return [ + 'engine' => $row['ENGINE'], + 'collation' => $row['TABLE_COLLATION'], + 'charset' => $row['CHARACTER_SET_NAME'], + 'autoincrement' => $row['AUTO_INCREMENT'], + 'comment' => $row['TABLE_COMMENT'], + 'create_options' => $this->parseCreateOptions($row['CREATE_OPTIONS']), + ]; + } + /** * @return string[]|true[] */ @@ -386,4 +451,168 @@ private function parseCreateOptions(?string $string): array return $options; } + + /** + * Selects column definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' TABLE_NAME,'; + } + + $sql .= <<<'SQL' + COLUMN_NAME AS Field, + COLUMN_TYPE AS Type, + IS_NULLABLE AS `Null`, + COLUMN_KEY AS `Key`, + COLUMN_DEFAULT AS `Default`, + EXTRA AS Extra, + COLUMN_COMMENT AS Comment, + CHARACTER_SET_NAME AS CharacterSet, + COLLATION_NAME AS Collation +FROM information_schema.COLUMNS +SQL; + + $conditions = ['TABLE_SCHEMA = ?']; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'TABLE_NAME = ?'; + $params[] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions) + . ' ORDER BY ORDINAL_POSITION'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects index definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseIndexes(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' TABLE_NAME,'; + } + + $sql .= <<<'SQL' + 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'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects foreign key definitions of the tables in the specified database. If the table name is specified, + * narrows down the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT DISTINCT'; + + if ($tableName === null) { + $sql .= ' k.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + k.CONSTRAINT_NAME, + k.COLUMN_NAME, + k.REFERENCED_TABLE_NAME, + k.REFERENCED_COLUMN_NAME, + k.ORDINAL_POSITION /*!50116, + c.UPDATE_RULE, + c.DELETE_RULE */ +FROM information_schema.key_column_usage k /*!50116 +INNER JOIN information_schema.referential_constraints c +ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME +AND c.TABLE_NAME = k.TABLE_NAME +AND c.CONSTRAINT_SCHEMA = k.TABLE_SCHEMA */ +SQL; + + $conditions = ['k.TABLE_SCHEMA = ?']; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'k.TABLE_NAME = ?'; + $params[] = $tableName; + } + + $conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL'; + + $sql .= ' WHERE ' . implode(' AND ', $conditions) + . ' ORDER BY k.ORDINAL_POSITION'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects column definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseTableMetadata(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' t.TABLE_NAME,'; + } + + $sql .= <<<'SQL' + t.ENGINE, + t.AUTO_INCREMENT, + t.TABLE_COMMENT, + t.CREATE_OPTIONS, + t.TABLE_COLLATION, + ccsa.CHARACTER_SET_NAME +FROM information_schema.TABLES t + INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa + ON ccsa.COLLATION_NAME = t.TABLE_COLLATION +SQL; + + $conditions = ['t.TABLE_SCHEMA = ?']; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 't.TABLE_NAME = ?'; + $params[] = $tableName; + } + + $conditions[] = "t.TABLE_TYPE = 'BASE TABLE'"; + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + return $this->_conn->executeQuery($sql, $params); + } } From d1d629df46025f5a61e4022252696a9f0ba24386 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Tue, 23 Nov 2021 19:03:10 -0800 Subject: [PATCH 23/38] Deprecations --- psalm.xml.dist | 9 +++++++++ src/Platforms/AbstractPlatform.php | 6 ++++++ src/Platforms/DB2Platform.php | 2 ++ src/Platforms/MySQLPlatform.php | 9 +++++++++ src/Platforms/OraclePlatform.php | 9 +++++++++ src/Platforms/PostgreSQLPlatform.php | 2 ++ src/Platforms/SQLServerPlatform.php | 2 ++ src/Platforms/SqlitePlatform.php | 2 ++ src/Schema/MySQLSchemaManager.php | 1 + src/Schema/OracleSchemaManager.php | 1 + 10 files changed, 43 insertions(+) diff --git a/psalm.xml.dist b/psalm.xml.dist index 4b194dda9f3..89cbb13c8f9 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -194,6 +194,15 @@ See https://github.com/doctrine/dbal/pull/4897 --> + + + + + + + diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index 6a94d20ed55..9014f38a557 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -3071,6 +3071,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * @param string $table * @param string $database * @@ -3126,6 +3128,8 @@ public function getListViewsSQL($database) } /** + * @deprecated + * * Returns the list of indexes for the current database. * * The current database parameter is optional but will always be passed @@ -3148,6 +3152,8 @@ public function getListTableIndexesSQL($table, $database = null) } /** + * @deprecated + * * @param string $table * * @return string diff --git a/src/Platforms/DB2Platform.php b/src/Platforms/DB2Platform.php index 20f2fcc0ea3..a1473b3aac4 100644 --- a/src/Platforms/DB2Platform.php +++ b/src/Platforms/DB2Platform.php @@ -272,6 +272,8 @@ public function getTruncateTableSQL($tableName, $cascade = false) } /** + * @deprecated + * * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited. * * @param string $table diff --git a/src/Platforms/MySQLPlatform.php b/src/Platforms/MySQLPlatform.php index 9bdf1971c1f..067259500a4 100644 --- a/src/Platforms/MySQLPlatform.php +++ b/src/Platforms/MySQLPlatform.php @@ -147,6 +147,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * {@inheritDoc} * * Two approaches to listing the table indexes. The information_schema is @@ -179,6 +181,8 @@ public function getListViewsSQL($database) } /** + * @deprecated + * * @param string $table * @param string|null $database * @@ -338,6 +342,8 @@ public function getListTablesSQL() } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) @@ -357,6 +363,9 @@ public function getListTableColumnsSQL($table, $database = null) ' ORDER BY ORDINAL_POSITION ASC'; } + /** + * @deprecated + */ public function getListTableMetadataSQL(string $table, ?string $database = null): string { return sprintf( diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 4cd7145c865..12bf003a75c 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -430,6 +430,8 @@ protected function _getCreateTableSQL($name, array $columns, array $options = [] } /** + * @deprecated + * * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html @@ -625,6 +627,8 @@ private function getAutoincrementIdentifierName(Identifier $table): string } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) @@ -668,6 +672,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) @@ -1189,6 +1195,9 @@ public function getBlobTypeDeclarationSQL(array $column) return 'BLOB'; } + /** + * @deprecated + */ public function getListTableCommentsSQL(string $table, ?string $database = null): string { $tableCommentsName = 'user_tab_comments'; diff --git a/src/Platforms/PostgreSQLPlatform.php b/src/Platforms/PostgreSQLPlatform.php index 25619d2b4ec..e2d2192028a 100644 --- a/src/Platforms/PostgreSQLPlatform.php +++ b/src/Platforms/PostgreSQLPlatform.php @@ -386,6 +386,8 @@ private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) diff --git a/src/Platforms/SQLServerPlatform.php b/src/Platforms/SQLServerPlatform.php index 5a1c515401c..6ff8f1a37ba 100644 --- a/src/Platforms/SQLServerPlatform.php +++ b/src/Platforms/SQLServerPlatform.php @@ -896,6 +896,8 @@ public function getListTablesSQL() } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) diff --git a/src/Platforms/SqlitePlatform.php b/src/Platforms/SqlitePlatform.php index 59cce35abda..6e67c097378 100644 --- a/src/Platforms/SqlitePlatform.php +++ b/src/Platforms/SqlitePlatform.php @@ -462,6 +462,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php index abe9a59a7fb..7f71dfab6ee 100644 --- a/src/Schema/MySQLSchemaManager.php +++ b/src/Schema/MySQLSchemaManager.php @@ -376,6 +376,7 @@ public function listTables() { $currentDatabase = $this->_conn->getDatabase() ?? ''; + /** @var array>> $columns */ $columns = $this->selectDatabaseColumns($currentDatabase) ->fetchAllAssociativeGrouped(); diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 3ba1381e70b..bfb36b7e3ba 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -372,6 +372,7 @@ public function listTables() assert($currentDatabase !== null); + /** @var array>> $columns */ $columns = $this->selectDatabaseColumns($currentDatabase) ->fetchAllAssociativeGrouped(); From 28e32413dc47db9a4e50bbeb831a15892c7b34e2 Mon Sep 17 00:00:00 2001 From: mondrake Date: Thu, 25 Nov 2021 12:09:02 +0100 Subject: [PATCH 24/38] fix rebase --- src/Schema/AbstractSchemaManager.php | 20 --- src/Schema/OracleSchemaManager.php | 194 --------------------------- 2 files changed, 214 deletions(-) diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index beed6d5a708..fe56e4a56bb 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -309,26 +309,6 @@ public function listTables() return $tables; } - /** - * Helper method to group a set of object records by the table name. - * - * @param array> $data The resultset to be grouped. - * @param string $groupingField The name of the resultset field to use for grouping. - * - * @return array>> An associative array with key being the table name, - * and value a simple array of records associated with - * the table. - */ - protected function groupObjectRecordsByTable(array $data, string $groupingField): array - { - $output = []; - foreach ($data as $record) { - $output[(string) $record[$groupingField]][] = $record; - } - - return $output; - } - /** * @param string $name * diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 0ac73b81210..7d62dd49655 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -323,200 +323,6 @@ private function getQuotedIdentifierName($identifier): string return $identifier; } - /** - * {@inheritdoc} - */ - public function listTables() - { - $currentDatabase = (string) $this->_conn->getDatabase(); - - $columnsData = $this->groupObjectRecordsByTable( - $this->getDatabaseColumns($currentDatabase), - 'TABLE_NAME' - ); - $foreignKeysData = $this->groupObjectRecordsByTable( - $this->getDatabaseForeignKeys($currentDatabase), - 'TABLE_NAME' - ); - $indexesData = $this->groupObjectRecordsByTable( - $this->getDatabaseIndexes($currentDatabase), - 'TABLE_NAME' - ); - - $tables = []; - - foreach (array_keys($columnsData) as $tableName) { - $columns = $this->_getPortableTableColumnList( - $tableName, - '', - $columnsData[$tableName] - ); - - $foreignKeys = []; - if (! empty($foreignKeysData[$tableName])) { - $foreignKeys = $this->_getPortableTableForeignKeysList( - $foreignKeysData[$tableName] - ); - } - - $indexes = []; - if (! empty($indexesData[$tableName])) { - $indexes = $this->_getPortableTableIndexesList( - $indexesData[$tableName], - $tableName - ); - } - - $table = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); - if (! empty($columnsData[$tableName][0]['TABLE_COMMENTS'])) { - $table->addOption('comment', $columnsData[$tableName][0]['TABLE_COMMENTS']); - } - - $tables[] = $table; - } - - return $tables; - } - - /** - * Gets all column definitions in one database call. - * - * @return array> - */ - private function getDatabaseColumns(string $databaseName, ?string $tableName = null): array - { - $sql = <<<'SQL' - SELECT C.OWNER, - C.TABLE_NAME, - C.COLUMN_NAME, - C.DATA_TYPE, - C.DATA_DEFAULT, - C.DATA_PRECISION, - C.DATA_SCALE, - C.CHAR_LENGTH, - C.DATA_LENGTH, - C.NULLABLE, - D.COMMENTS, - E.COMMENTS TABLE_COMMENTS - FROM ALL_TAB_COLUMNS C - LEFT JOIN ALL_TAB_COMMENTS E ON E.OWNER = C.OWNER AND E.TABLE_NAME = C.TABLE_NAME - LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND - D.COLUMN_NAME = C.COLUMN_NAME -SQL; - - $conditions = []; - $params = []; - - $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); - $sql .= <<<'SQL' - ORDER BY C.TABLE_NAME, C.COLUMN_ID -SQL; - - return $this->_conn->fetchAllAssociative($sql, $params); - } - - /** - * Gets all indexes definitions in one database call. - * - * @return array> - */ - private function getDatabaseIndexes(string $databaseName, ?string $tableName = null): array - { - $sql = <<<'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, - IND_COL.COLUMN_NAME AS COLUMN_NAME, - IND_COL.COLUMN_POSITION AS COLUMN_POS, - CON.CONSTRAINT_TYPE AS IS_PRIMARY - FROM ALL_IND_COLUMNS IND_COL - LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME - LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME -SQL; - - $conditions = []; - $params = []; - - $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); - $sql .= <<<'SQL' - ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION -SQL; - - return $this->_conn->fetchAllAssociative($sql, $params); - } - - /** - * Gets all foreign keys definitions in one database call. - * - * @return array> - */ - private function getDatabaseForeignKeys(string $databaseName, ?string $tableName = null): array - { - $sql = <<<'SQL' - SELECT COLS.TABLE_NAME, - ALC.CONSTRAINT_NAME, - ALC.DELETE_RULE, - COLS.COLUMN_NAME LOCAL_COLUMN, - COLS.POSITION, - R_COLS.TABLE_NAME REFERENCES_TABLE, - R_COLS.COLUMN_NAME FOREIGN_COLUMN - FROM ALL_CONS_COLUMNS COLS - LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME - 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 = []; - $params = []; - - $conditions[] = 'ALC.CONSTRAINT_TYPE = \'R\''; - $conditions[] = 'COLS.OWNER = :OWNER'; - $params['OWNER'] = $databaseName; - - if ($tableName !== null) { - $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; - $params['TABLE_NAME'] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions); - $sql .= <<<'SQL' - ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION -SQL; - - return $this->_conn->fetchAllAssociative($sql, $params); - } - - /** - * Remove quotes from the given identifier, if needed. - * - * Uppercases the given identifier if it is not quoted by intention to reflect Oracle's internal auto uppercasing - * strategy of unquoted identifiers. - */ - private function unquoteIdentifier(string $identifier): string - { - $temp = new Identifier($identifier); - - return $temp->isQuoted() ? $temp->getName() : strtoupper($identifier); - } - /** * {@inheritdoc} */ From f1ca87e5c73eaac76067dcac7835927acf1bc3a3 Mon Sep 17 00:00:00 2001 From: mondrake Date: Thu, 25 Nov 2021 13:01:32 +0100 Subject: [PATCH 25/38] Fix deprecation silencing --- psalm.xml.dist | 7 +++++++ src/Schema/OracleSchemaManager.php | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/psalm.xml.dist b/psalm.xml.dist index 89cbb13c8f9..aa7124a8fae 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -197,12 +197,19 @@ + + + + + + + diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 7d62dd49655..bfb36b7e3ba 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -8,7 +8,6 @@ use Doctrine\DBAL\Types\Type; use function array_change_key_case; -use function array_keys; use function array_values; use function assert; use function implode; From c418dce92f778e069ecfc05f9fae269b599346d3 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Tue, 23 Nov 2021 19:03:10 -0800 Subject: [PATCH 26/38] Deprecations --- psalm.xml.dist | 16 ++++++++++++++++ src/Platforms/AbstractPlatform.php | 6 ++++++ src/Platforms/DB2Platform.php | 2 ++ src/Platforms/MySQLPlatform.php | 9 +++++++++ src/Platforms/OraclePlatform.php | 9 +++++++++ src/Platforms/PostgreSQLPlatform.php | 2 ++ src/Platforms/SQLServerPlatform.php | 2 ++ src/Platforms/SqlitePlatform.php | 2 ++ src/Schema/MySQLSchemaManager.php | 1 + src/Schema/OracleSchemaManager.php | 1 + 10 files changed, 50 insertions(+) diff --git a/psalm.xml.dist b/psalm.xml.dist index 4b194dda9f3..aa7124a8fae 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -194,6 +194,22 @@ See https://github.com/doctrine/dbal/pull/4897 --> + + + + + + + + + + + + + + diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index 6a94d20ed55..9014f38a557 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -3071,6 +3071,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * @param string $table * @param string $database * @@ -3126,6 +3128,8 @@ public function getListViewsSQL($database) } /** + * @deprecated + * * Returns the list of indexes for the current database. * * The current database parameter is optional but will always be passed @@ -3148,6 +3152,8 @@ public function getListTableIndexesSQL($table, $database = null) } /** + * @deprecated + * * @param string $table * * @return string diff --git a/src/Platforms/DB2Platform.php b/src/Platforms/DB2Platform.php index 20f2fcc0ea3..a1473b3aac4 100644 --- a/src/Platforms/DB2Platform.php +++ b/src/Platforms/DB2Platform.php @@ -272,6 +272,8 @@ public function getTruncateTableSQL($tableName, $cascade = false) } /** + * @deprecated + * * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited. * * @param string $table diff --git a/src/Platforms/MySQLPlatform.php b/src/Platforms/MySQLPlatform.php index 9bdf1971c1f..067259500a4 100644 --- a/src/Platforms/MySQLPlatform.php +++ b/src/Platforms/MySQLPlatform.php @@ -147,6 +147,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * {@inheritDoc} * * Two approaches to listing the table indexes. The information_schema is @@ -179,6 +181,8 @@ public function getListViewsSQL($database) } /** + * @deprecated + * * @param string $table * @param string|null $database * @@ -338,6 +342,8 @@ public function getListTablesSQL() } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) @@ -357,6 +363,9 @@ public function getListTableColumnsSQL($table, $database = null) ' ORDER BY ORDINAL_POSITION ASC'; } + /** + * @deprecated + */ public function getListTableMetadataSQL(string $table, ?string $database = null): string { return sprintf( diff --git a/src/Platforms/OraclePlatform.php b/src/Platforms/OraclePlatform.php index 4cd7145c865..12bf003a75c 100644 --- a/src/Platforms/OraclePlatform.php +++ b/src/Platforms/OraclePlatform.php @@ -430,6 +430,8 @@ protected function _getCreateTableSQL($name, array $columns, array $options = [] } /** + * @deprecated + * * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html @@ -625,6 +627,8 @@ private function getAutoincrementIdentifierName(Identifier $table): string } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) @@ -668,6 +672,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) @@ -1189,6 +1195,9 @@ public function getBlobTypeDeclarationSQL(array $column) return 'BLOB'; } + /** + * @deprecated + */ public function getListTableCommentsSQL(string $table, ?string $database = null): string { $tableCommentsName = 'user_tab_comments'; diff --git a/src/Platforms/PostgreSQLPlatform.php b/src/Platforms/PostgreSQLPlatform.php index 25619d2b4ec..e2d2192028a 100644 --- a/src/Platforms/PostgreSQLPlatform.php +++ b/src/Platforms/PostgreSQLPlatform.php @@ -386,6 +386,8 @@ private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) diff --git a/src/Platforms/SQLServerPlatform.php b/src/Platforms/SQLServerPlatform.php index 5a1c515401c..6ff8f1a37ba 100644 --- a/src/Platforms/SQLServerPlatform.php +++ b/src/Platforms/SQLServerPlatform.php @@ -896,6 +896,8 @@ public function getListTablesSQL() } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) diff --git a/src/Platforms/SqlitePlatform.php b/src/Platforms/SqlitePlatform.php index 59cce35abda..6e67c097378 100644 --- a/src/Platforms/SqlitePlatform.php +++ b/src/Platforms/SqlitePlatform.php @@ -462,6 +462,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php index abe9a59a7fb..7f71dfab6ee 100644 --- a/src/Schema/MySQLSchemaManager.php +++ b/src/Schema/MySQLSchemaManager.php @@ -376,6 +376,7 @@ public function listTables() { $currentDatabase = $this->_conn->getDatabase() ?? ''; + /** @var array>> $columns */ $columns = $this->selectDatabaseColumns($currentDatabase) ->fetchAllAssociativeGrouped(); diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 3ba1381e70b..bfb36b7e3ba 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -372,6 +372,7 @@ public function listTables() assert($currentDatabase !== null); + /** @var array>> $columns */ $columns = $this->selectDatabaseColumns($currentDatabase) ->fetchAllAssociativeGrouped(); From c1cf64f71859523b9a6c77e619699e473bcbd5ab Mon Sep 17 00:00:00 2001 From: mondrake Date: Thu, 25 Nov 2021 15:43:30 +0100 Subject: [PATCH 27/38] Add conversion for SQLite --- src/Schema/SqliteSchemaManager.php | 182 ++++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 6 deletions(-) diff --git a/src/Schema/SqliteSchemaManager.php b/src/Schema/SqliteSchemaManager.php index 20b5c60aac3..5f93e57aa78 100644 --- a/src/Schema/SqliteSchemaManager.php +++ b/src/Schema/SqliteSchemaManager.php @@ -6,18 +6,22 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\SQLite; use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\StringType; use Doctrine\DBAL\Types\TextType; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_change_key_case; +use function array_key_exists; use function array_map; use function array_merge; use function array_reverse; use function array_values; +use function assert; use function explode; use function file_exists; +use function implode; use function preg_match; use function preg_match_all; use function preg_quote; @@ -428,7 +432,12 @@ protected function _getPortableTableForeignKeysList($tableForeignKeys) $list = []; foreach ($tableForeignKeys as $value) { $value = array_change_key_case($value, CASE_LOWER); - $name = $value['constraint_name']; + + if (! array_key_exists('constraint_name', $value)) { + continue; + } + + $name = $value['constraint_name']; if (! isset($list[$name])) { if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') { $value['on_delete'] = null; @@ -577,17 +586,79 @@ private function getCreateTableSQL(string $table): string */ public function listTableDetails($name): Table { - $table = parent::listTableDetails($name); + $currentDatabase = $this->_conn->getDatabase(); - $tableCreateSql = $this->getCreateTableSQL($name); + assert($currentDatabase !== null); - $comment = $this->parseTableCommentFromSQL($name, $tableCreateSql); + $options = []; + $comment = $this->parseTableCommentFromSQL($name, $this->getCreateTableSQL($name)); if ($comment !== null) { - $table->addOption('comment', $comment); + $options['comment'] = $comment; + } + + return new Table( + $name, + $this->_getPortableTableColumnList( + $name, + $currentDatabase, + $this->selectDatabaseColumns($currentDatabase, $name) + ->fetchAllAssociative() + ), + $this->_getPortableTableIndexesList( + $this->selectDatabaseIndexes($currentDatabase, $name) + ->fetchAllAssociative(), + $name + ), + [], + $this->_getPortableTableForeignKeysList( + $this->selectDatabaseForeignKeys($currentDatabase, $name) + ->fetchAllAssociative() + ), + $options + ); + } + + /** + * {@inheritdoc} + */ + public function listTables() + { + $currentDatabase = $this->_conn->getDatabase(); + + assert($currentDatabase !== null); + + /** @var array>> $columns */ + $columns = $this->selectDatabaseColumns($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $indexes = $this->selectDatabaseIndexes($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $foreignKeys = $this->selectDatabaseForeignKeys($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $tables = []; + + foreach ($columns as $tableName => $tableColumns) { + $options = []; + + $comment = $this->parseTableCommentFromSQL($tableName, $this->getCreateTableSQL($tableName)); + if ($comment !== null) { + $options['comment'] = $comment; + } + + $tables[] = new Table( + $tableName, + $this->_getPortableTableColumnList($tableName, $currentDatabase, $tableColumns), + $this->_getPortableTableIndexesList($indexes[$tableName] ?? [], $tableName), + [], + $this->_getPortableTableForeignKeysList($foreignKeys[$tableName] ?? []), + $options + ); } - return $table; + return $tables; } public function createComparator(): Comparator @@ -611,4 +682,103 @@ public function getSchemaSearchPaths() // SQLite does not support schemas or databases return []; } + + /** + * Selects column definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = <<_conn->executeQuery($sql, $params); + } + + /** + * Selects index definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseIndexes(string $databaseName, ?string $tableName = null): Result + { + $sql = <<_conn->executeQuery($sql, $params); + } + + /** + * Selects foreign key definitions of the tables in the specified database. If the table name is specified, + * narrows down the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result + { + $sql = <<_conn->executeQuery($sql, $params); + } } From d3371ac6f8601685df05ced3dc540d779c9a2168 Mon Sep 17 00:00:00 2001 From: mondrake Date: Sun, 28 Nov 2021 18:01:36 +0100 Subject: [PATCH 28/38] Fix wrong merge --- src/Schema/AbstractSchemaManager.php | 22 ------ src/Schema/OracleSchemaManager.php | 103 --------------------------- 2 files changed, 125 deletions(-) diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index 4fe43c0891e..fe56e4a56bb 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -309,28 +309,6 @@ public function listTables() return $tables; } - /** - * Helper method to group a set of object records by the table name. - * - * @param string $sql An SQL statement to be executed. - * @param list|array $params Query parameters - * @param string $groupingField The name of the resultset field to use for grouping. - * - * @return array>> An associative array with key being the table name, - * and value a simple array of records associated with - * the table. - */ - protected function getObjectRecordsByTable(string $sql, array $params, string $groupingField): array - { - $input = $this->_conn->fetchAllAssociative($sql, $params); - $output = []; - foreach ($input as $record) { - $output[(string) $record[$groupingField]][] = $record; - } - - return $output; - } - /** * @param string $name * diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 0ca21c1d0f0..7d62dd49655 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -323,109 +323,6 @@ private function getQuotedIdentifierName($identifier): string return $identifier; } - /** - * {@inheritdoc} - */ - public function listTables() - { - $currentDatabase = $this->_conn->getDatabase(); - - assert($currentDatabase !== null); - - // Get all column definitions in one database call. - $sql = <<<'SQL' - SELECT C.*, D.COMMENTS AS COMMENTS - FROM ALL_TAB_COLUMNS C - LEFT JOIN ALL_COL_COMMENTS D ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND - D.COLUMN_NAME = C.COLUMN_NAME - WHERE C.OWNER = :OWNER - ORDER BY C.TABLE_NAME, C.COLUMN_ID -SQL; - - $columnsData = $this->getObjectRecordsByTable( - $sql, - ['OWNER' => $currentDatabase], - 'TABLE_NAME' - ); - - // Get all foreign keys definitions in one database call. - $sql = <<<'SQL' - SELECT COLS.TABLE_NAME, - ALC.CONSTRAINT_NAME, - ALC.DELETE_RULE, - COLS.COLUMN_NAME "local_column", - COLS.POSITION, - R_COLS.TABLE_NAME "references_table", - R_COLS.COLUMN_NAME "foreign_column" - FROM ALL_CONS_COLUMNS COLS - LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME - 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 - WHERE ALC.CONSTRAINT_TYPE = 'R' AND COLS.OWNER = :OWNER - ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME, COLS.POSITION -SQL; - - $foreignKeysData = $this->getObjectRecordsByTable( - $sql, - ['OWNER' => $currentDatabase], - 'TABLE_NAME' - ); - - // Get all indexes definitions in one database call. - $sql = <<<'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, - IND_COL.COLUMN_NAME AS COLUMN_NAME, - IND_COL.COLUMN_POSITION AS COLUMN_POS, - CON.CONSTRAINT_TYPE AS IS_PRIMARY - FROM ALL_IND_COLUMNS IND_COL - LEFT JOIN ALL_INDEXES IND ON IND.OWNER = IND_COL.INDEX_OWNER AND IND.INDEX_NAME = IND_COL.INDEX_NAME - LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME - WHERE IND_COL.INDEX_OWNER = :OWNER - ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME, IND_COL.COLUMN_POSITION -SQL; - - $indexesData = $this->getObjectRecordsByTable( - $sql, - ['OWNER' => $currentDatabase], - 'TABLE_NAME' - ); - - $tables = []; - - foreach (array_keys($columnsData) as $tableName) { - $unquotedTableName = trim($tableName, '"'); - - $columns = $this->_getPortableTableColumnList( - $tableName, - '', - $columnsData[$unquotedTableName] - ); - - $foreignKeys = []; - if (isset($foreignKeysData[$unquotedTableName])) { - $foreignKeys = $this->_getPortableTableForeignKeysList( - $foreignKeysData[$unquotedTableName] - ); - } - - $indexes = []; - if (isset($indexesData[$unquotedTableName])) { - $indexes = $this->_getPortableTableIndexesList( - $indexesData[$unquotedTableName], - $tableName - ); - } - - $tables[] = new Table($tableName, $columns, $indexes, [], $foreignKeys, []); - } - - return $tables; - } - /** * {@inheritdoc} */ From ade4064fac34cf2f4b0baf940953f17cc1069571 Mon Sep 17 00:00:00 2001 From: mondrake Date: Tue, 30 Nov 2021 12:24:30 +0100 Subject: [PATCH 29/38] Add conversion of DB2SchemaManager --- src/Schema/DB2SchemaManager.php | 286 +++++++++++++++++++++++++++++++- 1 file changed, 280 insertions(+), 6 deletions(-) diff --git a/src/Schema/DB2SchemaManager.php b/src/Schema/DB2SchemaManager.php index e06baf36e4f..2a596d9c369 100644 --- a/src/Schema/DB2SchemaManager.php +++ b/src/Schema/DB2SchemaManager.php @@ -4,10 +4,13 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\DB2Platform; +use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use function array_change_key_case; +use function assert; +use function implode; use function preg_match; use function str_replace; use function strpos; @@ -234,16 +237,287 @@ protected function _getPortableViewDefinition($view) */ public function listTableDetails($name): Table { - $table = parent::listTableDetails($name); + $currentDatabase = $this->_conn->getDatabase(); - $sql = $this->_platform->getListTableCommentsSQL($name); + assert($currentDatabase !== null); - $tableOptions = $this->_conn->fetchAssociative($sql); + $options = []; + $comment = $this->selectDatabaseTableComments($currentDatabase, $name) + ->fetchOne(); - if ($tableOptions !== false) { - $table->addOption('comment', $tableOptions['REMARKS']); + if ($comment !== false) { + $options['comment'] = $comment; } - return $table; + return new Table( + $name, + $this->_getPortableTableColumnList( + $name, + $currentDatabase, + $this->selectDatabaseColumns($currentDatabase, $name) + ->fetchAllAssociative() + ), + $this->_getPortableTableIndexesList( + $this->selectDatabaseIndexes($currentDatabase, $name) + ->fetchAllAssociative(), + $name + ), + [], + $this->_getPortableTableForeignKeysList( + $this->selectDatabaseForeignKeys($currentDatabase, $name) + ->fetchAllAssociative() + ), + $options + ); + } + + /** + * {@inheritdoc} + */ + public function listTables() + { + $currentDatabase = $this->_conn->getDatabase(); + + assert($currentDatabase !== null); + + /** @var array>> $columns */ + $columns = $this->selectDatabaseColumns($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $indexes = $this->selectDatabaseIndexes($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $foreignKeys = $this->selectDatabaseForeignKeys($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $comments = $this->selectDatabaseTableComments($currentDatabase) + ->fetchAllKeyValue(); + + $tables = []; + + foreach ($columns as $tableName => $tableColumns) { + $options = []; + + if (isset($comments[$tableName])) { + $options['comment'] = $comments[$tableName]; + } + + $tables[] = new Table( + $tableName, + $this->_getPortableTableColumnList($tableName, $currentDatabase, $tableColumns), + $this->_getPortableTableIndexesList($indexes[$tableName] ?? [], $tableName), + [], + $this->_getPortableTableForeignKeysList($foreignKeys[$tableName] ?? []), + $options + ); + } + + return $tables; + } + + /** + * Selects column definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result + { + // We do the funky subquery and join syscat.columns.default this crazy way because + // as of db2 v10, the column is CLOB(64k) and the distinct operator won't allow a CLOB, + // it wants shorter stuff like a varchar. + + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' SUBQ.TABNAME,'; + } + + $sql .= <<<'SQL' + COLS.DEFAULT, + SUBQ.* + FROM ( + SELECT DISTINCT + C.TABSCHEMA, + C.TABNAME, + C.COLNAME, + C.COLNO, + C.TYPENAME, + C.CODEPAGE, + C.NULLS, + C.LENGTH, + C.SCALE, + C.IDENTITY, + TC.TYPE AS TABCONSTTYPE, + C.REMARKS AS COMMENT, + K.COLSEQ, + CASE + WHEN C.GENERATED = 'D' THEN 1 + ELSE 0 + END AS AUTOINCREMENT + FROM SYSCAT.COLUMNS C + JOIN SYSCAT.TABLES AS T + ON C.TABSCHEMA = T.TABSCHEMA AND C.TABNAME = T.TABNAME + LEFT JOIN (SYSCAT.KEYCOLUSE K + JOIN SYSCAT.TABCONST TC + ON (K.TABSCHEMA = TC.TABSCHEMA AND K.TABNAME = TC.TABNAME AND TC.TYPE = 'P') + ) + ON (C.TABSCHEMA = K.TABSCHEMA AND C.TABNAME = K.TABNAME AND C.COLNAME = K.COLNAME) +SQL; + + $conditions = ['T.TYPE = \'T\'', 'T.TABSCHEMA <> \'SYSIBMTS\'', 'T.OWNER = ?']; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'UPPER(c.tabname) = UPPER(?)'; + $params[] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + $sql .= <<<'SQL' + ORDER BY C.COLNO + ) SUBQ + JOIN SYSCAT.COLUMNS COLS + ON SUBQ.TABSCHEMA = COLS.TABSCHEMA + AND SUBQ.TABNAME = COLS.TABNAME + AND SUBQ.COLNO = COLS.COLNO + ORDER BY SUBQ.COLNO +SQL; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects index definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseIndexes(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' IDX.TABNAME,'; + } + + $sql .= <<<'SQL' + IDX.INDNAME AS KEY_NAME, + IDXCOL.COLNAME AS COLUMN_NAME, + CASE + WHEN IDX.UNIQUERULE = 'P' THEN 1 + ELSE 0 + END AS PRIMARY, + CASE + WHEN IDX.UNIQUERULE = 'D' THEN 1 + ELSE 0 + END AS NON_UNIQUE + FROM SYSCAT.INDEXES AS IDX + JOIN SYSCAT.TABLES AS T + 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 = ['T.TYPE = \'T\'', 'T.TABSCHEMA <> \'SYSIBMTS\'', 'T.OWNER = ?']; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'UPPER(T.TABNAME) = UPPER(?)'; + $params[] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + $sql .= ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ ASC'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects foreign key definitions of the tables in the specified database. If the table name is specified, + * narrows down the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' FK.REFTABNAME,'; + } + + $sql .= <<<'SQL' + FKCOL.COLNAME AS LOCAL_COLUMN, + FK.REFTABNAME AS FOREIGN_TABLE, + PKCOL.COLNAME AS FOREIGN_COLUMN, + FK.CONSTNAME AS INDEX_NAME, + CASE + WHEN FK.UPDATERULE = 'R' THEN 'RESTRICT' + ELSE NULL + END AS ON_UPDATE, + CASE + WHEN FK.DELETERULE = 'C' THEN 'CASCADE' + WHEN FK.DELETERULE = 'N' THEN 'SET NULL' + WHEN FK.DELETERULE = 'R' THEN 'RESTRICT' + ELSE NULL + END AS ON_DELETE + FROM SYSCAT.REFERENCES AS FK + JOIN SYSCAT.TABLES AS T + ON FK.TABSCHEMA = T.TABSCHEMA AND FK.TABNAME = T.TABNAME + JOIN SYSCAT.KEYCOLUSE AS FKCOL + ON FK.CONSTNAME = FKCOL.CONSTNAME AND FK.TABSCHEMA = FKCOL.TABSCHEMA AND FK.TABNAME = FKCOL.TABNAME + JOIN SYSCAT.KEYCOLUSE AS PKCOL + ON FK.REFKEYNAME = PKCOL.CONSTNAME AND FK.REFTABSCHEMA = PKCOL.TABSCHEMA AND FK.REFTABNAME = PKCOL.TABNAME +SQL; + + $conditions = ['T.TYPE = \'T\'', 'T.TABSCHEMA <> \'SYSIBMTS\'', 'T.OWNER = ?']; + $params = [$databaseName]; + + if ($tableName !== null) { + $conditions[] = 'UPPER(T.TABNAME) = UPPER(?)'; + $params[] = $tableName; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + $sql .= ' ORDER BY FK.CONSTNAME, FKCOL.COLSEQ ASC'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects comments of the tables in the specified database. If the table name is specified, narrows down the + * selection to this table. + * + * @throws Exception + */ + private function selectDatabaseTableComments(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' NAME,'; + } + + $sql .= ' REMARKS'; + + $conditions = []; + $params = []; + + if ($tableName !== null) { + $conditions[] = 'NAME = UPPER(?)'; + $params[] = $tableName; + } + + $sql .= ' FROM SYSIBM.SYSTABLES'; + + if ($conditions !== []) { + $sql .= ' WHERE ' . implode(' AND ', $conditions); + } + + return $this->_conn->executeQuery($sql, $params); } } From 63a72120719d282ed1153ff0d97996ecebda002e Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 1 Dec 2021 11:16:51 +0100 Subject: [PATCH 30/38] Add conversion of PostgreSQLSchemaManager (#23) --- psalm.xml.dist | 8 + src/Platforms/DB2Platform.php | 7 + src/Platforms/PostgreSQLPlatform.php | 7 + src/Platforms/SqlitePlatform.php | 4 + src/Schema/PostgreSQLSchemaManager.php | 261 ++++++++++++++++++++++++- 5 files changed, 281 insertions(+), 6 deletions(-) diff --git a/psalm.xml.dist b/psalm.xml.dist index 99dbc95040e..cabb43a0e76 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -214,9 +214,17 @@ + + + + + + + + diff --git a/src/Platforms/DB2Platform.php b/src/Platforms/DB2Platform.php index a1473b3aac4..3fe7d80ae88 100644 --- a/src/Platforms/DB2Platform.php +++ b/src/Platforms/DB2Platform.php @@ -347,6 +347,8 @@ public function getListViewsSQL($database) } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) @@ -371,6 +373,8 @@ public function getListTableIndexesSQL($table, $database = null) } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) @@ -895,6 +899,9 @@ protected function getReservedKeywordsClass() return Keywords\DB2Keywords::class; } + /** + * @deprecated + */ public function getListTableCommentsSQL(string $table): string { return sprintf( diff --git a/src/Platforms/PostgreSQLPlatform.php b/src/Platforms/PostgreSQLPlatform.php index e2d2192028a..45ae89dce0c 100644 --- a/src/Platforms/PostgreSQLPlatform.php +++ b/src/Platforms/PostgreSQLPlatform.php @@ -294,6 +294,8 @@ public function getListViewsSQL($database) } /** + * @deprecated + * * @param string $table * @param string|null $database * @@ -340,6 +342,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html @@ -1262,6 +1266,9 @@ private function getOldColumnComment(ColumnDiff $columnDiff): ?string return $columnDiff->fromColumn !== null ? $this->getColumnComment($columnDiff->fromColumn) : null; } + /** + * @deprecated + */ public function getListTableMetadataSQL(string $table, ?string $schema = null): string { if ($schema !== null) { diff --git a/src/Platforms/SqlitePlatform.php b/src/Platforms/SqlitePlatform.php index fa0a25e192c..29ceaea9a98 100644 --- a/src/Platforms/SqlitePlatform.php +++ b/src/Platforms/SqlitePlatform.php @@ -474,6 +474,8 @@ public function getListTableColumnsSQL($table, $database = null) } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) @@ -868,6 +870,8 @@ public function getCreateTableSQL(Table $table, $createFlags = null) } /** + * @deprecated + * * @param string $table * @param string|null $database * diff --git a/src/Schema/PostgreSQLSchemaManager.php b/src/Schema/PostgreSQLSchemaManager.php index e5ea801503f..6c671d7368c 100644 --- a/src/Schema/PostgreSQLSchemaManager.php +++ b/src/Schema/PostgreSQLSchemaManager.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; @@ -569,16 +570,264 @@ private function parseDefaultExpression(?string $default): ?string */ public function listTableDetails($name): Table { - $table = parent::listTableDetails($name); + $currentDatabase = $this->_conn->getDatabase(); - $sql = $this->_platform->getListTableMetadataSQL($name); + assert($currentDatabase !== null); - $tableOptions = $this->_conn->fetchAssociative($sql); + $options = []; + $comment = $this->selectDatabaseTableComments($currentDatabase, $name) + ->fetchOne(); - if ($tableOptions !== false) { - $table->addOption('comment', $tableOptions['table_comment']); + if ($comment !== false) { + $options['comment'] = $comment; } - return $table; + return new Table( + $name, + $this->_getPortableTableColumnList( + $name, + $currentDatabase, + $this->selectDatabaseColumns($currentDatabase, $name) + ->fetchAllAssociative() + ), + $this->_getPortableTableIndexesList( + $this->selectDatabaseIndexes($currentDatabase, $name) + ->fetchAllAssociative(), + $name + ), + [], + $this->_getPortableTableForeignKeysList( + $this->selectDatabaseForeignKeys($currentDatabase, $name) + ->fetchAllAssociative() + ), + $options + ); + } + + /** + * {@inheritdoc} + */ + public function listTables() + { + $currentDatabase = $this->_conn->getDatabase(); + + assert($currentDatabase !== null); + + /** @var array>> $columns */ + $columns = $this->selectDatabaseColumns($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $indexes = $this->selectDatabaseIndexes($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $foreignKeys = $this->selectDatabaseForeignKeys($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $tables = []; + + foreach ($columns as $tableName => $tableColumns) { + $options = []; + + $comment = $this->selectDatabaseTableComments($currentDatabase, $tableName)->fetchOne(); + if ($comment !== false) { + $options['comment'] = $comment; + } + + $tables[] = new Table( + $tableName, + $this->_getPortableTableColumnList($tableName, $currentDatabase, $tableColumns), + $this->_getPortableTableIndexesList($indexes[$tableName] ?? [], $tableName), + [], + $this->_getPortableTableForeignKeysList($foreignKeys[$tableName] ?? []), + $options + ); + } + + return $tables; + } + + /** + * Selects column definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' c.relname,'; + } + + $sql .= <<<'SQL' + a.attnum, + quote_ident(a.attname) AS field, + t.typname AS type, + format_type(a.atttypid, a.atttypmod) AS complete_type, + (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation, + (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, + (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM + pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, + a.attnotnull AS isnotnull, + (SELECT 't' + FROM pg_index + WHERE c.oid = pg_index.indrelid + AND pg_index.indkey[0] = a.attnum + AND pg_index.indisprimary = 't' + ) AS pri, + (SELECT pg_get_expr(adbin, adrelid) + FROM pg_attrdef + WHERE c.oid = pg_attrdef.adrelid + AND pg_attrdef.adnum=a.attnum + ) AS default, + (SELECT pg_description.description + FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid + ) AS comment + FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n +SQL; + + $conditions = [ + 'a.attnum > 0', + 'a.attrelid = c.oid', + 'a.atttypid = t.oid', + 'n.oid = c.relnamespace', + 'c.relkind = \'r\'', + ]; + $params = []; + + if ($tableName !== null) { + $conditions[] = $this->getTableWhereClause($tableName, 'c', 'n'); + } else { + $conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')"; + $conditions[] = 'n.nspname = ANY(current_schemas(false))'; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + $sql .= ' ORDER BY a.attnum'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects index definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseIndexes(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' pg_index.indrelid :: REGCLASS as tablename,'; + } + + $sql .= <<<'SQL' + quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary, + pg_index.indkey, pg_index.indrelid, + pg_get_expr(indpred, indrelid) AS where + FROM pg_class, pg_index + WHERE oid IN ( + SELECT indexrelid + FROM pg_index si, pg_class sc, pg_namespace sn +SQL; + + $conditions = ['sc.oid=si.indrelid', 'sc.relnamespace = sn.oid']; + $params = []; + + if ($tableName !== null) { + $conditions[] = $this->getTableWhereClause($tableName, 'sc', 'sn'); + } else { + $conditions[] = "sn.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')"; + $conditions[] = 'sn.nspname = ANY(current_schemas(false))'; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + $sql .= ') AND pg_index.indexrelid = oid'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects foreign key definitions of the tables in the specified database. If the table name is specified, + * narrows down the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' r.conrelid :: REGCLASS as tablename,'; + } + + $sql .= <<<'SQL' + quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef + FROM pg_catalog.pg_constraint r + WHERE r.conrelid IN + ( + SELECT c.oid + FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n +SQL; + + $conditions = ['n.oid = c.relnamespace']; + $params = []; + + if ($tableName !== null) { + $conditions[] = $this->getTableWhereClause($tableName); + } else { + $conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')"; + $conditions[] = 'n.nspname = ANY(current_schemas(false))'; + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + $sql .= ') AND r.contype = \'f\''; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects comments of the tables in the specified database. If the table name is specified, narrows down the + * selection to this table. + * + * @throws Exception + */ + private function selectDatabaseTableComments(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT obj_description(?::regclass) AS table_comment;'; + + return $this->_conn->executeQuery($sql, [$tableName]); + } + + /** + * @param string $table + * @param string $classAlias + * @param string $namespaceAlias + */ + private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n'): string + { + $whereClause = $namespaceAlias . ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND "; + if (strpos($table, '.') !== false) { + [$schema, $table] = explode('.', $table); + $schema = $this->_platform->quoteStringLiteral($schema); + } else { + $schema = 'ANY(current_schemas(false))'; + } + + $table = new Identifier($table); + $table = $this->_platform->quoteStringLiteral($table->getName()); + + return $whereClause . sprintf( + '%s.relname = %s AND %s.nspname = %s', + $classAlias, + $table, + $namespaceAlias, + $schema + ); } } From d2aeefe82d3558dbecbeb3cd6653e725a140a689 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 1 Dec 2021 17:57:24 +0100 Subject: [PATCH 31/38] Convert SQLServerSchemaManager --- .github/workflows/continuous-integration.yml | 497 ------------------ composer.json | 1 + psalm.xml.dist | 10 + src/Connection.php | 5 +- src/Driver/IBMDB2/Driver.php | 3 + src/Driver/SQLSrv/Statement.php | 4 +- src/Platforms/SQLServerPlatform.php | 7 + src/Schema/SQLServerSchemaManager.php | 288 +++++++++- tests/Driver/API/ExceptionConverterTest.php | 79 --- .../API/MySQL/ExceptionConverterTest.php | 109 ---- .../Driver/API/OCI/ExceptionConverterTest.php | 66 --- .../API/PostgreSQL/ExceptionConverterTest.php | 67 --- .../API/SQLite/ExceptionConverterTest.php | 68 --- 13 files changed, 311 insertions(+), 893 deletions(-) delete mode 100644 tests/Driver/API/ExceptionConverterTest.php delete mode 100644 tests/Driver/API/MySQL/ExceptionConverterTest.php delete mode 100644 tests/Driver/API/OCI/ExceptionConverterTest.php delete mode 100644 tests/Driver/API/PostgreSQL/ExceptionConverterTest.php delete mode 100644 tests/Driver/API/SQLite/ExceptionConverterTest.php diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 0b0d3f086ed..c4af0256359 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -15,392 +15,19 @@ env: fail-fast: true jobs: - phpunit-smoke-check: - name: "PHPUnit with SQLite" - runs-on: "ubuntu-20.04" - - strategy: - matrix: - php-version: - - "7.3" - - "7.4" - - "8.0" - - "8.1" - dependencies: - - "highest" - include: - - dependencies: "lowest" - php-version: "7.3" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 2 - - - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" - run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' - if: "${{ matrix.php-version=='8.1' }}" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - coverage: "pcov" - ini-values: "zend.assertions=1" - - - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" - with: - dependency-versions: "${{ matrix.dependencies }}" - - - name: "Run PHPUnit" - run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage.xml" - - - name: "Upload coverage file" - uses: "actions/upload-artifact@v2" - with: - name: "phpunit-sqlite-${{ matrix.deps }}-${{ matrix.php-version }}.coverage" - path: "coverage.xml" - - phpunit-oci8: - name: "PHPUnit on OCI8" - runs-on: "ubuntu-20.04" - needs: "phpunit-smoke-check" - - strategy: - matrix: - php-version: - - "7.4" - - "8.1" - - services: - oracle: - image: "wnameless/oracle-xe-11g-r2" - ports: - - "1521:1521" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 2 - - - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" - run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' - if: "${{ matrix.php-version=='8.1' }}" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - extensions: "oci8" - coverage: "pcov" - ini-values: "zend.assertions=1" - - - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" - - - name: "Run PHPUnit" - run: "vendor/bin/phpunit -c ci/github/phpunit/oci8.xml --coverage-clover=coverage.xml" - - - name: "Upload coverage file" - uses: "actions/upload-artifact@v2" - with: - name: "${{ github.job }}-${{ matrix.php-version }}.coverage" - path: "coverage.xml" - - phpunit-pdo-oci: - name: "PHPUnit on PDO_OCI" - runs-on: "ubuntu-20.04" - needs: "phpunit-smoke-check" - - strategy: - matrix: - php-version: - - "7.4" - - "8.1" - - services: - oracle: - image: "wnameless/oracle-xe-11g-r2" - ports: - - "1521:1521" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 2 - - - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" - run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' - if: "${{ matrix.php-version=='8.1' }}" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - extensions: "pdo_oci" - coverage: "pcov" - ini-values: "zend.assertions=1" - - - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" - - - name: "Run PHPUnit" - run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_oci.xml --coverage-clover=coverage.xml" - - - name: "Upload coverage file" - uses: "actions/upload-artifact@v2" - with: - name: "${{ github.job }}-${{ matrix.php-version }}.coverage" - path: "coverage.xml" - - phpunit-postgres: - name: "PHPUnit with PostgreSQL" - runs-on: "ubuntu-20.04" - needs: "phpunit-smoke-check" - - strategy: - matrix: - php-version: - - "7.4" - postgres-version: - - "9.4" - - "13" - - "14" - include: - - php-version: "8.1" - postgres-version: "14" - - services: - postgres: - image: "postgres:${{ matrix.postgres-version }}" - env: - POSTGRES_PASSWORD: "postgres" - - options: >- - --health-cmd "pg_isready" - - ports: - - "5432:5432" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 2 - - - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" - run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' - if: "${{ matrix.php-version=='8.1' }}" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - coverage: "pcov" - ini-values: "zend.assertions=1" - - - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" - - - name: "Run PHPUnit" - run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml" - - - name: "Upload coverage file" - uses: "actions/upload-artifact@v2" - with: - name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}.coverage" - path: "coverage.xml" - - phpunit-mariadb: - name: "PHPUnit with MariaDB" - runs-on: "ubuntu-20.04" - needs: "phpunit-smoke-check" - - strategy: - matrix: - php-version: - - "7.4" - mariadb-version: - - "10.0" - - "10.2" - - "10.5" - extension: - - "mysqli" - - "pdo_mysql" - include: - - php-version: "8.1" - mariadb-version: "10.5" - extension: "mysqli" - - php-version: "8.1" - mariadb-version: "10.5" - extension: "pdo_mysql" - - services: - mariadb: - image: "mariadb:${{ matrix.mariadb-version }}" - env: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: "doctrine_tests" - - options: >- - --health-cmd "mysqladmin ping --silent" - - ports: - - "3306:3306" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 2 - - - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" - run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' - if: "${{ matrix.php-version=='8.1' }}" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - coverage: "pcov" - ini-values: "zend.assertions=1" - extensions: "${{ matrix.extension }}" - - - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" - - - name: "Run PHPUnit" - run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml" - - - name: "Upload coverage file" - uses: "actions/upload-artifact@v2" - with: - name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}.coverage" - path: "coverage.xml" - - phpunit-mysql: - name: "PHPUnit with MySQL" - runs-on: "ubuntu-20.04" - needs: "phpunit-smoke-check" - - strategy: - matrix: - php-version: - - "7.4" - - "8.0" - mysql-version: - - "5.7" - - "8.0" - extension: - - "mysqli" - - "pdo_mysql" - config-file-suffix: - - "" - include: - - php-version: "7.3" - mysql-version: "8.0" - extension: "mysqli" - custom-entrypoint: >- - --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" - - php-version: "7.3" - mysql-version: "8.0" - extension: "pdo_mysql" - custom-entrypoint: >- - --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" - - mysql-version: "5.7" - - mysql-version: "8.0" - # https://stackoverflow.com/questions/60902904/how-to-pass-mysql-native-password-to-mysql-service-in-github-actions - custom-entrypoint: >- - --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" - - config-file-suffix: "-tls" - php-version: "7.4" - mysql-version: "8.0" - extension: "mysqli" - - php-version: "8.1" - mysql-version: "8.0" - extension: "mysqli" - custom-entrypoint: >- - --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" - - php-version: "8.1" - mysql-version: "8.0" - extension: "pdo_mysql" - custom-entrypoint: >- - --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" - - services: - mysql: - image: "mysql:${{ matrix.mysql-version }}" - - options: >- - --health-cmd "mysqladmin ping --silent" - -e MYSQL_ALLOW_EMPTY_PASSWORD=yes - -e MYSQL_DATABASE=doctrine_tests - ${{ matrix.custom-entrypoint }} - - ports: - - "3306:3306" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 2 - - - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" - run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' - if: "${{ matrix.php-version=='8.1' }}" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - coverage: "pcov" - ini-values: "zend.assertions=1" - extensions: "${{ matrix.extension }}" - - - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" - - - name: "Copy TLS-related files" - run: 'docker cp "${{ job.services.mysql.id }}:/var/lib/mysql/ca.pem" . && docker cp "${{ job.services.mysql.id }}:/var/lib/mysql/client-cert.pem" . && docker cp "${{ job.services.mysql.id }}:/var/lib/mysql/client-key.pem" .' - if: "${{ endsWith(matrix.config-file-suffix, 'tls') }}" - - - name: "Run PHPUnit" - run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}${{ matrix.config-file-suffix }}.xml --coverage-clover=coverage.xml" - - - name: "Upload coverage file" - uses: "actions/upload-artifact@v2" - with: - name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.config-file-suffix }}-${{ matrix.php-version }}.coverage" - path: "coverage.xml" phpunit-mssql: name: "PHPUnit with SQL Server" runs-on: "ubuntu-20.04" - needs: "phpunit-smoke-check" strategy: matrix: php-version: - - "7.3" - - "7.4" - "8.1" extension: - - "sqlsrv" - "pdo_sqlsrv" collation: - "Latin1_General_100_CI_AS_SC_UTF8" - include: - - collation: "Latin1_General_100_CS_AS_SC_UTF8" - php-version: "7.4" - extension: "sqlsrv" - - collation: "Latin1_General_100_CS_AS_SC_UTF8" - php-version: "7.4" - extension: "pdo_sqlsrv" services: mssql: @@ -446,127 +73,3 @@ jobs: with: name: "${{ github.job }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.collation }}.coverage" path: "coverage.xml" - - phpunit-ibm-db2: - name: "PHPUnit with IBM DB2" - runs-on: "ubuntu-18.04" - needs: "phpunit-smoke-check" - - strategy: - matrix: - php-version: - - "7.3" - - "7.4" - - services: - ibm_db2: - image: "ibmcom/db2:11.5.0.0" - env: - DB2INST1_PASSWORD: "Doctrine2018" - LICENSE: "accept" - DBNAME: "doctrine" - - options: "--privileged=true" - - ports: - - "50000:50000" - - steps: - - name: "Perform healthcheck from the outside" - run: "docker logs -f ${{ job.services.ibm_db2.id }} | sed '/(*) Setup has completed./ q'" - - - name: "Create temporary tablespace" - run: "docker exec ${{ job.services.ibm_db2.id }} su - db2inst1 -c 'db2 CONNECT TO doctrine && db2 CREATE USER TEMPORARY TABLESPACE doctrine_tbsp PAGESIZE 4 K'" - - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 2 - - - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" - run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' - if: "${{ matrix.php-version=='8.1' }}" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - coverage: "pcov" - ini-values: "zend.assertions=1,extension=ibm_db2.so, ibm_db2.instance_name=db2inst1" - - - name: "Install ibm_db2 extension" - run: "ci/github/ext/install-ibm_db2.sh ${{ matrix.php-version }}" - - - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v1" - - - name: "Run PHPUnit" - run: "vendor/bin/phpunit -c ci/github/phpunit/ibm_db2.xml --coverage-clover=coverage.xml" - - - name: "Upload coverage file" - uses: "actions/upload-artifact@v2" - with: - name: "${{ github.job }}-${{ matrix.php-version }}.coverage" - path: "coverage.xml" - - development-deps: - name: "PHPUnit with SQLite and development dependencies" - runs-on: "ubuntu-20.04" - - strategy: - matrix: - php-version: - - "7.4" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - - - name: "Lower minimum stability" - run: "composer config minimum-stability dev" - - - name: "Install development dependencies with Composer" - uses: "ramsey/composer-install@v1" - with: - composer-options: "--prefer-dist" - - - name: "Run PHPUnit" - run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml" - - upload_coverage: - name: "Upload coverage to Codecov" - runs-on: "ubuntu-20.04" - needs: - - "phpunit-smoke-check" - - "phpunit-oci8" - - "phpunit-pdo-oci" - - "phpunit-postgres" - - "phpunit-mariadb" - - "phpunit-mysql" - - "phpunit-mssql" - - "phpunit-ibm-db2" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 2 - - - name: "Download coverage files" - uses: "actions/download-artifact@v2" - with: - path: "reports" - - - name: "Display structure of downloaded files" - run: ls -R - working-directory: reports - - - name: "Upload to Codecov" - uses: "codecov/codecov-action@v1" - with: - directory: reports diff --git a/composer.json b/composer.json index 6d5f58437ce..e0dbe01024a 100644 --- a/composer.json +++ b/composer.json @@ -49,6 +49,7 @@ "squizlabs/php_codesniffer": "3.6.1", "symfony/cache": "^5.2|^6.0", "symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0", + "symfony/var-dumper": "^2.0.5|^3.0|^4.0|^5.0|^6.0", "vimeo/psalm": "4.13.0" }, "suggest": { diff --git a/psalm.xml.dist b/psalm.xml.dist index cabb43a0e76..4bd2151025a 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -222,6 +222,9 @@ + + + @@ -419,6 +422,13 @@ + + + + + + + diff --git a/src/Connection.php b/src/Connection.php index 5b905912d30..ad0deec8554 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -1266,10 +1266,9 @@ public function getNestTransactionsWithSavepoints() } /** - * Returns the savepoint name to use for nested transactions are false if they are not supported - * "savepointFormat" parameter is not set + * Returns the savepoint name to use for nested transactions. * - * @return mixed A string with the savepoint name or false. + * @return string */ protected function _getNestedTransactionSavePointName() { diff --git a/src/Driver/IBMDB2/Driver.php b/src/Driver/IBMDB2/Driver.php index 621ebf05ff2..142b369b3ed 100644 --- a/src/Driver/IBMDB2/Driver.php +++ b/src/Driver/IBMDB2/Driver.php @@ -5,6 +5,9 @@ use Doctrine\DBAL\Driver\AbstractDB2Driver; use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed; +use function db2_connect; +use function db2_pconnect; + final class Driver extends AbstractDB2Driver { /** diff --git a/src/Driver/SQLSrv/Statement.php b/src/Driver/SQLSrv/Statement.php index 5947db503f0..035567a352c 100644 --- a/src/Driver/SQLSrv/Statement.php +++ b/src/Driver/SQLSrv/Statement.php @@ -47,14 +47,14 @@ final class Statement implements StatementInterface /** * References to the variables bound as statement parameters. * - * @var mixed + * @var array */ private $variables = []; /** * Bound parameter types. * - * @var int[] + * @var array */ private $types = []; diff --git a/src/Platforms/SQLServerPlatform.php b/src/Platforms/SQLServerPlatform.php index 6ff8f1a37ba..d066b8e4cfb 100644 --- a/src/Platforms/SQLServerPlatform.php +++ b/src/Platforms/SQLServerPlatform.php @@ -931,6 +931,8 @@ public function getListTableColumnsSQL($table, $database = null) } /** + * @deprecated + * * @param string $table * @param string|null $database * @@ -957,6 +959,8 @@ public function getListTableForeignKeysSQL($table, $database = null) } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) @@ -1604,6 +1608,9 @@ protected function getCommentOnTableSQL(string $tableName, ?string $comment): st ); } + /** + * @deprecated + */ public function getListTableMetadataSQL(string $table): string { return sprintf( diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php index c5c1b4e242c..3ab95508b2e 100644 --- a/src/Schema/SQLServerSchemaManager.php +++ b/src/Schema/SQLServerSchemaManager.php @@ -5,11 +5,14 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\SQLServer; use Doctrine\DBAL\Platforms\SQLServerPlatform; +use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function assert; use function count; +use function explode; +use function implode; use function is_string; use function preg_match; use function sprintf; @@ -326,7 +329,7 @@ private function getColumnConstraints(string $table, string $column): iterable * * @throws Exception */ - public function listTableDetails($name): Table +/* public function listTableDetails($name): Table { $table = parent::listTableDetails($name); @@ -339,7 +342,7 @@ public function listTableDetails($name): Table } return $table; - } + }*/ /** * @throws Exception @@ -368,4 +371,285 @@ private function getDatabaseCollation(): string return $this->databaseCollation; } + + /** + * {@inheritdoc} + */ + public function listTableDetails($name): Table + { + $currentDatabase = $this->_conn->getDatabase(); + + assert($currentDatabase !== null); + + $options = []; + $comment = $this->selectDatabaseTableComments($currentDatabase, $name) + ->fetchOne(); + + if ($comment !== false) { + $options['comment'] = $comment; + } + + return new Table( + $name, + $this->_getPortableTableColumnList( + $name, + $currentDatabase, + $this->selectDatabaseColumns($currentDatabase, $name) + ->fetchAllAssociative() + ), + $this->_getPortableTableIndexesList( + $this->selectDatabaseIndexes($currentDatabase, $name) + ->fetchAllAssociative(), + $name + ), + [], + $this->_getPortableTableForeignKeysList( + $this->selectDatabaseForeignKeys($currentDatabase, $name) + ->fetchAllAssociative() + ), + $options + ); + } + + /** + * {@inheritdoc} + */ + public function listTables() + { + $currentDatabase = $this->_conn->getDatabase(); + + assert($currentDatabase !== null); + + /** @var array>> $columns */ + $columns = $this->selectDatabaseColumns($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $indexes = $this->selectDatabaseIndexes($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $foreignKeys = $this->selectDatabaseForeignKeys($currentDatabase) + ->fetchAllAssociativeGrouped(); + + $comments = $this->selectDatabaseTableComments($currentDatabase) + ->fetchAllKeyValue(); + + $tables = []; + + foreach ($columns as $tableName => $tableColumns) { + $options = []; + + if (isset($comments[$tableName])) { + $options['comment'] = $comments[$tableName]; + } + + $tables[] = new Table( + $tableName, + $this->_getPortableTableColumnList($tableName, $currentDatabase, $tableColumns), + $this->_getPortableTableIndexesList($indexes[$tableName] ?? [], $tableName), + [], + $this->_getPortableTableForeignKeysList($foreignKeys[$tableName] ?? []), + $options + ); + } + + return $tables; + } + + /** + * Selects column definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' obj.name AS tablename,'; + } + + $sql .= <<<'SQL' + col.name, + type.name AS type, + col.max_length AS length, + ~col.is_nullable AS notnull, + def.definition AS [default], + col.scale, + col.precision, + col.is_identity AS autoincrement, + col.collation_name AS collation, + CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type + 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.schemas AS scm + ON obj.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 + AND col.column_id = prop.minor_id + AND prop.name = 'MS_Description' +SQL; + + $conditions = ['obj.type = \'U\'']; + $params = []; + + if ($tableName !== null) { + $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name'); + } + + $sql .= ' WHERE ' . implode(' AND ', $conditions); + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects index definitions of the tables in the specified database. If the table name is specified, narrows down + * the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseIndexes(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' tbl.name AS tablename,'; + } + + $sql .= <<<'SQL' + idx.name AS key_name, + col.name AS column_name, + ~idx.is_unique AS non_unique, + idx.is_primary_key AS [primary], + CASE idx.type + WHEN '1' THEN 'clustered' + WHEN '2' THEN 'nonclustered' + ELSE NULL + END AS flags + FROM sys.tables AS tbl + JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id + JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id + JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id + 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 ASC, idxcol.key_ordinal ASC'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects foreign key definitions of the tables in the specified database. If the table name is specified, + * narrows down the selection to this table. + * + * @throws Exception + */ + private function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' OBJECT_NAME (f.parent_object_id),'; + } + + $sql .= <<<'SQL' + 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, + 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( + $tableName, + 'SCHEMA_NAME (f.schema_id)', + 'OBJECT_NAME (f.parent_object_id)' + ); + $sql .= ' WHERE ' . implode(' AND ', $conditions); + } + + $sql .= ' ORDER BY fc.constraint_column_id'; + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * Selects comments of the tables in the specified database. If the table name is specified, narrows down the + * selection to this table. + * + * @throws Exception + */ + private function selectDatabaseTableComments(string $databaseName, ?string $tableName = null): Result + { + $sql = 'SELECT'; + + if ($tableName === null) { + $sql .= ' tbl.name,'; + } + + $sql .= <<<'SQL' + p.value AS [table_comment] + FROM + sys.tables AS tbl + 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); + + return $this->_conn->executeQuery($sql, $params); + } + + /** + * 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. + */ + private function getTableWhereClause($table, $schemaColumn, $tableColumn): string + { + if (strpos($table, '.') !== false) { + [$schema, $table] = explode('.', $table); + $schema = $this->_platform->quoteStringLiteral($schema); + $table = $this->_platform->quoteStringLiteral($table); + } else { + $schema = 'SCHEMA_NAME()'; + $table = $this->_platform->quoteStringLiteral($table); + } + + return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); + } } diff --git a/tests/Driver/API/ExceptionConverterTest.php b/tests/Driver/API/ExceptionConverterTest.php deleted file mode 100644 index 35bdcc397a6..00000000000 --- a/tests/Driver/API/ExceptionConverterTest.php +++ /dev/null @@ -1,79 +0,0 @@ -converter = $this->createConverter(); - } - - abstract protected function createConverter(): ExceptionConverter; - - /** - * @param class-string $expectedClass - * - * @dataProvider exceptionConversionProvider - */ - public function testConvertsException( - string $expectedClass, - int $errorCode, - ?string $sqlState = null, - string $message = '', - ?Query $query = null - ): void { - $driverException = $this->getMockForAbstractClass( - AbstractException::class, - [$message, $sqlState, $errorCode] - ); - - if ($query !== null) { - $expectedMessage = 'An exception occurred while executing a query: ' . $message; - } else { - $expectedMessage = 'An exception occurred in the driver: ' . $message; - } - - $dbalException = $this->converter->convert($driverException, $query); - - self::assertInstanceOf($expectedClass, $dbalException); - self::assertSame($driverException->getCode(), $dbalException->getCode()); - self::assertSame($driverException->getSQLState(), $dbalException->getSQLState()); - self::assertSame($driverException, $dbalException->getPrevious()); - self::assertSame($expectedMessage, $dbalException->getMessage()); - self::assertSame($query, $dbalException->getQuery()); - } - - /** - * @return iterable - */ - public static function exceptionConversionProvider(): iterable - { - foreach (static::getExceptionConversionData() as $expectedClass => $items) { - foreach ($items as $item) { - yield array_merge([$expectedClass], $item); - } - } - - yield [DriverException::class, 1, 'HY000', 'The message']; - yield [DriverException::class, 1, 'HY000', 'The message', new Query('SELECT x', [], [])]; - } - - /** - * @return array - */ - abstract protected static function getExceptionConversionData(): array; -} diff --git a/tests/Driver/API/MySQL/ExceptionConverterTest.php b/tests/Driver/API/MySQL/ExceptionConverterTest.php deleted file mode 100644 index 79a86875685..00000000000 --- a/tests/Driver/API/MySQL/ExceptionConverterTest.php +++ /dev/null @@ -1,109 +0,0 @@ - [ - [1044], - [1045], - [1046], - [1049], - [1095], - [1142], - [1143], - [1227], - [1370], - [2002], - [2005], - ], - ForeignKeyConstraintViolationException::class => [ - [1216], - [1217], - [1451], - [1452], - ], - InvalidFieldNameException::class => [ - [1054], - [1166], - [1611], - ], - NonUniqueFieldNameException::class => [ - [1052], - [1060], - [1110], - ], - NotNullConstraintViolationException::class => [ - [1048], - [1121], - [1138], - [1171], - [1252], - [1263], - [1364], - [1566], - ], - SyntaxErrorException::class => [ - [1064], - [1149], - [1287], - [1341], - [1342], - [1343], - [1344], - [1382], - [1479], - [1541], - [1554], - [1626], - ], - TableExistsException::class => [ - [1050], - ], - TableNotFoundException::class => [ - [1051], - [1146], - ], - UniqueConstraintViolationException::class => [ - [1062], - [1557], - [1569], - [1586], - ], - DeadlockException::class => [ - [1213], - ], - LockWaitTimeoutException::class => [ - [1205], - ], - ]; - } -} diff --git a/tests/Driver/API/OCI/ExceptionConverterTest.php b/tests/Driver/API/OCI/ExceptionConverterTest.php deleted file mode 100644 index 13f01331dca..00000000000 --- a/tests/Driver/API/OCI/ExceptionConverterTest.php +++ /dev/null @@ -1,66 +0,0 @@ - [ - [1017], - [12545], - ], - ForeignKeyConstraintViolationException::class => [ - [2292], - ], - InvalidFieldNameException::class => [ - [904], - ], - NonUniqueFieldNameException::class => [ - [918], - [960], - ], - NotNullConstraintViolationException::class => [ - [1400], - ], - SyntaxErrorException::class => [ - [923], - ], - TableExistsException::class => [ - [955], - ], - TableNotFoundException::class => [ - [942], - ], - UniqueConstraintViolationException::class => [ - [1], - [2299], - [38911], - ], - ]; - } -} diff --git a/tests/Driver/API/PostgreSQL/ExceptionConverterTest.php b/tests/Driver/API/PostgreSQL/ExceptionConverterTest.php deleted file mode 100644 index 65e39096300..00000000000 --- a/tests/Driver/API/PostgreSQL/ExceptionConverterTest.php +++ /dev/null @@ -1,67 +0,0 @@ - [ - [7, null, 'SQLSTATE[08006]'], - ], - ForeignKeyConstraintViolationException::class => [ - [0, '23503'], - ], - InvalidFieldNameException::class => [ - [0, '42703'], - ], - NonUniqueFieldNameException::class => [ - [0, '42702'], - ], - NotNullConstraintViolationException::class => [ - [0, '23502'], - ], - SyntaxErrorException::class => [ - [0, '42601'], - ], - TableExistsException::class => [ - [0, '42P07'], - ], - TableNotFoundException::class => [ - [0, '42P01'], - ], - UniqueConstraintViolationException::class => [ - [0, '23505'], - ], - DeadlockException::class => [ - [0, '40001'], - [0, '40P01'], - ], - ]; - } -} diff --git a/tests/Driver/API/SQLite/ExceptionConverterTest.php b/tests/Driver/API/SQLite/ExceptionConverterTest.php deleted file mode 100644 index 35b4c0a575d..00000000000 --- a/tests/Driver/API/SQLite/ExceptionConverterTest.php +++ /dev/null @@ -1,68 +0,0 @@ - [ - [0, null, 'unable to open database file'], - ], - InvalidFieldNameException::class => [ - [0, null, 'has no column named'], - ], - NonUniqueFieldNameException::class => [ - [0, null, 'ambiguous column name'], - ], - NotNullConstraintViolationException::class => [ - [0, null, 'may not be NULL'], - ], - ReadOnlyException::class => [ - [0, null, 'attempt to write a readonly database'], - ], - SyntaxErrorException::class => [ - [0, null, 'syntax error'], - ], - TableExistsException::class => [ - [0, null, 'already exists'], - ], - TableNotFoundException::class => [ - [0, null, 'no such table:'], - ], - UniqueConstraintViolationException::class => [ - [0, null, 'must be unique'], - [0, null, 'is not unique'], - [0, null, 'are not unique'], - ], - LockWaitTimeoutException::class => [ - [0, null, 'database is locked'], - ], - ]; - } -} From 9dea121427e045f2120b8ee8758c70cff4f22841 Mon Sep 17 00:00:00 2001 From: mondrake Date: Wed, 1 Dec 2021 18:04:17 +0100 Subject: [PATCH 32/38] fix --- .github/workflows/continuous-integration.yml | 497 +++++++++++++++++++ composer.json | 1 - 2 files changed, 497 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c4af0256359..0b0d3f086ed 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -15,19 +15,392 @@ env: fail-fast: true jobs: + phpunit-smoke-check: + name: "PHPUnit with SQLite" + runs-on: "ubuntu-20.04" + + strategy: + matrix: + php-version: + - "7.3" + - "7.4" + - "8.0" + - "8.1" + dependencies: + - "highest" + include: + - dependencies: "lowest" + php-version: "7.3" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" + run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' + if: "${{ matrix.php-version=='8.1' }}" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + coverage: "pcov" + ini-values: "zend.assertions=1" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + with: + dependency-versions: "${{ matrix.dependencies }}" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage.xml" + + - name: "Upload coverage file" + uses: "actions/upload-artifact@v2" + with: + name: "phpunit-sqlite-${{ matrix.deps }}-${{ matrix.php-version }}.coverage" + path: "coverage.xml" + + phpunit-oci8: + name: "PHPUnit on OCI8" + runs-on: "ubuntu-20.04" + needs: "phpunit-smoke-check" + + strategy: + matrix: + php-version: + - "7.4" + - "8.1" + + services: + oracle: + image: "wnameless/oracle-xe-11g-r2" + ports: + - "1521:1521" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" + run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' + if: "${{ matrix.php-version=='8.1' }}" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + extensions: "oci8" + coverage: "pcov" + ini-values: "zend.assertions=1" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit -c ci/github/phpunit/oci8.xml --coverage-clover=coverage.xml" + + - name: "Upload coverage file" + uses: "actions/upload-artifact@v2" + with: + name: "${{ github.job }}-${{ matrix.php-version }}.coverage" + path: "coverage.xml" + + phpunit-pdo-oci: + name: "PHPUnit on PDO_OCI" + runs-on: "ubuntu-20.04" + needs: "phpunit-smoke-check" + + strategy: + matrix: + php-version: + - "7.4" + - "8.1" + + services: + oracle: + image: "wnameless/oracle-xe-11g-r2" + ports: + - "1521:1521" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" + run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' + if: "${{ matrix.php-version=='8.1' }}" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + extensions: "pdo_oci" + coverage: "pcov" + ini-values: "zend.assertions=1" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_oci.xml --coverage-clover=coverage.xml" + + - name: "Upload coverage file" + uses: "actions/upload-artifact@v2" + with: + name: "${{ github.job }}-${{ matrix.php-version }}.coverage" + path: "coverage.xml" + + phpunit-postgres: + name: "PHPUnit with PostgreSQL" + runs-on: "ubuntu-20.04" + needs: "phpunit-smoke-check" + + strategy: + matrix: + php-version: + - "7.4" + postgres-version: + - "9.4" + - "13" + - "14" + include: + - php-version: "8.1" + postgres-version: "14" + + services: + postgres: + image: "postgres:${{ matrix.postgres-version }}" + env: + POSTGRES_PASSWORD: "postgres" + + options: >- + --health-cmd "pg_isready" + + ports: + - "5432:5432" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" + run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' + if: "${{ matrix.php-version=='8.1' }}" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + coverage: "pcov" + ini-values: "zend.assertions=1" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml" + + - name: "Upload coverage file" + uses: "actions/upload-artifact@v2" + with: + name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}.coverage" + path: "coverage.xml" + + phpunit-mariadb: + name: "PHPUnit with MariaDB" + runs-on: "ubuntu-20.04" + needs: "phpunit-smoke-check" + + strategy: + matrix: + php-version: + - "7.4" + mariadb-version: + - "10.0" + - "10.2" + - "10.5" + extension: + - "mysqli" + - "pdo_mysql" + include: + - php-version: "8.1" + mariadb-version: "10.5" + extension: "mysqli" + - php-version: "8.1" + mariadb-version: "10.5" + extension: "pdo_mysql" + + services: + mariadb: + image: "mariadb:${{ matrix.mariadb-version }}" + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: "doctrine_tests" + + options: >- + --health-cmd "mysqladmin ping --silent" + + ports: + - "3306:3306" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" + run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' + if: "${{ matrix.php-version=='8.1' }}" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + coverage: "pcov" + ini-values: "zend.assertions=1" + extensions: "${{ matrix.extension }}" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml" + + - name: "Upload coverage file" + uses: "actions/upload-artifact@v2" + with: + name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}.coverage" + path: "coverage.xml" + + phpunit-mysql: + name: "PHPUnit with MySQL" + runs-on: "ubuntu-20.04" + needs: "phpunit-smoke-check" + + strategy: + matrix: + php-version: + - "7.4" + - "8.0" + mysql-version: + - "5.7" + - "8.0" + extension: + - "mysqli" + - "pdo_mysql" + config-file-suffix: + - "" + include: + - php-version: "7.3" + mysql-version: "8.0" + extension: "mysqli" + custom-entrypoint: >- + --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" + - php-version: "7.3" + mysql-version: "8.0" + extension: "pdo_mysql" + custom-entrypoint: >- + --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" + - mysql-version: "5.7" + - mysql-version: "8.0" + # https://stackoverflow.com/questions/60902904/how-to-pass-mysql-native-password-to-mysql-service-in-github-actions + custom-entrypoint: >- + --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" + - config-file-suffix: "-tls" + php-version: "7.4" + mysql-version: "8.0" + extension: "mysqli" + - php-version: "8.1" + mysql-version: "8.0" + extension: "mysqli" + custom-entrypoint: >- + --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" + - php-version: "8.1" + mysql-version: "8.0" + extension: "pdo_mysql" + custom-entrypoint: >- + --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" + + services: + mysql: + image: "mysql:${{ matrix.mysql-version }}" + + options: >- + --health-cmd "mysqladmin ping --silent" + -e MYSQL_ALLOW_EMPTY_PASSWORD=yes + -e MYSQL_DATABASE=doctrine_tests + ${{ matrix.custom-entrypoint }} + + ports: + - "3306:3306" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" + run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' + if: "${{ matrix.php-version=='8.1' }}" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + coverage: "pcov" + ini-values: "zend.assertions=1" + extensions: "${{ matrix.extension }}" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + + - name: "Copy TLS-related files" + run: 'docker cp "${{ job.services.mysql.id }}:/var/lib/mysql/ca.pem" . && docker cp "${{ job.services.mysql.id }}:/var/lib/mysql/client-cert.pem" . && docker cp "${{ job.services.mysql.id }}:/var/lib/mysql/client-key.pem" .' + if: "${{ endsWith(matrix.config-file-suffix, 'tls') }}" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}${{ matrix.config-file-suffix }}.xml --coverage-clover=coverage.xml" + + - name: "Upload coverage file" + uses: "actions/upload-artifact@v2" + with: + name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.config-file-suffix }}-${{ matrix.php-version }}.coverage" + path: "coverage.xml" phpunit-mssql: name: "PHPUnit with SQL Server" runs-on: "ubuntu-20.04" + needs: "phpunit-smoke-check" strategy: matrix: php-version: + - "7.3" + - "7.4" - "8.1" extension: + - "sqlsrv" - "pdo_sqlsrv" collation: - "Latin1_General_100_CI_AS_SC_UTF8" + include: + - collation: "Latin1_General_100_CS_AS_SC_UTF8" + php-version: "7.4" + extension: "sqlsrv" + - collation: "Latin1_General_100_CS_AS_SC_UTF8" + php-version: "7.4" + extension: "pdo_sqlsrv" services: mssql: @@ -73,3 +446,127 @@ jobs: with: name: "${{ github.job }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.collation }}.coverage" path: "coverage.xml" + + phpunit-ibm-db2: + name: "PHPUnit with IBM DB2" + runs-on: "ubuntu-18.04" + needs: "phpunit-smoke-check" + + strategy: + matrix: + php-version: + - "7.3" + - "7.4" + + services: + ibm_db2: + image: "ibmcom/db2:11.5.0.0" + env: + DB2INST1_PASSWORD: "Doctrine2018" + LICENSE: "accept" + DBNAME: "doctrine" + + options: "--privileged=true" + + ports: + - "50000:50000" + + steps: + - name: "Perform healthcheck from the outside" + run: "docker logs -f ${{ job.services.ibm_db2.id }} | sed '/(*) Setup has completed./ q'" + + - name: "Create temporary tablespace" + run: "docker exec ${{ job.services.ibm_db2.id }} su - db2inst1 -c 'db2 CONNECT TO doctrine && db2 CREATE USER TEMPORARY TABLESPACE doctrine_tbsp PAGESIZE 4 K'" + + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Temporarily remove support for PSR Log 3 on PHP 8.1" + run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json' + if: "${{ matrix.php-version=='8.1' }}" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + coverage: "pcov" + ini-values: "zend.assertions=1,extension=ibm_db2.so, ibm_db2.instance_name=db2inst1" + + - name: "Install ibm_db2 extension" + run: "ci/github/ext/install-ibm_db2.sh ${{ matrix.php-version }}" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v1" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit -c ci/github/phpunit/ibm_db2.xml --coverage-clover=coverage.xml" + + - name: "Upload coverage file" + uses: "actions/upload-artifact@v2" + with: + name: "${{ github.job }}-${{ matrix.php-version }}.coverage" + path: "coverage.xml" + + development-deps: + name: "PHPUnit with SQLite and development dependencies" + runs-on: "ubuntu-20.04" + + strategy: + matrix: + php-version: + - "7.4" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + + - name: "Lower minimum stability" + run: "composer config minimum-stability dev" + + - name: "Install development dependencies with Composer" + uses: "ramsey/composer-install@v1" + with: + composer-options: "--prefer-dist" + + - name: "Run PHPUnit" + run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml" + + upload_coverage: + name: "Upload coverage to Codecov" + runs-on: "ubuntu-20.04" + needs: + - "phpunit-smoke-check" + - "phpunit-oci8" + - "phpunit-pdo-oci" + - "phpunit-postgres" + - "phpunit-mariadb" + - "phpunit-mysql" + - "phpunit-mssql" + - "phpunit-ibm-db2" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 2 + + - name: "Download coverage files" + uses: "actions/download-artifact@v2" + with: + path: "reports" + + - name: "Display structure of downloaded files" + run: ls -R + working-directory: reports + + - name: "Upload to Codecov" + uses: "codecov/codecov-action@v1" + with: + directory: reports diff --git a/composer.json b/composer.json index e0dbe01024a..6d5f58437ce 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,6 @@ "squizlabs/php_codesniffer": "3.6.1", "symfony/cache": "^5.2|^6.0", "symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0", - "symfony/var-dumper": "^2.0.5|^3.0|^4.0|^5.0|^6.0", "vimeo/psalm": "4.13.0" }, "suggest": { From b9e55fca329b0114d9f331c508fcc782aa286643 Mon Sep 17 00:00:00 2001 From: mondrake Date: Thu, 9 Dec 2021 19:17:52 +0100 Subject: [PATCH 33/38] Fixes --- src/Platforms/AbstractMySQLPlatform.php | 11 +++++++++++ src/Schema/SqliteSchemaManager.php | 1 + 2 files changed, 12 insertions(+) diff --git a/src/Platforms/AbstractMySQLPlatform.php b/src/Platforms/AbstractMySQLPlatform.php index f95d58d29d7..59da35d6a7d 100644 --- a/src/Platforms/AbstractMySQLPlatform.php +++ b/src/Platforms/AbstractMySQLPlatform.php @@ -145,6 +145,8 @@ public function getListTableConstraintsSQL($table) } /** + * @deprecated + * * {@inheritDoc} * * Two approaches to listing the table indexes. The information_schema is @@ -172,6 +174,8 @@ public function getListViewsSQL($database) } /** + * @deprecated + * * @param string $table * @param string|null $database * @@ -314,6 +318,8 @@ public function supportsColumnCollation() } /** + * @deprecated + * * {@inheritDoc} */ public function getListTablesSQL() @@ -322,6 +328,8 @@ public function getListTablesSQL() } /** + * @deprecated + * * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) @@ -334,6 +342,9 @@ public function getListTableColumnsSQL($table, $database = null) ' ORDER BY ORDINAL_POSITION ASC'; } + /** + * @deprecated + */ public function getListTableMetadataSQL(string $table, ?string $database = null): string { return sprintf( diff --git a/src/Schema/SqliteSchemaManager.php b/src/Schema/SqliteSchemaManager.php index 211d86fb6b2..cb7e2a7d052 100644 --- a/src/Schema/SqliteSchemaManager.php +++ b/src/Schema/SqliteSchemaManager.php @@ -1,3 +1,4 @@ + Date: Thu, 9 Dec 2021 19:23:02 +0100 Subject: [PATCH 34/38] Fix psalm.xml.dist --- psalm.xml.dist | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/psalm.xml.dist b/psalm.xml.dist index 1dc5229753b..efaec676fe8 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -202,6 +202,7 @@ See https://github.com/doctrine/dbal/pull/4897 --> +