diff --git a/UPGRADE.md b/UPGRADE.md
index ec3e46e77cd..9820c4ca5f0 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -8,6 +8,17 @@ awareness about deprecated code.
# Upgrade to 3.4
+# Deprecated `AbstractPlatform` schema introspection methods
+
+The following schema introspection methods have been deprecated:
+
+- `AbstractPlatform::getListTablesSQL()`,
+- `AbstractPlatform::getListTableColumnsSQL()`,
+- `AbstractPlatform::getListTableIndexesSQL()`,
+- `AbstractPlatform::getListTableForeignKeysSQL()`.
+
+The queries used for schema introspection are an internal implementation detail of the DBAL.
+
# Deprecated `collate` option for MySQL
This undocumented option is deprecated in favor of `collation`.
diff --git a/psalm.xml.dist b/psalm.xml.dist
index c3bc709d3a1..82bc4967339 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -212,6 +212,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Exception/DatabaseRequired.php b/src/Exception/DatabaseRequired.php
new file mode 100644
index 00000000000..d87ad3e655d
--- /dev/null
+++ b/src/Exception/DatabaseRequired.php
@@ -0,0 +1,20 @@
+fromColumn !== null ? $this->getColumnComment($columnDiff->fromColumn) : null;
}
+ /**
+ * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
+ */
public function getListTableMetadataSQL(string $table, ?string $schema = null): string
{
if ($schema !== null) {
diff --git a/src/Platforms/SQLServerPlatform.php b/src/Platforms/SQLServerPlatform.php
index 732cb636346..7a27a61f458 100644
--- a/src/Platforms/SQLServerPlatform.php
+++ b/src/Platforms/SQLServerPlatform.php
@@ -904,6 +904,8 @@ public function getListTablesSQL()
}
/**
+ * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
+ *
* {@inheritDoc}
*/
public function getListTableColumnsSQL($table, $database = null)
@@ -937,6 +939,8 @@ public function getListTableColumnsSQL($table, $database = null)
}
/**
+ * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
+ *
* @param string $table
* @param string|null $database
*
@@ -963,6 +967,8 @@ public function getListTableForeignKeysSQL($table, $database = null)
}
/**
+ * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
+ *
* {@inheritDoc}
*/
public function getListTableIndexesSQL($table, $database = null)
@@ -1610,6 +1616,9 @@ protected function getCommentOnTableSQL(string $tableName, ?string $comment): st
);
}
+ /**
+ * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
+ */
public function getListTableMetadataSQL(string $table): string
{
return sprintf(
diff --git a/src/Platforms/SqlitePlatform.php b/src/Platforms/SqlitePlatform.php
index eb7d00ea57c..12b34032c93 100644
--- a/src/Platforms/SqlitePlatform.php
+++ b/src/Platforms/SqlitePlatform.php
@@ -472,6 +472,8 @@ public function getListTableConstraintsSQL($table)
}
/**
+ * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
+ *
* {@inheritDoc}
*/
public function getListTableColumnsSQL($table, $database = null)
@@ -482,6 +484,8 @@ public function getListTableColumnsSQL($table, $database = null)
}
/**
+ * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
+ *
* {@inheritDoc}
*/
public function getListTableIndexesSQL($table, $database = null)
@@ -876,6 +880,8 @@ public function getCreateTableSQL(Table $table, $createFlags = null)
}
/**
+ * @deprecated The SQL used for schema introspection is an implementation detail and should not be relied upon.
+ *
* @param string $table
* @param string|null $database
*
diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php
index e44580b54b2..73fbef88b11 100644
--- a/src/Schema/AbstractSchemaManager.php
+++ b/src/Schema/AbstractSchemaManager.php
@@ -7,13 +7,16 @@
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Exception\DatabaseRequired;
use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Result;
use Doctrine\Deprecations\Deprecation;
use Throwable;
use function array_filter;
use function array_intersect;
use function array_map;
+use function array_shift;
use function array_values;
use function assert;
use function call_user_func_array;
@@ -309,6 +312,54 @@ public function listTables()
return $tables;
}
+ /**
+ * @return list
+ *
+ * @throws Exception
+ */
+ protected function doListTables(): array
+ {
+ $currentDatabase = $this->_conn->getDatabase();
+
+ if ($currentDatabase === null) {
+ throw DatabaseRequired::new(__METHOD__);
+ }
+
+ /** @var array>> $columns */
+ $columns = $this->fetchAllAssociativeGrouped(
+ $this->selectDatabaseColumns($currentDatabase)
+ );
+
+ $indexes = $this->fetchAllAssociativeGrouped(
+ $this->selectDatabaseIndexes($currentDatabase)
+ );
+
+ if ($this->_platform->supportsForeignKeyConstraints()) {
+ $foreignKeys = $this->fetchAllAssociativeGrouped(
+ $this->selectDatabaseForeignKeys($currentDatabase)
+ );
+ } else {
+ $foreignKeys = [];
+ }
+
+ $tableOptions = $this->getDatabaseTableOptions($currentDatabase);
+
+ $tables = [];
+
+ foreach ($columns as $tableName => $tableColumns) {
+ $tables[] = new Table(
+ $tableName,
+ $this->_getPortableTableColumnList($tableName, $currentDatabase, $tableColumns),
+ $this->_getPortableTableIndexesList($indexes[$tableName] ?? [], $tableName),
+ [],
+ $this->_getPortableTableForeignKeysList($foreignKeys[$tableName] ?? []),
+ $tableOptions[$tableName] ?? []
+ );
+ }
+
+ return $tables;
+ }
+
/**
* @param string $name
*
@@ -330,6 +381,109 @@ public function listTableDetails($name)
return new Table($name, $columns, $indexes, [], $foreignKeys);
}
+ /**
+ * @param string $name
+ *
+ * @throws Exception
+ */
+ protected function doListTableDetails($name): Table
+ {
+ $currentDatabase = $this->_conn->getDatabase();
+
+ if ($currentDatabase === null) {
+ throw DatabaseRequired::new(__METHOD__);
+ }
+
+ $normalizedName = $this->normalizeName($name);
+
+ $tableOptions = $this->getDatabaseTableOptions($currentDatabase, $normalizedName);
+
+ if ($this->_platform->supportsForeignKeyConstraints()) {
+ $foreignKeys = $this->_getPortableTableForeignKeysList(
+ $this->selectDatabaseForeignKeys($currentDatabase, $normalizedName)
+ ->fetchAllAssociative()
+ );
+ } else {
+ $foreignKeys = [];
+ }
+
+ return new Table(
+ $name,
+ $this->_getPortableTableColumnList(
+ $name,
+ $currentDatabase,
+ $this->selectDatabaseColumns($currentDatabase, $normalizedName)
+ ->fetchAllAssociative()
+ ),
+ $this->_getPortableTableIndexesList(
+ $this->selectDatabaseIndexes($currentDatabase, $normalizedName)
+ ->fetchAllAssociative(),
+ $name
+ ),
+ [],
+ $foreignKeys,
+ $tableOptions[$normalizedName] ?? []
+ );
+ }
+
+ /**
+ * An extension point for those platforms where case sensitivity of the object name depends on whether it's quoted.
+ *
+ * Such platforms should convert a possibly quoted name into a value of the corresponding case.
+ */
+ protected function normalizeName(string $name): string
+ {
+ return $name;
+ }
+
+ /**
+ * 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
+ *
+ * @abstract
+ */
+ protected function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result
+ {
+ throw Exception::notSupported(__METHOD__);
+ }
+
+ /**
+ * 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
+ */
+ protected function selectDatabaseIndexes(string $databaseName, ?string $tableName = null): Result
+ {
+ throw Exception::notSupported(__METHOD__);
+ }
+
+ /**
+ * 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
+ */
+ protected function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result
+ {
+ throw Exception::notSupported(__METHOD__);
+ }
+
+ /**
+ * Returns table options for the tables in the specified database. If the table name is specified, narrows down
+ * the selection to this table.
+ *
+ * @return array>
+ *
+ * @throws Exception
+ */
+ protected function getDatabaseTableOptions(string $databaseName, ?string $tableName = null): array
+ {
+ throw Exception::notSupported(__METHOD__);
+ }
+
/**
* Lists the views this connection has.
*
@@ -1336,4 +1490,20 @@ public function createComparator(): Comparator
{
return new Comparator($this->getDatabasePlatform());
}
+
+ /**
+ * @return array>>
+ *
+ * @throws Exception
+ */
+ private function fetchAllAssociativeGrouped(Result $result): array
+ {
+ $data = [];
+
+ foreach ($result->fetchAllAssociative() as $row) {
+ $data[array_shift($row)][] = $row;
+ }
+
+ return $data;
+ }
}
diff --git a/src/Schema/DB2SchemaManager.php b/src/Schema/DB2SchemaManager.php
index e06baf36e4f..79b8567fd28 100644
--- a/src/Schema/DB2SchemaManager.php
+++ b/src/Schema/DB2SchemaManager.php
@@ -4,14 +4,17 @@
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 implode;
use function preg_match;
use function str_replace;
use function strpos;
use function strtolower;
+use function strtoupper;
use function substr;
use const CASE_LOWER;
@@ -38,6 +41,22 @@ public function listTableNames()
return $this->filterAssetNames($this->_getPortableTablesList($tables));
}
+ /**
+ * {@inheritDoc}
+ */
+ public function listTables()
+ {
+ return $this->doListTables();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function listTableDetails($name)
+ {
+ return $this->doListTableDetails($name);
+ }
+
/**
* {@inheritdoc}
*
@@ -229,21 +248,173 @@ protected function _getPortableViewDefinition($view)
return new View($view['name'], $sql);
}
+ protected function normalizeName(string $name): string
+ {
+ $identifier = new Identifier($name);
+
+ return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name);
+ }
+
+ protected function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result
+ {
+ $sql = 'SELECT';
+
+ if ($tableName === null) {
+ $sql .= ' C.TABNAME,';
+ }
+
+ $sql .= <<<'SQL'
+ C.COLNAME,
+ C.TYPENAME,
+ C.CODEPAGE,
+ C.NULLS,
+ C.LENGTH,
+ C.SCALE,
+ C.REMARKS AS COMMENT,
+ CASE
+ WHEN C.GENERATED = 'D' THEN 1
+ ELSE 0
+ END AS AUTOINCREMENT,
+ C.DEFAULT
+FROM SYSCAT.COLUMNS C
+ JOIN SYSCAT.TABLES AS T
+ ON T.TABSCHEMA = C.TABSCHEMA
+ AND T.TABNAME = C.TABNAME
+SQL;
+
+ $conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"];
+ $params = [$databaseName];
+
+ if ($tableName !== null) {
+ $conditions[] = 'C.TABNAME = ?';
+ $params[] = $tableName;
+ }
+
+ $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO';
+
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
+ protected 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 = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"];
+ $params = [$databaseName];
+
+ if ($tableName !== null) {
+ $conditions[] = 'IDX.TABNAME = ?';
+ $params[] = $tableName;
+ }
+
+ $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ';
+
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
+ protected function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result
+ {
+ $sql = 'SELECT';
+
+ if ($tableName === null) {
+ $sql .= ' R.TABNAME,';
+ }
+
+ $sql .= <<<'SQL'
+ FKCOL.COLNAME AS LOCAL_COLUMN,
+ R.REFTABNAME AS FOREIGN_TABLE,
+ PKCOL.COLNAME AS FOREIGN_COLUMN,
+ R.CONSTNAME AS INDEX_NAME,
+ CASE
+ WHEN R.UPDATERULE = 'R' THEN 'RESTRICT'
+ END AS ON_UPDATE,
+ CASE
+ WHEN R.DELETERULE = 'C' THEN 'CASCADE'
+ WHEN R.DELETERULE = 'N' THEN 'SET NULL'
+ WHEN R.DELETERULE = 'R' THEN 'RESTRICT'
+ END AS ON_DELETE
+ FROM SYSCAT.REFERENCES AS R
+ JOIN SYSCAT.TABLES AS T
+ ON T.TABSCHEMA = R.TABSCHEMA
+ AND T.TABNAME = R.TABNAME
+ JOIN SYSCAT.KEYCOLUSE AS FKCOL
+ ON FKCOL.CONSTNAME = R.CONSTNAME
+ AND FKCOL.TABSCHEMA = R.TABSCHEMA
+ AND FKCOL.TABNAME = R.TABNAME
+ JOIN SYSCAT.KEYCOLUSE AS PKCOL
+ ON PKCOL.CONSTNAME = R.REFKEYNAME
+ AND PKCOL.TABSCHEMA = R.REFTABSCHEMA
+ AND PKCOL.TABNAME = R.REFTABNAME
+ AND PKCOL.COLSEQ = FKCOL.COLSEQ
+SQL;
+
+ $conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"];
+ $params = [$databaseName];
+
+ if ($tableName !== null) {
+ $conditions[] = 'R.TABNAME = ?';
+ $params[] = $tableName;
+ }
+
+ $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ';
+
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
/**
- * {@inheritdoc}
+ * {@inheritDoc}
*/
- public function listTableDetails($name): Table
+ protected function getDatabaseTableOptions(string $databaseName, ?string $tableName = null): array
{
- $table = parent::listTableDetails($name);
+ $sql = 'SELECT NAME, REMARKS';
+
+ $conditions = [];
+ $params = [];
+
+ if ($tableName !== null) {
+ $conditions[] = 'NAME = ?';
+ $params[] = $tableName;
+ }
+
+ $sql .= ' FROM SYSIBM.SYSTABLES';
+
+ if ($conditions !== []) {
+ $sql .= ' WHERE ' . implode(' AND ', $conditions);
+ }
- $sql = $this->_platform->getListTableCommentsSQL($name);
+ /** @var array> $metadata */
+ $metadata = $this->_conn->executeQuery($sql, $params)
+ ->fetchAllAssociativeIndexed();
- $tableOptions = $this->_conn->fetchAssociative($sql);
+ $tableOptions = [];
+ foreach ($metadata as $table => $data) {
+ $data = array_change_key_case($data, CASE_LOWER);
- if ($tableOptions !== false) {
- $table->addOption('comment', $tableOptions['REMARKS']);
+ $tableOptions[$table] = ['comment' => $data['remarks']];
}
- return $table;
+ return $tableOptions;
}
}
diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php
index 7c175bb60bf..501add0614f 100644
--- a/src/Schema/MySQLSchemaManager.php
+++ b/src/Schema/MySQLSchemaManager.php
@@ -5,12 +5,14 @@
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MySQL;
+use Doctrine\DBAL\Result;
use Doctrine\DBAL\Types\Type;
use function array_change_key_case;
use function array_shift;
use function assert;
use function explode;
+use function implode;
use function is_string;
use function preg_match;
use function strpos;
@@ -47,6 +49,22 @@ class MySQLSchemaManager extends AbstractSchemaManager
"''" => "'",
];
+ /**
+ * {@inheritDoc}
+ */
+ public function listTables()
+ {
+ return $this->doListTables();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function listTableDetails($name)
+ {
+ return $this->doListTableDetails($name);
+ }
+
/**
* {@inheritdoc}
*/
@@ -328,42 +346,162 @@ protected function _getPortableTableForeignKeysList($tableForeignKeys)
return $result;
}
- /**
- * {@inheritdoc}
- */
- public function listTableDetails($name)
+ public function createComparator(): Comparator
{
- $table = parent::listTableDetails($name);
+ return new MySQL\Comparator($this->getDatabasePlatform());
+ }
- $sql = $this->_platform->getListTableMetadataSQL($name);
+ protected function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result
+ {
+ $sql = 'SELECT';
- $tableOptions = $this->_conn->fetchAssociative($sql);
+ if ($tableName === null) {
+ $sql .= ' TABLE_NAME,';
+ }
- if ($tableOptions === false) {
- return $table;
+ $sql .= <<<'SQL'
+ COLUMN_NAME AS field,
+ COLUMN_TYPE AS type,
+ IS_NULLABLE AS `null`,
+ COLUMN_KEY AS `key`,
+ COLUMN_DEFAULT AS `default`,
+ 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;
}
- $table->addOption('engine', $tableOptions['ENGINE']);
+ $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY ORDINAL_POSITION';
- if ($tableOptions['TABLE_COLLATION'] !== null) {
- $table->addOption('collation', $tableOptions['TABLE_COLLATION']);
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
+ protected 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;
}
- $table->addOption('charset', $tableOptions['CHARACTER_SET_NAME']);
+ $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX';
- if ($tableOptions['AUTO_INCREMENT'] !== null) {
- $table->addOption('autoincrement', $tableOptions['AUTO_INCREMENT']);
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
+ protected 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;
}
- $table->addOption('comment', $tableOptions['TABLE_COMMENT']);
- $table->addOption('create_options', $this->parseCreateOptions($tableOptions['CREATE_OPTIONS']));
+ $conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL';
- return $table;
+ $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY k.ORDINAL_POSITION';
+
+ return $this->_conn->executeQuery($sql, $params);
}
- public function createComparator(): Comparator
+ /**
+ * {@inheritDoc}
+ */
+ protected function getDatabaseTableOptions(string $databaseName, ?string $tableName = null): array
{
- return new MySQL\Comparator($this->getDatabasePlatform());
+ $sql = <<<'SQL'
+ SELECT t.TABLE_NAME,
+ 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);
+
+ /** @var array> $metadata */
+ $metadata = $this->_conn->executeQuery($sql, $params)
+ ->fetchAllAssociativeIndexed();
+
+ $tableOptions = [];
+ foreach ($metadata as $table => $data) {
+ $data = array_change_key_case($data, CASE_LOWER);
+
+ $tableOptions[$table] = [
+ 'engine' => $data['engine'],
+ 'collation' => $data['table_collation'],
+ 'charset' => $data['character_set_name'],
+ 'autoincrement' => $data['auto_increment'],
+ 'comment' => $data['table_comment'],
+ 'create_options' => $this->parseCreateOptions($data['create_options']),
+ ];
+ }
+
+ return $tableOptions;
}
/**
diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php
index 762492e727a..004ff4249f6 100644
--- a/src/Schema/OracleSchemaManager.php
+++ b/src/Schema/OracleSchemaManager.php
@@ -4,15 +4,18 @@
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 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;
@@ -24,6 +27,22 @@
*/
class OracleSchemaManager extends AbstractSchemaManager
{
+ /**
+ * {@inheritDoc}
+ */
+ public function listTables()
+ {
+ return $this->doListTables();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function listTableDetails($name)
+ {
+ return $this->doListTableDetails($name);
+ }
+
/**
* {@inheritdoc}
*/
@@ -318,22 +337,155 @@ private function getQuotedIdentifierName($identifier): string
return $identifier;
}
+ protected 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);
+ }
+
+ protected 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,
+ 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);
+ }
+
+ protected 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);
+ }
+
/**
- * {@inheritdoc}
+ * {@inheritDoc}
*/
- public function listTableDetails($name): Table
+ protected function getDatabaseTableOptions(string $databaseName, ?string $tableName = null): array
{
- $table = parent::listTableDetails($name);
+ $sql = 'SELECT TABLE_NAME, COMMENTS';
+
+ $conditions = ['OWNER = :OWNER'];
+ $params = ['OWNER' => $databaseName];
+
+ if ($tableName !== null) {
+ $conditions[] = 'TABLE_NAME = :TABLE_NAME';
+ $params['TABLE_NAME'] = $tableName;
+ }
- $sql = $this->_platform->getListTableCommentsSQL($name);
+ $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions);
- $tableOptions = $this->_conn->fetchAssociative($sql);
+ /** @var array> $metadata */
+ $metadata = $this->_conn->executeQuery($sql, $params)
+ ->fetchAllAssociativeIndexed();
- if ($tableOptions !== false) {
- $tableOptions = array_change_key_case($tableOptions, CASE_LOWER);
- $table->addOption('comment', $tableOptions['comments']);
+ $tableOptions = [];
+ foreach ($metadata as $table => $data) {
+ $data = array_change_key_case($data, CASE_LOWER);
+
+ $tableOptions[$table] = [
+ 'comment' => $data['comments'],
+ ];
}
- return $table;
+ return $tableOptions;
+ }
+
+ protected function normalizeName(string $name): string
+ {
+ $identifier = new Identifier($name);
+
+ return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name);
}
}
diff --git a/src/Schema/PostgreSQLSchemaManager.php b/src/Schema/PostgreSQLSchemaManager.php
index a76e57aae4d..c4eb31e003d 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\JsonType;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
@@ -39,6 +40,22 @@ class PostgreSQLSchemaManager extends AbstractSchemaManager
/** @var string[]|null */
private $existingSchemaPaths;
+ /**
+ * {@inheritDoc}
+ */
+ public function listTables()
+ {
+ return $this->doListTables();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function listTableDetails($name)
+ {
+ return $this->doListTableDetails($name);
+ }
+
/**
* Gets all the existing schema names.
*
@@ -571,21 +588,180 @@ private function parseDefaultExpression(?string $default): ?string
return str_replace("''", "'", $default);
}
+ protected 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) . ' ORDER BY a.attnum';
+
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
+ protected 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) . ') AND pg_index.indexrelid = oid';
+
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
+ protected 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) . ") AND r.contype = 'f'";
+
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
/**
- * {@inheritdoc}
+ * {@inheritDoc}
*/
- public function listTableDetails($name): Table
+ protected function getDatabaseTableOptions(string $databaseName, ?string $tableName = null): array
{
- $table = parent::listTableDetails($name);
+ if ($tableName === null) {
+ $tables = $this->listTableNames();
+ } else {
+ $tables = [$tableName];
+ }
- $sql = $this->_platform->getListTableMetadataSQL($name);
+ $tableOptions = [];
+ foreach ($tables as $table) {
+ $sql = 'SELECT obj_description(?::regclass) AS table_comment;';
+ $comment = $this->_conn->executeQuery($sql, [$table])->fetchOne();
- $tableOptions = $this->_conn->fetchAssociative($sql);
+ if ($comment === null) {
+ continue;
+ }
- if ($tableOptions !== false) {
- $table->addOption('comment', $tableOptions['table_comment']);
+ $tableOptions[$table]['comment'] = $comment;
}
- return $table;
+ return $tableOptions;
+ }
+
+ /**
+ * @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
+ );
}
}
diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php
index 8a8713f09b0..8188465ac48 100644
--- a/src/Schema/SQLServerSchemaManager.php
+++ b/src/Schema/SQLServerSchemaManager.php
@@ -5,11 +5,15 @@
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 array_change_key_case;
use function assert;
use function count;
+use function explode;
+use function implode;
use function is_string;
use function preg_match;
use function sprintf;
@@ -17,6 +21,8 @@
use function strpos;
use function strtok;
+use const CASE_LOWER;
+
/**
* SQL Server Schema Manager.
*
@@ -27,6 +33,22 @@ class SQLServerSchemaManager extends AbstractSchemaManager
/** @var string|null */
private $databaseCollation;
+ /**
+ * {@inheritDoc}
+ */
+ public function listTables()
+ {
+ return $this->doListTables();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function listTableDetails($name)
+ {
+ return $this->doListTableDetails($name);
+ }
+
/**
* {@inheritDoc}
*/
@@ -321,26 +343,6 @@ private function getColumnConstraints(string $table, string $column): iterable
);
}
- /**
- * @param string $name
- *
- * @throws Exception
- */
- public function listTableDetails($name): Table
- {
- $table = parent::listTableDetails($name);
-
- $sql = $this->_platform->getListTableMetadataSQL($name);
-
- $tableOptions = $this->_conn->fetchAssociative($sql);
-
- if ($tableOptions !== false) {
- $table->addOption('comment', $tableOptions['table_comment']);
- }
-
- return $table;
- }
-
/**
* @throws Exception
*/
@@ -368,4 +370,198 @@ private function getDatabaseCollation(): string
return $this->databaseCollation;
}
+
+ protected 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 avoids driver error for sql_variant type
+ CAST(prop.value AS NVARCHAR(MAX)) AS comment
+ 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);
+ }
+
+ protected 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, idxcol.key_ordinal';
+
+ return $this->_conn->executeQuery($sql, $params);
+ }
+
+ protected 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);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getDatabaseTableOptions(string $databaseName, ?string $tableName = null): array
+ {
+ $sql = <<<'SQL'
+ SELECT
+ tbl.name,
+ 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);
+
+ /** @var array> $metadata */
+ $metadata = $this->_conn->executeQuery($sql, $params)
+ ->fetchAllAssociativeIndexed();
+
+ $tableOptions = [];
+ foreach ($metadata as $table => $data) {
+ $data = array_change_key_case($data, CASE_LOWER);
+
+ $tableOptions[$table] = [
+ 'comment' => $data['table_comment'],
+ ];
+ }
+
+ return $tableOptions;
+ }
+
+ /**
+ * 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/src/Schema/SqliteSchemaManager.php b/src/Schema/SqliteSchemaManager.php
index 3e0e5cfec68..ffa44498b06 100644
--- a/src/Schema/SqliteSchemaManager.php
+++ b/src/Schema/SqliteSchemaManager.php
@@ -6,6 +6,7 @@
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;
@@ -17,6 +18,7 @@
use function array_reverse;
use function explode;
use function file_exists;
+use function implode;
use function preg_match;
use function preg_match_all;
use function preg_quote;
@@ -38,6 +40,22 @@
*/
class SqliteSchemaManager extends AbstractSchemaManager
{
+ /**
+ * {@inheritDoc}
+ */
+ public function listTables()
+ {
+ return $this->doListTables();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function listTableDetails($name)
+ {
+ return $this->doListTableDetails($name);
+ }
+
/**
* {@inheritdoc}
*
@@ -562,26 +580,6 @@ private function getCreateTableSQL(string $table): string
return '';
}
- /**
- * {@inheritDoc}
- *
- * @param string $name
- */
- public function listTableDetails($name): Table
- {
- $table = parent::listTableDetails($name);
-
- $tableCreateSql = $this->getCreateTableSQL($name);
-
- $comment = $this->parseTableCommentFromSQL($name, $tableCreateSql);
-
- if ($comment !== null) {
- $table->addOption('comment', $comment);
- }
-
- return $table;
- }
-
public function createComparator(): Comparator
{
return new SQLite\Comparator($this->getDatabasePlatform());
@@ -603,4 +601,105 @@ public function getSchemaSearchPaths()
// SQLite does not support schemas or databases
return [];
}
+
+ protected function selectDatabaseColumns(string $databaseName, ?string $tableName = null): Result
+ {
+ $sql = <<_conn->executeQuery($sql, $params);
+ }
+
+ protected function selectDatabaseIndexes(string $databaseName, ?string $tableName = null): Result
+ {
+ $sql = <<_conn->executeQuery($sql, $params);
+ }
+
+ protected function selectDatabaseForeignKeys(string $databaseName, ?string $tableName = null): Result
+ {
+ $sql = <<_conn->executeQuery($sql, $params);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getDatabaseTableOptions(string $databaseName, ?string $tableName = null): array
+ {
+ if ($tableName === null) {
+ $tables = $this->listTableNames();
+ } else {
+ $tables = [$tableName];
+ }
+
+ $tableOptions = [];
+ foreach ($tables as $table) {
+ $comment = $this->parseTableCommentFromSQL($table, $this->getCreateTableSQL($table));
+
+ if ($comment === null) {
+ continue;
+ }
+
+ $tableOptions[$table]['comment'] = $comment;
+ }
+
+ return $tableOptions;
+ }
}