ReferentialAction::NO_ACTION, 'C' => ReferentialAction::CASCADE, 'N' => ReferentialAction::SET_NULL, 'R' => ReferentialAction::RESTRICT, ]; /** @internal This class can be instantiated only by a database platform. */ public function __construct(private Connection $connection, private DB2Platform $platform) { } /** {@inheritDoc} */ public function getAllDatabaseNames(): iterable { throw NotSupported::new(__METHOD__); } /** {@inheritDoc} */ public function getAllSchemaNames(): iterable { throw NotSupported::new(__METHOD__); } /** * {@inheritDoc} * * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscattables * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=registers-current-user */ public function getAllTableNames(): iterable { $sql = <<<'SQL' SELECT TABNAME FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT USER AND TYPE = 'T' ORDER BY TABNAME SQL; foreach ($this->connection->iterateNumeric($sql) as $row) { yield new TableMetadataRow(null, $row[0], []); } } /** {@inheritDoc} */ public function getTableColumnsForAllTables(): iterable { return $this->getTableColumns(null); } /** {@inheritDoc} */ public function getTableColumnsForTable(?string $schemaName, string $tableName): iterable { if ($schemaName !== null) { throw UnsupportedName::fromNonNullSchemaName($schemaName, __METHOD__); } return $this->getTableColumns($tableName); } /** * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscatcolumns * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscattables * * @return iterable * * @throws Exception */ private function getTableColumns(?string $tableName): iterable { $params = []; $sql = sprintf( <<<'SQL' SELECT C.TABNAME, C.COLNAME, C.TYPENAME, C.CODEPAGE, C.NULLS, C.LENGTH, C.SCALE, C.REMARKS, C.GENERATED, C.DEFAULT FROM SYSCAT.COLUMNS C JOIN SYSCAT.TABLES AS T ON T.TABSCHEMA = C.TABSCHEMA AND T.TABNAME = C.TABNAME WHERE %s AND T.TYPE = 'T' ORDER BY C.TABNAME, C.COLNO SQL, $this->buildTableQueryPredicate('C', $tableName, $params), ); foreach ($this->connection->iterateNumeric($sql, $params) as $row) { yield $this->createTableColumn($row); } } /** * @param list $row * * @throws TypesException */ private function createTableColumn(array $row): TableColumnMetadataRow { [ $tableName, $columnName, $typeName, $codePage, $nulls, $length, $scale, $remarks, $generated, $default, ] = $row; $editor = Column::editor() ->setQuotedName($columnName); $type = $this->platform->getDoctrineTypeMapping($typeName); switch (strtolower($typeName)) { case 'varchar': if ($codePage === 0) { $type = Types::BINARY; } $editor->setLength($length); break; case 'character': if ($codePage === 0) { $type = Types::BINARY; } $editor ->setLength($length) ->setFixed(true); break; case 'clob': $editor->setLength($length); break; case 'decimal': case 'double': case 'real': $editor ->setPrecision($length) ->setScale($scale); break; } $editor ->setTypeName($type) ->setNotNull($nulls === 'N') ->setDefaultValue($this->parseDefaultExpression($default)) ->setAutoincrement($generated === 'D'); if ($remarks !== null) { $editor->setComment($remarks); } return new TableColumnMetadataRow(null, $tableName, $editor->create()); } private function parseDefaultExpression(?string $expression): ?string { if ($expression === null || $expression === 'NULL') { return null; } if (preg_match('/^\'(.*)\'$/s', $expression, $matches) === 1) { return str_replace("''", "'", $matches[1]); } return $expression; } /** {@inheritDoc} */ public function getIndexColumnsForAllTables(): iterable { return $this->getIndexColumns(null); } /** {@inheritDoc} */ public function getIndexColumnsForTable(?string $schemaName, string $tableName): iterable { if ($schemaName !== null) { throw UnsupportedName::fromNonNullSchemaName($schemaName, __METHOD__); } return $this->getIndexColumns($tableName); } /** * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscatindexcoluse * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscatindexes * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscattables * * @return iterable * * @throws Exception */ private function getIndexColumns(?string $tableName): iterable { $params = []; $sql = sprintf( <<<'SQL' SELECT I.TABNAME, I.INDNAME, I.UNIQUERULE, ICU.COLNAME FROM SYSCAT.INDEXES AS I JOIN SYSCAT.TABLES AS T ON I.TABSCHEMA = T.TABSCHEMA AND I.TABNAME = T.TABNAME JOIN SYSCAT.INDEXCOLUSE AS ICU ON I.INDSCHEMA = ICU.INDSCHEMA AND I.INDNAME = ICU.INDNAME WHERE %s AND T.TYPE = 'T' AND I.UNIQUERULE != 'P' ORDER BY I.TABNAME, I.INDNAME, ICU.COLSEQ SQL, $this->buildTableQueryPredicate('I', $tableName, $params), ); foreach ($this->connection->iterateNumeric($sql, $params) as $row) { yield new IndexColumnMetadataRow( schemaName: null, tableName: $row[0], indexName: $row[1], type: $row[2] === 'U' ? IndexType::UNIQUE : IndexType::REGULAR, isClustered: false, predicate: null, columnName: $row[3], columnLength: null, ); } } /** {@inheritDoc} */ public function getPrimaryKeyConstraintColumnsForAllTables(): iterable { return $this->getPrimaryKeyConstraintColumns(null); } /** {@inheritDoc} */ public function getPrimaryKeyConstraintColumnsForTable(?string $schemaName, string $tableName): iterable { if ($schemaName !== null) { throw UnsupportedName::fromNonNullSchemaName($schemaName, __METHOD__); } return $this->getPrimaryKeyConstraintColumns($tableName); } /** * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscatcoluse * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscattabconst * * @return iterable * * @throws Exception */ private function getPrimaryKeyConstraintColumns(?string $tableName): iterable { $params = []; $sql = sprintf( <<<'SQL' SELECT TC.TABNAME, TC.CONSTNAME, KCU.COLNAME FROM SYSCAT.TABCONST TC JOIN SYSCAT.KEYCOLUSE KCU ON KCU.TABSCHEMA = TC.TABSCHEMA AND KCU.TABNAME = TC.TABNAME AND KCU.CONSTNAME = TC.CONSTNAME WHERE %s AND TC.TYPE = 'P' ORDER BY TC.TABNAME, TC.CONSTNAME, KCU.COLSEQ SQL, $this->buildTableQueryPredicate('TC', $tableName, $params), ); foreach ($this->connection->iterateNumeric($sql, $params) as $row) { yield new PrimaryKeyConstraintColumnRow( schemaName: null, tableName: $row[0], constraintName: $row[1], isClustered: true, columnName: $row[2], ); } } /** {@inheritDoc} */ public function getForeignKeyConstraintColumnsForAllTables(): iterable { return $this->getForeignKeyConstraintColumns(null); } /** {@inheritDoc} */ public function getForeignKeyConstraintColumnsForTable(?string $schemaName, string $tableName): iterable { if ($schemaName !== null) { throw UnsupportedName::fromNonNullSchemaName($schemaName, __METHOD__); } return $this->getForeignKeyConstraintColumns($tableName); } /** * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscatkeycoluse * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscatreferences * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscattables * * @return iterable * * @throws Exception */ private function getForeignKeyConstraintColumns(?string $tableName): iterable { $params = []; $sql = sprintf( <<<'SQL' SELECT R.TABNAME, R.CONSTNAME, R.REFTABNAME, R.UPDATERULE, R.DELETERULE, PKCU.COLNAME, FKCU.COLNAME FROM SYSCAT.REFERENCES AS R JOIN SYSCAT.TABLES AS T ON T.TABSCHEMA = R.TABSCHEMA AND T.TABNAME = R.TABNAME JOIN SYSCAT.KEYCOLUSE AS PKCU ON PKCU.CONSTNAME = R.CONSTNAME AND PKCU.TABSCHEMA = R.TABSCHEMA AND PKCU.TABNAME = R.TABNAME JOIN SYSCAT.KEYCOLUSE AS FKCU ON FKCU.CONSTNAME = R.REFKEYNAME AND FKCU.TABSCHEMA = R.REFTABSCHEMA AND FKCU.TABNAME = R.REFTABNAME AND FKCU.COLSEQ = PKCU.COLSEQ WHERE %s AND T.TYPE = 'T' ORDER BY R.TABNAME, R.CONSTNAME, PKCU.COLSEQ SQL, $this->buildTableQueryPredicate('R', $tableName, $params), ); foreach ($this->connection->iterateNumeric($sql, $params) as $row) { yield new ForeignKeyConstraintColumnMetadataRow( referencingSchemaName: null, referencingTableName: $row[0], id: null, name: $row[1], referencedSchemaName: null, referencedTableName: $row[2], matchType: MatchType::SIMPLE, onUpdateAction: self::REFERENTIAL_ACTIONS[$row[3]], onDeleteAction: self::REFERENTIAL_ACTIONS[$row[4]], isDeferrable: false, isDeferred: false, referencingColumnName: $row[5], referencedColumnName: $row[6], ); } } /** {@inheritDoc} */ public function getTableOptionsForAllTables(): iterable { return $this->getTableOptions(null); } /** {@inheritDoc} */ public function getTableOptionsForTable(?string $schemaName, string $tableName): iterable { if ($schemaName !== null) { throw UnsupportedName::fromNonNullSchemaName($schemaName, __METHOD__); } return $this->getTableOptions($tableName); } /** * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscattables * * @return iterable * * @throws Exception */ private function getTableOptions(?string $tableName): iterable { $params = []; $sql = sprintf( <<<'SQL' SELECT TABNAME, REMARKS FROM SYSCAT.TABLES T WHERE %s AND TYPE = 'T' ORDER BY TABNAME SQL, $this->buildTableQueryPredicate(null, $tableName, $params), ); foreach ($this->connection->iterateNumeric($sql, $params) as $row) { yield new TableMetadataRow(null, $row[0], [ 'comment' => $row[1], ]); } } /** * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=registers-current-user * * @param ?non-empty-string $relation * @param list $params * * @return non-empty-string */ private function buildTableQueryPredicate(?string $relation, ?string $tableName, array &$params): string { $qualifier = $relation !== null ? $relation . '.' : ''; $conditions = [$qualifier . 'TABSCHEMA = CURRENT USER']; if ($tableName !== null) { $conditions[] = $qualifier . 'TABNAME = ?'; $params[] = $tableName; } return implode(' AND ', $conditions); } /** * {@inheritDoc} * * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=views-syscatviews * @link https://www.ibm.com/docs/en/db2/12.1.0?topic=registers-current-user */ public function getAllViews(): iterable { $sql = <<<'SQL' SELECT VIEWNAME, TEXT FROM SYSCAT.VIEWS WHERE VIEWSCHEMA = CURRENT USER ORDER BY VIEWNAME SQL; foreach ($this->connection->iterateNumeric($sql) as $row) { yield new ViewMetadataRow(null, ...$row); } } /** {@inheritDoc} */ public function getAllSequences(): iterable { throw NotSupported::new(__METHOD__); } }