From 61dcd2a8bcfe792f06452a4fa24516b8422a0dcb Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 10 Jan 2025 17:25:45 +0100 Subject: [PATCH 01/11] Fix binary type format documentation The table contained an error which broke the parsing of the table. Row content cannot be at the same line as the row span definition. --- docs/en/reference/types.rst | 433 ++++++++++++++++++------------------ 1 file changed, 216 insertions(+), 217 deletions(-) diff --git a/docs/en/reference/types.rst b/docs/en/reference/types.rst index 273a191156c..271ba00fc5f 100644 --- a/docs/en/reference/types.rst +++ b/docs/en/reference/types.rst @@ -471,224 +471,223 @@ The following table shows an overview of Doctrine's type abstraction. The matrix contains the mapping information for how a specific Doctrine type is mapped to the database and back to PHP. Please also notice the mapping specific footnotes for additional information. -:: - +-------------------+---------------+-----------------------------------------------------------------------------------------------+ - | Doctrine | PHP | Database vendor | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | Name | Version | Type | - +===================+===============+==========================+=========+==========================================================+ - | **smallint** | ``integer`` | **MySQL** | *all* | ``SMALLINT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``SMALLINT`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``NUMBER(5)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``SMALLINT`` ``IDENTITY`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQLite** | *all* | ``INTEGER`` [15] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **integer** | ``integer`` | **MySQL** | *all* | ``INT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``INT`` [12] | - | | | | +----------------------------------------------------------+ - | | | | | ``SERIAL`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``NUMBER(10)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``INT`` ``IDENTITY`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQLite** | *all* | ``INTEGER`` [15] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **bigint** | ``string`` | **MySQL** | *all* | ``BIGINT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | - | | [8] +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``BIGINT`` [12] | - | | | | +----------------------------------------------------------+ - | | | | | ``BIGSERIAL`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``NUMBER(20)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``BIGINT`` ``IDENTITY`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQLite** | *all* | ``INTEGER`` [15] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **decimal** [7] | ``string`` | **MySQL** | *all* | ``NUMERIC(p, s)`` ``UNSIGNED`` [10] | - | | [9] +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``NUMERIC(p, s)`` | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQL Server** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **smallfloat** | ``float`` | **MySQL** | *all* | ``FLOAT`` ``UNSIGNED`` [10] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``REAL`` | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQL Server** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **float** | ``float`` | **MySQL** | *all* | ``DOUBLE PRECISION`` ``UNSIGNED`` [10] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``DOUBLE PRECISION`` | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQL Server** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **string** | ``string`` | **MySQL** | *all* | ``VARCHAR(n)`` [3] | - | [2] [5] | +--------------------------+ | | - | | | **PostgreSQL** | | | - | | +--------------------------+ +----------------------------------------------------------+ - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``VARCHAR2(n)`` [3] | - | | | | +----------------------------------------------------------+ - | | | | | ``CHAR(n)`` [4] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``NVARCHAR(n)`` [3] | - | | | | +----------------------------------------------------------+ - | | | | | ``NCHAR(n)`` [4] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **ascii_string** | ``string`` | **SQL Server** | | ``VARCHAR(n)`` | - | | | | | ``CHAR(n)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **text** | ``string`` | **MySQL** | *all* | ``TINYTEXT`` [16] | - | | | | +----------------------------------------------------------+ - | | | | | ``TEXT`` [17] | - | | | | +----------------------------------------------------------+ - | | | | | ``MEDIUMTEXT`` [18] | - | | | | +----------------------------------------------------------+ - | | | | | ``LONGTEXT`` [19] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TEXT`` | - | | +--------------------------+ | | - | | | **Oracle** | *all* | ``CLOB`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **guid** | ``string`` | **MySQL** | *all* | ``CHAR(36)`` [1] | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``UNIQUEIDENTIFIER`` | - | | +--------------------------+ | | - | | | **PostgreSQL** | *all* | ``UUID`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **binary** | ``resource`` | **MySQL** | *all* | ``VARBINARY(n)`` [3] | - | [2] [6] | +--------------------------+ | | - | | | **SQL Server** | +----------------------------------------------------------+ - | | +--------------------------+ | ``BINARY(n)`` [4] | - | | | **Oracle** | *all* | ``RAW(n)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``BYTEA`` [15] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQLite** | *all* | ``BLOB`` [15] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **blob** | ``resource`` | **MySQL** | *all* | ``TINYBLOB`` [16] | - | | | | +----------------------------------------------------------+ - | | | | | ``BLOB`` [17] | - | | | | +----------------------------------------------------------+ - | | | | | ``MEDIUMBLOB`` [18] | - | | | | +----------------------------------------------------------+ - | | | | | ``LONGBLOB`` [19] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``BLOB`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``VARBINARY(MAX)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``BYTEA`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **boolean** | ``boolean`` | **MySQL** | *all* | ``TINYINT(1)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``BOOLEAN`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``BIT`` | - | | +--------------------------+ | | - | | | **Oracle** | *all* | ``NUMBER(1)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **date** | ``\DateTime`` | **MySQL** | *all* | ``DATE`` | - | | +--------------------------+ | | - | | | **PostgreSQL** | | | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+ | - | | | **SQL Server** | "all" | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **datetime** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` [13] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``DATETIME`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITHOUT TIME ZONE`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``TIMESTAMP(0)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **datetimetz** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` [14] [15] | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+ | - | | | **SQL Server** | "all" | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITH TIME ZONE`` | - | | +--------------------------+ | | - | | | **Oracle** | | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **time** | ``\DateTime`` | **MySQL** | *all* | ``TIME`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TIME(0) WITHOUT TIME ZONE`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``DATE`` [15] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | "all" | ``TIME(0)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **simple array** | ``array`` | **MySQL** | *all* | ``TINYTEXT`` [16] | - | [1] | | | +----------------------------------------------------------+ - | | | | | ``TEXT`` [17] | - | | | | +----------------------------------------------------------+ - | | | | | ``MEDIUMTEXT`` [18] | - | | | | +----------------------------------------------------------+ - | | | | | ``LONGTEXT`` [19] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TEXT`` | - | | +--------------------------+ | | - | | | **Oracle** | *all* | ``CLOB`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **json** | ``mixed`` | **MySQL** | *all* | ``JSON`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``JSON`` [20] | - | | | | +----------------------------------------------------------+ - | | | | | ``JSONB`` [21] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``CLOB`` [1] | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` [1] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ ++-------------------+---------------+-----------------------------------------------------------------------------------------------+ +| Doctrine | PHP | Database vendor | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | Name | Version | Type | ++===================+===============+==========================+=========+==========================================================+ +| **smallint** | ``integer`` | **MySQL** | *all* | ``SMALLINT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``SMALLINT`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``NUMBER(5)`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``SMALLINT`` ``IDENTITY`` [11] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQLite** | *all* | ``INTEGER`` [15] | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **integer** | ``integer`` | **MySQL** | *all* | ``INT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``INT`` [12] | +| | | | +----------------------------------------------------------+ +| | | | | ``SERIAL`` [11] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``NUMBER(10)`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``INT`` ``IDENTITY`` [11] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQLite** | *all* | ``INTEGER`` [15] | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **bigint** | ``string`` | **MySQL** | *all* | ``BIGINT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | +| | [8] +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``BIGINT`` [12] | +| | | | +----------------------------------------------------------+ +| | | | | ``BIGSERIAL`` [11] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``NUMBER(20)`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``BIGINT`` ``IDENTITY`` [11] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQLite** | *all* | ``INTEGER`` [15] | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **decimal** [7] | ``string`` | **MySQL** | *all* | ``NUMERIC(p, s)`` ``UNSIGNED`` [10] | +| | [9] +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``NUMERIC(p, s)`` | +| | +--------------------------+ | | +| | | **Oracle** | | | +| | +--------------------------+ | | +| | | **SQL Server** | | | +| | +--------------------------+ | | +| | | **SQLite** | | | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **smallfloat** | ``float`` | **MySQL** | *all* | ``FLOAT`` ``UNSIGNED`` [10] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``REAL`` | +| | +--------------------------+ | | +| | | **Oracle** | | | +| | +--------------------------+ | | +| | | **SQL Server** | | | +| | +--------------------------+ | | +| | | **SQLite** | | | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **float** | ``float`` | **MySQL** | *all* | ``DOUBLE PRECISION`` ``UNSIGNED`` [10] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``DOUBLE PRECISION`` | +| | +--------------------------+ | | +| | | **Oracle** | | | +| | +--------------------------+ | | +| | | **SQL Server** | | | +| | +--------------------------+ | | +| | | **SQLite** | | | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **string** | ``string`` | **MySQL** | *all* | ``VARCHAR(n)`` [3] | +| [2] [5] | +--------------------------+ | | +| | | **PostgreSQL** | | | +| | +--------------------------+ +----------------------------------------------------------+ +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``VARCHAR2(n)`` [3] | +| | | | +----------------------------------------------------------+ +| | | | | ``CHAR(n)`` [4] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``NVARCHAR(n)`` [3] | +| | | | +----------------------------------------------------------+ +| | | | | ``NCHAR(n)`` [4] | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **ascii_string** | ``string`` | **SQL Server** | | ``VARCHAR(n)`` | +| | | | | ``CHAR(n)`` | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **text** | ``string`` | **MySQL** | *all* | ``TINYTEXT`` [16] | +| | | | +----------------------------------------------------------+ +| | | | | ``TEXT`` [17] | +| | | | +----------------------------------------------------------+ +| | | | | ``MEDIUMTEXT`` [18] | +| | | | +----------------------------------------------------------+ +| | | | | ``LONGTEXT`` [19] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``TEXT`` | +| | +--------------------------+ | | +| | | **Oracle** | *all* | ``CLOB`` | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``VARCHAR(MAX)`` | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **guid** | ``string`` | **MySQL** | *all* | ``CHAR(36)`` [1] | +| | +--------------------------+ | | +| | | **Oracle** | | | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``UNIQUEIDENTIFIER`` | +| | +--------------------------+ | | +| | | **PostgreSQL** | *all* | ``UUID`` | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **binary** | ``string`` | **MySQL** | *all* | ``VARBINARY(n)`` [3] | +| [2] [6] | +--------------------------+ | | +| | | **SQL Server** | | ``BINARY(n)`` [4] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``RAW(n)`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``BYTEA`` [15] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQLite** | *all* | ``BLOB`` [15] | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **blob** | ``resource`` | **MySQL** | *all* | ``TINYBLOB`` [16] | +| | | | +----------------------------------------------------------+ +| | | | | ``BLOB`` [17] | +| | | | +----------------------------------------------------------+ +| | | | | ``MEDIUMBLOB`` [18] | +| | | | +----------------------------------------------------------+ +| | | | | ``LONGBLOB`` [19] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``BLOB`` | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``VARBINARY(MAX)`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``BYTEA`` | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **boolean** | ``boolean`` | **MySQL** | *all* | ``TINYINT(1)`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``BOOLEAN`` | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``BIT`` | +| | +--------------------------+ | | +| | | **Oracle** | *all* | ``NUMBER(1)`` | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **date** | ``\DateTime`` | **MySQL** | *all* | ``DATE`` | +| | +--------------------------+ | | +| | | **PostgreSQL** | | | +| | +--------------------------+ | | +| | | **Oracle** | | | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+ | +| | | **SQL Server** | "all" | | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **datetime** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` [13] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``DATETIME`` | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITHOUT TIME ZONE`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``TIMESTAMP(0)`` | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **datetimetz** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` [14] [15] | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+ | +| | | **SQL Server** | "all" | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITH TIME ZONE`` | +| | +--------------------------+ | | +| | | **Oracle** | | | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **time** | ``\DateTime`` | **MySQL** | *all* | ``TIME`` | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``TIME(0) WITHOUT TIME ZONE`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``DATE`` [15] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | "all" | ``TIME(0)`` | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **simple array** | ``array`` | **MySQL** | *all* | ``TINYTEXT`` [16] | +| [1] | | | +----------------------------------------------------------+ +| | | | | ``TEXT`` [17] | +| | | | +----------------------------------------------------------+ +| | | | | ``MEDIUMTEXT`` [18] | +| | | | +----------------------------------------------------------+ +| | | | | ``LONGTEXT`` [19] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``TEXT`` | +| | +--------------------------+ | | +| | | **Oracle** | *all* | ``CLOB`` | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``VARCHAR(MAX)`` | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ +| **json** | ``mixed`` | **MySQL** | *all* | ``JSON`` | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **PostgreSQL** | *all* | ``JSON`` [20] | +| | | | +----------------------------------------------------------+ +| | | | | ``JSONB`` [21] | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **Oracle** | *all* | ``CLOB`` [1] | +| | +--------------------------+ | | +| | | **SQLite** | | | +| | +--------------------------+---------+----------------------------------------------------------+ +| | | **SQL Server** | *all* | ``VARCHAR(MAX)`` [1] | ++-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ **Notes** From 7ad1450695e1ea3b5b9bd2346781ee0a72d90a54 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 10:06:32 -0800 Subject: [PATCH 02/11] Use function db2_field_name --- src/Driver/IBMDB2/Result.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Driver/IBMDB2/Result.php b/src/Driver/IBMDB2/Result.php index b2cf62c8154..3acf6358e8c 100644 --- a/src/Driver/IBMDB2/Result.php +++ b/src/Driver/IBMDB2/Result.php @@ -11,6 +11,7 @@ use function db2_fetch_array; use function db2_fetch_assoc; +use function db2_field_name; use function db2_free_result; use function db2_num_fields; use function db2_num_rows; From d81174d7bf8ba582e43ab5e13bc17f79aa1f4d75 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 12:10:12 -0800 Subject: [PATCH 03/11] Use SYSCAT.TABLES instead of SYSIBM.SYSTABLES on IBM DB2 1. This is consistent with using other views from SYSCAT 2. SYSCAT is considered more stable and externally facing, while SYSIBM is considered internal. --- src/Schema/DB2SchemaManager.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Schema/DB2SchemaManager.php b/src/Schema/DB2SchemaManager.php index 93f43078382..54827999775 100644 --- a/src/Schema/DB2SchemaManager.php +++ b/src/Schema/DB2SchemaManager.php @@ -23,6 +23,8 @@ /** * IBM Db2 Schema Manager. * + * @link https://www.ibm.com/docs/en/db2/11.5?topic=sql-catalog-views + * * @extends AbstractSchemaManager */ class DB2SchemaManager extends AbstractSchemaManager @@ -194,10 +196,10 @@ protected function normalizeName(string $name): string protected function selectTableNames(string $databaseName): Result { $sql = <<<'SQL' -SELECT NAME -FROM SYSIBM.SYSTABLES +SELECT TABNAME AS NAME +FROM SYSCAT.TABLES WHERE TYPE = 'T' - AND CREATOR = ? + AND TABSCHEMA = ? SQL; return $this->connection->executeQuery($sql, [$databaseName]); @@ -336,17 +338,17 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = 'SELECT NAME, REMARKS'; + $sql = 'SELECT TABNAME AS NAME, REMARKS'; $conditions = []; $params = []; if ($tableName !== null) { - $conditions[] = 'NAME = ?'; + $conditions[] = 'TABNAME = ?'; $params[] = $tableName; } - $sql .= ' FROM SYSIBM.SYSTABLES'; + $sql .= ' FROM SYSCAT.TABLES'; if ($conditions !== []) { $sql .= ' WHERE ' . implode(' AND ', $conditions); From a0e0e49ea2e3dfc6f428b5a65238bf8b4440d0c3 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 12:33:36 -0800 Subject: [PATCH 04/11] Use sys.tables instead of sys.objects on SQL Server sys.tables represents only user-defined tables, so this way there is no need to filter them by type = 'U'. --- src/Schema/SQLServerSchemaManager.php | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php index 236d49ce3e6..31ea9a75dfc 100644 --- a/src/Schema/SQLServerSchemaManager.php +++ b/src/Schema/SQLServerSchemaManager.php @@ -305,9 +305,8 @@ protected function selectTableNames(string $databaseName): Result $sql = <<<'SQL' SELECT name AS table_name, SCHEMA_NAME(schema_id) AS schema_name -FROM sys.objects -WHERE type = 'U' - AND name != 'sysdiagrams' +FROM sys.tables +WHERE name != 'sysdiagrams' ORDER BY name SQL; @@ -319,7 +318,7 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = $sql = 'SELECT'; if ($tableName === null) { - $sql .= ' obj.name AS table_name, scm.name AS schema_name,'; + $sql .= ' tbl.name AS table_name, scm.name AS schema_name,'; } $sql .= <<<'SQL' @@ -338,25 +337,25 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = FROM sys.columns AS col JOIN sys.types AS type ON col.user_type_id = type.user_type_id - JOIN sys.objects AS obj - ON col.object_id = obj.object_id + JOIN sys.tables AS tbl + ON col.object_id = tbl.object_id JOIN sys.schemas AS scm - ON obj.schema_id = scm.schema_id + ON tbl.schema_id = scm.schema_id LEFT JOIN sys.default_constraints def ON col.default_object_id = def.object_id AND col.object_id = def.parent_object_id LEFT JOIN sys.extended_properties AS prop - ON obj.object_id = prop.major_id + ON tbl.object_id = prop.major_id AND col.column_id = prop.minor_id AND prop.name = 'MS_Description' SQL; // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams - $conditions = ["obj.type = 'U'", "obj.name != 'sysdiagrams'"]; + $conditions = ["tbl.name != 'sysdiagrams'"]; $params = []; if ($tableName !== null) { - $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name'); + $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); } $sql .= ' WHERE ' . implode(' AND ', $conditions); @@ -421,14 +420,14 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN 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, + SCHEMA_NAME (t.SCHEMA_ID) ReferenceSchemaName, OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc - INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id + INNER JOIN sys.tables AS t ON t.OBJECT_ID = fc.referenced_object_id ON f.OBJECT_ID = fc.constraint_object_id SQL; From afb7544920297cc878e6af6131cab023d0313f59 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 12:36:37 -0800 Subject: [PATCH 05/11] Use the proper case for SQL Server system views --- src/Schema/SQLServerSchemaManager.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php index 31ea9a75dfc..3a512cbf005 100644 --- a/src/Schema/SQLServerSchemaManager.php +++ b/src/Schema/SQLServerSchemaManager.php @@ -412,23 +412,23 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN $sql = 'SELECT'; if ($tableName === null) { - $sql .= ' OBJECT_NAME (f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,'; + $sql .= ' OBJECT_NAME(f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,'; } $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 (t.SCHEMA_ID) ReferenceSchemaName, - OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, - COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, + 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(t.schema_id) ReferenceSchemaName, + OBJECT_NAME(f.referenced_object_id) AS ReferenceTableName, + COL_NAME(fc.referenced_object_id, fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc - INNER JOIN sys.tables AS t ON t.OBJECT_ID = fc.referenced_object_id - ON f.OBJECT_ID = fc.constraint_object_id + INNER JOIN sys.tables AS t ON t.object_id = fc.referenced_object_id + ON f.object_id = fc.constraint_object_id SQL; $conditions = []; From 1e13649b33ced7470f1f7bff3cc5c91f60ac9cf6 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 12:40:33 -0800 Subject: [PATCH 06/11] Remove unused columns --- src/Schema/SQLServerSchemaManager.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php index 3a512cbf005..3b73c6ad2c3 100644 --- a/src/Schema/SQLServerSchemaManager.php +++ b/src/Schema/SQLServerSchemaManager.php @@ -417,8 +417,6 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN $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(t.schema_id) ReferenceSchemaName, OBJECT_NAME(f.referenced_object_id) AS ReferenceTableName, From 483244c24e55fc2ce03ae377aa5d6f5b09d8a188 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 13:07:41 -0800 Subject: [PATCH 07/11] Remove DISTINCT --- src/Schema/MySQLSchemaManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php index e53b38c39f8..0ad71015b53 100644 --- a/src/Schema/MySQLSchemaManager.php +++ b/src/Schema/MySQLSchemaManager.php @@ -438,7 +438,7 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT DISTINCT'; + $sql = 'SELECT'; if ($tableName === null) { $sql .= ' k.TABLE_NAME,'; From bf5168fa6cbfab9b10890eb91ef8a2e0ac35fe7a Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 13:58:43 -0800 Subject: [PATCH 08/11] Fix introspection of table options in non-default schemas --- src/Platforms/SQLServerPlatform.php | 9 +++- src/Schema/PostgreSQLSchemaManager.php | 10 ++++- src/Schema/SQLServerSchemaManager.php | 26 ++++++----- .../Schema/PostgreSQLSchemaManagerTest.php | 39 ----------------- .../SchemaManagerFunctionalTestCase.php | 43 +++++++++++++++++++ 5 files changed, 74 insertions(+), 53 deletions(-) diff --git a/src/Platforms/SQLServerPlatform.php b/src/Platforms/SQLServerPlatform.php index 16d7bd4e73d..eed1cbab4d8 100644 --- a/src/Platforms/SQLServerPlatform.php +++ b/src/Platforms/SQLServerPlatform.php @@ -38,6 +38,7 @@ use function preg_match; use function preg_match_all; use function sprintf; +use function str_contains; use function str_ends_with; use function str_replace; use function str_starts_with; @@ -1777,11 +1778,17 @@ private function generateIdentifierName($identifier): string protected function getCommentOnTableSQL(string $tableName, ?string $comment): string { + if (str_contains($tableName, '.')) { + [$schemaName, $tableName] = explode('.', $tableName); + } else { + $schemaName = 'dbo'; + } + return $this->getAddExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', - $this->quoteStringLiteral('dbo'), + $this->quoteStringLiteral($schemaName), 'TABLE', $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), ); diff --git a/src/Schema/PostgreSQLSchemaManager.php b/src/Schema/PostgreSQLSchemaManager.php index 1716249fc14..8346d175c5d 100644 --- a/src/Schema/PostgreSQLSchemaManager.php +++ b/src/Schema/PostgreSQLSchemaManager.php @@ -726,7 +726,8 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { $sql = <<<'SQL' -SELECT c.relname, +SELECT n.nspname AS schema_name, + c.relname AS table_name, CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged, obj_description(c.oid, 'pg_class') AS comment FROM pg_class c @@ -738,7 +739,12 @@ protected function fetchTableOptionsByTable(string $databaseName, ?string $table $sql .= ' WHERE ' . implode(' AND ', $conditions); - return $this->_conn->fetchAllAssociativeIndexed($sql); + $tableOptions = []; + foreach ($this->_conn->iterateAssociative($sql) as $row) { + $tableOptions[$this->_getPortableTableDefinition($row)] = $row; + } + + return $tableOptions; } /** diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php index acef511abea..a46e552147d 100644 --- a/src/Schema/SQLServerSchemaManager.php +++ b/src/Schema/SQLServerSchemaManager.php @@ -227,9 +227,15 @@ protected function _getPortableTableForeignKeysList($tableForeignKeys) $name = $tableForeignKey['ForeignKey']; if (! isset($foreignKeys[$name])) { + $referencedTableName = $tableForeignKey['ReferenceTableName']; + + if ($tableForeignKey['ReferenceSchemaName'] !== 'dbo') { + $referencedTableName = $tableForeignKey['ReferenceSchemaName'] . '.' . $referencedTableName; + } + $foreignKeys[$name] = [ 'local_columns' => [$tableForeignKey['ColumnName']], - 'foreign_table' => $tableForeignKey['ReferenceTableName'], + 'foreign_table' => $referencedTableName, 'foreign_columns' => [$tableForeignKey['ReferenceColumnName']], 'name' => $name, 'options' => [ @@ -556,31 +562,29 @@ protected function fetchTableOptionsByTable(string $databaseName, ?string $table { $sql = <<<'SQL' SELECT - tbl.name, + scm.name AS schema_name, + tbl.name AS table_name, p.value AS [table_comment] FROM sys.tables AS tbl + JOIN sys.schemas AS scm + ON tbl.schema_id = scm.schema_id INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 SQL; - $conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"]; - $params = []; + $conditions = ["p.name = N'MS_Description'"]; if ($tableName !== null) { - $conditions[] = "tbl.name = N'" . $tableName . "'"; + $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); } $sql .= ' WHERE ' . implode(' AND ', $conditions); - /** @var array> $metadata */ - $metadata = $this->_conn->executeQuery($sql, $params) - ->fetchAllAssociativeIndexed(); - $tableOptions = []; - foreach ($metadata as $table => $data) { + foreach ($this->_conn->iterateAssociative($sql) as $data) { $data = array_change_key_case($data, CASE_LOWER); - $tableOptions[$table] = [ + $tableOptions[$this->_getPortableTableDefinition($data)] = [ 'comment' => $data['table_comment'], ]; } diff --git a/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php b/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php index a98bf4d08b3..2695b0a31c6 100644 --- a/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php +++ b/tests/Functional/Schema/PostgreSQLSchemaManagerTest.php @@ -25,7 +25,6 @@ use function array_map; use function array_merge; -use function array_pop; use function array_unshift; use function assert; use function count; @@ -163,44 +162,6 @@ public function testAlterTableAutoIncrementDrop(callable $comparatorFactory): vo self::assertFalse($tableFinal->getColumn('id')->getAutoincrement()); } - public function testTableWithSchema(): void - { - $this->connection->executeStatement('CREATE SCHEMA nested'); - - $nestedRelatedTable = new Table('nested.schemarelated'); - $column = $nestedRelatedTable->addColumn('id', Types::INTEGER); - $column->setAutoincrement(true); - $nestedRelatedTable->setPrimaryKey(['id']); - - $nestedSchemaTable = new Table('nested.schematable'); - $column = $nestedSchemaTable->addColumn('id', Types::INTEGER); - $column->setAutoincrement(true); - $nestedSchemaTable->setPrimaryKey(['id']); - $nestedSchemaTable->addForeignKeyConstraint($nestedRelatedTable, ['id'], ['id']); - - $this->schemaManager->createTable($nestedRelatedTable); - $this->schemaManager->createTable($nestedSchemaTable); - - $tableNames = $this->schemaManager->listTableNames(); - self::assertContains('nested.schematable', $tableNames); - - $tables = $this->schemaManager->listTables(); - self::assertNotNull($this->findTableByName($tables, 'nested.schematable')); - - $nestedSchemaTable = $this->schemaManager->introspectTable('nested.schematable'); - self::assertTrue($nestedSchemaTable->hasColumn('id')); - - $primaryKey = $nestedSchemaTable->getPrimaryKey(); - - self::assertNotNull($primaryKey); - self::assertEquals(['id'], $primaryKey->getColumns()); - - $relatedFks = $nestedSchemaTable->getForeignKeys(); - self::assertCount(1, $relatedFks); - $relatedFk = array_pop($relatedFks); - self::assertEquals('nested.schemarelated', $relatedFk->getForeignTableName()); - } - public function testListSameTableNameColumnsWithDifferentSchema(): void { $this->connection->executeStatement('CREATE SCHEMA another'); diff --git a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php index 65b2d2fb6cf..d67a932efd7 100644 --- a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -1813,6 +1813,49 @@ protected function findTableByName(array $tables, string $name): ?Table return null; } + + public function testTableWithSchema(): void + { + if (! $this->connection->getDatabasePlatform()->supportsSchemas()) { + self::markTestSkipped('The currently used database platform does not support schemas.'); + } + + $this->connection->executeStatement('CREATE SCHEMA nested'); + + $nestedRelatedTable = new Table('nested.schemarelated'); + $column = $nestedRelatedTable->addColumn('id', Types::INTEGER); + $column->setAutoincrement(true); + $nestedRelatedTable->setPrimaryKey(['id']); + + $nestedSchemaTable = new Table('nested.schematable'); + $column = $nestedSchemaTable->addColumn('id', Types::INTEGER); + $column->setAutoincrement(true); + $nestedSchemaTable->setPrimaryKey(['id']); + $nestedSchemaTable->addForeignKeyConstraint($nestedRelatedTable->getName(), ['id'], ['id']); + $nestedSchemaTable->setComment('This is a comment'); + + $this->schemaManager->createTable($nestedRelatedTable); + $this->schemaManager->createTable($nestedSchemaTable); + + $tableNames = $this->schemaManager->listTableNames(); + self::assertContains('nested.schematable', $tableNames); + + $tables = $this->schemaManager->listTables(); + self::assertNotNull($this->findTableByName($tables, 'nested.schematable')); + + $nestedSchemaTable = $this->schemaManager->introspectTable('nested.schematable'); + self::assertTrue($nestedSchemaTable->hasColumn('id')); + + $primaryKey = $nestedSchemaTable->getPrimaryKey(); + self::assertNotNull($primaryKey); + self::assertEquals(['id'], $primaryKey->getColumns()); + + $relatedFks = array_values($nestedSchemaTable->getForeignKeys()); + self::assertCount(1, $relatedFks); + $relatedFk = $relatedFks[0]; + self::assertEquals('nested.schemarelated', $relatedFk->getForeignTableName()); + self::assertEquals('This is a comment', $nestedSchemaTable->getComment()); + } } interface ListTableColumnsDispatchEventListener From 4443706b219f1c0d733a2278738ea37db972219a Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Fri, 24 Jan 2025 22:21:02 -0800 Subject: [PATCH 09/11] Refactor building schema introspection queries 1. Try to keep the query as static as possible to make them more readable and possible to analyze by IDEs. As an exception to this rule, consolidate exclusion of special tables in one place. 2. Use more specific fetch methods where possible. For example, where it eliminates the need to normalize fetched column names. 3. Bind all parameters instead of using string literals. 4. Order introspected rows by schema name and table name. --- src/Schema/DB2SchemaManager.php | 127 ++++++++--------- src/Schema/MySQLSchemaManager.php | 116 ++++++++-------- src/Schema/OracleSchemaManager.php | 119 ++++++++-------- src/Schema/PostgreSQLSchemaManager.php | 155 +++++++++++---------- src/Schema/SQLServerSchemaManager.php | 184 ++++++++++++------------- src/Schema/SQLiteSchemaManager.php | 126 ++++++++--------- 6 files changed, 411 insertions(+), 416 deletions(-) diff --git a/src/Schema/DB2SchemaManager.php b/src/Schema/DB2SchemaManager.php index 54827999775..0efefa6f575 100644 --- a/src/Schema/DB2SchemaManager.php +++ b/src/Schema/DB2SchemaManager.php @@ -12,6 +12,7 @@ use function array_change_key_case; use function implode; use function preg_match; +use function sprintf; use function str_replace; use function strpos; use function strtolower; @@ -207,13 +208,18 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['C.TABSCHEMA = ?']; + $params = [$databaseName]; - if ($tableName === null) { - $sql .= ' C.TABNAME AS NAME,'; + if ($tableName !== null) { + $conditions[] = 'C.TABNAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' +SELECT + C.TABNAME AS NAME, C.COLNAME, C.TYPENAME, C.CODEPAGE, @@ -230,30 +236,30 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = JOIN SYSCAT.TABLES AS T ON T.TABSCHEMA = C.TABSCHEMA AND T.TABNAME = C.TABNAME -SQL; - - $conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"]; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'C.TABNAME = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO'; + WHERE %s + AND T.TYPE = 'T' +ORDER BY C.TABNAME, C.COLNO +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['IDX.TABSCHEMA = ?']; + $params = [$databaseName]; - if ($tableName === null) { - $sql .= ' IDX.TABNAME AS NAME,'; + if ($tableName !== null) { + $conditions[] = 'IDX.TABNAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + IDX.TABNAME AS NAME, IDX.INDNAME AS KEY_NAME, IDXCOL.COLNAME AS COLUMN_NAME, CASE @@ -269,30 +275,32 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = ON IDX.TABSCHEMA = T.TABSCHEMA AND IDX.TABNAME = T.TABNAME JOIN SYSCAT.INDEXCOLUSE AS IDXCOL ON IDX.INDSCHEMA = IDXCOL.INDSCHEMA AND IDX.INDNAME = IDXCOL.INDNAME -SQL; - - $conditions = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"]; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'IDX.TABNAME = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ'; + WHERE %s + AND T.TYPE = 'T' + ORDER BY IDX.TABNAME, + IDX.INDNAME, + IDXCOL.COLSEQ +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['R.TABSCHEMA = ?']; + $params = [$databaseName]; - if ($tableName === null) { - $sql .= ' R.TABNAME AS NAME,'; + if ($tableName !== null) { + $conditions[] = 'R.TABNAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + R.TABNAME AS NAME, FKCOL.COLNAME AS LOCAL_COLUMN, R.REFTABNAME AS FOREIGN_TABLE, PKCOL.COLNAME AS FOREIGN_COLUMN, @@ -318,17 +326,14 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN AND PKCOL.TABSCHEMA = R.REFTABSCHEMA AND PKCOL.TABNAME = R.REFTABNAME AND PKCOL.COLSEQ = FKCOL.COLSEQ -SQL; - - $conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"]; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'R.TABNAME = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ'; + WHERE %s + AND T.TYPE = 'T' + ORDER BY R.TABNAME, + R.CONSTNAME, + FKCOL.COLSEQ +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } @@ -338,31 +343,29 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = 'SELECT TABNAME AS NAME, REMARKS'; - - $conditions = []; - $params = []; + $conditions = ['TABSCHEMA = ?']; + $params = [$databaseName]; if ($tableName !== null) { $conditions[] = 'TABNAME = ?'; $params[] = $tableName; } - $sql .= ' FROM SYSCAT.TABLES'; - - if ($conditions !== []) { - $sql .= ' WHERE ' . implode(' AND ', $conditions); - } - - /** @var array> $metadata */ - $metadata = $this->connection->executeQuery($sql, $params) - ->fetchAllAssociativeIndexed(); + $sql = sprintf( + <<<'SQL' + SELECT TABNAME, + REMARKS + FROM SYSCAT.TABLES + WHERE %s + AND TYPE = 'T' + ORDER BY TABNAME +SQL, + implode(' AND ', $conditions), + ); $tableOptions = []; - foreach ($metadata as $table => $data) { - $data = array_change_key_case($data, CASE_LOWER); - - $tableOptions[$table] = ['comment' => $data['remarks']]; + foreach ($this->connection->iterateKeyValue($sql, $params) as $table => $remarks) { + $tableOptions[$table] = ['comment' => $remarks]; } return $tableOptions; diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php index 0ad71015b53..245b3b193da 100644 --- a/src/Schema/MySQLSchemaManager.php +++ b/src/Schema/MySQLSchemaManager.php @@ -26,6 +26,7 @@ use function is_string; use function preg_match; use function preg_match_all; +use function sprintf; use function str_contains; use function strtok; use function strtolower; @@ -367,17 +368,23 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $columnTypeSQL = $this->platform->getColumnTypeSQLSnippet('c', $databaseName); - - $sql = 'SELECT'; + // The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition + // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions + // caused by https://bugs.mysql.com/bug.php?id=81347 + $conditions = ['c.TABLE_SCHEMA = ?', 't.TABLE_SCHEMA = ?']; + $params = [$databaseName, $databaseName]; - if ($tableName === null) { - $sql .= ' c.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 't.TABLE_NAME = ?'; + $params[] = $tableName; } - $sql .= <<platform->getColumnTypeSQLSnippet('c', $databaseName), + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['TABLE_SCHEMA = ?']; + $params = [$databaseName]; - if ($tableName === null) { - $sql .= ' TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'TABLE_NAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' +SELECT + TABLE_NAME, NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name, SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type FROM information_schema.STATISTICS -SQL; - - $conditions = ['TABLE_SCHEMA = ?']; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'TABLE_NAME = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX'; +WHERE %s +ORDER BY TABLE_NAME, + SEQ_IN_INDEX +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + // The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition + // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions + // caused by https://bugs.mysql.com/bug.php?id=81347 + $conditions = ['k.TABLE_SCHEMA = ?', 'c.CONSTRAINT_SCHEMA = ?']; + $params = [$databaseName, $databaseName]; - if ($tableName === null) { - $sql .= ' k.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'k.TABLE_NAME = ?'; + $params[] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' +SELECT + k.TABLE_NAME, k.CONSTRAINT_NAME, k.COLUMN_NAME, k.REFERENCED_TABLE_NAME, @@ -456,25 +465,14 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN INNER JOIN information_schema.referential_constraints c ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND c.TABLE_NAME = k.TABLE_NAME -SQL; - - $conditions = ['k.TABLE_SCHEMA = ?']; - $params = [$databaseName]; - - if ($tableName !== null) { - $conditions[] = 'k.TABLE_NAME = ?'; - $params[] = $tableName; - } - - // The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition - // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions - // caused by https://bugs.mysql.com/bug.php?id=81347 - $conditions[] = 'c.CONSTRAINT_SCHEMA = ?'; - $params[] = $databaseName; - - $conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL'; - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY k.CONSTRAINT_NAME, k.ORDINAL_POSITION'; +WHERE %s +AND k.REFERENCED_COLUMN_NAME IS NOT NULL +ORDER BY k.TABLE_NAME, + k.CONSTRAINT_NAME, + k.ORDINAL_POSITION +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 0222eca5458..3a0bf7a89d2 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -16,6 +16,7 @@ use function implode; use function is_string; use function preg_match; +use function sprintf; use function str_contains; use function str_replace; use function str_starts_with; @@ -330,13 +331,18 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['C.OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; - if ($tableName === null) { - $sql .= ' C.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'C.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + C.TABLE_NAME, C.COLUMN_NAME, C.DATA_TYPE, C.DATA_DEFAULT, @@ -354,30 +360,29 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = ON D.OWNER = C.OWNER AND D.TABLE_NAME = C.TABLE_NAME AND D.COLUMN_NAME = C.COLUMN_NAME -SQL; - - $conditions = ['C.OWNER = :OWNER']; - $params = ['OWNER' => $databaseName]; - - if ($tableName !== null) { - $conditions[] = 'C.TABLE_NAME = :TABLE_NAME'; - $params['TABLE_NAME'] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID'; + WHERE %s + ORDER BY C.TABLE_NAME, C.COLUMN_ID +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ['IND_COL.INDEX_OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; - if ($tableName === null) { - $sql .= ' IND_COL.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + IND_COL.TABLE_NAME, IND_COL.INDEX_NAME AS NAME, IND.INDEX_TYPE AS TYPE, DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE, @@ -391,31 +396,31 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = LEFT JOIN ALL_CONSTRAINTS CON ON CON.OWNER = IND_COL.INDEX_OWNER AND CON.INDEX_NAME = IND_COL.INDEX_NAME -SQL; - - $conditions = ['IND_COL.INDEX_OWNER = :OWNER']; - $params = ['OWNER' => $databaseName]; - - if ($tableName !== null) { - $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME'; - $params['TABLE_NAME'] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME' - . ', IND_COL.COLUMN_POSITION'; + WHERE %s + ORDER BY IND_COL.TABLE_NAME, + IND_COL.INDEX_NAME, + IND_COL.COLUMN_POSITION +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER']; + $params = ['OWNER' => $databaseName]; - if ($tableName === null) { - $sql .= ' COLS.TABLE_NAME,'; + if ($tableName !== null) { + $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; + $params['TABLE_NAME'] = $tableName; } - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + COLS.TABLE_NAME, ALC.CONSTRAINT_NAME, ALC.DELETE_RULE, ALC.DEFERRABLE, @@ -429,18 +434,13 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND R_COLS.POSITION = COLS.POSITION -SQL; - - $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER']; - $params = ['OWNER' => $databaseName]; - - if ($tableName !== null) { - $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; - $params['TABLE_NAME'] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME' - . ', COLS.POSITION'; + WHERE %s + ORDER BY COLS.TABLE_NAME, + COLS.CONSTRAINT_NAME, + COLS.POSITION +SQL, + implode(' AND ', $conditions), + ); return $this->connection->executeQuery($sql, $params); } @@ -450,8 +450,6 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = 'SELECT TABLE_NAME, COMMENTS'; - $conditions = ['OWNER = :OWNER']; $params = ['OWNER' => $databaseName]; @@ -460,19 +458,20 @@ protected function fetchTableOptionsByTable(string $databaseName, ?string $table $params['TABLE_NAME'] = $tableName; } - $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions); - - /** @var array> $metadata */ - $metadata = $this->connection->executeQuery($sql, $params) - ->fetchAllAssociativeIndexed(); + $sql = sprintf( + <<<'SQL' + SELECT TABLE_NAME, + COMMENTS + FROM ALL_TAB_COMMENTS + WHERE %s + ORDER BY TABLE_NAME +SQL, + implode(' AND ', $conditions), + ); $tableOptions = []; - foreach ($metadata as $table => $data) { - $data = array_change_key_case($data, CASE_LOWER); - - $tableOptions[$table] = [ - 'comment' => $data['comments'], - ]; + foreach ($this->connection->iterateKeyValue($sql, $params) as $table => $comments) { + $tableOptions[$table] = ['comment' => $comments]; } return $tableOptions; diff --git a/src/Schema/PostgreSQLSchemaManager.php b/src/Schema/PostgreSQLSchemaManager.php index a3b4138ad7a..8054b4097ca 100644 --- a/src/Schema/PostgreSQLSchemaManager.php +++ b/src/Schema/PostgreSQLSchemaManager.php @@ -13,7 +13,6 @@ use function array_change_key_case; use function array_key_exists; use function array_map; -use function array_merge; use function assert; use function explode; use function implode; @@ -412,13 +411,13 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT '; + $params = []; - if ($tableName === null) { - $sql .= 'c.relname AS table_name, n.nspname AS schema_name,'; - } - - $sql .= sprintf(<<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + n.nspname AS schema_name, + c.relname AS table_name, a.attnum, quote_ident(a.attname) AS field, t.typname AS type, @@ -450,41 +449,37 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = ON d.objid = c.oid AND d.deptype = 'e' AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') - SQL, $this->platform->getDefaultColumnValueSQLSnippet()); - - $conditions = array_merge([ - 'a.attnum > 0', - 'd.refobjid IS NULL', - - // 'r' for regular tables - 'p' for partitioned tables - "c.relkind IN('r', 'p')", - - // exclude partitions (tables that inherit from partitioned tables) - <<<'SQL' - NOT EXISTS ( - SELECT 1 - FROM pg_inherits - INNER JOIN pg_class parent on pg_inherits.inhparent = parent.oid - AND parent.relkind = 'p' - WHERE inhrelid = c.oid - ) + WHERE a.attnum > 0 + AND d.refobjid IS NULL + -- 'r' for regular tables - 'p' for partitioned tables + AND c.relkind IN('r', 'p') + -- exclude partitions (tables that inherit from partitioned tables) + AND NOT EXISTS ( + SELECT 1 + FROM pg_inherits + INNER JOIN pg_class parent on pg_inherits.inhparent = parent.oid + AND parent.relkind = 'p' + WHERE inhrelid = c.oid + ) + AND %s + ORDER BY a.attnum SQL, - ], $this->buildQueryConditions($tableName)); - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum'; + $this->platform->getDefaultColumnValueSQLSnippet(), + implode(' AND ', $this->buildQueryConditions($tableName, $params)), + ); - return $this->connection->executeQuery($sql); + return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $params = []; - if ($tableName === null) { - $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; - } - - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + tn.nspname AS schema_name, + tc.relname AS table_name, quote_ident(ic.relname) AS relname, i.indisunique, i.indisprimary, @@ -497,28 +492,26 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = JOIN pg_class AS ic ON ic.oid = i.indexrelid WHERE ic.oid IN ( SELECT indexrelid - FROM pg_index i, pg_class c, pg_namespace n -SQL; - - $conditions = array_merge([ - 'c.oid = i.indrelid', - 'c.relnamespace = n.oid', - ], $this->buildQueryConditions($tableName)); - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ')'; + FROM pg_index i + JOIN pg_class AS c ON c.oid = i.indrelid + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE %s) + SQL, + implode(' AND ', $this->buildQueryConditions($tableName, $params)), + ); - return $this->connection->executeQuery($sql); + return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; + $params = []; - if ($tableName === null) { - $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; - } - - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + tn.nspname AS schema_name, + tc.relname AS table_name, quote_ident(r.conname) as conname, pg_get_constraintdef(r.oid, true) as condef, r.condeferrable, @@ -529,14 +522,16 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN WHERE r.conrelid IN ( SELECT c.oid - FROM pg_class c, pg_namespace n -SQL; - - $conditions = array_merge(['n.oid = c.relnamespace'], $this->buildQueryConditions($tableName)); - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ") AND r.contype = 'f'"; + FROM pg_class c + JOIN pg_namespace n + ON n.oid = c.relnamespace + WHERE %s) + AND r.contype = 'f' + SQL, + implode(' AND ', $this->buildQueryConditions($tableName, $params)), + ); - return $this->connection->executeQuery($sql); + return $this->connection->executeQuery($sql, $params); } /** @@ -544,37 +539,47 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = <<<'SQL' -SELECT c.relname, - CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged, - obj_description(c.oid, 'pg_class') AS comment -FROM pg_class c - INNER JOIN pg_namespace n - ON n.oid = c.relnamespace -SQL; + $params = []; - $conditions = array_merge(["c.relkind = 'r'"], $this->buildQueryConditions($tableName)); - - $sql .= ' WHERE ' . implode(' AND ', $conditions); + $sql = sprintf( + <<<'SQL' + SELECT c.relname, + CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged, + obj_description(c.oid, 'pg_class') AS comment + FROM pg_class c + INNER JOIN pg_namespace n + ON n.oid = c.relnamespace + WHERE + c.relkind = 'r' + AND %s + SQL, + implode(' AND ', $this->buildQueryConditions($tableName, $params)), + ); - return $this->connection->fetchAllAssociativeIndexed($sql); + return $this->connection->fetchAllAssociativeIndexed($sql, $params); } - /** @return list */ - private function buildQueryConditions(?string $tableName): array + /** + * @param list $params + * + * @return non-empty-list + */ + private function buildQueryConditions(?string $tableName, array &$params): array { $conditions = []; if ($tableName !== null) { if (str_contains($tableName, '.')) { [$schemaName, $tableName] = explode('.', $tableName); - $conditions[] = 'n.nspname = ' . $this->platform->quoteStringLiteral($schemaName); + + $conditions[] = 'n.nspname = ?'; + $params[] = $schemaName; } else { $conditions[] = 'n.nspname = ANY(current_schemas(false))'; } - $identifier = new Identifier($tableName); - $conditions[] = 'c.relname = ' . $this->platform->quoteStringLiteral($identifier->getName()); + $conditions[] = 'c.relname = ?'; + $params[] = $tableName; } $conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')"; diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php index 3b73c6ad2c3..2d9bc9cf6a9 100644 --- a/src/Schema/SQLServerSchemaManager.php +++ b/src/Schema/SQLServerSchemaManager.php @@ -10,7 +10,6 @@ use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; -use function array_change_key_case; use function assert; use function explode; use function func_get_arg; @@ -23,8 +22,6 @@ use function str_replace; use function strtok; -use const CASE_LOWER; - /** * SQL Server Schema Manager. * @@ -303,8 +300,8 @@ protected function selectTableNames(string $databaseName): Result { // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams $sql = <<<'SQL' -SELECT name AS table_name, - SCHEMA_NAME(schema_id) AS schema_name +SELECT SCHEMA_NAME(schema_id) AS schema_name, + name AS table_name FROM sys.tables WHERE name != 'sysdiagrams' ORDER BY name @@ -315,13 +312,13 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; - - if ($tableName === null) { - $sql .= ' tbl.name AS table_name, scm.name AS schema_name,'; - } + $params = []; - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + scm.name AS schema_name, + tbl.name AS table_name, col.name, type.name AS type, col.max_length AS length, @@ -348,30 +345,26 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = ON tbl.object_id = prop.major_id AND col.column_id = prop.minor_id AND prop.name = 'MS_Description' -SQL; - - // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams - $conditions = ["tbl.name != 'sysdiagrams'"]; - $params = []; - - if ($tableName !== null) { - $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions); + WHERE %s + ORDER BY scm.name, + tbl.name, + col.column_id +SQL, + $this->getWhereClause($tableName, 'scm.name', 'tbl.name', $params), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; - - if ($tableName === null) { - $sql .= ' tbl.name AS table_name, scm.name AS schema_name,'; - } + $params = []; - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + scm.name AS schema_name, + tbl.name AS table_name, idx.name AS key_name, col.name AS column_name, ~idx.is_unique AS non_unique, @@ -392,30 +385,27 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id -SQL; - - $conditions = []; - $params = []; - - if ($tableName !== null) { - $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); - $sql .= ' WHERE ' . implode(' AND ', $conditions); - } - - $sql .= ' ORDER BY idx.index_id, idxcol.key_ordinal'; + WHERE %s + ORDER BY scm.name, + tbl.name, + idx.index_id, + idxcol.key_ordinal +SQL, + $this->getWhereClause($tableName, 'scm.name', 'tbl.name', $params), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = 'SELECT'; - - if ($tableName === null) { - $sql .= ' OBJECT_NAME(f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,'; - } + $params = []; - $sql .= <<<'SQL' + $sql = sprintf( + <<<'SQL' + SELECT + SCHEMA_NAME(f.schema_id) AS schema_name, + OBJECT_NAME(f.parent_object_id) AS table_name, f.name AS ForeignKey, COL_NAME(fc.parent_object_id, fc.parent_column_id) AS ColumnName, SCHEMA_NAME(t.schema_id) ReferenceSchemaName, @@ -425,24 +415,22 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc - INNER JOIN sys.tables AS t ON t.object_id = fc.referenced_object_id ON f.object_id = fc.constraint_object_id -SQL; - - $conditions = []; - $params = []; - - if ($tableName !== null) { - $conditions[] = $this->getTableWhereClause( + INNER JOIN sys.tables AS t + ON t.object_id = fc.referenced_object_id + WHERE %s + ORDER BY 1, + 2, + 3, + fc.constraint_column_id +SQL, + $this->getWhereClause( $tableName, 'SCHEMA_NAME(f.schema_id)', 'OBJECT_NAME(f.parent_object_id)', - ); - - $sql .= ' WHERE ' . implode(' AND ', $conditions); - } - - $sql .= ' ORDER BY fc.constraint_column_id'; + $params, + ), + ); return $this->connection->executeQuery($sql, $params); } @@ -452,35 +440,28 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN */ protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT tbl.name, - p.value AS [table_comment] + p.value FROM sys.tables AS tbl + JOIN sys.schemas AS scm + ON tbl.schema_id = scm.schema_id INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 -SQL; - - $conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"]; - $params = []; - - if ($tableName !== null) { - $conditions[] = "tbl.name = N'" . $tableName . "'"; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions); - - /** @var array> $metadata */ - $metadata = $this->connection->executeQuery($sql, $params) - ->fetchAllAssociativeIndexed(); + WHERE + p.name = N'MS_Description' + AND %s +SQL, + $this->getWhereClause($tableName, 'scm.name', 'tbl.name', $params), + ); $tableOptions = []; - foreach ($metadata as $table => $data) { - $data = array_change_key_case($data, CASE_LOWER); - - $tableOptions[$table] = [ - 'comment' => $data['table_comment'], - ]; + foreach ($this->connection->iterateKeyValue($sql, $params) as $name => $value) { + $tableOptions[$name] = ['comment' => $value]; } return $tableOptions; @@ -489,21 +470,36 @@ protected function fetchTableOptionsByTable(string $databaseName, ?string $table /** * Returns the where clause to filter schema and table name in a query. * - * @param string $table The full qualified name of the table. - * @param string $schemaColumn The name of the column to compare the schema to in the where clause. - * @param string $tableColumn The name of the column to compare the table to in the where clause. + * @param ?string $tableName The full qualified name of the table. + * @param string $schemaColumn The name of the column to compare the schema to in the where clause. + * @param string $tableColumn The name of the column to compare the table to in the where clause. + * @param list $params */ - private function getTableWhereClause(string $table, string $schemaColumn, string $tableColumn): string - { - if (str_contains($table, '.')) { - [$schema, $table] = explode('.', $table); - $schema = $this->platform->quoteStringLiteral($schema); - $table = $this->platform->quoteStringLiteral($table); - } else { - $schema = 'SCHEMA_NAME()'; - $table = $this->platform->quoteStringLiteral($table); + private function getWhereClause( + ?string $tableName, + string $schemaColumn, + string $tableColumn, + array &$params, + ): string { + $conditions = []; + + if ($tableName !== null) { + if (str_contains($tableName, '.')) { + [$schemaName, $tableName] = explode('.', $tableName); + + $conditions = [sprintf('%s = ?', $schemaColumn)]; + $params[] = $schemaName; + } else { + $conditions = [sprintf('%s = SCHEMA_NAME()', $schemaColumn)]; + } + + $conditions[] = sprintf('%s = ?', $tableColumn); + $params[] = $tableName; } - return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); + // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams + $conditions[] = sprintf("%s != 'sysdiagrams'", $tableColumn); + + return implode(' AND ', $conditions); } } diff --git a/src/Schema/SQLiteSchemaManager.php b/src/Schema/SQLiteSchemaManager.php index 0d2fcb6b0cf..bfc2d78a100 100644 --- a/src/Schema/SQLiteSchemaManager.php +++ b/src/Schema/SQLiteSchemaManager.php @@ -27,6 +27,7 @@ use function preg_quote; use function preg_replace; use function rtrim; +use function sprintf; use function str_contains; use function str_replace; use function str_starts_with; @@ -424,9 +425,7 @@ protected function selectTableNames(string $databaseName): Result SELECT name AS table_name FROM sqlite_master WHERE type = 'table' - AND name != 'sqlite_sequence' - AND name != 'geometry_columns' - AND name != 'spatial_ref_sys' + AND name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence') UNION ALL SELECT name FROM sqlite_temp_master @@ -439,77 +438,62 @@ protected function selectTableNames(string $databaseName): Result protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT t.name AS table_name, c.* FROM sqlite_master t JOIN pragma_table_info(t.name) c -SQL; - - $conditions = [ - "t.type = 'table'", - "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", - ]; - $params = []; - - if ($tableName !== null) { - $conditions[] = 't.name = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid'; + WHERE %s + ORDER BY t.name, + c.cid +SQL, + $this->getWhereClause($tableName, $params), + ); return $this->connection->executeQuery($sql, $params); } protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT t.name AS table_name, i.name, i."unique" FROM sqlite_master t JOIN pragma_index_list(t.name) i -SQL; - - $conditions = [ - "t.type = 'table'", - "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", - ]; - $params = []; - - if ($tableName !== null) { - $conditions[] = 't.name = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq'; + WHERE %s + ORDER BY t.name, i.seq +SQL, + $this->getWhereClause($tableName, $params), + ); return $this->connection->executeQuery($sql, $params); } protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT t.name AS table_name, p.* FROM sqlite_master t JOIN pragma_foreign_key_list(t.name) p - ON p."seq" != '-1' -SQL; - - $conditions = [ - "t.type = 'table'", - "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", - ]; - $params = []; - - if ($tableName !== null) { - $conditions[] = 't.name = ?'; - $params[] = $tableName; - } - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq'; + ON p.seq != '-1' + WHERE %s + ORDER BY t.name, + p.id DESC, + p.seq +SQL, + $this->getWhereClause($tableName, $params), + ); return $this->connection->executeQuery($sql, $params); } @@ -615,27 +599,21 @@ protected function fetchIndexColumns(string $databaseName, ?string $tableName = */ private function fetchPrimaryKeyColumns(?string $tableName = null): array { - $sql = <<<'SQL' + $params = []; + + $sql = sprintf( + <<<'SQL' SELECT t.name AS table_name, p.name FROM sqlite_master t JOIN pragma_table_info(t.name) p - SQL; - - $conditions = [ - "t.type = 'table'", - "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", - ]; - $params = []; - - if ($tableName !== null) { - $conditions[] = 't.name = ?'; - $params[] = $tableName; - } - - $conditions[] = 'p.pk > 0'; - - $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.pk'; + WHERE %s + AND p.pk > 0 + ORDER BY t.name, + p.pk + SQL, + $this->getWhereClause($tableName, $params), + ); return $this->connection->fetchAllAssociative($sql, $params); } @@ -664,4 +642,20 @@ protected function fetchTableOptionsByTable(string $databaseName, ?string $table return $tableOptions; } + + /** @param list $params */ + private function getWhereClause(?string $tableName, array &$params): string + { + $conditions = [ + "t.type = 'table'", + "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", + ]; + + if ($tableName !== null) { + $conditions[] = 't.name = ?'; + $params[] = $tableName; + } + + return implode(' AND ', $conditions); + } } From c5e1e0244395f7e7f71c1572538c93ceac178e08 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 17:10:48 -0800 Subject: [PATCH 10/11] Deprecate `AbstractSchemaManager::_getPortableTableDefinition()` --- UPGRADE.md | 5 +++++ src/Schema/AbstractSchemaManager.php | 6 +++++- src/Schema/DB2SchemaManager.php | 2 ++ src/Schema/MySQLSchemaManager.php | 2 ++ src/Schema/OracleSchemaManager.php | 2 ++ src/Schema/PostgreSQLSchemaManager.php | 2 ++ src/Schema/SQLServerSchemaManager.php | 2 ++ src/Schema/SQLiteSchemaManager.php | 2 ++ 8 files changed, 22 insertions(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index 45d1cc8cce2..78a6b5f0340 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,11 @@ awareness about deprecated code. # Upgrade to 4.3 +## Deprecated `AbstractSchemaManager::_getPortableTableDefinition()` + +The `AbstractSchemaManager::_getPortableTableDefinition()` method has been deprecated. Use the schema name and the +unqualified table name separately instead. + ## Deprecated `PostgreSQLSchemaManager` methods related to the current schema The following `PostgreSQLSchemaManager` methods have been deprecated: diff --git a/src/Schema/AbstractSchemaManager.php b/src/Schema/AbstractSchemaManager.php index 4731cccdb1c..4740b4e7d4f 100644 --- a/src/Schema/AbstractSchemaManager.php +++ b/src/Schema/AbstractSchemaManager.php @@ -837,7 +837,11 @@ protected function _getPortableTableIndexesList(array $rows, string $tableName): return $indexes; } - /** @param array $table */ + /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * + * @param array $table + */ abstract protected function _getPortableTableDefinition(array $table): string; /** @param array $view */ diff --git a/src/Schema/DB2SchemaManager.php b/src/Schema/DB2SchemaManager.php index 93f43078382..fbe995149e0 100644 --- a/src/Schema/DB2SchemaManager.php +++ b/src/Schema/DB2SchemaManager.php @@ -101,6 +101,8 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php index e53b38c39f8..388952bc54a 100644 --- a/src/Schema/MySQLSchemaManager.php +++ b/src/Schema/MySQLSchemaManager.php @@ -61,6 +61,8 @@ class MySQLSchemaManager extends AbstractSchemaManager private ?DefaultTableOptions $defaultTableOptions = null; /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string diff --git a/src/Schema/OracleSchemaManager.php b/src/Schema/OracleSchemaManager.php index 0222eca5458..a30c3fc599d 100644 --- a/src/Schema/OracleSchemaManager.php +++ b/src/Schema/OracleSchemaManager.php @@ -43,6 +43,8 @@ protected function _getPortableViewDefinition(array $view): View } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string diff --git a/src/Schema/PostgreSQLSchemaManager.php b/src/Schema/PostgreSQLSchemaManager.php index a3b4138ad7a..4a8cfad82da 100644 --- a/src/Schema/PostgreSQLSchemaManager.php +++ b/src/Schema/PostgreSQLSchemaManager.php @@ -142,6 +142,8 @@ protected function _getPortableViewDefinition(array $view): View } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string diff --git a/src/Schema/SQLServerSchemaManager.php b/src/Schema/SQLServerSchemaManager.php index 236d49ce3e6..f922e4477a9 100644 --- a/src/Schema/SQLServerSchemaManager.php +++ b/src/Schema/SQLServerSchemaManager.php @@ -236,6 +236,8 @@ protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey) } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string diff --git a/src/Schema/SQLiteSchemaManager.php b/src/Schema/SQLiteSchemaManager.php index 0d2fcb6b0cf..becf0cd3f39 100644 --- a/src/Schema/SQLiteSchemaManager.php +++ b/src/Schema/SQLiteSchemaManager.php @@ -92,6 +92,8 @@ public function listTableForeignKeys(string $table): array } /** + * @deprecated Use the schema name and the unqualified table name separately instead. + * * {@inheritDoc} */ protected function _getPortableTableDefinition(array $table): string From 62ae320d77ea01c26f05be7f5dbb9c16b4944dce Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 1 Feb 2025 17:58:41 -0800 Subject: [PATCH 11/11] Auto-quote introspected PostgreSQL identifiers --- src/Schema/PostgreSQLSchemaManager.php | 17 ++- .../Schema/OracleSchemaManagerTest.php | 116 ------------------ .../SchemaManagerFunctionalTestCase.php | 92 ++++++++++++++ 3 files changed, 104 insertions(+), 121 deletions(-) diff --git a/src/Schema/PostgreSQLSchemaManager.php b/src/Schema/PostgreSQLSchemaManager.php index 1716249fc14..bfa9a182db4 100644 --- a/src/Schema/PostgreSQLSchemaManager.php +++ b/src/Schema/PostgreSQLSchemaManager.php @@ -294,9 +294,16 @@ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null foreach ($tableIndexes as $row) { $colNumbers = array_map('intval', explode(' ', $row['indkey'])); $columnNameSql = sprintf( - 'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC', + <<<'SQL' + SELECT attnum, + quote_ident(attname) AS attname + FROM pg_attribute + WHERE attrelid = %d + AND attnum IN (%s) + ORDER BY attnum + SQL, $row['indrelid'], - implode(' ,', $colNumbers), + implode(', ', $colNumbers), ); $indexColumns = $this->_conn->fetchAllAssociative($columnNameSql); @@ -612,7 +619,7 @@ protected function selectTableColumns(string $databaseName, ?string $tableName = $sql = 'SELECT'; if ($tableName === null) { - $sql .= ' c.relname AS table_name, n.nspname AS schema_name,'; + $sql .= ' quote_ident(c.relname) AS table_name, quote_ident(n.nspname) AS schema_name,'; } $sql .= sprintf(<<<'SQL' @@ -664,7 +671,7 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName = $sql = 'SELECT'; if ($tableName === null) { - $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; + $sql .= ' quote_ident(tc.relname) AS table_name, quote_ident(tn.nspname) AS schema_name,'; } $sql .= <<<'SQL' @@ -698,7 +705,7 @@ protected function selectForeignKeyColumns(string $databaseName, ?string $tableN $sql = 'SELECT'; if ($tableName === null) { - $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; + $sql .= ' quote_ident(tc.relname) AS table_name, quote_ident(tn.nspname) AS schema_name,'; } $sql .= <<<'SQL' diff --git a/tests/Functional/Schema/OracleSchemaManagerTest.php b/tests/Functional/Schema/OracleSchemaManagerTest.php index 7305fa91e03..e5cdd346135 100644 --- a/tests/Functional/Schema/OracleSchemaManagerTest.php +++ b/tests/Functional/Schema/OracleSchemaManagerTest.php @@ -71,122 +71,6 @@ public function testAlterTableColumnNotNull(callable $comparatorFactory): void self::assertTrue($columns['bar']->getNotnull()); } - public function testListTableDetailsWithDifferentIdentifierQuotingRequirements(): void - { - $primaryTableName = '"Primary_Table"'; - $offlinePrimaryTable = new Table($primaryTableName); - $offlinePrimaryTable->addColumn( - '"Id"', - Types::INTEGER, - ['autoincrement' => true, 'comment' => 'Explicit casing.'], - ); - $offlinePrimaryTable->addColumn('select', Types::INTEGER, ['comment' => 'Reserved keyword.']); - $offlinePrimaryTable->addColumn('foo', Types::INTEGER, ['comment' => 'Implicit uppercasing.']); - $offlinePrimaryTable->addColumn('BAR', Types::INTEGER); - $offlinePrimaryTable->addColumn('"BAZ"', Types::INTEGER); - $offlinePrimaryTable->addIndex(['select'], 'from'); - $offlinePrimaryTable->addIndex(['foo'], 'foo_index'); - $offlinePrimaryTable->addIndex(['BAR'], 'BAR_INDEX'); - $offlinePrimaryTable->addIndex(['"BAZ"'], 'BAZ_INDEX'); - $offlinePrimaryTable->setPrimaryKey(['"Id"']); - - $foreignTableName = 'foreign'; - $offlineForeignTable = new Table($foreignTableName); - $offlineForeignTable->addColumn('id', Types::INTEGER, ['autoincrement' => true]); - $offlineForeignTable->addColumn('"Fk"', Types::INTEGER); - $offlineForeignTable->addIndex(['"Fk"'], '"Fk_index"'); - $offlineForeignTable->addForeignKeyConstraint( - $primaryTableName, - ['"Fk"'], - ['"Id"'], - [], - '"Primary_Table_Fk"', - ); - $offlineForeignTable->setPrimaryKey(['id']); - - $this->dropTableIfExists($foreignTableName); - $this->dropTableIfExists($primaryTableName); - - $this->schemaManager->createTable($offlinePrimaryTable); - $this->schemaManager->createTable($offlineForeignTable); - - $onlinePrimaryTable = $this->schemaManager->introspectTable($primaryTableName); - $onlineForeignTable = $this->schemaManager->introspectTable($foreignTableName); - - $platform = $this->connection->getDatabasePlatform(); - - // Primary table assertions - self::assertSame($primaryTableName, $onlinePrimaryTable->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('"Id"')); - self::assertSame('"Id"', $onlinePrimaryTable->getColumn('"Id"')->getQuotedName($platform)); - self::assertTrue($onlinePrimaryTable->hasPrimaryKey()); - - $primaryKey = $onlinePrimaryTable->getPrimaryKey(); - - self::assertNotNull($primaryKey); - self::assertSame(['"Id"'], $primaryKey->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('select')); - self::assertSame('"select"', $onlinePrimaryTable->getColumn('select')->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('foo')); - self::assertSame('FOO', $onlinePrimaryTable->getColumn('foo')->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('BAR')); - self::assertSame('BAR', $onlinePrimaryTable->getColumn('BAR')->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasColumn('"BAZ"')); - self::assertSame('BAZ', $onlinePrimaryTable->getColumn('"BAZ"')->getQuotedName($platform)); - - self::assertTrue($onlinePrimaryTable->hasIndex('from')); - self::assertTrue($onlinePrimaryTable->getIndex('from')->hasColumnAtPosition('"select"')); - self::assertSame(['"select"'], $onlinePrimaryTable->getIndex('from')->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasIndex('foo_index')); - self::assertTrue($onlinePrimaryTable->getIndex('foo_index')->hasColumnAtPosition('foo')); - self::assertSame(['FOO'], $onlinePrimaryTable->getIndex('foo_index')->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasIndex('BAR_INDEX')); - self::assertTrue($onlinePrimaryTable->getIndex('BAR_INDEX')->hasColumnAtPosition('BAR')); - self::assertSame(['BAR'], $onlinePrimaryTable->getIndex('BAR_INDEX')->getQuotedColumns($platform)); - - self::assertTrue($onlinePrimaryTable->hasIndex('BAZ_INDEX')); - self::assertTrue($onlinePrimaryTable->getIndex('BAZ_INDEX')->hasColumnAtPosition('"BAZ"')); - self::assertSame(['BAZ'], $onlinePrimaryTable->getIndex('BAZ_INDEX')->getQuotedColumns($platform)); - - // Foreign table assertions - self::assertTrue($onlineForeignTable->hasColumn('id')); - self::assertSame('ID', $onlineForeignTable->getColumn('id')->getQuotedName($platform)); - self::assertTrue($onlineForeignTable->hasPrimaryKey()); - - $primaryKey = $onlineForeignTable->getPrimaryKey(); - - self::assertNotNull($primaryKey); - self::assertSame(['ID'], $primaryKey->getQuotedColumns($platform)); - - self::assertTrue($onlineForeignTable->hasColumn('"Fk"')); - self::assertSame('"Fk"', $onlineForeignTable->getColumn('"Fk"')->getQuotedName($platform)); - - self::assertTrue($onlineForeignTable->hasIndex('"Fk_index"')); - self::assertTrue($onlineForeignTable->getIndex('"Fk_index"')->hasColumnAtPosition('"Fk"')); - self::assertSame(['"Fk"'], $onlineForeignTable->getIndex('"Fk_index"')->getQuotedColumns($platform)); - - self::assertTrue($onlineForeignTable->hasForeignKey('"Primary_Table_Fk"')); - self::assertSame( - $primaryTableName, - $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignTableName($platform), - ); - self::assertSame( - ['"Fk"'], - $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedLocalColumns($platform), - ); - self::assertSame( - ['"Id"'], - $onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignColumns($platform), - ); - } - public function testListTableColumnsSameTableNamesInDifferentSchemas(): void { $table = $this->createListTableColumns(); diff --git a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php index 65b2d2fb6cf..1e4ec5ab767 100644 --- a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -8,7 +8,9 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\AbstractAsset; @@ -1716,6 +1718,96 @@ private function createReservedKeywordTables(): void $schemaManager->createSchemaObjects($schema); } + /** @throws Exception */ + public function testQuotedIdentifiers(): void + { + $platform = $this->connection->getDatabasePlatform(); + + if ($platform instanceof DB2Platform) { + self::markTestIncomplete( + 'Introspection of lower-case identifiers as quoted is currently not implemented on IBM DB2.', + ); + } + + if (! $platform instanceof OraclePlatform && ! $platform instanceof PostgreSQLPlatform) { + self::markTestSkipped('The current platform does not auto-quote introspected identifiers.'); + } + + $artists = new Table('"Artists"'); + $artists->addColumn('"Id"', Types::INTEGER); + $artists->addColumn('"Name"', Types::INTEGER); + $artists->addIndex(['"Name"'], '"Idx_Name"'); + $artists->setPrimaryKey(['"Id"']); + + $tracks = new Table('"Tracks"'); + $tracks->addColumn('"Id"', Types::INTEGER); + $tracks->addColumn('"Artist_Id"', Types::INTEGER); + $tracks->addIndex(['"Artist_Id"'], '"Idx_Artist_Id"'); + $tracks->addForeignKeyConstraint( + '"Artists"', + ['"Artist_Id"'], + ['"Id"'], + [], + '"Artists_Fk"', + ); + $tracks->setPrimaryKey(['"Id"']); + + $this->dropTableIfExists('"Tracks"'); + $this->dropTableIfExists('"Artists"'); + + $this->schemaManager->createTable($artists); + $this->schemaManager->createTable($tracks); + + $artists = $this->schemaManager->introspectTable('"Artists"'); + $tracks = $this->schemaManager->introspectTable('"Tracks"'); + + $platform = $this->connection->getDatabasePlatform(); + + // Primary table assertions + self::assertSame('"Artists"', $artists->getQuotedName($platform)); + self::assertSame('"Id"', $artists->getColumn('"Id"')->getQuotedName($platform)); + self::assertSame('"Name"', $artists->getColumn('"Name"')->getQuotedName($platform)); + self::assertSame(['"Name"'], $artists->getIndex('"Idx_Name"')->getQuotedColumns($platform)); + + $primaryKey = $artists->getPrimaryKey(); + self::assertNotNull($primaryKey); + self::assertSame(['"Id"'], $primaryKey->getQuotedColumns($platform)); + + // Foreign table assertions + self::assertTrue($tracks->hasColumn('"Id"')); + self::assertSame('"Id"', $tracks->getColumn('"Id"')->getQuotedName($platform)); + + $primaryKey = $tracks->getPrimaryKey(); + self::assertNotNull($primaryKey); + self::assertSame(['"Id"'], $primaryKey->getQuotedColumns($platform)); + + self::assertTrue($tracks->hasColumn('"Artist_Id"')); + self::assertSame( + '"Artist_Id"', + $tracks->getColumn('"Artist_Id"')->getQuotedName($platform), + ); + + self::assertTrue($tracks->hasIndex('"Idx_Artist_Id"')); + self::assertSame( + ['"Artist_Id"'], + $tracks->getIndex('"Idx_Artist_Id"')->getQuotedColumns($platform), + ); + + self::assertTrue($tracks->hasForeignKey('"Artists_Fk"')); + self::assertSame( + '"Artists"', + $tracks->getForeignKey('"Artists_Fk"')->getQuotedForeignTableName($platform), + ); + self::assertSame( + ['"Artist_Id"'], + $tracks->getForeignKey('"Artists_Fk"')->getQuotedLocalColumns($platform), + ); + self::assertSame( + ['"Id"'], + $tracks->getForeignKey('"Artists_Fk"')->getQuotedForeignColumns($platform), + ); + } + public function testChangeIndexWithForeignKeys(): void { $this->dropTableIfExists('child');